try_runtime_core/commands/on_runtime_upgrade/
mbms.rs

1use std::{fmt::Debug, ops::DerefMut, str::FromStr, sync::Arc, time::Duration};
2
3use log::Level;
4use parity_scale_codec::{Codec, Encode};
5use sc_executor::sp_wasm_interface::HostFunctions;
6use sp_core::{twox_128, Hasher, H256};
7use sp_runtime::{
8    traits::{Block as BlockT, NumberFor},
9    DeserializeOwned, ExtrinsicInclusionMode,
10};
11use sp_state_machine::TestExternalities;
12use tokio::sync::Mutex;
13
14use crate::{
15    commands::on_runtime_upgrade::Command,
16    common::{
17        empty_block::{
18            inherents::providers::ProviderVariant,
19            production::{core_version, mine_block},
20        },
21        misc_logging::{basti_log, LogLevelGuard},
22        state::{build_executor, state_machine_call_with_proof, RuntimeChecks},
23    },
24    SharedParams, LOG_TARGET,
25};
26
27/// Checks multi block migrations (MBMs) for a runtime upgrade.
28pub struct MbmChecker<Block, HostFns> {
29    pub command: Command,
30    pub shared: SharedParams,
31    pub runtime_checks: RuntimeChecks,
32    pub _phantom: core::marker::PhantomData<(Block, HostFns)>,
33}
34
35impl<Block, HostFns> MbmChecker<Block, HostFns>
36where
37    Block: BlockT<Hash = H256> + DeserializeOwned,
38    Block::Header: DeserializeOwned,
39    <Block::Hash as FromStr>::Err: Debug,
40    NumberFor<Block>: FromStr,
41    <NumberFor<Block> as FromStr>::Err: Debug,
42    HostFns: HostFunctions,
43{
44    pub async fn check_mbms(&self) -> sc_cli::Result<()> {
45        basti_log(
46            Level::Info,
47            &format!(
48                "🔬 Running Multi-Block-Migrations with checks: {:?}",
49                self.command.checks
50            ),
51        );
52
53        let executor = build_executor(&self.shared);
54        let ext = self
55            .command
56            .state
57            .to_ext::<Block, HostFns>(&self.shared, &executor, None, self.runtime_checks)
58            .await?;
59
60        if core_version::<Block, HostFns>(&ext, &executor)? < 5 {
61            return Err("Your runtime does not support Multi-Block-Migrations. Please disable the check with `--mbms false` or update your runtime.".into());
62        }
63
64        let inner_ext = Arc::new(Mutex::new(ext.inner_ext));
65        let mut parent_header = ext.header.clone();
66        let mut parent_block_building_info = None;
67        let provider_variant =
68            ProviderVariant::Smart(Duration::from_millis(self.command.blocktime));
69        let mut n = 0;
70
71        let mut ext_guard = inner_ext.lock().await;
72        let ext = ext_guard.deref_mut();
73        Self::modify_spec_name(ext).await?;
74        drop(ext_guard);
75
76        // This actually runs the MBMs block by block:
77        loop {
78            let _quiet = LogLevelGuard::new(log::LevelFilter::Info);
79            let (next_block_building_info, next_header, mode) = mine_block::<Block, HostFns>(
80                inner_ext.clone(),
81                &executor,
82                parent_block_building_info,
83                parent_header.clone(),
84                provider_variant,
85                frame_try_runtime::TryStateSelect::None,
86            )
87            .await?;
88
89            parent_block_building_info = Some(next_block_building_info);
90            parent_header = next_header;
91            // The first block does not yet have the MBMs enabled.
92            let first_is_free = n == 0;
93
94            if n > (self.command.mbm_max_blocks + 1) {
95                // +1 for the MBM init block
96                log::error!(target: LOG_TARGET, "MBM reached its maximum number of allowed blocks after {} blocks. Increase --mbm-max-blocks if you think this is not a bug.", n);
97                return Err("MBM max blocks reached".into());
98            } else if first_is_free || Self::poll_mbms_ongoing(mode, inner_ext.clone()).await {
99                n += 1;
100                log::info!(target: LOG_TARGET, "MBM ongoing for {n} blocks");
101            } else {
102                log::info!(target: LOG_TARGET, "MBM finished after {n} blocks");
103                break;
104            }
105        }
106
107        let mut ext_guard = inner_ext.lock().await;
108        let ext = ext_guard.deref_mut();
109        log::info!(target: LOG_TARGET, "MBM finished. Executing block one more time.");
110
111        let _ = state_machine_call_with_proof::<Block, HostFns>(
112            ext,
113            &mut Default::default(),
114            &executor,
115            "TryRuntime_on_runtime_upgrade",
116            self.command.checks.encode().as_ref(),
117            Default::default(), // TODO
118            None,
119        )?;
120
121        Ok(())
122    }
123
124    /// Modify up the spec name in storage such that the `was_upgraded` check will always return
125    /// true because of changes spec name.
126    async fn modify_spec_name<H>(ext: &mut TestExternalities<H>) -> sc_cli::Result<()>
127    where
128        H: Hasher + 'static,
129        H::Out: Codec + Ord,
130    {
131        let key = [twox_128(b"System"), twox_128(b"LastRuntimeUpgrade")].concat();
132        let version = frame_system::LastRuntimeUpgradeInfo {
133            spec_version: 0.into(),
134            spec_name: "definitely-something-different".into(),
135        };
136
137        ext.execute_with(|| {
138            sp_io::storage::set(&key, &version.encode());
139        });
140        ext.commit_all()?;
141
142        Ok(())
143    }
144
145    /// Are there any Multi-Block-Migrations ongoing?
146    async fn poll_mbms_ongoing<H>(
147        mode: Option<ExtrinsicInclusionMode>,
148        ext_mutex: std::sync::Arc<Mutex<TestExternalities<H>>>,
149    ) -> bool
150    where
151        H: Hasher + 'static,
152        H::Out: Codec + Ord,
153    {
154        if mode == Some(ExtrinsicInclusionMode::OnlyInherents) {
155            log::info!(target: LOG_TARGET, "Runtime reports OnlyInherents");
156            return true;
157        }
158
159        let mut ext_guard = ext_mutex.lock().await;
160        let ext = ext_guard.deref_mut();
161
162        ext.execute_with(|| {
163            let mbm_in_progress_key =
164                [twox_128(b"MultiBlockMigrations"), twox_128(b"Cursor")].concat();
165            let mbm_in_progress = sp_io::storage::get(&mbm_in_progress_key).unwrap_or_default();
166
167            !mbm_in_progress.is_empty()
168        })
169    }
170}