try_runtime_core/common/
state.rs

1// This file is part of try-runtime-cli.
2
3// Copyright (C) Parity Technologies (UK) Ltd.
4// SPDX-License-Identifier: Apache-2.0
5
6// Licensed under the Apache License, Version 2.0 (the "License");
7// you may not use this file except in compliance with the License.
8// You may obtain a copy of the License at
9//
10// 	http://www.apache.org/licenses/LICENSE-2.0
11//
12// Unless required by applicable law or agreed to in writing, software
13// distributed under the License is distributed on an "AS IS" BASIS,
14// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15// See the License for the specific language governing permissions and
16// limitations under the License.
17
18use 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/// A `Live` variant for [`State`]
49#[derive(Debug, Clone, clap::Args)]
50pub struct LiveState {
51    /// The url to connect to.
52    #[arg(
53		short,
54		long,
55		value_parser = parse::url,
56	)]
57    pub uri: String,
58
59    /// The block hash at which to fetch the state.
60    ///
61    /// If non provided, then the latest finalized head is used.
62    #[arg(
63		short,
64		long,
65		value_parser = parse::hash,
66	)]
67    pub at: Option<String>,
68
69    /// A pallet to scrape. Can be provided multiple times. If empty, entire chain state will
70    /// be scraped.
71    ///
72    /// This is equivalent to passing `xx_hash_64(pallet)` to `--hashed_prefixes`.
73    #[arg(short, long, num_args = 1..)]
74    pub pallet: Vec<String>,
75
76    /// Storage entry key prefixes to scrape and inject into the test externalities. Pass as 0x
77    /// prefixed hex strings. By default, all keys are scraped and included.
78    #[arg(long = "prefix", value_parser = parse::hash, num_args = 1..)]
79    pub hashed_prefixes: Vec<String>,
80
81    /// Fetch the child-keys as well.
82    ///
83    /// Default is `false`, if specific `--pallets` are specified, `true` otherwise. In other
84    /// words, if you scrape the whole state the child tree data is included out of the box.
85    /// Otherwise, it must be enabled explicitly using this flag.
86    #[arg(long)]
87    pub child_tree: bool,
88}
89
90impl LiveState {
91    /// Return the `at` block hash as a `Hash`, if it exists.
92    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    /// Converts this `LiveState` into a `LiveState` for the previous block.
103    ///
104    /// Useful for opertations like when you want to execute a block, but also need the state of the
105    /// block *before* it.
106    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        // We want to execute the block `at`, therefore need the state of the block *before* it.
111        let at = self.at::<Block>()?;
112
113        // Get the block number requested by the user, or the current block number if they
114        // didn't specify one.
115        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/// The source of runtime *state* to use.
133#[derive(Debug, Clone, clap::Subcommand)]
134pub enum State {
135    /// Use a state snapshot as the source of runtime state.
136    Snap {
137        #[clap(short = 'p', long = "path", alias = "snapshot-path")]
138        path: Option<PathBuf>,
139    },
140
141    /// Use a live chain as the source of runtime state.
142    Live(LiveState),
143}
144
145/// Checks to perform on the given runtime, compared to the existing runtime.
146#[derive(Debug, Clone, Copy)]
147pub struct RuntimeChecks {
148    /// Enforce the `spec_name`s match
149    pub name_matches: bool,
150    /// Enforce the `spec_version` of the given is greater or equal to the existing
151    /// runtime.
152    pub version_increases: bool,
153    /// Enforce that the given runtime is compiled with the try-runtime feature.
154    pub try_runtime_feature_enabled: bool,
155}
156
157impl State {
158    /// Create the [`RemoteExternalities`].
159    ///
160    /// This will override the code as it sees fit based on [`Runtime`]. It will also check the
161    /// spec-version and name.
162    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                        // we always download the code, but we almost always won't use it, based on
213                        // `Runtime`.
214                        well_known_keys::CODE.to_vec(),
215                        // we will always download this key, since it helps detect if we should do
216                        // runtime migration or not.
217                        [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        // possibly overwrite the state version, should hardly be needed.
226        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        // then, we prepare to replace the code based on what the CLI wishes.
238        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        // build the main ext.
246        let mut ext = builder.build().await?;
247
248        // actually replace the code if needed.
249        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            // NOTE: see the impl notes of `read_runtime_version`, the ext is almost not used here,
255            // only as a backup.
256            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
324/// Build wasm executor by default config.
325pub(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        // There is not that much we can do if someone is using unknown host functions.
341        // They would need to fork the `cli` to add their custom host functions.
342        .with_allow_missing_host_functions(true)
343        .build()
344}
345
346/// Ensure that the given `ext` is compiled with `try-runtime`
347fn 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
366/// Execute the given `method` and `data` on top of `ext`, returning the results (encoded) and the
367/// state `changes`.
368pub(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
393/// Same as [`state_machine_call`], but it also computes and returns the storage proof and ref time
394/// information.
395///
396/// Make sure [`LOG_TARGET`] is enabled in logging.
397pub(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
459/// Converts a [`sp_state_machine::StorageProof`] into a JSON string.
460fn 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}