try_runtime_core/commands/
follow_chain.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, str::FromStr};
19
20use parity_scale_codec::Encode;
21use sc_executor::sp_wasm_interface::HostFunctions;
22use serde::{de::DeserializeOwned, Serialize};
23use sp_core::H256;
24use sp_runtime::{
25    generic::SignedBlock,
26    traits::{Block as BlockT, Header as HeaderT, NumberFor},
27};
28use substrate_rpc_client::{ws_client, ChainApi, FinalizedHeaders, Subscription, WsClient};
29
30use crate::{
31    common::{
32        parse,
33        state::{build_executor, state_machine_call_with_proof, LiveState, RuntimeChecks, State},
34    },
35    full_extensions, rpc_err_handler, SharedParams, LOG_TARGET,
36};
37
38const SUB: &str = "chain_subscribeFinalizedHeads";
39const UN_SUB: &str = "chain_unsubscribeFinalizedHeads";
40
41/// Configurations for [`run`].
42#[derive(Debug, Clone, clap::Parser)]
43pub struct Command {
44    /// The url to connect to.
45    #[arg(short, long, value_parser = parse::url)]
46    pub uri: String,
47
48    /// If set, then the state root check is enabled.
49    #[arg(long)]
50    pub state_root_check: bool,
51
52    /// Which try-state targets to execute when running this command.
53    ///
54    /// Expected values:
55    /// - `all`
56    /// - `none`
57    /// - A comma separated list of pallets, as per pallet names in `construct_runtime!()` (e.g.
58    ///   `Staking, System`).
59    /// - `rr-[x]` where `[x]` is a number. Then, the given number of pallets are checked in a
60    ///   round-robin fashion.
61    #[arg(long, default_value = "all")]
62    pub try_state: frame_try_runtime::TryStateSelect,
63
64    /// If present, a single connection to a node will be kept and reused for fetching blocks.
65    #[arg(long)]
66    pub keep_connection: bool,
67}
68
69/// Start listening for with `SUB` at `url`.
70///
71/// Returns a pair `(client, subscription)` - `subscription` alone will be useless, because it
72/// relies on the related alive `client`.
73async fn start_subscribing<Header: DeserializeOwned + Serialize + Send + Sync + 'static>(
74    url: &str,
75) -> sc_cli::Result<(WsClient, Subscription<Header>)> {
76    let client = ws_client(url)
77        .await
78        .map_err(|e| sc_cli::Error::Application(e.into()))?;
79
80    log::info!(target: LOG_TARGET, "subscribing to {:?} / {:?}", SUB, UN_SUB);
81
82    let sub = ChainApi::<(), (), Header, ()>::subscribe_finalized_heads(&client)
83        .await
84        .map_err(|e| sc_cli::Error::Application(e.into()))?;
85    Ok((client, sub))
86}
87
88// Runs the `follow_chain` command.
89pub async fn run<Block, HostFns>(shared: SharedParams, command: Command) -> sc_cli::Result<()>
90where
91    Block: BlockT<Hash = H256> + DeserializeOwned,
92    Block::Header: DeserializeOwned,
93    <Block::Hash as FromStr>::Err: Debug,
94    NumberFor<Block>: FromStr,
95    <NumberFor<Block> as FromStr>::Err: Debug,
96    HostFns: HostFunctions,
97{
98    let (rpc, subscription) = start_subscribing::<Block::Header>(&command.uri).await?;
99    let mut finalized_headers: FinalizedHeaders<Block, _, _> =
100        FinalizedHeaders::new(&rpc, subscription);
101
102    let mut maybe_state_ext = None;
103    let executor = build_executor::<HostFns>(&shared);
104
105    while let Some(header) = finalized_headers.next().await {
106        let hash = header.hash();
107        let number = header.number();
108
109        let block =
110            ChainApi::<(), Block::Hash, Block::Header, SignedBlock<Block>>::block(&rpc, Some(hash))
111                .await
112                .map_err(|e| {
113                    if matches!(e, substrate_rpc_client::Error::ParseError(_)) {
114                        log::error!(
115                            target: LOG_TARGET,
116                            "failed to parse the block format of remote against the local \
117                            codebase. The block format has changed, and follow-chain cannot run in \
118							this case. Try running this command in a branch of your codebase that
119							has the same block format as the remote chain. For now, we replace the \
120                            block with an empty one."
121                        );
122                    }
123                    rpc_err_handler(e)
124                })?
125                .expect("if header exists, block should also exist.")
126                .block;
127
128        log::debug!(
129            target: LOG_TARGET,
130            "new block event: {:?} => {:?}, extrinsics: {}",
131            hash,
132            number,
133            block.extrinsics().len()
134        );
135
136        // create an ext at the state of this block, whatever is the first subscription event.
137        if maybe_state_ext.is_none() {
138            let state = State::Live(LiveState {
139                uri: command.uri.clone(),
140                // a bit dodgy, we have to un-parse the has to a string again and re-parse it
141                // inside.
142                at: Some(hex::encode(header.parent_hash().encode())),
143                pallet: vec![],
144                child_tree: true,
145                hashed_prefixes: vec![],
146            });
147            let runtime_checks = RuntimeChecks {
148                name_matches: !shared.disable_spec_name_check,
149                version_increases: false,
150                try_runtime_feature_enabled: true,
151            };
152            let ext = state
153                .to_ext::<Block, HostFns>(&shared, &executor, None, runtime_checks)
154                .await?;
155            maybe_state_ext = Some(ext);
156        }
157
158        let state_ext = maybe_state_ext
159            .as_mut()
160            .expect("state_ext either existed or was just created");
161
162        let mut overlayed_changes = Default::default();
163        let result = state_machine_call_with_proof::<Block, HostFns>(
164            state_ext,
165            &mut overlayed_changes,
166            &executor,
167            "TryRuntime_execute_block",
168            (
169                block,
170                command.state_root_check,
171                true,
172                command.try_state.clone(),
173            )
174                .encode()
175                .as_ref(),
176            full_extensions(executor.clone()),
177            shared
178                .export_proof
179                .as_ref()
180                .map(|path| path.as_path().join(format!("{}.json", number))),
181        );
182
183        if let Err(why) = result {
184            log::error!(
185                target: LOG_TARGET,
186                "failed to execute block {:?} due to {:?}",
187                number,
188                why
189            );
190            continue;
191        }
192
193        let storage_changes = overlayed_changes
194            .drain_storage_changes(
195                &state_ext.backend,
196                // Note that in case a block contains a runtime upgrade, state version could
197                // potentially be incorrect here, this is very niche and would only result in
198                // unaligned roots, so this use case is ignored for now.
199                state_ext.state_version,
200            )
201            .unwrap();
202
203        state_ext.backend.apply_transaction(
204            storage_changes.transaction_storage_root,
205            storage_changes.transaction,
206        );
207
208        log::info!(
209            target: LOG_TARGET,
210            "executed block {}, new storage root {:?}",
211            number,
212            state_ext.as_backend().root(),
213        );
214    }
215
216    log::error!(target: LOG_TARGET, "ws subscription must have terminated.");
217    Ok(())
218}