Skip to content

Commit f6c7b57

Browse files
committed
Implement --from and --to and add tests
1 parent 65b296a commit f6c7b57

2 files changed

Lines changed: 142 additions & 22 deletions

File tree

‎cli/tests/execute_block.rs‎

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -121,5 +121,45 @@ async fn execute_block_works() {
121121
.status
122122
.success());
123123
})
124+
.await;
125+
126+
// Test passing --from and --to to execute a range of blocks.
127+
common::run_with_timeout(Duration::from_secs(120), async move {
128+
let ws_url = format!("ws://localhost:{}", port);
129+
let from = 3u64;
130+
let to = 5u64;
131+
132+
fn execute_block_range(ws_url: &str, from: u64, to: u64) -> tokio::process::Child {
133+
Command::new(cargo_bin("try-runtime"))
134+
.stdout(std::process::Stdio::piped())
135+
.stderr(std::process::Stdio::piped())
136+
.arg("--runtime=existing")
137+
.args(["execute-block"])
138+
.args([format!("--from={}", from), format!("--to={}", to)])
139+
.args(["live", format!("--uri={}", ws_url).as_str()])
140+
.kill_on_drop(true)
141+
.spawn()
142+
.unwrap()
143+
}
144+
145+
let mut block_execution = execute_block_range(&ws_url, from, to);
146+
147+
// Expect the last block in the range to be successfully executed.
148+
let expected_output = format!(r#".*Block #{} successfully executed"#, to);
149+
let re = Regex::new(expected_output.as_str()).unwrap();
150+
let matched =
151+
common::wait_for_stream_pattern_match(block_execution.stderr.take().unwrap(), re).await;
152+
153+
// Assert that all blocks in the range were executed.
154+
assert!(matched.is_ok());
155+
156+
// Assert that the block-execution exited successfully.
157+
assert!(block_execution
158+
.wait_with_output()
159+
.await
160+
.unwrap()
161+
.status
162+
.success());
163+
})
124164
.await
125165
}

‎core/src/commands/execute_block.rs‎

Lines changed: 102 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -18,12 +18,13 @@
1818
use std::{fmt::Debug, str::FromStr};
1919

2020
use parity_scale_codec::Encode;
21-
use sc_executor::sp_wasm_interface::HostFunctions;
21+
use sc_executor::{sp_wasm_interface::HostFunctions, WasmExecutor};
22+
use sp_rpc::{list::ListOrValue, number::NumberOrHex};
2223
use sp_runtime::{
2324
generic::SignedBlock,
2425
traits::{Block as BlockT, Header as HeaderT, NumberFor},
2526
};
26-
use substrate_rpc_client::{ws_client, ChainApi};
27+
use substrate_rpc_client::{ws_client, ChainApi, WsClient};
2728

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

54+
/// Block number to start execution from.
55+
#[arg(long, requires = "to")]
56+
pub from: Option<u64>,
57+
58+
/// Block number to stop execution at.
59+
#[arg(long, requires = "from")]
60+
pub to: Option<u64>,
61+
5362
/// The ws uri from which to fetch the block.
5463
///
5564
/// This will always fetch the next block of whatever `state` is referring to, because this is
@@ -98,33 +107,104 @@ where
98107
let block_ws_uri = command.block_ws_uri();
99108
let rpc = ws_client(&block_ws_uri).await?;
100109

