try_runtime_core/commands/
follow_chain.rs1use 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#[derive(Debug, Clone, clap::Parser)]
43pub struct Command {
44 #[arg(short, long, value_parser = parse::url)]
46 pub uri: String,
47
48 #[arg(long)]
50 pub state_root_check: bool,
51
52 #[arg(long, default_value = "all")]
62 pub try_state: frame_try_runtime::TryStateSelect,
63
64 #[arg(long)]
66 pub keep_connection: bool,
67}
68
69async 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
88pub 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 if maybe_state_ext.is_none() {
138 let state = State::Live(LiveState {
139 uri: command.uri.clone(),
140 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 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}