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