1use std::{fmt::Debug, path::PathBuf, str::FromStr};
19
20use frame_remote_externalities::{
21 Builder, Mode, OfflineConfig, OnlineConfig, RemoteExternalities, SnapshotConfig,
22};
23use parity_scale_codec::Decode;
24use sc_cli::{execution_method_from_cli, RuntimeVersion};
25use sc_executor::{
26 sp_wasm_interface::HostFunctions, HeapAllocStrategy, WasmExecutor, DEFAULT_HEAP_ALLOC_STRATEGY,
27};
28use sp_api::{CallContext, StorageProof};
29use sp_core::{
30 hexdisplay::HexDisplay, storage::well_known_keys, traits::ReadRuntimeVersion, twox_128, Hasher,
31};
32use sp_externalities::Extensions;
33use sp_runtime::{
34 traits::{BlakeTwo256, Block as BlockT, HashingFor, Header as HeaderT},
35 DeserializeOwned,
36};
37use sp_state_machine::{OverlayedChanges, StateMachine, TestExternalities, TrieBackendBuilder};
38use substrate_rpc_client::{ws_client, ChainApi};
39
40use crate::{
41 common::{
42 parse,
43 shared_parameters::{Runtime, SharedParams},
44 },
45 hash_of, rpc_err_handler, LOG_TARGET,
46};
47
48#[derive(Debug, Clone, clap::Args)]
50pub struct LiveState {
51 #[arg(
53 short,
54 long,
55 value_parser = parse::url,
56 )]
57 pub uri: String,
58
59 #[arg(
63 short,
64 long,
65 value_parser = parse::hash,
66 )]
67 pub at: Option<String>,
68
69 #[arg(short, long, num_args = 1..)]
74 pub pallet: Vec<String>,
75
76 #[arg(long = "prefix", value_parser = parse::hash, num_args = 1..)]
79 pub hashed_prefixes: Vec<String>,
80
81 #[arg(long)]
87 pub child_tree: bool,
88}
89
90impl LiveState {
91 pub fn at<Block: BlockT>(&self) -> sc_cli::Result<Option<<Block>::Hash>>
93 where
94 <Block::Hash as FromStr>::Err: Debug,
95 {
96 self.at
97 .clone()
98 .map(|s| hash_of::<Block>(s.as_str()))
99 .transpose()
100 }
101
102 pub async fn to_prev_block_live_state<Block: BlockT>(self) -> sc_cli::Result<LiveState>
107 where
108 <Block::Hash as FromStr>::Err: Debug,
109 {
110 let at = self.at::<Block>()?;
112
113 let rpc = ws_client(&self.uri).await?;
116 let previous_hash = ChainApi::<(), Block::Hash, Block::Header, ()>::header(&rpc, at)
117 .await
118 .map_err(rpc_err_handler)
119 .and_then(|maybe_header| {
120 maybe_header
121 .ok_or("header_not_found")
122 .map(|h| *h.parent_hash())
123 })?;
124
125 Ok(LiveState {
126 at: Some(hex::encode(previous_hash)),
127 ..self
128 })
129 }
130}
131
132#[derive(Debug, Clone, clap::Subcommand)]
134pub enum State {
135 Snap {
137 #[clap(short = 'p', long = "path", alias = "snapshot-path")]
138 path: Option<PathBuf>,
139 },
140
141 Live(LiveState),
143}
144
145#[derive(Debug, Clone, Copy)]
147pub struct RuntimeChecks {
148 pub name_matches: bool,
150 pub version_increases: bool,
153 pub try_runtime_feature_enabled: bool,
155}
156
157impl State {
158 pub async fn to_ext<Block: BlockT + DeserializeOwned, HostFns: HostFunctions>(
163 &self,
164 shared: &SharedParams,
165 executor: &WasmExecutor<HostFns>,
166 state_snapshot: Option<SnapshotConfig>,
167 runtime_checks: RuntimeChecks,
168 ) -> sc_cli::Result<RemoteExternalities<Block>>
169 where
170 Block::Header: DeserializeOwned,
171 <Block::Hash as FromStr>::Err: Debug,
172 {
173 let builder = match self {
174 State::Snap { path } => {
175 let path = path
176 .as_ref()
177 .ok_or_else(|| "no snapshot path provided".to_string())?;
178
179 Builder::<Block>::new().mode(Mode::Offline(OfflineConfig {
180 state_snapshot: SnapshotConfig::new(path),
181 }))
182 }
183 State::Live(LiveState {
184 pallet,
185 uri,
186 at,
187 child_tree,
188 hashed_prefixes,
189 }) => {
190 let at = match at {
191 Some(at_str) => Some(hash_of::<Block>(at_str)?),
192 None => None,
193 };
194 let hashed_prefixes = hashed_prefixes
195 .iter()
196 .map(|p_str| {
197 hex::decode(p_str).map_err(|e| {
198 format!(
199 "Error decoding `hashed_prefixes` hex string entry '{:?}' to bytes: {:?}",
200 p_str, e
201 )
202 })
203 })
204 .collect::<Result<Vec<_>, _>>()?;
205 Builder::<Block>::new().mode(Mode::Online(OnlineConfig {
206 at,
207 transport: uri.to_owned().into(),
208 state_snapshot,
209 pallets: pallet.clone(),
210 child_trie: *child_tree,
211 hashed_keys: vec![
212 well_known_keys::CODE.to_vec(),
215 [twox_128(b"System"), twox_128(b"LastRuntimeUpgrade")].concat(),
218 [twox_128(b"System"), twox_128(b"Number")].concat(),
219 ],
220 hashed_prefixes,
221 }))
222 }
223 };
224
225 let builder = if let Some(state_version) = shared.overwrite_state_version {
227 log::warn!(
228 target: LOG_TARGET,
229 "overwriting state version to {:?}, you better know what you are doing.",
230 state_version
231 );
232 builder.overwrite_state_version(state_version)
233 } else {
234 builder
235 };
236
237 let maybe_code_to_overwrite = match shared.runtime {
239 Runtime::Path(ref path) => Some(std::fs::read(path).map_err(|e| {
240 format!("error while reading runtime file from {:?}: {:?}", path, e)
241 })?),
242 Runtime::Existing => None,
243 };
244
245 let mut ext = builder.build().await?;
247
248 if let Some(new_code) = maybe_code_to_overwrite {
250 let original_code = ext
251 .execute_with(|| sp_io::storage::get(well_known_keys::CODE))
252 .expect("':CODE:' is always downloaded in try-runtime-cli; qed");
253
254 ext.insert(well_known_keys::CODE.to_vec(), new_code.clone());
257 let old_version = <RuntimeVersion as Decode>::decode(
258 &mut &*executor
259 .read_runtime_version(&original_code, &mut ext.ext())
260 .unwrap(),
261 )
262 .unwrap();
263 let old_code_hash =
264 HexDisplay::from(BlakeTwo256::hash(&original_code).as_fixed_bytes()).to_string();
265 log::info!(
266 target: LOG_TARGET,
267 "Original runtime [Name: {:?}] [Version: {:?}] [Code hash: 0x{}...{}]",
268 old_version.spec_name,
269 old_version.spec_version,
270 &old_code_hash[0..4],
271 &old_code_hash[old_code_hash.len() - 4..],
272 );
273 log::debug!(
274 target: LOG_TARGET,
275 "Original runtime full code hash: 0x{:?}",
276 old_code_hash,
277 );
278 let new_version = <RuntimeVersion as Decode>::decode(
279 &mut &*executor
280 .read_runtime_version(&new_code, &mut ext.ext())
281 .unwrap(),
282 )
283 .unwrap();
284 let new_code_hash =
285 HexDisplay::from(BlakeTwo256::hash(&new_code).as_fixed_bytes()).to_string();
286 log::info!(
287 target: LOG_TARGET,
288 "New runtime [Name: {:?}] [Version: {:?}] [Code hash: 0x{}...{}]",
289 new_version.spec_name,
290 new_version.spec_version,
291 &new_code_hash[0..4],
292 &new_code_hash[new_code_hash.len() - 4..],
293 );
294 log::debug!(
295 target: LOG_TARGET,
296 "New runtime code hash: 0x{:?}",
297 new_code_hash
298 );
299
300 if runtime_checks.name_matches && new_version.spec_name != old_version.spec_name {
301 return Err(
302 "Spec names must match. Use `--disable-spec-name-check` to disable this check."
303 .into(),
304 );
305 }
306
307 if runtime_checks.version_increases
308 && new_version.spec_version <= old_version.spec_version
309 {
310 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());
311 }
312 }
313
314 if runtime_checks.try_runtime_feature_enabled
315 && !ensure_try_runtime::<Block, HostFns>(executor, &mut ext)
316 {
317 return Err("Given runtime is not compiled with the try-runtime feature.".into());
318 }
319
320 Ok(ext)
321 }
322}
323
324pub(crate) fn build_executor<H: HostFunctions>(shared: &SharedParams) -> WasmExecutor<H> {
326 let heap_pages =
327 shared
328 .heap_pages
329 .map_or(DEFAULT_HEAP_ALLOC_STRATEGY, |p| HeapAllocStrategy::Static {
330 extra_pages: p as _,
331 });
332
333 WasmExecutor::builder()
334 .with_execution_method(execution_method_from_cli(
335 shared.wasm_method,
336 shared.wasmtime_instantiation_strategy,
337 ))
338 .with_onchain_heap_alloc_strategy(heap_pages)
339 .with_offchain_heap_alloc_strategy(heap_pages)
340 .with_allow_missing_host_functions(true)
343 .build()
344}
345
346fn ensure_try_runtime<Block: BlockT, HostFns: HostFunctions>(
348 executor: &WasmExecutor<HostFns>,
349 ext: &mut TestExternalities<HashingFor<Block>>,
350) -> bool {
351 use sp_api::RuntimeApiInfo;
352 let final_code = ext
353 .execute_with(|| sp_io::storage::get(well_known_keys::CODE))
354 .expect("':CODE:' is always downloaded in try-runtime-cli; qed");
355 let final_version = <RuntimeVersion as Decode>::decode(
356 &mut &*executor
357 .read_runtime_version(&final_code, &mut ext.ext())
358 .unwrap(),
359 )
360 .unwrap();
361 final_version
362 .api_version(&<dyn frame_try_runtime::TryRuntime<Block>>::ID)
363 .is_some()
364}
365
366pub(crate) fn state_machine_call<Block: BlockT, HostFns: HostFunctions>(
369 ext: &TestExternalities<HashingFor<Block>>,
370 executor: &WasmExecutor<HostFns>,
371 method: &'static str,
372 data: &[u8],
373 mut extensions: Extensions,
374) -> sc_cli::Result<(OverlayedChanges<HashingFor<Block>>, Vec<u8>)> {
375 let mut changes = Default::default();
376 let encoded_result = StateMachine::new(
377 &ext.backend,
378 &mut changes,
379 executor,
380 method,
381 data,
382 &mut extensions,
383 &sp_state_machine::backend::BackendRuntimeCode::new(&ext.backend).runtime_code()?,
384 CallContext::Offchain,
385 )
386 .execute()
387 .map_err(|e| format!("failed to execute '{}': {}", method, e))
388 .map_err::<sc_cli::Error, _>(Into::into)?;
389
390 Ok((changes, encoded_result))
391}
392
393pub(crate) fn state_machine_call_with_proof<Block: BlockT, HostFns: HostFunctions>(
398 ext: &TestExternalities<HashingFor<Block>>,
399 storage_overlay: &mut OverlayedChanges<HashingFor<Block>>,
400 executor: &WasmExecutor<HostFns>,
401 method: &'static str,
402 data: &[u8],
403 mut extensions: Extensions,
404 maybe_export_proof: Option<PathBuf>,
405) -> sc_cli::Result<(StorageProof, Vec<u8>)> {
406 let runtime_code_backend = sp_state_machine::backend::BackendRuntimeCode::new(&ext.backend);
407 let proving_backend = TrieBackendBuilder::wrap(&ext.backend)
408 .with_recorder(Default::default())
409 .build();
410 let runtime_code = runtime_code_backend.runtime_code()?;
411
412 let encoded_result = StateMachine::new(
413 &proving_backend,
414 storage_overlay,
415 executor,
416 method,
417 data,
418 &mut extensions,
419 &runtime_code,
420 CallContext::Offchain,
421 )
422 .execute()
423 .map_err(|e| format!("failed to execute {}: {}", method, e))
424 .map_err::<sc_cli::Error, _>(Into::into)?;
425
426 let proof = proving_backend
427 .extract_proof()
428 .expect("A recorder was set and thus, a storage proof can be extracted; qed");
429
430 if let Some(path) = maybe_export_proof {
431 let mut file = std::fs::File::create(&path).map_err(|e| {
432 log::error!(
433 target: LOG_TARGET,
434 "Failed to create file {}: {:?}",
435 path.to_string_lossy(),
436 e
437 );
438 e
439 })?;
440
441 log::info!(target: LOG_TARGET, "Writing storage proof to {}", path.to_string_lossy());
442
443 use std::io::Write as _;
444 file.write_all(storage_proof_to_raw_json(&proof).as_bytes())
445 .map_err(|e| {
446 log::error!(
447 target: LOG_TARGET,
448 "Failed to write storage proof to {}: {:?}",
449 path.to_string_lossy(),
450 e
451 );
452 e
453 })?;
454 }
455
456 Ok((proof, encoded_result))
457}
458
459fn storage_proof_to_raw_json(storage_proof: &sp_state_machine::StorageProof) -> String {
461 serde_json::Value::Object(
462 storage_proof
463 .to_memory_db::<sp_runtime::traits::BlakeTwo256>()
464 .drain()
465 .iter()
466 .map(|(key, (value, _n))| {
467 (
468 format!("0x{}", hex::encode(key.as_bytes())),
469 serde_json::Value::String(format!("0x{}", hex::encode(value))),
470 )
471 })
472 .collect(),
473 )
474 .to_string()
475}