101-
let live_state = match command.state {
102-
State::Live(live_state) => {
103-
// If no --at is provided, get the latest block to replay
104-
if live_state.at.is_some() {
105-
live_state
106-
} else {
110+
// If --from and --to is passed, they take precedence over LiveState --at.
111+
if let (Some(from), Some(to)) = (command.from, command.to) {
112+
if from > to {
113+
return Err(sc_cli::Error::Application(
114+
format!("--from ({from}) must be less than or equal to --to ({to})").into(),
115+
));
116+
}
117+
118+
let block_numbers = (from..=to).map(NumberOrHex::Number).collect::<Vec<_>>();
119+
120+
let hash_list = ChainApi::<(), Block::Hash, Block::Header, SignedBlock<Block>>::block_hash(
121+
&rpc,
122+
Some(ListOrValue::List(block_numbers)),
123+
)
124+
.await
125+
.map_err(rpc_err_handler)?;
126+
127+
if let ListOrValue::List(hashes) = hash_list {
128+
for (block_number, hash) in (from..=to).zip(hashes) {
129+
let Some(hash) = hash else {
130+
log::warn!(target: LOG_TARGET, "skipping block {block_number}, hash was None");
131+
continue;
132+
};
133+
134+
log::info!(target: LOG_TARGET, "hash found, block number: {block_number}, hash: {hash}");
135+
107136
let header =
108137
ChainApi::<(), Block::Hash, Block::Header, SignedBlock<Block>>::header(
109-
&rpc, None,
138+
&rpc,
139+
Some(hash),
110140
)
111141
.await
112142
.map_err(rpc_err_handler)?
113-
.expect("header exists, block should also exist; qed");
114-
LiveState {
115-
uri: vec![block_ws_uri],
143+
.expect("hash exists, header should exist;");
144+
145+
let live_state = LiveState {
146+
uri: vec![block_ws_uri.clone()],
116147
at: Some(hex::encode(header.hash().encode())),
117148
pallet: Default::default(),
118149
hashed_prefixes: Default::default(),
119150
child_tree: Default::default(),
120-
}
151+
};
152+
153+
let _ =
154+
execute_block::<Block, HostFns>(&shared, &command, &executor, &rpc, live_state)
155+
.await;
121156
}
122157
}
123-
_ => {
124-
unreachable!("execute block currently only supports Live state")
125-
}
126-
};
127158

159+
Ok(())
160+
} else {
161+
let live_state = match &command.state {
162+
State::Live(live_state) => {
163+
// If no --at is provided, get the latest block to replay
164+
if live_state.at.is_some() {
165+
live_state.clone()
166+
} else {
167+
let header =
168+
ChainApi::<(), Block::Hash, Block::Header, SignedBlock<Block>>::header(
169+
&rpc, None,
170+
)
171+
.await
172+
.map_err(rpc_err_handler)?
173+
.expect("header exists, block should also exist; qed");
174+
LiveState {
175+
uri: vec![block_ws_uri],
176+
at: Some(hex::encode(header.hash().encode())),
177+
pallet: Default::default(),
178+
hashed_prefixes: Default::default(),
179+
child_tree: Default::default(),
180+
}
181+
}
182+
}
183+
_ => {
184+
unreachable!("execute block currently only supports Live state")
185+
}
186+
};
187+
188+
execute_block::<Block, HostFns>(&shared, &command, &executor, &rpc, live_state).await
189+
}
190+
}
191+
192+
// Perform block execution on live state
193+
pub async fn execute_block<Block, HostFns>(
194+
shared: &SharedParams,
195+
command: &Command,
196+
executor: &WasmExecutor<HostFns>,
197+
rpc: &WsClient,
198+
live_state: LiveState,
199+
) -> sc_cli::Result<()>
200+
where
201+
Block: BlockT + serde::de::DeserializeOwned,
202+
<Block::Hash as FromStr>::Err: Debug,
203+
Block::Hash: serde::de::DeserializeOwned,
204+
Block::Header: serde::de::DeserializeOwned,
205+
<NumberFor<Block> as TryInto<u64>>::Error: Debug,
206+
HostFns: HostFunctions,
207+
{
128208
// The block we want to *execute* at is the block passed by the user
129209
let execute_at = live_state.at::<Block>()?;
130210

@@ -137,12 +217,12 @@ where
137217
try_runtime_feature_enabled: true,
138218
};
139219
let ext = State::Live(prev_block_live_state)
140-
.to_ext::<Block, HostFns>(&shared, &executor, None, runtime_checks)
220+
.to_ext::<Block, HostFns>(shared, executor, None, runtime_checks)
141221
.await?;
142222

143223
// Execute the desired block on top of it
144224
let block =
145-
ChainApi::<(), Block::Hash, Block::Header, SignedBlock<Block>>::block(&rpc, execute_at)
225+
ChainApi::<(), Block::Hash, Block::Header, SignedBlock<Block>>::block(rpc, execute_at)
146226
.await
147227
.map_err(rpc_err_handler)?
148228
.expect("header exists, block should also exist; qed")
@@ -161,18 +241,18 @@ where
161241
block.clone(),
162242
state_root_check,
163243
signature_check,
164-
command.try_state,
244+
command.try_state.clone(),
165245
)
166246
.encode();
167247

168248
let _ = state_machine_call_with_proof::<Block, HostFns>(
169249
&ext,
170250
&mut Default::default(),
171-
&executor,
251+
executor,
172252
"TryRuntime_execute_block",
173253
&payload,
174254
full_extensions(executor.clone()),
175-
shared.export_proof,
255+
shared.export_proof.clone(),
176256
)?;
177257

178258
Ok(())

0 commit comments

Comments
 (0)