From dc35acf2e83ea65cbb36799403c3ef3bb631e98d Mon Sep 17 00:00:00 2001 From: Oliver Tale-Yazdi Date: Tue, 30 May 2023 11:50:28 +0200 Subject: [PATCH 01/63] Add stepped migrations and pallet Signed-off-by: Oliver Tale-Yazdi --- Cargo.lock | 24 +++- Cargo.toml | 1 + frame/migrations/Cargo.toml | 37 ++++++ frame/migrations/src/lib.rs | 138 +++++++++++++++++++++ frame/migrations/src/mock.rs | 113 +++++++++++++++++ frame/migrations/src/tests.rs | 61 +++++++++ frame/support/src/migrations.rs | 64 +++++++++- frame/support/src/storage/transactional.rs | 16 +++ 8 files changed, 448 insertions(+), 6 deletions(-) create mode 100644 frame/migrations/Cargo.toml create mode 100644 frame/migrations/src/lib.rs create mode 100644 frame/migrations/src/mock.rs create mode 100644 frame/migrations/src/tests.rs diff --git a/Cargo.lock b/Cargo.lock index 23a5fb7926925..c9ac3723944e3 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4679,12 +4679,9 @@ dependencies = [ [[package]] name = "log" -version = "0.4.17" +version = "0.4.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "abb12e687cfb44aa40f41fc3978ef76448f9b6038cad6aef4259d3c095a2382e" -dependencies = [ - "cfg-if", -] +checksum = "518ef76f2f87365916b142844c16d8fefd85039bc5699050210a7778ee1cd1de" [[package]] name = "lru" @@ -6610,6 +6607,23 @@ dependencies = [ "sp-weights", ] +[[package]] +name = "pallet-migrations" +version = "0.1.0" +dependencies = [ + "frame-benchmarking", + "frame-support", + "frame-system", + "impl-trait-for-tuples", + "log", + "parity-scale-codec", + "scale-info", + "sp-core", + "sp-io", + "sp-runtime", + "sp-tracing", +] + [[package]] name = "pallet-mmr" version = "4.0.0-dev" diff --git a/Cargo.toml b/Cargo.toml index eabedd13918a5..483231a71a045 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -119,6 +119,7 @@ members = [ "frame/lottery", "frame/membership", "frame/merkle-mountain-range", + "frame/migrations", "frame/multisig", "frame/nicks", "frame/node-authorization", diff --git a/frame/migrations/Cargo.toml b/frame/migrations/Cargo.toml new file mode 100644 index 0000000000000..45fbf3060db45 --- /dev/null +++ b/frame/migrations/Cargo.toml @@ -0,0 +1,37 @@ +[package] +name = "pallet-migrations" +version = "0.1.0" +description = "FRAME pallet to execute multi-block migrations." +authors = ["Parity Technologies "] +homepage = "https://substrate.io" +edition = "2021" +license = "Apache-2.0" +repository = "https://github.com/paritytech/substrate" + +[package.metadata.docs.rs] +targets = ["x86_64-unknown-linux-gnu"] + +[dependencies] +codec = { package = "parity-scale-codec", version = "3.0.0", default-features = false, features = [ "derive"] } +scale-info = { version = "2.0.0", default-features = false, features = ["derive"] } + +frame-benchmarking = { version = "4.0.0-dev", default-features = false, optional = true, path = "../benchmarking" } +frame-support = { version = "4.0.0-dev", default-features = false, path = "../support" } +frame-system = { version = "4.0.0-dev", default-features = false, path = "../system" } +impl-trait-for-tuples = "0.2.2" +log = "0.4.18" +sp-tracing = { version = "7.0.0", path = "../../primitives/tracing" } + +[dev-dependencies] +sp-core = { version = "8.0.0", path = "../../primitives/core" } +sp-io = { version = "8.0.0", path = "../../primitives/io" } +sp-runtime = { version = "8.0.0", path = "../../primitives/runtime" } + +[features] +default = ["std"] + +std = ["codec/std", "frame-benchmarking?/std", "frame-support/std", "frame-system/std", "scale-info/std", "sp-runtime/std"] + +runtime-benchmarks = ["frame-benchmarking/runtime-benchmarks", "frame-support/runtime-benchmarks", "frame-system/runtime-benchmarks", "sp-runtime/runtime-benchmarks"] + +try-runtime = ["frame-support/try-runtime"] diff --git a/frame/migrations/src/lib.rs b/frame/migrations/src/lib.rs new file mode 100644 index 0000000000000..70ef623c80dcd --- /dev/null +++ b/frame/migrations/src/lib.rs @@ -0,0 +1,138 @@ +// This file is part of Substrate. + +// Copyright (C) Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: Apache-2.0 + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#![cfg_attr(not(feature = "std"), no_std)] + +pub use pallet::*; + +mod mock; +mod tests; + +use frame_support::{ + defensive, + migrations::*, + traits::{ConstU32, Get}, + weights::{Weight, WeightMeter}, + BoundedVec, +}; + +#[frame_support::pallet] +pub mod pallet { + use super::*; + use frame_support::pallet_prelude::*; + use frame_system::pallet_prelude::*; + + #[pallet::pallet] + pub struct Pallet(_); + + #[pallet::config] + pub trait Config: frame_system::Config { + type RuntimeEvent: From> + IsType<::RuntimeEvent>; + + type Migrations: Get>>; + + type ServiceWeight: Get; + } + + #[pallet::storage] + pub type Cursor = StorageValue<_, (u32, SteppedMigrationCursor), OptionQuery>; + + #[pallet::event] + #[pallet::generate_deposit(pub(super) fn deposit_event)] + pub enum Event { + UpgradeStarted, + UpgradeCompleted { migrations: u32 }, + + MigrationAdvanced { index: u32 }, + MigrationCompleted { index: u32 }, + MigrationFailed { index: u32 }, // TODO add `inner` error + } + + // Errors inform users that something went wrong. + #[pallet::error] + pub enum Error { + TooLarge, // Errors cannot have fields. + } + + #[pallet::hooks] + impl Hooks> for Pallet { + fn on_runtime_upgrade() -> Weight { + if Cursor::::exists() { + defensive!("Migrations in progress will be aborted."); + return Default::default() // FAIL-CI + } + Cursor::::set(Some(Default::default())); + Self::deposit_event(Event::UpgradeStarted); + + Default::default() // FAIL-CI + } + + fn on_initialize(n: T::BlockNumber) -> Weight { + let mut meter = WeightMeter::from_limit(T::ServiceWeight::get()); + + let Some((index, inner_cursor)) = Cursor::::get() else { + log::info!("[Block {n}] Nothing to do: waiting for cursor to become `Some`."); + return meter.consumed; + }; + let migrations = T::Migrations::get(); + + let Some(migration) = migrations.get(index as usize) else { + log::info!("[Block {n}] All migrations processed ({} >= {}).", index, migrations.len()); + Cursor::::kill(); + Self::deposit_event(Event::UpgradeCompleted { migrations: migrations.len() as u32 }); + return meter.consumed; + }; + log::info!("[Block {n}] Advancing migration {index}."); + + match migration.transactional_step(Some(inner_cursor), &mut meter) { + Ok(Some(inner_cursor)) => { + log::info!("[Block {n}] Migration {index} advanced."); + Cursor::::set(Some((index, inner_cursor))); + Self::deposit_event(Event::MigrationAdvanced { index }); + }, + Ok(None) => { + log::info!("[Block {n}] Migration {index} done."); + Cursor::::set(Some((index.saturating_add(1), Default::default()))); + Self::deposit_event(Event::MigrationCompleted { index }); + }, + Err(err) => { + log::error!("[Block {n}] Migration {index} failed: {err:?}"); + Cursor::::kill(); + // TODO: handle errors + Cursor::::set(Some((index.saturating_add(1), Default::default()))); + Self::deposit_event(Event::MigrationFailed { index }); + }, + } + + meter.consumed + } + } + + #[pallet::call] + impl Pallet { + #[pallet::call_index(0)] + #[pallet::weight(0)] + pub fn force_set_cursor( + origin: OriginFor, + cursor: Option<(u32, SteppedMigrationCursor)>, + ) -> DispatchResult { + ensure_root(origin)?; + Cursor::::set(cursor); + Ok(()) + } + } +} diff --git a/frame/migrations/src/mock.rs b/frame/migrations/src/mock.rs new file mode 100644 index 0000000000000..46cb127d1499e --- /dev/null +++ b/frame/migrations/src/mock.rs @@ -0,0 +1,113 @@ +// This file is part of Substrate. + +// Copyright (C) Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: Apache-2.0 + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#![cfg(test)] + +//use crate::GetMigrations; +use codec::{Decode, Encode}; +use frame_support::{ + migrations::*, + traits::{ConstU16, ConstU64}, + weights::{Weight, WeightMeter}, +}; +use sp_core::H256; +use sp_runtime::{ + testing::Header, + traits::{BlakeTwo256, IdentityLookup}, +}; + +type UncheckedExtrinsic = frame_system::mocking::MockUncheckedExtrinsic; +type Block = frame_system::mocking::MockBlock; + +// Configure a mock runtime to test the pallet. +frame_support::construct_runtime!( + pub enum Test where + Block = Block, + NodeBlock = Block, + UncheckedExtrinsic = UncheckedExtrinsic, + { + System: frame_system, + Migrations: crate, + } +); + +impl frame_system::Config for Test { + type BaseCallFilter = frame_support::traits::Everything; + type BlockWeights = (); + type BlockLength = (); + type DbWeight = (); + type RuntimeOrigin = RuntimeOrigin; + type RuntimeCall = RuntimeCall; + type Index = u64; + type BlockNumber = u64; + type Hash = H256; + type Hashing = BlakeTwo256; + type AccountId = u64; + type Lookup = IdentityLookup; + type Header = Header; + type RuntimeEvent = RuntimeEvent; + type BlockHashCount = ConstU64<250>; + type Version = (); + type PalletInfo = PalletInfo; + type AccountData = (); + type OnNewAccount = (); + type OnKilledAccount = (); + type SystemWeightInfo = (); + type SS58Prefix = ConstU16<42>; + type OnSetCode = (); + type MaxConsumers = frame_support::traits::ConstU32<16>; +} + +frame_support::parameter_types! { + pub const ServiceWeight: Weight = Weight::MAX; + /// Stepped migrations need to be allocated as objects. + /// + /// This is different from the normal compile-time tuple config, but allows them to carry + /// configuration. + pub SteppedMigrations: Vec> = vec![ + Box::new(MockedMigrateToV1(1)), + Box::new(MockedMigrateToV1(2)), + ]; +} + +impl crate::Config for Test { + type RuntimeEvent = RuntimeEvent; + type ServiceWeight = ServiceWeight; + type Migrations = SteppedMigrations; +} + +// Build genesis storage according to the mock runtime. +pub fn new_test_ext() -> sp_io::TestExternalities { + frame_system::GenesisConfig::default().build_storage::().unwrap().into() +} + +pub struct MockedMigrateToV1(u32); +impl SteppedMigration for MockedMigrateToV1 { + fn step( + &self, + cursor: &Option, + meter: &mut WeightMeter, + ) -> Result, SteppedMigrationError> { + let mut count: u32 = + cursor.as_ref().and_then(|c| Decode::decode(&mut &c[..]).ok()).unwrap_or(0); + if count == self.0 { + return Ok(None) + } + count += 1; + Ok(Some(count.encode().try_into().unwrap())) + } +} diff --git a/frame/migrations/src/tests.rs b/frame/migrations/src/tests.rs new file mode 100644 index 0000000000000..897a4d8ae97c9 --- /dev/null +++ b/frame/migrations/src/tests.rs @@ -0,0 +1,61 @@ +// This file is part of Substrate. + +// Copyright (C) Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: Apache-2.0 + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#![cfg(test)] + +use crate::{mock::*, Error}; +use frame_support::{ + assert_noop, assert_ok, + traits::{OnInitialize, OnRuntimeUpgrade}, + weights::Weight, +}; + +/* +log output: + + [Block 0] Advancing migration 0. + [Block 0] Migration 0 advanced. + [Block 1] Advancing migration 0. + [Block 1] Migration 0 done. + [Block 2] Advancing migration 1. + [Block 2] Migration 1 advanced. + [Block 3] Advancing migration 1. + [Block 3] Migration 1 advanced. + [Block 4] Advancing migration 1. + [Block 4] Migration 1 done. + [Block 5] All migrations processed (2 >= 2). + [Block 6] Nothing to do: waiting for cursor to become `Some`. + [Block 7] Nothing to do: waiting for cursor to become `Some`. + [Block 8] Nothing to do: waiting for cursor to become `Some`. + [Block 9] Nothing to do: waiting for cursor to become `Some`. +*/ +#[test] +fn basic_works() { + sp_tracing::try_init_simple(); + + new_test_ext().execute_with(|| { + System::set_block_number(1); + + Migrations::on_runtime_upgrade(); + + for i in 0..10 { + Migrations::on_initialize(i); + } + + dbg!(System::events()); + }); +} diff --git a/frame/support/src/migrations.rs b/frame/support/src/migrations.rs index 8bda2662a237e..99d4968ef1e3f 100644 --- a/frame/support/src/migrations.rs +++ b/frame/support/src/migrations.rs @@ -18,8 +18,9 @@ #[cfg(feature = "try-runtime")] use crate::storage::unhashed::contains_prefixed_key; use crate::{ + storage::transactional::with_transaction_opaque_err, traits::{GetStorageVersion, NoStorageVersionSet, PalletInfoAccess, StorageVersion}, - weights::{RuntimeDbWeight, Weight}, + weights::{RuntimeDbWeight, Weight, WeightMeter}, }; use impl_trait_for_tuples::impl_for_tuples; use sp_core::Get; @@ -209,3 +210,64 @@ impl, DbWeight: Get> frame_support::traits Ok(()) } } + +/// A migration that can proceed in multiple steps. +pub trait SteppedMigration { + /// Try to migrate as much as possible with the given weight. + /// + /// **ANY STORAGE CHANGES MUST BE ROLLED-BACK BY THE CALLER UPON ERROR.** This is necessary + /// since the caller cannot return a cursor in the error case. `Self::transactional_step` is + /// provided as convenience for a caller. A cursor of `None` implies that the migration is at + /// its end. TODO: Think about iterator `fuse` requirement. + fn step( + &self, + cursor: &Option, + meter: &mut WeightMeter, + ) -> Result, SteppedMigrationError>; + + /// Same as [`Self::step`], but rolls back pending changes in the error case. + fn transactional_step( + &self, + mut cursor: Option, + meter: &mut WeightMeter, + ) -> Result, SteppedMigrationError> { + with_transaction_opaque_err(move || match self.step(&cursor, meter) { + Ok(new_cursor) => { + cursor = new_cursor; + sp_api::TransactionOutcome::Commit(Ok(cursor)) + }, + Err(err) => sp_api::TransactionOutcome::Rollback(Err(err)), + }) + .map_err(|()| SteppedMigrationError::Internal)? + } +} + +/// An opaque cursor that defines the "position" or a migration. +pub type SteppedMigrationCursor = crate::BoundedVec>; + +#[derive(Debug)] +pub enum SteppedMigrationError { + // Transient errors: + /// The remaining weight is not enough to do anything. + /// + /// Can be resolved by calling with at least `required` weight. Note that calling it with + /// exactly `required` weight could cause it to not make any progress. + InsufficientWeight { + required: Weight, + }, + /// Implementation specific error that should resolve itself at a later point in time. + /// + /// The number of re-tries can be decided upon by the caller. `inner` is undefined. + Transient { + inner: u8, + }, + // permanent errors: + /// The migration encountered a permanent error and cannot continue. + /// + /// This can happen if the storage is corrupted or an assumption got invalidated while the + /// migration was running. + Permanent { + inner: u8, + }, + Internal, +} diff --git a/frame/support/src/storage/transactional.rs b/frame/support/src/storage/transactional.rs index d42e1809e9129..0671db4a3a86b 100644 --- a/frame/support/src/storage/transactional.rs +++ b/frame/support/src/storage/transactional.rs @@ -127,6 +127,22 @@ where } } +/// Same as [`with_transaction`] but casts any internal error to `()`. +/// +/// This rids `E` of the `From` bound that is required by `with_transaction`. +pub fn with_transaction_opaque_err(f: F) -> Result, ()> +where + F: FnOnce() -> TransactionOutcome>, +{ + with_transaction(move || -> TransactionOutcome, DispatchError>> { + match f() { + TransactionOutcome::Commit(res) => TransactionOutcome::Commit(Ok(res)), + TransactionOutcome::Rollback(res) => TransactionOutcome::Rollback(Ok(res)), + } + }) + .map_err(|_| ()) +} + /// Same as [`with_transaction`] but without a limit check on nested transactional layers. /// /// This is mostly for backwards compatibility before there was a transactional layer limit. From 283d51cced7fd72944ba02f825b02a0a614771ec Mon Sep 17 00:00:00 2001 From: Oliver Tale-Yazdi Date: Tue, 30 May 2023 11:55:06 +0200 Subject: [PATCH 02/63] Cleanup Signed-off-by: Oliver Tale-Yazdi --- frame/migrations/src/lib.rs | 20 ++++++++++++----- frame/migrations/src/mock.rs | 39 +++++++++++++++++---------------- frame/migrations/src/tests.rs | 28 +++++++++++------------ frame/support/src/migrations.rs | 13 ++++------- 4 files changed, 53 insertions(+), 47 deletions(-) diff --git a/frame/migrations/src/lib.rs b/frame/migrations/src/lib.rs index 70ef623c80dcd..b99bfabe326fe 100644 --- a/frame/migrations/src/lib.rs +++ b/frame/migrations/src/lib.rs @@ -43,8 +43,13 @@ pub mod pallet { pub trait Config: frame_system::Config { type RuntimeEvent: From> + IsType<::RuntimeEvent>; + /// All the multi-block migrations to run. + /// + /// Should only be updated in a runtime-upgrade once all the old ones have completed. (Check + /// `Cursor` for `None`). type Migrations: Get>>; + /// The weight to spend each block to execute migrations. type ServiceWeight: Get; } @@ -54,19 +59,21 @@ pub mod pallet { #[pallet::event] #[pallet::generate_deposit(pub(super) fn deposit_event)] pub enum Event { + /// Runtime upgrade started. UpgradeStarted, + /// Runtime upgrade completed with `migrations`. UpgradeCompleted { migrations: u32 }, - + /// Migration `index` made progress. MigrationAdvanced { index: u32 }, + /// Migration `index` completed. MigrationCompleted { index: u32 }, - MigrationFailed { index: u32 }, // TODO add `inner` error + /// Migration `index` failed. TODO add `inner` error + MigrationFailed { index: u32 }, } // Errors inform users that something went wrong. #[pallet::error] - pub enum Error { - TooLarge, // Errors cannot have fields. - } + pub enum Error {} #[pallet::hooks] impl Hooks> for Pallet { @@ -124,6 +131,9 @@ pub mod pallet { #[pallet::call] impl Pallet { + /// Allows root to set the cursor to any value. + /// + /// Should normally not be needed and is only in place as emergency measure. #[pallet::call_index(0)] #[pallet::weight(0)] pub fn force_set_cursor( diff --git a/frame/migrations/src/mock.rs b/frame/migrations/src/mock.rs index 46cb127d1499e..3a8bb633bf6c4 100644 --- a/frame/migrations/src/mock.rs +++ b/frame/migrations/src/mock.rs @@ -72,6 +72,24 @@ impl frame_system::Config for Test { type MaxConsumers = frame_support::traits::ConstU32<16>; } +/// Succeeds after `inner` steps. +pub struct MockedMigrate(u32); +impl SteppedMigration for MockedMigrate { + fn step( + &self, + cursor: &Option, + meter: &mut WeightMeter, + ) -> Result, SteppedMigrationError> { + let mut count: u32 = + cursor.as_ref().and_then(|c| Decode::decode(&mut &c[..]).ok()).unwrap_or(0); + if count == self.0 { + return Ok(None) + } + count += 1; + Ok(Some(count.encode().try_into().unwrap())) + } +} + frame_support::parameter_types! { pub const ServiceWeight: Weight = Weight::MAX; /// Stepped migrations need to be allocated as objects. @@ -79,8 +97,8 @@ frame_support::parameter_types! { /// This is different from the normal compile-time tuple config, but allows them to carry /// configuration. pub SteppedMigrations: Vec> = vec![ - Box::new(MockedMigrateToV1(1)), - Box::new(MockedMigrateToV1(2)), + Box::new(MockedMigrate(1)), + Box::new(MockedMigrate(2)), ]; } @@ -94,20 +112,3 @@ impl crate::Config for Test { pub fn new_test_ext() -> sp_io::TestExternalities { frame_system::GenesisConfig::default().build_storage::().unwrap().into() } - -pub struct MockedMigrateToV1(u32); -impl SteppedMigration for MockedMigrateToV1 { - fn step( - &self, - cursor: &Option, - meter: &mut WeightMeter, - ) -> Result, SteppedMigrationError> { - let mut count: u32 = - cursor.as_ref().and_then(|c| Decode::decode(&mut &c[..]).ok()).unwrap_or(0); - if count == self.0 { - return Ok(None) - } - count += 1; - Ok(Some(count.encode().try_into().unwrap())) - } -} diff --git a/frame/migrations/src/tests.rs b/frame/migrations/src/tests.rs index 897a4d8ae97c9..9a1aaf01a0583 100644 --- a/frame/migrations/src/tests.rs +++ b/frame/migrations/src/tests.rs @@ -27,20 +27,20 @@ use frame_support::{ /* log output: - [Block 0] Advancing migration 0. - [Block 0] Migration 0 advanced. - [Block 1] Advancing migration 0. - [Block 1] Migration 0 done. - [Block 2] Advancing migration 1. - [Block 2] Migration 1 advanced. - [Block 3] Advancing migration 1. - [Block 3] Migration 1 advanced. - [Block 4] Advancing migration 1. - [Block 4] Migration 1 done. - [Block 5] All migrations processed (2 >= 2). - [Block 6] Nothing to do: waiting for cursor to become `Some`. - [Block 7] Nothing to do: waiting for cursor to become `Some`. - [Block 8] Nothing to do: waiting for cursor to become `Some`. + [Block 0] Advancing migration 0. + [Block 0] Migration 0 advanced. + [Block 1] Advancing migration 0. + [Block 1] Migration 0 done. + [Block 2] Advancing migration 1. + [Block 2] Migration 1 advanced. + [Block 3] Advancing migration 1. + [Block 3] Migration 1 advanced. + [Block 4] Advancing migration 1. + [Block 4] Migration 1 done. + [Block 5] All migrations processed (2 >= 2). + [Block 6] Nothing to do: waiting for cursor to become `Some`. + [Block 7] Nothing to do: waiting for cursor to become `Some`. + [Block 8] Nothing to do: waiting for cursor to become `Some`. [Block 9] Nothing to do: waiting for cursor to become `Some`. */ #[test] diff --git a/frame/support/src/migrations.rs b/frame/support/src/migrations.rs index 99d4968ef1e3f..c86f50c59262a 100644 --- a/frame/support/src/migrations.rs +++ b/frame/support/src/migrations.rs @@ -252,22 +252,17 @@ pub enum SteppedMigrationError { /// /// Can be resolved by calling with at least `required` weight. Note that calling it with /// exactly `required` weight could cause it to not make any progress. - InsufficientWeight { - required: Weight, - }, + InsufficientWeight { required: Weight }, /// Implementation specific error that should resolve itself at a later point in time. /// /// The number of re-tries can be decided upon by the caller. `inner` is undefined. - Transient { - inner: u8, - }, + Transient { inner: u8 }, // permanent errors: /// The migration encountered a permanent error and cannot continue. /// /// This can happen if the storage is corrupted or an assumption got invalidated while the /// migration was running. - Permanent { - inner: u8, - }, + Permanent { inner: u8 }, + /// An internal error that should never happen. Internal, } From 7c53a74aacde04a2e55375947d9e8975794bdefb Mon Sep 17 00:00:00 2001 From: Oliver Tale-Yazdi Date: Tue, 30 May 2023 11:57:06 +0200 Subject: [PATCH 03/63] Comment Signed-off-by: Oliver Tale-Yazdi --- frame/migrations/src/lib.rs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/frame/migrations/src/lib.rs b/frame/migrations/src/lib.rs index b99bfabe326fe..3c449426a9dfa 100644 --- a/frame/migrations/src/lib.rs +++ b/frame/migrations/src/lib.rs @@ -53,6 +53,9 @@ pub mod pallet { type ServiceWeight: Get; } + /// The currently active migration to run and its cursor. + /// + /// `None` indicates that no migration process is running. #[pallet::storage] pub type Cursor = StorageValue<_, (u32, SteppedMigrationCursor), OptionQuery>; From edb005c7e3fb87852a202d7e555a52d7c7894a68 Mon Sep 17 00:00:00 2001 From: Oliver Tale-Yazdi Date: Wed, 31 May 2023 13:39:54 +0200 Subject: [PATCH 04/63] Suspension and benches Signed-off-by: Oliver Tale-Yazdi --- Cargo.lock | 5 +- bin/node/runtime/Cargo.toml | 4 + bin/node/runtime/src/lib.rs | 16 ++++ frame/executive/Cargo.toml | 2 + frame/executive/src/lib.rs | 17 ++++ frame/migrations/Cargo.toml | 22 ++++-- frame/migrations/src/benchmarking.rs | 68 ++++++++++++++++ frame/migrations/src/lib.rs | 28 ++++--- frame/migrations/src/mock.rs | 4 +- frame/migrations/src/tests.rs | 2 +- frame/migrations/src/weights.rs | 111 +++++++++++++++++++++++++++ frame/support/src/migrations.rs | 17 +++- frame/system/src/lib.rs | 20 +++++ primitives/storage/src/lib.rs | 2 + 14 files changed, 295 insertions(+), 23 deletions(-) create mode 100644 frame/migrations/src/benchmarking.rs create mode 100644 frame/migrations/src/weights.rs diff --git a/Cargo.lock b/Cargo.lock index c9ac3723944e3..130bdfe6b835b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2627,6 +2627,7 @@ dependencies = [ "sp-io", "sp-runtime", "sp-std", + "sp-storage", "sp-tracing", "sp-version", ] @@ -3935,6 +3936,7 @@ dependencies = [ "pallet-lottery", "pallet-membership", "pallet-message-queue", + "pallet-migrations", "pallet-mmr", "pallet-multisig", "pallet-nft-fractionalization", @@ -6609,7 +6611,7 @@ dependencies = [ [[package]] name = "pallet-migrations" -version = "0.1.0" +version = "1.0.0" dependencies = [ "frame-benchmarking", "frame-support", @@ -6621,6 +6623,7 @@ dependencies = [ "sp-core", "sp-io", "sp-runtime", + "sp-std", "sp-tracing", ] diff --git a/bin/node/runtime/Cargo.toml b/bin/node/runtime/Cargo.toml index 6e1b15931bd8e..9a93538346a4a 100644 --- a/bin/node/runtime/Cargo.toml +++ b/bin/node/runtime/Cargo.toml @@ -85,6 +85,7 @@ pallet-identity = { version = "4.0.0-dev", default-features = false, path = "../ pallet-lottery = { version = "4.0.0-dev", default-features = false, path = "../../../frame/lottery" } pallet-membership = { version = "4.0.0-dev", default-features = false, path = "../../../frame/membership" } pallet-message-queue = { version = "7.0.0-dev", default-features = false, path = "../../../frame/message-queue" } +pallet-migrations = { version = "1.0.0", default-features = false, path = "../../../frame/migrations" } pallet-mmr = { version = "4.0.0-dev", default-features = false, path = "../../../frame/merkle-mountain-range" } pallet-multisig = { version = "4.0.0-dev", default-features = false, path = "../../../frame/multisig" } pallet-nfts = { version = "4.0.0-dev", default-features = false, path = "../../../frame/nfts" } @@ -171,6 +172,7 @@ std = [ "pallet-lottery/std", "pallet-membership/std", "pallet-message-queue/std", + "pallet-migrations/std", "pallet-mmr/std", "pallet-multisig/std", "pallet-nomination-pools/std", @@ -265,6 +267,7 @@ runtime-benchmarks = [ "pallet-lottery/runtime-benchmarks", "pallet-membership/runtime-benchmarks", "pallet-message-queue/runtime-benchmarks", + "pallet-migrations/runtime-benchmarks", "pallet-mmr/runtime-benchmarks", "pallet-multisig/runtime-benchmarks", "pallet-nomination-pools-benchmarking/runtime-benchmarks", @@ -328,6 +331,7 @@ try-runtime = [ "pallet-lottery/try-runtime", "pallet-membership/try-runtime", "pallet-message-queue/try-runtime", + "pallet-migrations/try-runtime", "pallet-mmr/try-runtime", "pallet-multisig/try-runtime", "pallet-nomination-pools/try-runtime", diff --git a/bin/node/runtime/src/lib.rs b/bin/node/runtime/src/lib.rs index 6dc9841f6b44f..547452bb1cbf6 100644 --- a/bin/node/runtime/src/lib.rs +++ b/bin/node/runtime/src/lib.rs @@ -30,6 +30,7 @@ use frame_support::{ construct_runtime, dispatch::DispatchClass, instances::{Instance1, Instance2}, + migrations::SteppedMigration, ord_parameter_types, pallet_prelude::Get, parameter_types, @@ -1855,6 +1856,19 @@ impl pallet_statement::Config for Runtime { type MaxAllowedBytes = MaxAllowedBytes; } +parameter_types! { + pub MbmServiceWeight: Weight = Perbill::from_percent(80) * RuntimeBlockWeights::get().max_block; + pub const MbmMigrations: Vec> = vec![]; +} + +impl pallet_migrations::Config for Runtime { + type RuntimeEvent = RuntimeEvent; + type Migrations = MbmMigrations; // same as () + type Suspender = frame_system::Pallet; + type ServiceWeight = MbmServiceWeight; + type WeightInfo = pallet_migrations::weights::SubstrateWeight; +} + construct_runtime!( pub struct Runtime where Block = Block, @@ -1930,6 +1944,7 @@ construct_runtime!( MessageQueue: pallet_message_queue, Pov: frame_benchmarking_pallet_pov, Statement: pallet_statement, + MultiBlockMigrations: pallet_migrations, } ); @@ -2029,6 +2044,7 @@ mod benches { [pallet_lottery, Lottery] [pallet_membership, TechnicalMembership] [pallet_message_queue, MessageQueue] + [pallet_migrations, MultiBlockMigrations] [pallet_mmr, Mmr] [pallet_multisig, Multisig] [pallet_nomination_pools, NominationPoolsBench::] diff --git a/frame/executive/Cargo.toml b/frame/executive/Cargo.toml index 967b9899b4f9c..2f2a8cca2cd43 100644 --- a/frame/executive/Cargo.toml +++ b/frame/executive/Cargo.toml @@ -24,6 +24,7 @@ sp-core = { version = "8.0.0", default-features = false, path = "../../primitive sp-io = { version = "8.0.0", default-features = false, path = "../../primitives/io" } sp-runtime = { version = "8.0.0", default-features = false, path = "../../primitives/runtime" } sp-std = { version = "6.0.0", default-features = false, path = "../../primitives/std" } +sp-storage = { version = "8.0.0", default-features = false, path = "../../primitives/storage" } sp-tracing = { version = "7.0.0", default-features = false, path = "../../primitives/tracing" } [dev-dependencies] @@ -47,6 +48,7 @@ std = [ "sp-io/std", "sp-runtime/std", "sp-std/std", + "sp-storage/std", "sp-tracing/std", ] try-runtime = ["frame-support/try-runtime", "frame-try-runtime/try-runtime", "sp-runtime/try-runtime"] diff --git a/frame/executive/src/lib.rs b/frame/executive/src/lib.rs index 31cbb0ee7ba0d..1040b3544c864 100644 --- a/frame/executive/src/lib.rs +++ b/frame/executive/src/lib.rs @@ -671,6 +671,23 @@ where } } +pub struct ExtrinsicSuspender; +impl frame_support::migrations::ExtrinsicSuspender for ExtrinsicSuspender { + fn suspend() { + sp_io::storage::set(sp_storage::well_known_keys::EXTRINSICS_PAUSED, b""); + } + + fn resume() { + sp_io::storage::clear(sp_storage::well_known_keys::EXTRINSICS_PAUSED); + } +} + +impl frame_support::migrations::ExtrinsicSuspenderQuery for ExtrinsicSuspender { + fn is_suspended() -> bool { + sp_io::storage::exists(sp_storage::well_known_keys::EXTRINSICS_PAUSED) + } +} + #[cfg(test)] mod tests { use super::*; diff --git a/frame/migrations/Cargo.toml b/frame/migrations/Cargo.toml index 45fbf3060db45..997754f4cc788 100644 --- a/frame/migrations/Cargo.toml +++ b/frame/migrations/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "pallet-migrations" -version = "0.1.0" +version = "1.0.0" description = "FRAME pallet to execute multi-block migrations." authors = ["Parity Technologies "] homepage = "https://substrate.io" @@ -20,18 +20,26 @@ frame-support = { version = "4.0.0-dev", default-features = false, path = "../su frame-system = { version = "4.0.0-dev", default-features = false, path = "../system" } impl-trait-for-tuples = "0.2.2" log = "0.4.18" -sp-tracing = { version = "7.0.0", path = "../../primitives/tracing" } +sp-std = { version = "6.0.0", path = "../../primitives/std", default-features = false } [dev-dependencies] -sp-core = { version = "8.0.0", path = "../../primitives/core" } -sp-io = { version = "8.0.0", path = "../../primitives/io" } -sp-runtime = { version = "8.0.0", path = "../../primitives/runtime" } +sp-tracing = { version = "7.0.0", path = "../../primitives/tracing", features = [ "std" ] } +sp-core = { version = "8.0.0", path = "../../primitives/core", features = [ "std" ] } +sp-io = { version = "8.0.0", path = "../../primitives/io", features = [ "std" ] } +sp-runtime = { version = "8.0.0", path = "../../primitives/runtime", features = [ "std" ] } [features] default = ["std"] -std = ["codec/std", "frame-benchmarking?/std", "frame-support/std", "frame-system/std", "scale-info/std", "sp-runtime/std"] +std = [ + "codec/std", + "frame-benchmarking?/std", + "frame-support/std", + "frame-system/std", + "scale-info/std", + "sp-std/std", +] -runtime-benchmarks = ["frame-benchmarking/runtime-benchmarks", "frame-support/runtime-benchmarks", "frame-system/runtime-benchmarks", "sp-runtime/runtime-benchmarks"] +runtime-benchmarks = ["frame-benchmarking/runtime-benchmarks", "frame-support/runtime-benchmarks", "frame-system/runtime-benchmarks"] try-runtime = ["frame-support/try-runtime"] diff --git a/frame/migrations/src/benchmarking.rs b/frame/migrations/src/benchmarking.rs new file mode 100644 index 0000000000000..552e4848a60bb --- /dev/null +++ b/frame/migrations/src/benchmarking.rs @@ -0,0 +1,68 @@ +// This file is part of Substrate. + +// Copyright (C) Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: Apache-2.0 + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#![cfg(feature = "runtime-benchmarks")] + +use super::*; + +use frame_benchmarking::v2::*; +use frame_support::migrations::STEPPED_MIGRATION_CURSOR_LEN as CURSOR_LEN; +use frame_system::RawOrigin; + +#[benchmarks] +mod benches { + use super::*; + use frame_support::traits::Hooks; + + #[benchmark] + fn on_runtime_upgrade_bail() { + Cursor::::set(Some(cursor(0))); + assert!(Cursor::::exists()); + + #[block] + { + Pallet::::on_runtime_upgrade(); + } + } + + #[benchmark] + fn on_runtime_upgrade() { + assert!(!Cursor::::exists()); + + #[block] + { + Pallet::::on_runtime_upgrade(); + } + } + + /// Benchmarks the slowest path of `change_value`. + #[benchmark] + fn force_set_cursor() { + Cursor::::set(Some(cursor(0))); + + #[extrinsic_call] + _(RawOrigin::Root, Some(cursor(1))); + } + + fn cursor(i: u32) -> (u32, SteppedMigrationCursor) { + (u32::MAX - i, vec![1u8; CURSOR_LEN as usize].try_into().expect("Static length is good")) + } + + // Implements a test for each benchmark. Execute with: + // `cargo test -p pallet-migrations --features runtime-benchmarks`. + impl_benchmark_test_suite!(Pallet, crate::mock::new_test_ext(), crate::mock::Test); +} diff --git a/frame/migrations/src/lib.rs b/frame/migrations/src/lib.rs index 3c449426a9dfa..5ea5dbdf6ad12 100644 --- a/frame/migrations/src/lib.rs +++ b/frame/migrations/src/lib.rs @@ -19,8 +19,12 @@ pub use pallet::*; +mod benchmarking; mod mock; mod tests; +pub mod weights; + +pub use weights::WeightInfo; use frame_support::{ defensive, @@ -30,11 +34,14 @@ use frame_support::{ BoundedVec, }; +const LOG_TARGET: &'static str = "runtime::migrations"; + #[frame_support::pallet] pub mod pallet { use super::*; use frame_support::pallet_prelude::*; use frame_system::pallet_prelude::*; + use sp_std::{boxed::Box, vec::Vec}; #[pallet::pallet] pub struct Pallet(_); @@ -49,8 +56,12 @@ pub mod pallet { /// `Cursor` for `None`). type Migrations: Get>>; + type Suspender: ExtrinsicSuspender + ExtrinsicSuspenderQuery; + /// The weight to spend each block to execute migrations. type ServiceWeight: Get; + + type WeightInfo: WeightInfo; } /// The currently active migration to run and its cursor. @@ -74,15 +85,11 @@ pub mod pallet { MigrationFailed { index: u32 }, } - // Errors inform users that something went wrong. - #[pallet::error] - pub enum Error {} - #[pallet::hooks] impl Hooks> for Pallet { fn on_runtime_upgrade() -> Weight { if Cursor::::exists() { - defensive!("Migrations in progress will be aborted."); + log::error!(target: LOG_TARGET, "Defensive: migrations in progress will be aborted."); return Default::default() // FAIL-CI } Cursor::::set(Some(Default::default())); @@ -95,32 +102,27 @@ pub mod pallet { let mut meter = WeightMeter::from_limit(T::ServiceWeight::get()); let Some((index, inner_cursor)) = Cursor::::get() else { - log::info!("[Block {n}] Nothing to do: waiting for cursor to become `Some`."); + log::debug!(target: LOG_TARGET, "[Block {n:?}] Nothing to do: waiting for cursor to become `Some`."); return meter.consumed; }; let migrations = T::Migrations::get(); let Some(migration) = migrations.get(index as usize) else { - log::info!("[Block {n}] All migrations processed ({} >= {}).", index, migrations.len()); Cursor::::kill(); Self::deposit_event(Event::UpgradeCompleted { migrations: migrations.len() as u32 }); return meter.consumed; }; - log::info!("[Block {n}] Advancing migration {index}."); match migration.transactional_step(Some(inner_cursor), &mut meter) { Ok(Some(inner_cursor)) => { - log::info!("[Block {n}] Migration {index} advanced."); Cursor::::set(Some((index, inner_cursor))); Self::deposit_event(Event::MigrationAdvanced { index }); }, Ok(None) => { - log::info!("[Block {n}] Migration {index} done."); Cursor::::set(Some((index.saturating_add(1), Default::default()))); Self::deposit_event(Event::MigrationCompleted { index }); }, Err(err) => { - log::error!("[Block {n}] Migration {index} failed: {err:?}"); Cursor::::kill(); // TODO: handle errors Cursor::::set(Some((index.saturating_add(1), Default::default()))); @@ -138,13 +140,15 @@ pub mod pallet { /// /// Should normally not be needed and is only in place as emergency measure. #[pallet::call_index(0)] - #[pallet::weight(0)] + #[pallet::weight((0, DispatchClass::Mandatory))] pub fn force_set_cursor( origin: OriginFor, cursor: Option<(u32, SteppedMigrationCursor)>, ) -> DispatchResult { ensure_root(origin)?; + Cursor::::set(cursor); + Ok(()) } } diff --git a/frame/migrations/src/mock.rs b/frame/migrations/src/mock.rs index 3a8bb633bf6c4..93265981bd870 100644 --- a/frame/migrations/src/mock.rs +++ b/frame/migrations/src/mock.rs @@ -104,8 +104,10 @@ frame_support::parameter_types! { impl crate::Config for Test { type RuntimeEvent = RuntimeEvent; - type ServiceWeight = ServiceWeight; type Migrations = SteppedMigrations; + type Suspender = frame_system::Pallet; + type ServiceWeight = ServiceWeight; + type WeightInfo = (); } // Build genesis storage according to the mock runtime. diff --git a/frame/migrations/src/tests.rs b/frame/migrations/src/tests.rs index 9a1aaf01a0583..6f8102b454ea0 100644 --- a/frame/migrations/src/tests.rs +++ b/frame/migrations/src/tests.rs @@ -17,7 +17,7 @@ #![cfg(test)] -use crate::{mock::*, Error}; +use crate::mock::*; use frame_support::{ assert_noop, assert_ok, traits::{OnInitialize, OnRuntimeUpgrade}, diff --git a/frame/migrations/src/weights.rs b/frame/migrations/src/weights.rs new file mode 100644 index 0000000000000..aef7891d60ff7 --- /dev/null +++ b/frame/migrations/src/weights.rs @@ -0,0 +1,111 @@ + +//! Autogenerated weights for pallet_migrations +//! +//! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 4.0.0-dev +//! DATE: 2023-05-31, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! WORST CASE MAP SIZE: `1000000` +//! HOSTNAME: `i9`, CPU: `13th Gen Intel(R) Core(TM) i9-13900K` +//! EXECUTION: None, WASM-EXECUTION: Compiled, CHAIN: None, DB CACHE: 1024 + +// Executed Command: +// ./target/release/substrate +// benchmark +// pallet +// --pallet +// pallet_migrations +// --extrinsic +// +// --output +// frame/migrations/src/weights.rs +// --template +// .maintain/frame-weight-template.hbs +// --steps +// 50 +// --repeat +// 20 + +#![cfg_attr(rustfmt, rustfmt_skip)] +#![allow(unused_parens)] +#![allow(unused_imports)] +#![allow(missing_docs)] + +use frame_support::{traits::Get, weights::{Weight, constants::RocksDbWeight}}; +use core::marker::PhantomData; + +/// Weight functions needed for pallet_migrations. +pub trait WeightInfo { + fn on_runtime_upgrade_bail() -> Weight; + fn on_runtime_upgrade() -> Weight; + fn force_set_cursor() -> Weight; +} + +/// Weights for pallet_migrations using the Substrate node and recommended hardware. +pub struct SubstrateWeight(PhantomData); +impl WeightInfo for SubstrateWeight { + /// Storage: MultiBlockMigrations Cursor (r:1 w:0) + /// Proof: MultiBlockMigrations Cursor (max_values: Some(1), max_size: Some(1030), added: 1525, mode: MaxEncodedLen) + fn on_runtime_upgrade_bail() -> Weight { + // Proof Size summary in bytes: + // Measured: `165` + // Estimated: `2515` + // Minimum execution time: 3_001_000 picoseconds. + Weight::from_parts(3_217_000, 2515) + .saturating_add(T::DbWeight::get().reads(1_u64)) + } + /// Storage: MultiBlockMigrations Cursor (r:1 w:1) + /// Proof: MultiBlockMigrations Cursor (max_values: Some(1), max_size: Some(1030), added: 1525, mode: MaxEncodedLen) + fn on_runtime_upgrade() -> Weight { + // Proof Size summary in bytes: + // Measured: `109` + // Estimated: `2515` + // Minimum execution time: 2_524_000 picoseconds. + Weight::from_parts(2_637_000, 2515) + .saturating_add(T::DbWeight::get().reads(1_u64)) + .saturating_add(T::DbWeight::get().writes(1_u64)) + } + /// Storage: MultiBlockMigrations Cursor (r:0 w:1) + /// Proof: MultiBlockMigrations Cursor (max_values: Some(1), max_size: Some(1030), added: 1525, mode: MaxEncodedLen) + fn force_set_cursor() -> Weight { + // Proof Size summary in bytes: + // Measured: `0` + // Estimated: `0` + // Minimum execution time: 1_035_000 picoseconds. + Weight::from_parts(1_183_000, 0) + .saturating_add(T::DbWeight::get().writes(1_u64)) + } +} + +// For backwards compatibility and tests +impl WeightInfo for () { + /// Storage: MultiBlockMigrations Cursor (r:1 w:0) + /// Proof: MultiBlockMigrations Cursor (max_values: Some(1), max_size: Some(1030), added: 1525, mode: MaxEncodedLen) + fn on_runtime_upgrade_bail() -> Weight { + // Proof Size summary in bytes: + // Measured: `165` + // Estimated: `2515` + // Minimum execution time: 3_001_000 picoseconds. + Weight::from_parts(3_217_000, 2515) + .saturating_add(RocksDbWeight::get().reads(1_u64)) + } + /// Storage: MultiBlockMigrations Cursor (r:1 w:1) + /// Proof: MultiBlockMigrations Cursor (max_values: Some(1), max_size: Some(1030), added: 1525, mode: MaxEncodedLen) + fn on_runtime_upgrade() -> Weight { + // Proof Size summary in bytes: + // Measured: `109` + // Estimated: `2515` + // Minimum execution time: 2_524_000 picoseconds. + Weight::from_parts(2_637_000, 2515) + .saturating_add(RocksDbWeight::get().reads(1_u64)) + .saturating_add(RocksDbWeight::get().writes(1_u64)) + } + /// Storage: MultiBlockMigrations Cursor (r:0 w:1) + /// Proof: MultiBlockMigrations Cursor (max_values: Some(1), max_size: Some(1030), added: 1525, mode: MaxEncodedLen) + fn force_set_cursor() -> Weight { + // Proof Size summary in bytes: + // Measured: `0` + // Estimated: `0` + // Minimum execution time: 1_035_000 picoseconds. + Weight::from_parts(1_183_000, 0) + .saturating_add(RocksDbWeight::get().writes(1_u64)) + } +} diff --git a/frame/support/src/migrations.rs b/frame/support/src/migrations.rs index c86f50c59262a..49dd7797d9e1e 100644 --- a/frame/support/src/migrations.rs +++ b/frame/support/src/migrations.rs @@ -242,8 +242,12 @@ pub trait SteppedMigration { } } +/// The maximal length in bytes of a cursor for a stepped migration. +pub const STEPPED_MIGRATION_CURSOR_LEN: u32 = 1024; + /// An opaque cursor that defines the "position" or a migration. -pub type SteppedMigrationCursor = crate::BoundedVec>; +pub type SteppedMigrationCursor = + crate::BoundedVec>; #[derive(Debug)] pub enum SteppedMigrationError { @@ -266,3 +270,14 @@ pub enum SteppedMigrationError { /// An internal error that should never happen. Internal, } + +/// Can be used to pause extrinsic inclusion across the whole runtime. +pub trait ExtrinsicSuspender { + /// Pause all extrinsics that are not mandatory. + fn suspend(); + fn resume(); +} + +pub trait ExtrinsicSuspenderQuery { + fn is_suspended() -> bool; +} diff --git a/frame/system/src/lib.rs b/frame/system/src/lib.rs index 64db2f46fd4eb..1948536e6f89e 100644 --- a/frame/system/src/lib.rs +++ b/frame/system/src/lib.rs @@ -89,6 +89,7 @@ use frame_support::{ extract_actual_pays_fee, extract_actual_weight, DispatchClass, DispatchInfo, DispatchResult, DispatchResultWithPostInfo, PerDispatchClass, }, + migrations::{ExtrinsicSuspender, ExtrinsicSuspenderQuery}, storage::{self, StorageStreamIter}, traits::{ ConstU32, Contains, EnsureOrigin, Get, HandleLifetime, OnKilledAccount, OnNewAccount, @@ -536,6 +537,9 @@ pub mod pallet { #[pallet::storage] pub(super) type ExtrinsicCount = StorageValue<_, u32>; + #[pallet::storage] + pub type ExtrinsicsSuspended = StorageValue<_, (), OptionQuery>; + /// The current weight for the block. #[pallet::storage] #[pallet::whitelist_storage] @@ -1748,6 +1752,22 @@ impl Lookup for ChainContext { } } +impl ExtrinsicSuspender for Pallet { + fn suspend() { + ExtrinsicsSuspended::::put(()); + } + + fn resume() { + ExtrinsicsSuspended::::kill(); + } +} + +impl ExtrinsicSuspenderQuery for Pallet { + fn is_suspended() -> bool { + ExtrinsicsSuspended::::exists() + } +} + /// Prelude to be used alongside pallet macro, for ease of use. pub mod pallet_prelude { pub use crate::{ensure_none, ensure_root, ensure_signed, ensure_signed_or_root}; diff --git a/primitives/storage/src/lib.rs b/primitives/storage/src/lib.rs index aa1bc8e305c9b..514f4f3d1cdac 100644 --- a/primitives/storage/src/lib.rs +++ b/primitives/storage/src/lib.rs @@ -204,6 +204,8 @@ pub mod well_known_keys { /// Encodes to `0x3a65787472696e7369635f696e646578`. pub const EXTRINSIC_INDEX: &[u8] = b":extrinsic_index"; + pub const EXTRINSICS_PAUSED: &[u8] = b":extrinsics_paused"; + /// Current intra-block entropy (a universally unique `[u8; 32]` value) is stored here. pub const INTRABLOCK_ENTROPY: &[u8] = b":intrablock_entropy"; From 8331fbe4e81dfc75c8c375aba5590d8df2218915 Mon Sep 17 00:00:00 2001 From: Oliver Tale-Yazdi Date: Wed, 31 May 2023 15:08:06 +0200 Subject: [PATCH 05/63] Add generic to Executive Signed-off-by: Oliver Tale-Yazdi --- frame/executive/src/lib.rs | 9 +++++++-- frame/support/src/migrations.rs | 9 +++++++++ 2 files changed, 16 insertions(+), 2 deletions(-) diff --git a/frame/executive/src/lib.rs b/frame/executive/src/lib.rs index 1040b3544c864..3b12e1be69e79 100644 --- a/frame/executive/src/lib.rs +++ b/frame/executive/src/lib.rs @@ -165,6 +165,7 @@ pub struct Executive< UnsignedValidator, AllPalletsWithSystem, OnRuntimeUpgrade = (), + ExtrinsicSuspender = (), >( PhantomData<( System, @@ -173,6 +174,7 @@ pub struct Executive< UnsignedValidator, AllPalletsWithSystem, OnRuntimeUpgrade, + ExtrinsicSuspender, )>, ); @@ -187,8 +189,9 @@ impl< + OnFinalize + OffchainWorker, COnRuntimeUpgrade: OnRuntimeUpgrade, + ExtrinsicSuspender: frame_support::migrations::ExtrinsicSuspenderQuery, > ExecuteBlock - for Executive + for Executive where Block::Extrinsic: Checkable + Codec, CheckedOf: Applyable + GetDispatchInfo, @@ -205,6 +208,7 @@ where UnsignedValidator, AllPalletsWithSystem, COnRuntimeUpgrade, + ExtrinsicSuspender, >::execute_block(block); } } @@ -378,7 +382,8 @@ impl< + OnFinalize + OffchainWorker, COnRuntimeUpgrade: OnRuntimeUpgrade, - > Executive + ExtrinsicSuspender: frame_support::migrations::ExtrinsicSuspenderQuery, + > Executive where Block::Extrinsic: Checkable + Codec, CheckedOf: Applyable + GetDispatchInfo, diff --git a/frame/support/src/migrations.rs b/frame/support/src/migrations.rs index 49dd7797d9e1e..694b3c7b3caad 100644 --- a/frame/support/src/migrations.rs +++ b/frame/support/src/migrations.rs @@ -281,3 +281,12 @@ pub trait ExtrinsicSuspender { pub trait ExtrinsicSuspenderQuery { fn is_suspended() -> bool; } + +impl ExtrinsicSuspender for () { + fn suspend() {} + fn resume() {} +} + +impl ExtrinsicSuspenderQuery for () { + fn is_suspended() -> bool { false } +} From c23259f88df137470c7ffd2dd42e36325eb3ec8c Mon Sep 17 00:00:00 2001 From: Oliver Tale-Yazdi Date: Wed, 31 May 2023 18:06:02 +0200 Subject: [PATCH 06/63] Cleanup Signed-off-by: Oliver Tale-Yazdi --- frame/executive/src/lib.rs | 56 ++++++----- frame/migrations/Cargo.toml | 7 +- frame/migrations/src/benchmarking.rs | 12 +++ frame/migrations/src/lib.rs | 133 +++++++++++++++++++-------- frame/migrations/src/mock.rs | 69 +++++++++++--- frame/migrations/src/tests.rs | 45 ++++----- frame/support/src/migrations.rs | 26 +----- frame/system/src/lib.rs | 20 ---- primitives/runtime/src/lib.rs | 4 + primitives/storage/src/lib.rs | 2 - 10 files changed, 229 insertions(+), 145 deletions(-) diff --git a/frame/executive/src/lib.rs b/frame/executive/src/lib.rs index 3b12e1be69e79..d6fc8e38c28ba 100644 --- a/frame/executive/src/lib.rs +++ b/frame/executive/src/lib.rs @@ -118,7 +118,9 @@ use codec::{Codec, Encode}; use frame_support::{ - dispatch::{DispatchClass, DispatchInfo, GetDispatchInfo, PostDispatchInfo}, + dispatch::{ + DispatchClass, DispatchInfo, GetDispatchInfo, PostDispatchInfo, WithPostDispatchInfo, + }, pallet_prelude::InvalidTransaction, traits::{ EnsureInherentsAreFirst, ExecuteBlock, OffchainWorker, OnFinalize, OnIdle, OnInitialize, @@ -133,7 +135,7 @@ use sp_runtime::{ ValidateUnsigned, Zero, }, transaction_validity::{TransactionSource, TransactionValidity}, - ApplyExtrinsicResult, + ApplyExtrinsicResult, DispatchError, }; use sp_std::{marker::PhantomData, prelude::*}; @@ -191,8 +193,15 @@ impl< COnRuntimeUpgrade: OnRuntimeUpgrade, ExtrinsicSuspender: frame_support::migrations::ExtrinsicSuspenderQuery, > ExecuteBlock - for Executive -where + for Executive< + System, + Block, + Context, + UnsignedValidator, + AllPalletsWithSystem, + COnRuntimeUpgrade, + ExtrinsicSuspender, + > where Block::Extrinsic: Checkable + Codec, CheckedOf: Applyable + GetDispatchInfo, CallOf: @@ -383,8 +392,16 @@ impl< + OffchainWorker, COnRuntimeUpgrade: OnRuntimeUpgrade, ExtrinsicSuspender: frame_support::migrations::ExtrinsicSuspenderQuery, - > Executive -where + > + Executive< + System, + Block, + Context, + UnsignedValidator, + AllPalletsWithSystem, + COnRuntimeUpgrade, + ExtrinsicSuspender, + > where Block::Extrinsic: Checkable + Codec, CheckedOf: Applyable + GetDispatchInfo, CallOf: @@ -569,7 +586,15 @@ where // Decode parameters and dispatch let dispatch_info = xt.get_dispatch_info(); - let r = Applyable::apply::(xt, &dispatch_info, encoded_len)?; + // Check whether we need to error because extrinsics are paused. + let r = if dispatch_info.class != DispatchClass::Mandatory && + ExtrinsicSuspender::is_suspended() + { + // no refunds + Err(DispatchError::Suspended.with_weight(dispatch_info.weight)) + } else { + Applyable::apply::(xt, &dispatch_info, encoded_len)? + }; // Mandatory(inherents) are not allowed to fail. // @@ -676,23 +701,6 @@ where } } -pub struct ExtrinsicSuspender; -impl frame_support::migrations::ExtrinsicSuspender for ExtrinsicSuspender { - fn suspend() { - sp_io::storage::set(sp_storage::well_known_keys::EXTRINSICS_PAUSED, b""); - } - - fn resume() { - sp_io::storage::clear(sp_storage::well_known_keys::EXTRINSICS_PAUSED); - } -} - -impl frame_support::migrations::ExtrinsicSuspenderQuery for ExtrinsicSuspender { - fn is_suspended() -> bool { - sp_io::storage::exists(sp_storage::well_known_keys::EXTRINSICS_PAUSED) - } -} - #[cfg(test)] mod tests { use super::*; diff --git a/frame/migrations/Cargo.toml b/frame/migrations/Cargo.toml index 997754f4cc788..b66e5bcc0a6bf 100644 --- a/frame/migrations/Cargo.toml +++ b/frame/migrations/Cargo.toml @@ -21,12 +21,12 @@ frame-system = { version = "4.0.0-dev", default-features = false, path = "../sys impl-trait-for-tuples = "0.2.2" log = "0.4.18" sp-std = { version = "6.0.0", path = "../../primitives/std", default-features = false } +sp-runtime = { version = "8.0.0", path = "../../primitives/runtime", default-features = false } [dev-dependencies] sp-tracing = { version = "7.0.0", path = "../../primitives/tracing", features = [ "std" ] } sp-core = { version = "8.0.0", path = "../../primitives/core", features = [ "std" ] } sp-io = { version = "8.0.0", path = "../../primitives/io", features = [ "std" ] } -sp-runtime = { version = "8.0.0", path = "../../primitives/runtime", features = [ "std" ] } [features] default = ["std"] @@ -38,8 +38,9 @@ std = [ "frame-system/std", "scale-info/std", "sp-std/std", + "sp-runtime/std", ] -runtime-benchmarks = ["frame-benchmarking/runtime-benchmarks", "frame-support/runtime-benchmarks", "frame-system/runtime-benchmarks"] +runtime-benchmarks = ["frame-benchmarking/runtime-benchmarks", "frame-support/runtime-benchmarks", "frame-system/runtime-benchmarks", "sp-runtime/runtime-benchmarks",] -try-runtime = ["frame-support/try-runtime"] +try-runtime = ["frame-support/try-runtime", "sp-runtime/try-runtime",] diff --git a/frame/migrations/src/benchmarking.rs b/frame/migrations/src/benchmarking.rs index 552e4848a60bb..1f90405739ab1 100644 --- a/frame/migrations/src/benchmarking.rs +++ b/frame/migrations/src/benchmarking.rs @@ -49,6 +49,18 @@ mod benches { } } + #[benchmark] + fn on_init_loop_base() { + Cursor::::set(Some(cursor(0))); + System::::set_block_number(1); + Pallet::::on_runtime_upgrade(); + + #[block] + { + Pallet::::on_initialize(1); + } + } + /// Benchmarks the slowest path of `change_value`. #[benchmark] fn force_set_cursor() { diff --git a/frame/migrations/src/lib.rs b/frame/migrations/src/lib.rs index 5ea5dbdf6ad12..65bc222d55ccf 100644 --- a/frame/migrations/src/lib.rs +++ b/frame/migrations/src/lib.rs @@ -26,16 +26,23 @@ pub mod weights; pub use weights::WeightInfo; +use codec::{Decode, Encode, MaxEncodedLen}; use frame_support::{ defensive, migrations::*, - traits::{ConstU32, Get}, + traits::Get, weights::{Weight, WeightMeter}, - BoundedVec, }; +use sp_runtime::Saturating; const LOG_TARGET: &'static str = "runtime::migrations"; +#[derive(Debug, Clone, Eq, PartialEq, Encode, Decode, scale_info::TypeInfo, MaxEncodedLen)] +pub enum MigrationCursor { + Active(u32, Option), + Stuck, +} + #[frame_support::pallet] pub mod pallet { use super::*; @@ -56,7 +63,7 @@ pub mod pallet { /// `Cursor` for `None`). type Migrations: Get>>; - type Suspender: ExtrinsicSuspender + ExtrinsicSuspenderQuery; + type Suspender: ExtrinsicSuspenderQuery; /// The weight to spend each block to execute migrations. type ServiceWeight: Get; @@ -68,7 +75,14 @@ pub mod pallet { /// /// `None` indicates that no migration process is running. #[pallet::storage] - pub type Cursor = StorageValue<_, (u32, SteppedMigrationCursor), OptionQuery>; + pub type Cursor = StorageValue<_, MigrationCursor, OptionQuery>; + + /// Set of all successfully executed migrations. + /// + /// This is used as blacklist, to not re-execute migrations that have not been removed from the + /// codebase yet. + #[pallet::storage] + pub type Executed = StorageMap<_, Twox64Concat, [u8; 16], (), OptionQuery>; #[pallet::event] #[pallet::generate_deposit(pub(super) fn deposit_event)] @@ -76,13 +90,29 @@ pub mod pallet { /// Runtime upgrade started. UpgradeStarted, /// Runtime upgrade completed with `migrations`. - UpgradeCompleted { migrations: u32 }, + UpgradeCompleted { + migrations: u32, + }, + UpgradeFailed, + + /// Migration `index` was skipped, since it already executed in the past. + MigrationSkippedHistoric { + index: u32, + }, /// Migration `index` made progress. - MigrationAdvanced { index: u32 }, + MigrationAdvanced { + index: u32, + }, /// Migration `index` completed. - MigrationCompleted { index: u32 }, - /// Migration `index` failed. TODO add `inner` error - MigrationFailed { index: u32 }, + MigrationCompleted { + index: u32, + }, + /// Migration `index` failed. + /// + /// This implies that the whole upgrade failed and governance intervention is required. + MigrationFailed { + index: u32, + }, } #[pallet::hooks] @@ -92,8 +122,11 @@ pub mod pallet { log::error!(target: LOG_TARGET, "Defensive: migrations in progress will be aborted."); return Default::default() // FAIL-CI } - Cursor::::set(Some(Default::default())); - Self::deposit_event(Event::UpgradeStarted); + + if T::Migrations::get().len() > 0 { + Cursor::::set(Some(MigrationCursor::Active(0, None))); + Self::deposit_event(Event::UpgradeStarted); + } Default::default() // FAIL-CI } @@ -101,35 +134,57 @@ pub mod pallet { fn on_initialize(n: T::BlockNumber) -> Weight { let mut meter = WeightMeter::from_limit(T::ServiceWeight::get()); - let Some((index, inner_cursor)) = Cursor::::get() else { - log::debug!(target: LOG_TARGET, "[Block {n:?}] Nothing to do: waiting for cursor to become `Some`."); - return meter.consumed; - }; - let migrations = T::Migrations::get(); - - let Some(migration) = migrations.get(index as usize) else { - Cursor::::kill(); - Self::deposit_event(Event::UpgradeCompleted { migrations: migrations.len() as u32 }); - return meter.consumed; - }; - - match migration.transactional_step(Some(inner_cursor), &mut meter) { - Ok(Some(inner_cursor)) => { - Cursor::::set(Some((index, inner_cursor))); - Self::deposit_event(Event::MigrationAdvanced { index }); + let (mut index, mut cursor) = match Cursor::::get() { + None => { + log::debug!(target: LOG_TARGET, "[Block {n:?}] Nothing to migrate."); + return meter.consumed }, - Ok(None) => { - Cursor::::set(Some((index.saturating_add(1), Default::default()))); - Self::deposit_event(Event::MigrationCompleted { index }); + Some(MigrationCursor::Active(index, cursor)) => (index, cursor), + Some(MigrationCursor::Stuck) => { + defensive!("Migration stuck. Governance intervention required."); + return meter.consumed }, - Err(err) => { + }; + debug_assert!(T::Suspender::is_suspended()); + + let migrations = T::Migrations::get(); + for step in 0.. { + let Some(migration) = migrations.get(index as usize) else { + Self::deposit_event(Event::UpgradeCompleted { migrations: migrations.len() as u32 }); Cursor::::kill(); - // TODO: handle errors - Cursor::::set(Some((index.saturating_add(1), Default::default()))); - Self::deposit_event(Event::MigrationFailed { index }); - }, + return meter.consumed; + }; + + match migration.transactional_step(cursor, &mut meter) { + Ok(Some(next_cursor)) => { + Self::deposit_event(Event::MigrationAdvanced { index }); + cursor = Some(next_cursor); + // A migration has to make maximal progress per step, we therefore break. + break + }, + Ok(None) => { + Self::deposit_event(Event::MigrationCompleted { index }); + index.saturating_inc(); + cursor = None; + }, + Err(SteppedMigrationError::InsufficientWeight { required }) => { + if step == 0 || required.any_gt(meter.limit) { + Cursor::::set(Some(MigrationCursor::Stuck)); + Self::deposit_event(Event::UpgradeFailed); + } // else: Hope that it gets better next time. + return meter.consumed + }, + Err(SteppedMigrationError::Failed) => { + Self::deposit_event(Event::MigrationFailed { index }); + Self::deposit_event(Event::UpgradeFailed); + Cursor::::set(Some(MigrationCursor::Stuck)); + return meter.consumed + }, + } } + Cursor::::set(Some(MigrationCursor::Active(index, cursor))); + meter.consumed } } @@ -143,7 +198,7 @@ pub mod pallet { #[pallet::weight((0, DispatchClass::Mandatory))] pub fn force_set_cursor( origin: OriginFor, - cursor: Option<(u32, SteppedMigrationCursor)>, + cursor: Option, ) -> DispatchResult { ensure_root(origin)?; @@ -153,3 +208,9 @@ pub mod pallet { } } } + +impl ExtrinsicSuspenderQuery for Pallet { + fn is_suspended() -> bool { + Cursor::::exists() + } +} diff --git a/frame/migrations/src/mock.rs b/frame/migrations/src/mock.rs index 93265981bd870..46c128a827a14 100644 --- a/frame/migrations/src/mock.rs +++ b/frame/migrations/src/mock.rs @@ -18,10 +18,11 @@ #![cfg(test)] //use crate::GetMigrations; +use crate::Config; use codec::{Decode, Encode}; use frame_support::{ migrations::*, - traits::{ConstU16, ConstU64}, + traits::{ConstU16, ConstU64, OnFinalize, OnInitialize}, weights::{Weight, WeightMeter}, }; use sp_core::H256; @@ -72,21 +73,39 @@ impl frame_system::Config for Test { type MaxConsumers = frame_support::traits::ConstU32<16>; } -/// Succeeds after `inner` steps. -pub struct MockedMigrate(u32); +#[allow(dead_code)] +pub enum MockedMigrationKind { + SucceedAfter, + FailAfter, +} +use MockedMigrationKind::*; // C style + +pub struct MockedMigrate(MockedMigrationKind, u32); + impl SteppedMigration for MockedMigrate { fn step( &self, cursor: &Option, - meter: &mut WeightMeter, + _meter: &mut WeightMeter, ) -> Result, SteppedMigrationError> { let mut count: u32 = cursor.as_ref().and_then(|c| Decode::decode(&mut &c[..]).ok()).unwrap_or(0); - if count == self.0 { - return Ok(None) + log::debug!("MockedMigrate: Step {}", count); + if count != self.1 { + count += 1; + return Ok(Some(count.encode().try_into().unwrap())) + } + + match self.0 { + SucceedAfter => { + log::debug!("MockedMigrate: Succeeded after {} steps", count); + Ok(None) + }, + FailAfter => { + log::debug!("MockedMigrate: Failed after {} steps", count); + Err(SteppedMigrationError::Failed) + }, } - count += 1; - Ok(Some(count.encode().try_into().unwrap())) } } @@ -97,15 +116,17 @@ frame_support::parameter_types! { /// This is different from the normal compile-time tuple config, but allows them to carry /// configuration. pub SteppedMigrations: Vec> = vec![ - Box::new(MockedMigrate(1)), - Box::new(MockedMigrate(2)), + Box::new(MockedMigrate(SucceedAfter, 0)), + Box::new(MockedMigrate(SucceedAfter, 0)), + Box::new(MockedMigrate(SucceedAfter, 1)), + Box::new(MockedMigrate(SucceedAfter, 2)), ]; } impl crate::Config for Test { type RuntimeEvent = RuntimeEvent; type Migrations = SteppedMigrations; - type Suspender = frame_system::Pallet; + type Suspender = LoggingSuspender>; type ServiceWeight = ServiceWeight; type WeightInfo = (); } @@ -114,3 +135,29 @@ impl crate::Config for Test { pub fn new_test_ext() -> sp_io::TestExternalities { frame_system::GenesisConfig::default().build_storage::().unwrap().into() } + +pub struct LoggingSuspender(core::marker::PhantomData); +impl ExtrinsicSuspenderQuery for LoggingSuspender { + fn is_suspended() -> bool { + let res = Inner::is_suspended(); + log::debug!("IsSuspended: {res}"); + res + } +} + +pub fn assert_last_event(generic_event: ::RuntimeEvent) { + frame_system::Pallet::::assert_last_event(generic_event.into()); +} + +pub fn run_to_block(n: u64) { + while System::block_number() < n { + if System::block_number() > 1 { + Migrations::on_finalize(System::block_number()); + System::on_finalize(System::block_number()); + } + log::debug!("Block {}", System::block_number()); + System::set_block_number(System::block_number() + 1); + System::on_initialize(System::block_number()); + Migrations::on_initialize(System::block_number()); + } +} diff --git a/frame/migrations/src/tests.rs b/frame/migrations/src/tests.rs index 6f8102b454ea0..288daf8c2f2e4 100644 --- a/frame/migrations/src/tests.rs +++ b/frame/migrations/src/tests.rs @@ -18,44 +18,33 @@ #![cfg(test)] use crate::mock::*; +use crate::Event; use frame_support::{ - assert_noop, assert_ok, - traits::{OnInitialize, OnRuntimeUpgrade}, - weights::Weight, + traits::{OnRuntimeUpgrade}, }; -/* -log output: - - [Block 0] Advancing migration 0. - [Block 0] Migration 0 advanced. - [Block 1] Advancing migration 0. - [Block 1] Migration 0 done. - [Block 2] Advancing migration 1. - [Block 2] Migration 1 advanced. - [Block 3] Advancing migration 1. - [Block 3] Migration 1 advanced. - [Block 4] Advancing migration 1. - [Block 4] Migration 1 done. - [Block 5] All migrations processed (2 >= 2). - [Block 6] Nothing to do: waiting for cursor to become `Some`. - [Block 7] Nothing to do: waiting for cursor to become `Some`. - [Block 8] Nothing to do: waiting for cursor to become `Some`. - [Block 9] Nothing to do: waiting for cursor to become `Some`. -*/ +/// Example output: +/// +/// ```pre +/// Suspend +/// MockedMigrate: Step 1/2 +/// MockedMigrate: Step 2/2 +/// MockedMigrate: Succeeded after 2 steps +/// MockedMigrate: Step 1/3 +/// MockedMigrate: Step 2/3 +/// MockedMigrate: Step 3/3 +/// MockedMigrate: Succeeded after 3 steps +/// Resume +/// ``` #[test] fn basic_works() { sp_tracing::try_init_simple(); new_test_ext().execute_with(|| { System::set_block_number(1); - Migrations::on_runtime_upgrade(); - for i in 0..10 { - Migrations::on_initialize(i); - } - - dbg!(System::events()); + run_to_block(10); + assert_last_event::(Event::UpgradeCompleted { migrations: 4 }.into()); }); } diff --git a/frame/support/src/migrations.rs b/frame/support/src/migrations.rs index 694b3c7b3caad..7b2ce166b4012 100644 --- a/frame/support/src/migrations.rs +++ b/frame/support/src/migrations.rs @@ -238,7 +238,7 @@ pub trait SteppedMigration { }, Err(err) => sp_api::TransactionOutcome::Rollback(Err(err)), }) - .map_err(|()| SteppedMigrationError::Internal)? + .map_err(|()| SteppedMigrationError::Failed)? } } @@ -257,36 +257,20 @@ pub enum SteppedMigrationError { /// Can be resolved by calling with at least `required` weight. Note that calling it with /// exactly `required` weight could cause it to not make any progress. InsufficientWeight { required: Weight }, - /// Implementation specific error that should resolve itself at a later point in time. - /// - /// The number of re-tries can be decided upon by the caller. `inner` is undefined. - Transient { inner: u8 }, // permanent errors: /// The migration encountered a permanent error and cannot continue. /// /// This can happen if the storage is corrupted or an assumption got invalidated while the /// migration was running. - Permanent { inner: u8 }, - /// An internal error that should never happen. - Internal, -} - -/// Can be used to pause extrinsic inclusion across the whole runtime. -pub trait ExtrinsicSuspender { - /// Pause all extrinsics that are not mandatory. - fn suspend(); - fn resume(); + Failed, } pub trait ExtrinsicSuspenderQuery { fn is_suspended() -> bool; } -impl ExtrinsicSuspender for () { - fn suspend() {} - fn resume() {} -} - impl ExtrinsicSuspenderQuery for () { - fn is_suspended() -> bool { false } + fn is_suspended() -> bool { + false + } } diff --git a/frame/system/src/lib.rs b/frame/system/src/lib.rs index 1948536e6f89e..64db2f46fd4eb 100644 --- a/frame/system/src/lib.rs +++ b/frame/system/src/lib.rs @@ -89,7 +89,6 @@ use frame_support::{ extract_actual_pays_fee, extract_actual_weight, DispatchClass, DispatchInfo, DispatchResult, DispatchResultWithPostInfo, PerDispatchClass, }, - migrations::{ExtrinsicSuspender, ExtrinsicSuspenderQuery}, storage::{self, StorageStreamIter}, traits::{ ConstU32, Contains, EnsureOrigin, Get, HandleLifetime, OnKilledAccount, OnNewAccount, @@ -537,9 +536,6 @@ pub mod pallet { #[pallet::storage] pub(super) type ExtrinsicCount = StorageValue<_, u32>; - #[pallet::storage] - pub type ExtrinsicsSuspended = StorageValue<_, (), OptionQuery>; - /// The current weight for the block. #[pallet::storage] #[pallet::whitelist_storage] @@ -1752,22 +1748,6 @@ impl Lookup for ChainContext { } } -impl ExtrinsicSuspender for Pallet { - fn suspend() { - ExtrinsicsSuspended::::put(()); - } - - fn resume() { - ExtrinsicsSuspended::::kill(); - } -} - -impl ExtrinsicSuspenderQuery for Pallet { - fn is_suspended() -> bool { - ExtrinsicsSuspended::::exists() - } -} - /// Prelude to be used alongside pallet macro, for ease of use. pub mod pallet_prelude { pub use crate::{ensure_none, ensure_root, ensure_signed, ensure_signed_or_root}; diff --git a/primitives/runtime/src/lib.rs b/primitives/runtime/src/lib.rs index 363881e431e0e..90d66a8ff0785 100644 --- a/primitives/runtime/src/lib.rs +++ b/primitives/runtime/src/lib.rs @@ -557,6 +557,8 @@ pub enum DispatchError { Unavailable, /// Root origin is not allowed. RootNotAllowed, + /// The runtime is suspended and marks all transactions as failed. + Suspended, } /// Result of a `Dispatchable` which contains the `DispatchResult` and additional information about @@ -686,6 +688,7 @@ impl From for &'static str { Corruption => "State corrupt", Unavailable => "Resource unavailable", RootNotAllowed => "Root not allowed", + Suspended => "Extrinsics suspended", } } } @@ -733,6 +736,7 @@ impl traits::Printable for DispatchError { Corruption => "State corrupt".print(), Unavailable => "Resource unavailable".print(), RootNotAllowed => "Root not allowed".print(), + Suspended => "Extrinsics suspended".print(), } } } diff --git a/primitives/storage/src/lib.rs b/primitives/storage/src/lib.rs index 514f4f3d1cdac..aa1bc8e305c9b 100644 --- a/primitives/storage/src/lib.rs +++ b/primitives/storage/src/lib.rs @@ -204,8 +204,6 @@ pub mod well_known_keys { /// Encodes to `0x3a65787472696e7369635f696e646578`. pub const EXTRINSIC_INDEX: &[u8] = b":extrinsic_index"; - pub const EXTRINSICS_PAUSED: &[u8] = b":extrinsics_paused"; - /// Current intra-block entropy (a universally unique `[u8; 32]` value) is stored here. pub const INTRABLOCK_ENTROPY: &[u8] = b":intrablock_entropy"; From 3afdf0a679bbaa085958159215a9780847174d63 Mon Sep 17 00:00:00 2001 From: Oliver Tale-Yazdi Date: Wed, 31 May 2023 18:28:45 +0200 Subject: [PATCH 07/63] Add Executed Signed-off-by: Oliver Tale-Yazdi --- bin/node/runtime/src/lib.rs | 1 - frame/migrations/src/benchmarking.rs | 13 ++++++++----- frame/migrations/src/lib.rs | 14 ++++++++++---- frame/migrations/src/mock.rs | 13 +++++++++++-- frame/migrations/src/tests.rs | 10 +++++----- frame/support/src/migrations.rs | 5 ++++- 6 files changed, 38 insertions(+), 18 deletions(-) diff --git a/bin/node/runtime/src/lib.rs b/bin/node/runtime/src/lib.rs index 547452bb1cbf6..0e7835e36bd83 100644 --- a/bin/node/runtime/src/lib.rs +++ b/bin/node/runtime/src/lib.rs @@ -1864,7 +1864,6 @@ parameter_types! { impl pallet_migrations::Config for Runtime { type RuntimeEvent = RuntimeEvent; type Migrations = MbmMigrations; // same as () - type Suspender = frame_system::Pallet; type ServiceWeight = MbmServiceWeight; type WeightInfo = pallet_migrations::weights::SubstrateWeight; } diff --git a/frame/migrations/src/benchmarking.rs b/frame/migrations/src/benchmarking.rs index 1f90405739ab1..20a3d8c910d77 100644 --- a/frame/migrations/src/benchmarking.rs +++ b/frame/migrations/src/benchmarking.rs @@ -21,7 +21,7 @@ use super::*; use frame_benchmarking::v2::*; use frame_support::migrations::STEPPED_MIGRATION_CURSOR_LEN as CURSOR_LEN; -use frame_system::RawOrigin; +use frame_system::{Pallet as System, RawOrigin}; #[benchmarks] mod benches { @@ -52,12 +52,12 @@ mod benches { #[benchmark] fn on_init_loop_base() { Cursor::::set(Some(cursor(0))); - System::::set_block_number(1); + System::::set_block_number(1u32.into()); Pallet::::on_runtime_upgrade(); #[block] { - Pallet::::on_initialize(1); + Pallet::::on_initialize(1u32.into()); } } @@ -70,8 +70,11 @@ mod benches { _(RawOrigin::Root, Some(cursor(1))); } - fn cursor(i: u32) -> (u32, SteppedMigrationCursor) { - (u32::MAX - i, vec![1u8; CURSOR_LEN as usize].try_into().expect("Static length is good")) + fn cursor(i: u32) -> MigrationCursor { + MigrationCursor::Active( + u32::MAX - i, + Some(vec![1u8; CURSOR_LEN as usize].try_into().expect("Static length is good")), + ) } // Implements a test for each benchmark. Execute with: diff --git a/frame/migrations/src/lib.rs b/frame/migrations/src/lib.rs index 65bc222d55ccf..9e9c472a1a70e 100644 --- a/frame/migrations/src/lib.rs +++ b/frame/migrations/src/lib.rs @@ -63,8 +63,6 @@ pub mod pallet { /// `Cursor` for `None`). type Migrations: Get>>; - type Suspender: ExtrinsicSuspenderQuery; - /// The weight to spend each block to execute migrations. type ServiceWeight: Get; @@ -82,7 +80,8 @@ pub mod pallet { /// This is used as blacklist, to not re-execute migrations that have not been removed from the /// codebase yet. #[pallet::storage] - pub type Executed = StorageMap<_, Twox64Concat, [u8; 16], (), OptionQuery>; + pub type Executed = + StorageMap<_, Twox64Concat, BoundedVec>, (), OptionQuery>; #[pallet::event] #[pallet::generate_deposit(pub(super) fn deposit_event)] @@ -145,7 +144,7 @@ pub mod pallet { return meter.consumed }, }; - debug_assert!(T::Suspender::is_suspended()); + debug_assert!(::is_suspended()); let migrations = T::Migrations::get(); for step in 0.. { @@ -154,6 +153,12 @@ pub mod pallet { Cursor::::kill(); return meter.consumed; }; + if Executed::::contains_key(&migration.id()) { + Self::deposit_event(Event::MigrationSkippedHistoric { index }); + index.saturating_inc(); + cursor = None; + continue + } match migration.transactional_step(cursor, &mut meter) { Ok(Some(next_cursor)) => { @@ -164,6 +169,7 @@ pub mod pallet { }, Ok(None) => { Self::deposit_event(Event::MigrationCompleted { index }); + Executed::::insert(&migration.id(), ()); index.saturating_inc(); cursor = None; }, diff --git a/frame/migrations/src/mock.rs b/frame/migrations/src/mock.rs index 46c128a827a14..121f69e14379f 100644 --- a/frame/migrations/src/mock.rs +++ b/frame/migrations/src/mock.rs @@ -25,10 +25,11 @@ use frame_support::{ traits::{ConstU16, ConstU64, OnFinalize, OnInitialize}, weights::{Weight, WeightMeter}, }; -use sp_core::H256; +use sp_core::{ConstU32, H256}; use sp_runtime::{ testing::Header, traits::{BlakeTwo256, IdentityLookup}, + BoundedVec, }; type UncheckedExtrinsic = frame_system::mocking::MockUncheckedExtrinsic; @@ -73,6 +74,7 @@ impl frame_system::Config for Test { type MaxConsumers = frame_support::traits::ConstU32<16>; } +#[derive(Debug)] #[allow(dead_code)] pub enum MockedMigrationKind { SucceedAfter, @@ -83,6 +85,14 @@ use MockedMigrationKind::*; // C style pub struct MockedMigrate(MockedMigrationKind, u32); impl SteppedMigration for MockedMigrate { + fn id(&self) -> BoundedVec> { + format!("MockedMigrate({:?}, {})", self.0, self.1) + .as_bytes() + .to_vec() + .try_into() + .unwrap() + } + fn step( &self, cursor: &Option, @@ -126,7 +136,6 @@ frame_support::parameter_types! { impl crate::Config for Test { type RuntimeEvent = RuntimeEvent; type Migrations = SteppedMigrations; - type Suspender = LoggingSuspender>; type ServiceWeight = ServiceWeight; type WeightInfo = (); } diff --git a/frame/migrations/src/tests.rs b/frame/migrations/src/tests.rs index 288daf8c2f2e4..00f02736742c3 100644 --- a/frame/migrations/src/tests.rs +++ b/frame/migrations/src/tests.rs @@ -17,11 +17,8 @@ #![cfg(test)] -use crate::mock::*; -use crate::Event; -use frame_support::{ - traits::{OnRuntimeUpgrade}, -}; +use crate::{mock::*, Event, Executed}; +use frame_support::traits::OnRuntimeUpgrade; /// Example output: /// @@ -45,6 +42,9 @@ fn basic_works() { Migrations::on_runtime_upgrade(); run_to_block(10); + assert_last_event::(Event::UpgradeCompleted { migrations: 4 }.into()); + // Just three, since one was added twice. + assert_eq!(Executed::::iter().count(), 3); }); } diff --git a/frame/support/src/migrations.rs b/frame/support/src/migrations.rs index 7b2ce166b4012..fde035caf03de 100644 --- a/frame/support/src/migrations.rs +++ b/frame/support/src/migrations.rs @@ -21,9 +21,10 @@ use crate::{ storage::transactional::with_transaction_opaque_err, traits::{GetStorageVersion, NoStorageVersionSet, PalletInfoAccess, StorageVersion}, weights::{RuntimeDbWeight, Weight, WeightMeter}, + BoundedVec, }; use impl_trait_for_tuples::impl_for_tuples; -use sp_core::Get; +use sp_core::{ConstU32, Get}; use sp_io::{hashing::twox_128, storage::clear_prefix, KillStorageResult}; use sp_std::marker::PhantomData; #[cfg(feature = "try-runtime")] @@ -213,6 +214,8 @@ impl, DbWeight: Get> frame_support::traits /// A migration that can proceed in multiple steps. pub trait SteppedMigration { + fn id(&self) -> BoundedVec>; + /// Try to migrate as much as possible with the given weight. /// /// **ANY STORAGE CHANGES MUST BE ROLLED-BACK BY THE CALLER UPON ERROR.** This is necessary From 104fd6d88ff5813f98f611bf5a70ac75988109eb Mon Sep 17 00:00:00 2001 From: Oliver Tale-Yazdi Date: Wed, 31 May 2023 18:32:18 +0200 Subject: [PATCH 08/63] Add events Signed-off-by: Oliver Tale-Yazdi --- frame/migrations/src/lib.rs | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/frame/migrations/src/lib.rs b/frame/migrations/src/lib.rs index 9e9c472a1a70e..703d02ac7d505 100644 --- a/frame/migrations/src/lib.rs +++ b/frame/migrations/src/lib.rs @@ -92,8 +92,10 @@ pub mod pallet { UpgradeCompleted { migrations: u32, }, + /// Runtime upgrade failed. + /// + /// This is very bad and will require governance intervention. UpgradeFailed, - /// Migration `index` was skipped, since it already executed in the past. MigrationSkippedHistoric { index: u32, @@ -118,7 +120,9 @@ pub mod pallet { impl Hooks> for Pallet { fn on_runtime_upgrade() -> Weight { if Cursor::::exists() { - log::error!(target: LOG_TARGET, "Defensive: migrations in progress will be aborted."); + Self::deposit_event(Event::UpgradeFailed); + Cursor::::set(Some(MigrationCursor::Stuck)); + log::error!(target: LOG_TARGET, "Code for ongoing migrations was deleted."); return Default::default() // FAIL-CI } From 1e18cae7ec07977298ad6b44bc22b57ea2824695 Mon Sep 17 00:00:00 2001 From: Oliver Tale-Yazdi Date: Wed, 31 May 2023 18:48:59 +0200 Subject: [PATCH 09/63] Fixes Signed-off-by: Oliver Tale-Yazdi --- frame/migrations/src/benchmarking.rs | 11 +++++----- frame/migrations/src/lib.rs | 32 +++++++++++----------------- 2 files changed, 18 insertions(+), 25 deletions(-) diff --git a/frame/migrations/src/benchmarking.rs b/frame/migrations/src/benchmarking.rs index 20a3d8c910d77..60418b82cbf33 100644 --- a/frame/migrations/src/benchmarking.rs +++ b/frame/migrations/src/benchmarking.rs @@ -29,9 +29,8 @@ mod benches { use frame_support::traits::Hooks; #[benchmark] - fn on_runtime_upgrade_bail() { - Cursor::::set(Some(cursor(0))); - assert!(Cursor::::exists()); + fn on_runtime_upgrade() { + assert!(!Cursor::::exists()); #[block] { @@ -40,18 +39,18 @@ mod benches { } #[benchmark] - fn on_runtime_upgrade() { + fn on_init_base() { assert!(!Cursor::::exists()); + System::::set_block_number(1u32.into()); #[block] { - Pallet::::on_runtime_upgrade(); + Pallet::::on_initialize(1u32.into()); } } #[benchmark] fn on_init_loop_base() { - Cursor::::set(Some(cursor(0))); System::::set_block_number(1u32.into()); Pallet::::on_runtime_upgrade(); diff --git a/frame/migrations/src/lib.rs b/frame/migrations/src/lib.rs index 703d02ac7d505..1e2d96931fd83 100644 --- a/frame/migrations/src/lib.rs +++ b/frame/migrations/src/lib.rs @@ -37,9 +37,12 @@ use sp_runtime::Saturating; const LOG_TARGET: &'static str = "runtime::migrations"; +/// Points to the next migration to execute. #[derive(Debug, Clone, Eq, PartialEq, Encode, Decode, scale_info::TypeInfo, MaxEncodedLen)] pub enum MigrationCursor { + /// Points to the currently active migration and its cursor. Active(u32, Option), + /// Migration got stuck and cannot proceed. Stuck, } @@ -66,6 +69,7 @@ pub mod pallet { /// The weight to spend each block to execute migrations. type ServiceWeight: Get; + /// Weight information for the calls and functions of this pallet. type WeightInfo: WeightInfo; } @@ -89,41 +93,31 @@ pub mod pallet { /// Runtime upgrade started. UpgradeStarted, /// Runtime upgrade completed with `migrations`. - UpgradeCompleted { - migrations: u32, - }, + UpgradeCompleted { migrations: u32 }, /// Runtime upgrade failed. /// /// This is very bad and will require governance intervention. UpgradeFailed, /// Migration `index` was skipped, since it already executed in the past. - MigrationSkippedHistoric { - index: u32, - }, + MigrationSkippedHistoric { index: u32 }, /// Migration `index` made progress. - MigrationAdvanced { - index: u32, - }, + MigrationAdvanced { index: u32 }, /// Migration `index` completed. - MigrationCompleted { - index: u32, - }, + MigrationCompleted { index: u32 }, /// Migration `index` failed. /// /// This implies that the whole upgrade failed and governance intervention is required. - MigrationFailed { - index: u32, - }, + MigrationFailed { index: u32 }, } #[pallet::hooks] impl Hooks> for Pallet { fn on_runtime_upgrade() -> Weight { if Cursor::::exists() { + log::error!(target: LOG_TARGET, "Code for ongoing migrations was deleted."); Self::deposit_event(Event::UpgradeFailed); Cursor::::set(Some(MigrationCursor::Stuck)); - log::error!(target: LOG_TARGET, "Code for ongoing migrations was deleted."); - return Default::default() // FAIL-CI + return T::WeightInfo::on_runtime_upgrade() } if T::Migrations::get().len() > 0 { @@ -131,7 +125,7 @@ pub mod pallet { Self::deposit_event(Event::UpgradeStarted); } - Default::default() // FAIL-CI + T::WeightInfo::on_runtime_upgrade() } fn on_initialize(n: T::BlockNumber) -> Weight { @@ -139,7 +133,7 @@ pub mod pallet { let (mut index, mut cursor) = match Cursor::::get() { None => { - log::debug!(target: LOG_TARGET, "[Block {n:?}] Nothing to migrate."); + log::debug!(target: LOG_TARGET, "[Block {n:?}] Waiting for cursor to become `Some`."); return meter.consumed }, Some(MigrationCursor::Active(index, cursor)) => (index, cursor), From 7ab621a53cc182ffae7b893e43b1a33f3f0d1ee1 Mon Sep 17 00:00:00 2001 From: Oliver Tale-Yazdi Date: Thu, 1 Jun 2023 14:35:16 +0200 Subject: [PATCH 10/63] Make cursor generic Signed-off-by: Oliver Tale-Yazdi --- frame/migrations/src/lib.rs | 13 ++++++++----- frame/migrations/src/mock.rs | 11 ++++++++--- frame/support/src/migrations.rs | 17 ++++++----------- 3 files changed, 22 insertions(+), 19 deletions(-) diff --git a/frame/migrations/src/lib.rs b/frame/migrations/src/lib.rs index 1e2d96931fd83..bb1f78922c06e 100644 --- a/frame/migrations/src/lib.rs +++ b/frame/migrations/src/lib.rs @@ -39,9 +39,9 @@ const LOG_TARGET: &'static str = "runtime::migrations"; /// Points to the next migration to execute. #[derive(Debug, Clone, Eq, PartialEq, Encode, Decode, scale_info::TypeInfo, MaxEncodedLen)] -pub enum MigrationCursor { +pub enum MigrationCursor { /// Points to the currently active migration and its cursor. - Active(u32, Option), + Active(u32, Option), /// Migration got stuck and cannot proceed. Stuck, } @@ -64,7 +64,10 @@ pub mod pallet { /// /// Should only be updated in a runtime-upgrade once all the old ones have completed. (Check /// `Cursor` for `None`). - type Migrations: Get>>; + type Migrations: Get>>>; + + /// The cursor type that is shared across all migrations. + type Cursor: codec::FullCodec + codec::MaxEncodedLen + scale_info::TypeInfo + Parameter; /// The weight to spend each block to execute migrations. type ServiceWeight: Get; @@ -77,7 +80,7 @@ pub mod pallet { /// /// `None` indicates that no migration process is running. #[pallet::storage] - pub type Cursor = StorageValue<_, MigrationCursor, OptionQuery>; + pub type Cursor = StorageValue<_, MigrationCursor, OptionQuery>; /// Set of all successfully executed migrations. /// @@ -202,7 +205,7 @@ pub mod pallet { #[pallet::weight((0, DispatchClass::Mandatory))] pub fn force_set_cursor( origin: OriginFor, - cursor: Option, + cursor: Option>, ) -> DispatchResult { ensure_root(origin)?; diff --git a/frame/migrations/src/mock.rs b/frame/migrations/src/mock.rs index 121f69e14379f..ffaee40dde6ee 100644 --- a/frame/migrations/src/mock.rs +++ b/frame/migrations/src/mock.rs @@ -85,6 +85,8 @@ use MockedMigrationKind::*; // C style pub struct MockedMigrate(MockedMigrationKind, u32); impl SteppedMigration for MockedMigrate { + type Cursor = BoundedVec>; + fn id(&self) -> BoundedVec> { format!("MockedMigrate({:?}, {})", self.0, self.1) .as_bytes() @@ -95,9 +97,9 @@ impl SteppedMigration for MockedMigrate { fn step( &self, - cursor: &Option, + cursor: &Option, _meter: &mut WeightMeter, - ) -> Result, SteppedMigrationError> { + ) -> Result, SteppedMigrationError> { let mut count: u32 = cursor.as_ref().and_then(|c| Decode::decode(&mut &c[..]).ok()).unwrap_or(0); log::debug!("MockedMigrate: Step {}", count); @@ -119,13 +121,15 @@ impl SteppedMigration for MockedMigrate { } } +type MockedCursor = BoundedVec>; + frame_support::parameter_types! { pub const ServiceWeight: Weight = Weight::MAX; /// Stepped migrations need to be allocated as objects. /// /// This is different from the normal compile-time tuple config, but allows them to carry /// configuration. - pub SteppedMigrations: Vec> = vec![ + pub SteppedMigrations: Vec>> = vec![ Box::new(MockedMigrate(SucceedAfter, 0)), Box::new(MockedMigrate(SucceedAfter, 0)), Box::new(MockedMigrate(SucceedAfter, 1)), @@ -136,6 +140,7 @@ frame_support::parameter_types! { impl crate::Config for Test { type RuntimeEvent = RuntimeEvent; type Migrations = SteppedMigrations; + type Cursor = MockedCursor; type ServiceWeight = ServiceWeight; type WeightInfo = (); } diff --git a/frame/support/src/migrations.rs b/frame/support/src/migrations.rs index fde035caf03de..75ca37994928b 100644 --- a/frame/support/src/migrations.rs +++ b/frame/support/src/migrations.rs @@ -214,6 +214,8 @@ impl, DbWeight: Get> frame_support::traits /// A migration that can proceed in multiple steps. pub trait SteppedMigration { + type Cursor: codec::FullCodec + codec::MaxEncodedLen; + fn id(&self) -> BoundedVec>; /// Try to migrate as much as possible with the given weight. @@ -224,16 +226,16 @@ pub trait SteppedMigration { /// its end. TODO: Think about iterator `fuse` requirement. fn step( &self, - cursor: &Option, + cursor: &Option, meter: &mut WeightMeter, - ) -> Result, SteppedMigrationError>; + ) -> Result, SteppedMigrationError>; /// Same as [`Self::step`], but rolls back pending changes in the error case. fn transactional_step( &self, - mut cursor: Option, + mut cursor: Option, meter: &mut WeightMeter, - ) -> Result, SteppedMigrationError> { + ) -> Result, SteppedMigrationError> { with_transaction_opaque_err(move || match self.step(&cursor, meter) { Ok(new_cursor) => { cursor = new_cursor; @@ -245,13 +247,6 @@ pub trait SteppedMigration { } } -/// The maximal length in bytes of a cursor for a stepped migration. -pub const STEPPED_MIGRATION_CURSOR_LEN: u32 = 1024; - -/// An opaque cursor that defines the "position" or a migration. -pub type SteppedMigrationCursor = - crate::BoundedVec>; - #[derive(Debug)] pub enum SteppedMigrationError { // Transient errors: From acd99bb90da82d51bbb4dfb2fb126de6227ad26b Mon Sep 17 00:00:00 2001 From: Oliver Tale-Yazdi Date: Thu, 1 Jun 2023 15:09:34 +0200 Subject: [PATCH 11/63] Fix kitchensink --- bin/node/runtime/src/lib.rs | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/bin/node/runtime/src/lib.rs b/bin/node/runtime/src/lib.rs index 0e7835e36bd83..0799b4a78c959 100644 --- a/bin/node/runtime/src/lib.rs +++ b/bin/node/runtime/src/lib.rs @@ -1856,14 +1856,17 @@ impl pallet_statement::Config for Runtime { type MaxAllowedBytes = MaxAllowedBytes; } +type MbmCursor = BoundedVec>; + parameter_types! { pub MbmServiceWeight: Weight = Perbill::from_percent(80) * RuntimeBlockWeights::get().max_block; - pub const MbmMigrations: Vec> = vec![]; + pub const MbmMigrations: Vec>> = vec![]; } impl pallet_migrations::Config for Runtime { type RuntimeEvent = RuntimeEvent; - type Migrations = MbmMigrations; // same as () + type Migrations = MbmMigrations; + type Cursor = MbmCursor; type ServiceWeight = MbmServiceWeight; type WeightInfo = pallet_migrations::weights::SubstrateWeight; } From d217589d1ab9ff1189ce30e48748c29998982870 Mon Sep 17 00:00:00 2001 From: Oliver Tale-Yazdi Date: Thu, 1 Jun 2023 20:48:38 +0200 Subject: [PATCH 12/63] Add testing Signed-off-by: Oliver Tale-Yazdi --- frame/migrations/src/lib.rs | 106 ++++++++++++++++++++++---------- frame/migrations/src/mock.rs | 48 +++++++++------ frame/migrations/src/tests.rs | 88 ++++++++++++++++++++++++-- frame/support/src/migrations.rs | 22 ++++++- 4 files changed, 206 insertions(+), 58 deletions(-) diff --git a/frame/migrations/src/lib.rs b/frame/migrations/src/lib.rs index bb1f78922c06e..1bb1703dd8539 100644 --- a/frame/migrations/src/lib.rs +++ b/frame/migrations/src/lib.rs @@ -30,22 +30,38 @@ use codec::{Decode, Encode, MaxEncodedLen}; use frame_support::{ defensive, migrations::*, - traits::Get, + traits::{Get}, weights::{Weight, WeightMeter}, }; +use frame_system::Pallet as System; use sp_runtime::Saturating; const LOG_TARGET: &'static str = "runtime::migrations"; /// Points to the next migration to execute. #[derive(Debug, Clone, Eq, PartialEq, Encode, Decode, scale_info::TypeInfo, MaxEncodedLen)] -pub enum MigrationCursor { +pub enum MigrationCursor { /// Points to the currently active migration and its cursor. - Active(u32, Option), + Active(ActiveCursor), /// Migration got stuck and cannot proceed. Stuck, } +#[derive(Debug, Clone, Eq, PartialEq, Encode, Decode, scale_info::TypeInfo, MaxEncodedLen)] +pub struct ActiveCursor { + index: u32, + inner_cursor: Option, + started_at: BlockNumber, +} + +impl ActiveCursor { + pub(crate) fn advance(&mut self, current_block: BlockNumber) { + self.index.saturating_inc(); + self.inner_cursor = None; + self.started_at = current_block; + } +} + #[frame_support::pallet] pub mod pallet { use super::*; @@ -64,11 +80,16 @@ pub mod pallet { /// /// Should only be updated in a runtime-upgrade once all the old ones have completed. (Check /// `Cursor` for `None`). - type Migrations: Get>>>; + type Migrations: Get< + Vec>>, + >; /// The cursor type that is shared across all migrations. type Cursor: codec::FullCodec + codec::MaxEncodedLen + scale_info::TypeInfo + Parameter; + /// The identifier type that is shared across all migrations. + type Identifier: codec::FullCodec + codec::MaxEncodedLen + scale_info::TypeInfo; + /// The weight to spend each block to execute migrations. type ServiceWeight: Get; @@ -80,15 +101,15 @@ pub mod pallet { /// /// `None` indicates that no migration process is running. #[pallet::storage] - pub type Cursor = StorageValue<_, MigrationCursor, OptionQuery>; + pub type Cursor = + StorageValue<_, MigrationCursor, OptionQuery>; /// Set of all successfully executed migrations. /// /// This is used as blacklist, to not re-execute migrations that have not been removed from the - /// codebase yet. + /// codebase yet. Governance can regularly clear this out via `clear_historic`. #[pallet::storage] - pub type Executed = - StorageMap<_, Twox64Concat, BoundedVec>, (), OptionQuery>; + pub type Historic = StorageMap<_, Twox64Concat, T::Identifier, (), OptionQuery>; #[pallet::event] #[pallet::generate_deposit(pub(super) fn deposit_event)] @@ -104,13 +125,15 @@ pub mod pallet { /// Migration `index` was skipped, since it already executed in the past. MigrationSkippedHistoric { index: u32 }, /// Migration `index` made progress. - MigrationAdvanced { index: u32 }, + MigrationAdvanced { index: u32, step: T::BlockNumber }, /// Migration `index` completed. - MigrationCompleted { index: u32 }, + MigrationCompleted { index: u32, took: T::BlockNumber }, /// Migration `index` failed. /// /// This implies that the whole upgrade failed and governance intervention is required. - MigrationFailed { index: u32 }, + MigrationFailed { index: u32, took: T::BlockNumber }, + /// The list of historical migrations has been cleared. + HistoricCleared, } #[pallet::hooks] @@ -124,7 +147,11 @@ pub mod pallet { } if T::Migrations::get().len() > 0 { - Cursor::::set(Some(MigrationCursor::Active(0, None))); + Cursor::::set(Some(MigrationCursor::Active(ActiveCursor { + index: 0, + inner_cursor: None, + started_at: System::::block_number().saturating_add(1u32.into()), + }))); Self::deposit_event(Event::UpgradeStarted); } @@ -134,12 +161,12 @@ pub mod pallet { fn on_initialize(n: T::BlockNumber) -> Weight { let mut meter = WeightMeter::from_limit(T::ServiceWeight::get()); - let (mut index, mut cursor) = match Cursor::::get() { + let mut cursor = match Cursor::::get() { None => { log::debug!(target: LOG_TARGET, "[Block {n:?}] Waiting for cursor to become `Some`."); return meter.consumed }, - Some(MigrationCursor::Active(index, cursor)) => (index, cursor), + Some(MigrationCursor::Active(cursor)) => cursor, Some(MigrationCursor::Stuck) => { defensive!("Migration stuck. Governance intervention required."); return meter.consumed @@ -148,41 +175,45 @@ pub mod pallet { debug_assert!(::is_suspended()); let migrations = T::Migrations::get(); - for step in 0.. { - let Some(migration) = migrations.get(index as usize) else { + for iteration in 0.. { + let Some(migration) = migrations.get(cursor.index as usize) else { Self::deposit_event(Event::UpgradeCompleted { migrations: migrations.len() as u32 }); Cursor::::kill(); return meter.consumed; }; - if Executed::::contains_key(&migration.id()) { - Self::deposit_event(Event::MigrationSkippedHistoric { index }); - index.saturating_inc(); - cursor = None; + if Historic::::contains_key(&migration.id()) { + Self::deposit_event(Event::MigrationSkippedHistoric { + index: cursor.index, + }); + cursor.advance(System::::block_number()); continue } - match migration.transactional_step(cursor, &mut meter) { + let took = System::::block_number().saturating_sub(cursor.started_at); + match migration.transactional_step(cursor.inner_cursor.clone(), &mut meter) { Ok(Some(next_cursor)) => { - Self::deposit_event(Event::MigrationAdvanced { index }); - cursor = Some(next_cursor); + Self::deposit_event(Event::MigrationAdvanced { index: cursor.index, step: took }); + cursor.inner_cursor = Some(next_cursor); // A migration has to make maximal progress per step, we therefore break. break }, Ok(None) => { - Self::deposit_event(Event::MigrationCompleted { index }); - Executed::::insert(&migration.id(), ()); - index.saturating_inc(); - cursor = None; + Self::deposit_event(Event::MigrationCompleted { + index: cursor.index, + took, + }); + Historic::::insert(&migration.id(), ()); + cursor.advance(System::::block_number()); }, Err(SteppedMigrationError::InsufficientWeight { required }) => { - if step == 0 || required.any_gt(meter.limit) { + if iteration == 0 || required.any_gt(meter.limit) { Cursor::::set(Some(MigrationCursor::Stuck)); Self::deposit_event(Event::UpgradeFailed); } // else: Hope that it gets better next time. return meter.consumed }, - Err(SteppedMigrationError::Failed) => { - Self::deposit_event(Event::MigrationFailed { index }); + Err(SteppedMigrationError::Failed | SteppedMigrationError::Timeout) => { + Self::deposit_event(Event::MigrationFailed { index: cursor.index, took }); Self::deposit_event(Event::UpgradeFailed); Cursor::::set(Some(MigrationCursor::Stuck)); return meter.consumed @@ -190,7 +221,7 @@ pub mod pallet { } } - Cursor::::set(Some(MigrationCursor::Active(index, cursor))); + Cursor::::set(Some(MigrationCursor::Active(cursor))); meter.consumed } @@ -205,7 +236,7 @@ pub mod pallet { #[pallet::weight((0, DispatchClass::Mandatory))] pub fn force_set_cursor( origin: OriginFor, - cursor: Option>, + cursor: Option>, ) -> DispatchResult { ensure_root(origin)?; @@ -213,6 +244,17 @@ pub mod pallet { Ok(()) } + + #[pallet::call_index(1)] + #[pallet::weight(0)] + pub fn clear_historic(origin: OriginFor) -> DispatchResult { + ensure_root(origin)?; + + Historic::::clear(0, None); + Self::deposit_event(Event::HistoricCleared); + + Ok(()) + } } } diff --git a/frame/migrations/src/mock.rs b/frame/migrations/src/mock.rs index ffaee40dde6ee..3e3c232c1005d 100644 --- a/frame/migrations/src/mock.rs +++ b/frame/migrations/src/mock.rs @@ -19,6 +19,8 @@ //use crate::GetMigrations; use crate::Config; +use core::cell::RefCell; +use sp_core::Get; use codec::{Decode, Encode}; use frame_support::{ migrations::*, @@ -74,7 +76,7 @@ impl frame_system::Config for Test { type MaxConsumers = frame_support::traits::ConstU32<16>; } -#[derive(Debug)] +#[derive(Debug, Clone, Copy)] #[allow(dead_code)] pub enum MockedMigrationKind { SucceedAfter, @@ -86,13 +88,10 @@ pub struct MockedMigrate(MockedMigrationKind, u32); impl SteppedMigration for MockedMigrate { type Cursor = BoundedVec>; + type Identifier = BoundedVec>; - fn id(&self) -> BoundedVec> { - format!("MockedMigrate({:?}, {})", self.0, self.1) - .as_bytes() - .to_vec() - .try_into() - .unwrap() + fn id(&self) -> Self::Identifier { + mocked_id(self.0, self.1) } fn step( @@ -122,25 +121,38 @@ impl SteppedMigration for MockedMigrate { } type MockedCursor = BoundedVec>; +type MockedIdentifier = BoundedVec>; frame_support::parameter_types! { pub const ServiceWeight: Weight = Weight::MAX; - /// Stepped migrations need to be allocated as objects. - /// - /// This is different from the normal compile-time tuple config, but allows them to carry - /// configuration. - pub SteppedMigrations: Vec>> = vec![ - Box::new(MockedMigrate(SucceedAfter, 0)), - Box::new(MockedMigrate(SucceedAfter, 0)), - Box::new(MockedMigrate(SucceedAfter, 1)), - Box::new(MockedMigrate(SucceedAfter, 2)), - ]; +} + +thread_local! { + pub static MIGRATIONS: RefCell> = RefCell::new(vec![]); +} + +pub struct MigrationsStorage; +impl Get>>> for MigrationsStorage { + fn get() -> Vec>> { + MIGRATIONS.with(|m| + m.borrow().clone().into_iter().map(|(k, v)| Box::new(MockedMigrate(k, v)) as Box>).collect() + ) + } +} + +fn mocked_id(kind: MockedMigrationKind, steps: u32) -> MockedIdentifier { + format!("MockedMigrate({:?}, {})", kind, steps) + .as_bytes() + .to_vec() + .try_into() + .unwrap() } impl crate::Config for Test { type RuntimeEvent = RuntimeEvent; - type Migrations = SteppedMigrations; + type Migrations = MigrationsStorage; type Cursor = MockedCursor; + type Identifier = MockedIdentifier; type ServiceWeight = ServiceWeight; type WeightInfo = (); } diff --git a/frame/migrations/src/tests.rs b/frame/migrations/src/tests.rs index 00f02736742c3..8eb0b6883660b 100644 --- a/frame/migrations/src/tests.rs +++ b/frame/migrations/src/tests.rs @@ -17,8 +17,10 @@ #![cfg(test)] -use crate::{mock::*, Event, Executed}; +use crate::{mock::*, mock::MockedMigrationKind::*, Event, Historic}; use frame_support::traits::OnRuntimeUpgrade; +use sp_core::H256; +use frame_system::EventRecord; /// Example output: /// @@ -35,16 +37,92 @@ use frame_support::traits::OnRuntimeUpgrade; /// ``` #[test] fn basic_works() { - sp_tracing::try_init_simple(); + new_test_ext().execute_with(|| { + MIGRATIONS.with(|migrations| { + *migrations.borrow_mut() = vec![ + (SucceedAfter, 0), + (SucceedAfter, 1), + (SucceedAfter, 2), + ]; + }); + + System::set_block_number(1); + Migrations::on_runtime_upgrade(); + + run_to_block(10); + + // Just three, since two were added twice. + assert_eq!(Historic::::iter().count(), 3); + dbg!(System::events()); + assert_eq!(System::events(), vec![ + Event::UpgradeStarted.into_record(), + + Event::MigrationCompleted { index: 0, took: 0 }.into_record(), + Event::MigrationAdvanced { index: 1, step: 0 }.into_record(), + Event::MigrationCompleted { index: 1, took: 1 }.into_record(), + + Event::MigrationAdvanced { index: 2, step: 0 }.into_record(), + Event::MigrationAdvanced { index: 2, step: 1 }.into_record(), + Event::MigrationCompleted { index: 2, took: 2 }.into_record(), + + Event::UpgradeCompleted { migrations: 3 }.into_record(), + ]); + }); +} + +#[test] +fn historic_skipping_works() { new_test_ext().execute_with(|| { + MIGRATIONS.with(|migrations| { + *migrations.borrow_mut() = vec![ + (SucceedAfter, 0), + (SucceedAfter, 0), // Will be skipped + (SucceedAfter, 1), + (SucceedAfter, 2), + (SucceedAfter, 1), // Will be skipped + ]; + }); + System::set_block_number(1); Migrations::on_runtime_upgrade(); run_to_block(10); - assert_last_event::(Event::UpgradeCompleted { migrations: 4 }.into()); - // Just three, since one was added twice. - assert_eq!(Executed::::iter().count(), 3); + // Just three, since two were added twice. + assert_eq!(Historic::::iter().count(), 3); + assert_eq!(System::events(), vec![ + Event::UpgradeStarted.into_record(), + + Event::MigrationCompleted { index: 0, took: 0 }.into_record(), + + Event::MigrationSkippedHistoric { index: 1 }.into_record(), + + Event::MigrationAdvanced { index: 2, step: 0 }.into_record(), + Event::MigrationCompleted { index: 2, took: 1 }.into_record(), + + Event::MigrationAdvanced { index: 3, step: 0 }.into_record(), + Event::MigrationAdvanced { index: 3, step: 1 }.into_record(), + Event::MigrationCompleted { index: 3, took: 2 }.into_record(), + + Event::MigrationSkippedHistoric { index: 4 }.into_record(), + + Event::UpgradeCompleted { migrations: 5 }.into_record(), + ]); }); } + +trait IntoRecord { + fn into_record(self) -> EventRecord<::RuntimeEvent, H256>; +} + +impl IntoRecord for Event { + fn into_record(self) -> EventRecord<::RuntimeEvent, H256> { + let re: ::RuntimeEvent = self.into(); + EventRecord { + phase: frame_system::Phase::Initialization, + event: re, + topics: vec![], + } + } +} diff --git a/frame/support/src/migrations.rs b/frame/support/src/migrations.rs index 75ca37994928b..9a3e8a8e68902 100644 --- a/frame/support/src/migrations.rs +++ b/frame/support/src/migrations.rs @@ -21,10 +21,9 @@ use crate::{ storage::transactional::with_transaction_opaque_err, traits::{GetStorageVersion, NoStorageVersionSet, PalletInfoAccess, StorageVersion}, weights::{RuntimeDbWeight, Weight, WeightMeter}, - BoundedVec, }; use impl_trait_for_tuples::impl_for_tuples; -use sp_core::{ConstU32, Get}; +use sp_core::{Get}; use sp_io::{hashing::twox_128, storage::clear_prefix, KillStorageResult}; use sp_std::marker::PhantomData; #[cfg(feature = "try-runtime")] @@ -214,9 +213,24 @@ impl, DbWeight: Get> frame_support::traits /// A migration that can proceed in multiple steps. pub trait SteppedMigration { + /// The cursor type that stores the progress (aka. state) of this migration. type Cursor: codec::FullCodec + codec::MaxEncodedLen; - fn id(&self) -> BoundedVec>; + /// The unique identifier type of this migration. + type Identifier: codec::FullCodec + codec::MaxEncodedLen; + + /// The unique identifier of this migration. + /// + /// If two migrations have the same identifier, then they are assumed to be identical. + fn id(&self) -> Self::Identifier; + + /// The maximum number of steps that this migration can take at most. + /// + /// This can be used to enforce progress and prevent migrations to be stuck forever. A migration + /// that exceeds its max steps is treated as failed. `None` means that there is no limit. + fn max_steps(&self) -> Option { + None + } /// Try to migrate as much as possible with the given weight. /// @@ -261,6 +275,8 @@ pub enum SteppedMigrationError { /// This can happen if the storage is corrupted or an assumption got invalidated while the /// migration was running. Failed, + /// The migration took longer that its [`SteppedMigration::max_steps`]. + Timeout, } pub trait ExtrinsicSuspenderQuery { From 3351a54b65d3d3c4a1a5767d82f64d7cb5cc3913 Mon Sep 17 00:00:00 2001 From: Oliver Tale-Yazdi Date: Thu, 1 Jun 2023 20:53:32 +0200 Subject: [PATCH 13/63] Tests --- frame/migrations/src/lib.rs | 11 ++-- frame/migrations/src/mock.rs | 40 +++++++++---- frame/migrations/src/tests.rs | 103 ++++++++++++++++++-------------- frame/support/src/migrations.rs | 2 +- 4 files changed, 95 insertions(+), 61 deletions(-) diff --git a/frame/migrations/src/lib.rs b/frame/migrations/src/lib.rs index 1bb1703dd8539..5e553d87fca8a 100644 --- a/frame/migrations/src/lib.rs +++ b/frame/migrations/src/lib.rs @@ -30,7 +30,7 @@ use codec::{Decode, Encode, MaxEncodedLen}; use frame_support::{ defensive, migrations::*, - traits::{Get}, + traits::Get, weights::{Weight, WeightMeter}, }; use frame_system::Pallet as System; @@ -182,9 +182,7 @@ pub mod pallet { return meter.consumed; }; if Historic::::contains_key(&migration.id()) { - Self::deposit_event(Event::MigrationSkippedHistoric { - index: cursor.index, - }); + Self::deposit_event(Event::MigrationSkippedHistoric { index: cursor.index }); cursor.advance(System::::block_number()); continue } @@ -192,7 +190,10 @@ pub mod pallet { let took = System::::block_number().saturating_sub(cursor.started_at); match migration.transactional_step(cursor.inner_cursor.clone(), &mut meter) { Ok(Some(next_cursor)) => { - Self::deposit_event(Event::MigrationAdvanced { index: cursor.index, step: took }); + Self::deposit_event(Event::MigrationAdvanced { + index: cursor.index, + step: took, + }); cursor.inner_cursor = Some(next_cursor); // A migration has to make maximal progress per step, we therefore break. break diff --git a/frame/migrations/src/mock.rs b/frame/migrations/src/mock.rs index 3e3c232c1005d..3bc8f6a29dc2d 100644 --- a/frame/migrations/src/mock.rs +++ b/frame/migrations/src/mock.rs @@ -18,16 +18,15 @@ #![cfg(test)] //use crate::GetMigrations; -use crate::Config; -use core::cell::RefCell; -use sp_core::Get; +use crate::{Config, Historic}; use codec::{Decode, Encode}; +use core::cell::RefCell; use frame_support::{ migrations::*, traits::{ConstU16, ConstU64, OnFinalize, OnInitialize}, weights::{Weight, WeightMeter}, }; -use sp_core::{ConstU32, H256}; +use sp_core::{ConstU32, Get, H256}; use sp_runtime::{ testing::Header, traits::{BlakeTwo256, IdentityLookup}, @@ -132,15 +131,30 @@ thread_local! { } pub struct MigrationsStorage; -impl Get>>> for MigrationsStorage { - fn get() -> Vec>> { - MIGRATIONS.with(|m| - m.borrow().clone().into_iter().map(|(k, v)| Box::new(MockedMigrate(k, v)) as Box>).collect() - ) +impl Get>>> + for MigrationsStorage +{ + fn get() -> Vec>> + { + MIGRATIONS.with(|m| { + m.borrow() + .clone() + .into_iter() + .map(|(k, v)| { + Box::new(MockedMigrate(k, v)) + as Box< + dyn SteppedMigration< + Cursor = MockedCursor, + Identifier = MockedIdentifier, + >, + > + }) + .collect() + }) } } -fn mocked_id(kind: MockedMigrationKind, steps: u32) -> MockedIdentifier { +pub fn mocked_id(kind: MockedMigrationKind, steps: u32) -> MockedIdentifier { format!("MockedMigrate({:?}, {})", kind, steps) .as_bytes() .to_vec() @@ -148,6 +162,12 @@ fn mocked_id(kind: MockedMigrationKind, steps: u32) -> MockedIdentifier { .unwrap() } +pub fn historic() -> Vec { + let mut historic = Historic::::iter_keys().collect::>(); + historic.sort(); + historic +} + impl crate::Config for Test { type RuntimeEvent = RuntimeEvent; type Migrations = MigrationsStorage; diff --git a/frame/migrations/src/tests.rs b/frame/migrations/src/tests.rs index 8eb0b6883660b..b5f9ccca3910e 100644 --- a/frame/migrations/src/tests.rs +++ b/frame/migrations/src/tests.rs @@ -17,10 +17,13 @@ #![cfg(test)] -use crate::{mock::*, mock::MockedMigrationKind::*, Event, Historic}; +use crate::{ + mock::{MockedMigrationKind::*, *}, + Event, Historic, +}; use frame_support::traits::OnRuntimeUpgrade; -use sp_core::H256; use frame_system::EventRecord; +use sp_core::H256; /// Example output: /// @@ -39,11 +42,8 @@ use frame_system::EventRecord; fn basic_works() { new_test_ext().execute_with(|| { MIGRATIONS.with(|migrations| { - *migrations.borrow_mut() = vec![ - (SucceedAfter, 0), - (SucceedAfter, 1), - (SucceedAfter, 2), - ]; + *migrations.borrow_mut() = + vec![(SucceedAfter, 0), (SucceedAfter, 1), (SucceedAfter, 2)]; }); System::set_block_number(1); @@ -54,20 +54,19 @@ fn basic_works() { // Just three, since two were added twice. assert_eq!(Historic::::iter().count(), 3); dbg!(System::events()); - assert_eq!(System::events(), vec![ - Event::UpgradeStarted.into_record(), - - Event::MigrationCompleted { index: 0, took: 0 }.into_record(), - - Event::MigrationAdvanced { index: 1, step: 0 }.into_record(), - Event::MigrationCompleted { index: 1, took: 1 }.into_record(), - - Event::MigrationAdvanced { index: 2, step: 0 }.into_record(), - Event::MigrationAdvanced { index: 2, step: 1 }.into_record(), - Event::MigrationCompleted { index: 2, took: 2 }.into_record(), - - Event::UpgradeCompleted { migrations: 3 }.into_record(), - ]); + assert_eq!( + System::events(), + vec![ + Event::UpgradeStarted.into_record(), + Event::MigrationCompleted { index: 0, took: 0 }.into_record(), + Event::MigrationAdvanced { index: 1, step: 0 }.into_record(), + Event::MigrationCompleted { index: 1, took: 1 }.into_record(), + Event::MigrationAdvanced { index: 2, step: 0 }.into_record(), + Event::MigrationAdvanced { index: 2, step: 1 }.into_record(), + Event::MigrationCompleted { index: 2, took: 2 }.into_record(), + Event::UpgradeCompleted { migrations: 3 }.into_record(), + ] + ); }); } @@ -90,25 +89,43 @@ fn historic_skipping_works() { run_to_block(10); // Just three, since two were added twice. - assert_eq!(Historic::::iter().count(), 3); - assert_eq!(System::events(), vec![ - Event::UpgradeStarted.into_record(), - - Event::MigrationCompleted { index: 0, took: 0 }.into_record(), - - Event::MigrationSkippedHistoric { index: 1 }.into_record(), - - Event::MigrationAdvanced { index: 2, step: 0 }.into_record(), - Event::MigrationCompleted { index: 2, took: 1 }.into_record(), - - Event::MigrationAdvanced { index: 3, step: 0 }.into_record(), - Event::MigrationAdvanced { index: 3, step: 1 }.into_record(), - Event::MigrationCompleted { index: 3, took: 2 }.into_record(), - - Event::MigrationSkippedHistoric { index: 4 }.into_record(), - - Event::UpgradeCompleted { migrations: 5 }.into_record(), - ]); + assert_eq!( + historic(), + vec![ + mocked_id(SucceedAfter, 0), + mocked_id(SucceedAfter, 1), + mocked_id(SucceedAfter, 2), + ] + ); + + assert_eq!( + System::events(), + vec![ + Event::UpgradeStarted.into_record(), + Event::MigrationCompleted { index: 0, took: 0 }.into_record(), + Event::MigrationSkippedHistoric { index: 1 }.into_record(), + Event::MigrationAdvanced { index: 2, step: 0 }.into_record(), + Event::MigrationCompleted { index: 2, took: 1 }.into_record(), + Event::MigrationAdvanced { index: 3, step: 0 }.into_record(), + Event::MigrationAdvanced { index: 3, step: 1 }.into_record(), + Event::MigrationCompleted { index: 3, took: 2 }.into_record(), + Event::MigrationSkippedHistoric { index: 4 }.into_record(), + Event::UpgradeCompleted { migrations: 5 }.into_record(), + ] + ); + + System::reset_events(); + run_to_block(20); + assert!(System::events().is_empty()); + + assert_eq!( + historic(), + vec![ + mocked_id(SucceedAfter, 0), + mocked_id(SucceedAfter, 1), + mocked_id(SucceedAfter, 2), + ] + ); }); } @@ -119,10 +136,6 @@ trait IntoRecord { impl IntoRecord for Event { fn into_record(self) -> EventRecord<::RuntimeEvent, H256> { let re: ::RuntimeEvent = self.into(); - EventRecord { - phase: frame_system::Phase::Initialization, - event: re, - topics: vec![], - } + EventRecord { phase: frame_system::Phase::Initialization, event: re, topics: vec![] } } } diff --git a/frame/support/src/migrations.rs b/frame/support/src/migrations.rs index 9a3e8a8e68902..2351b6e35fa51 100644 --- a/frame/support/src/migrations.rs +++ b/frame/support/src/migrations.rs @@ -23,7 +23,7 @@ use crate::{ weights::{RuntimeDbWeight, Weight, WeightMeter}, }; use impl_trait_for_tuples::impl_for_tuples; -use sp_core::{Get}; +use sp_core::Get; use sp_io::{hashing::twox_128, storage::clear_prefix, KillStorageResult}; use sp_std::marker::PhantomData; #[cfg(feature = "try-runtime")] From 4eeb47e849e5ff9a687af893cb128c06d4bf301c Mon Sep 17 00:00:00 2001 From: Oliver Tale-Yazdi Date: Thu, 1 Jun 2023 22:20:15 +0200 Subject: [PATCH 14/63] Use default configs for System Signed-off-by: Oliver Tale-Yazdi --- frame/migrations/src/mock.rs | 24 +++++------------------- 1 file changed, 5 insertions(+), 19 deletions(-) diff --git a/frame/migrations/src/mock.rs b/frame/migrations/src/mock.rs index 3bc8f6a29dc2d..07207ab5e2eb0 100644 --- a/frame/migrations/src/mock.rs +++ b/frame/migrations/src/mock.rs @@ -21,7 +21,10 @@ use crate::{Config, Historic}; use codec::{Decode, Encode}; use core::cell::RefCell; +#[use_attr] +use frame_support::derive_impl; use frame_support::{ + macro_magic::use_attr, migrations::*, traits::{ConstU16, ConstU64, OnFinalize, OnInitialize}, weights::{Weight, WeightMeter}, @@ -48,31 +51,14 @@ frame_support::construct_runtime!( } ); +#[derive_impl(frame_system::config_preludes::TestDefaultConfig as frame_system::DefaultConfig)] impl frame_system::Config for Test { type BaseCallFilter = frame_support::traits::Everything; - type BlockWeights = (); - type BlockLength = (); - type DbWeight = (); type RuntimeOrigin = RuntimeOrigin; type RuntimeCall = RuntimeCall; - type Index = u64; - type BlockNumber = u64; - type Hash = H256; - type Hashing = BlakeTwo256; - type AccountId = u64; - type Lookup = IdentityLookup; - type Header = Header; type RuntimeEvent = RuntimeEvent; - type BlockHashCount = ConstU64<250>; - type Version = (); type PalletInfo = PalletInfo; - type AccountData = (); - type OnNewAccount = (); - type OnKilledAccount = (); - type SystemWeightInfo = (); - type SS58Prefix = ConstU16<42>; type OnSetCode = (); - type MaxConsumers = frame_support::traits::ConstU32<16>; } #[derive(Debug, Clone, Copy)] @@ -195,7 +181,7 @@ pub fn assert_last_event(generic_event: ::RuntimeEvent) frame_system::Pallet::::assert_last_event(generic_event.into()); } -pub fn run_to_block(n: u64) { +pub fn run_to_block(n: u32) { while System::block_number() < n { if System::block_number() > 1 { Migrations::on_finalize(System::block_number()); From 80922c99e23a84683142e26bbf541e2d792dd958 Mon Sep 17 00:00:00 2001 From: Oliver Tale-Yazdi Date: Thu, 1 Jun 2023 22:53:25 +0200 Subject: [PATCH 15/63] Tests Signed-off-by: Oliver Tale-Yazdi --- frame/migrations/src/lib.rs | 2 +- frame/migrations/src/mock.rs | 57 +++++++++--- frame/migrations/src/tests.rs | 169 +++++++++++++++++++--------------- 3 files changed, 142 insertions(+), 86 deletions(-) diff --git a/frame/migrations/src/lib.rs b/frame/migrations/src/lib.rs index 5e553d87fca8a..1314deb6b4b0e 100644 --- a/frame/migrations/src/lib.rs +++ b/frame/migrations/src/lib.rs @@ -140,7 +140,7 @@ pub mod pallet { impl Hooks> for Pallet { fn on_runtime_upgrade() -> Weight { if Cursor::::exists() { - log::error!(target: LOG_TARGET, "Code for ongoing migrations was deleted."); + defensive!("Code for ongoing migrations was deleted."); Self::deposit_event(Event::UpgradeFailed); Cursor::::set(Some(MigrationCursor::Stuck)); return T::WeightInfo::on_runtime_upgrade() diff --git a/frame/migrations/src/mock.rs b/frame/migrations/src/mock.rs index 07207ab5e2eb0..4e218b235b47c 100644 --- a/frame/migrations/src/mock.rs +++ b/frame/migrations/src/mock.rs @@ -17,8 +17,7 @@ #![cfg(test)] -//use crate::GetMigrations; -use crate::{Config, Historic}; +use crate::{Event, Historic}; use codec::{Decode, Encode}; use core::cell::RefCell; #[use_attr] @@ -26,15 +25,12 @@ use frame_support::derive_impl; use frame_support::{ macro_magic::use_attr, migrations::*, - traits::{ConstU16, ConstU64, OnFinalize, OnInitialize}, + traits::{OnFinalize, OnInitialize}, weights::{Weight, WeightMeter}, }; +use frame_system::EventRecord; use sp_core::{ConstU32, Get, H256}; -use sp_runtime::{ - testing::Header, - traits::{BlakeTwo256, IdentityLookup}, - BoundedVec, -}; +use sp_runtime::BoundedVec; type UncheckedExtrinsic = frame_system::mocking::MockUncheckedExtrinsic; type Block = frame_system::mocking::MockBlock; @@ -140,6 +136,13 @@ impl Get) { + MIGRATIONS.with(|m| *m.borrow_mut() = migrations); + } +} + pub fn mocked_id(kind: MockedMigrationKind, steps: u32) -> MockedIdentifier { format!("MockedMigrate({:?}, {})", kind, steps) .as_bytes() @@ -168,6 +171,12 @@ pub fn new_test_ext() -> sp_io::TestExternalities { frame_system::GenesisConfig::default().build_storage::().unwrap().into() } +/// Run this closure in test externalities. +pub fn test_closure(f: impl FnOnce() -> R) -> R { + let mut ext = new_test_ext(); + ext.execute_with(f) +} + pub struct LoggingSuspender(core::marker::PhantomData); impl ExtrinsicSuspenderQuery for LoggingSuspender { fn is_suspended() -> bool { @@ -177,10 +186,6 @@ impl ExtrinsicSuspenderQuery for LoggingSuspende } } -pub fn assert_last_event(generic_event: ::RuntimeEvent) { - frame_system::Pallet::::assert_last_event(generic_event.into()); -} - pub fn run_to_block(n: u32) { while System::block_number() < n { if System::block_number() > 1 { @@ -193,3 +198,31 @@ pub fn run_to_block(n: u32) { Migrations::on_initialize(System::block_number()); } } + +// Traits to make using events less insufferable: + +pub trait IntoRecord { + fn into_record(self) -> EventRecord<::RuntimeEvent, H256>; +} + +impl IntoRecord for Event { + fn into_record(self) -> EventRecord<::RuntimeEvent, H256> { + let re: ::RuntimeEvent = self.into(); + EventRecord { phase: frame_system::Phase::Initialization, event: re, topics: vec![] } + } +} + +pub trait IntoRecords { + fn into_records(self) -> Vec::RuntimeEvent, H256>>; +} + +impl IntoRecords for Vec { + fn into_records(self) -> Vec::RuntimeEvent, H256>> { + self.into_iter().map(|e| e.into_record()).collect() + } +} + +pub fn assert_events(events: Vec) { + assert_eq!(System::events(), events.into_records()); + System::reset_events(); +} diff --git a/frame/migrations/src/tests.rs b/frame/migrations/src/tests.rs index b5f9ccca3910e..7487cf0d526c8 100644 --- a/frame/migrations/src/tests.rs +++ b/frame/migrations/src/tests.rs @@ -18,77 +18,60 @@ #![cfg(test)] use crate::{ - mock::{MockedMigrationKind::*, *}, - Event, Historic, + mock::{MockedMigrationKind::*, Test as T, *}, + Cursor, Event, MigrationCursor, }; use frame_support::traits::OnRuntimeUpgrade; -use frame_system::EventRecord; -use sp_core::H256; - -/// Example output: -/// -/// ```pre -/// Suspend -/// MockedMigrate: Step 1/2 -/// MockedMigrate: Step 2/2 -/// MockedMigrate: Succeeded after 2 steps -/// MockedMigrate: Step 1/3 -/// MockedMigrate: Step 2/3 -/// MockedMigrate: Step 3/3 -/// MockedMigrate: Succeeded after 3 steps -/// Resume -/// ``` + #[test] fn basic_works() { - new_test_ext().execute_with(|| { - MIGRATIONS.with(|migrations| { - *migrations.borrow_mut() = - vec![(SucceedAfter, 0), (SucceedAfter, 1), (SucceedAfter, 2)]; - }); + test_closure(|| { + // Add three migrations. Each taking one block longer. + MigrationsStorage::set(vec![(SucceedAfter, 0), (SucceedAfter, 1), (SucceedAfter, 2)]); System::set_block_number(1); Migrations::on_runtime_upgrade(); - run_to_block(10); - // Just three, since two were added twice. - assert_eq!(Historic::::iter().count(), 3); - dbg!(System::events()); + // Check that the executed migrations are recorded into `Historical`. assert_eq!( - System::events(), + historic(), vec![ - Event::UpgradeStarted.into_record(), - Event::MigrationCompleted { index: 0, took: 0 }.into_record(), - Event::MigrationAdvanced { index: 1, step: 0 }.into_record(), - Event::MigrationCompleted { index: 1, took: 1 }.into_record(), - Event::MigrationAdvanced { index: 2, step: 0 }.into_record(), - Event::MigrationAdvanced { index: 2, step: 1 }.into_record(), - Event::MigrationCompleted { index: 2, took: 2 }.into_record(), - Event::UpgradeCompleted { migrations: 3 }.into_record(), + mocked_id(SucceedAfter, 0), + mocked_id(SucceedAfter, 1), + mocked_id(SucceedAfter, 2), ] ); + // Check that we got all events. + assert_events(vec![ + Event::UpgradeStarted, + Event::MigrationCompleted { index: 0, took: 0 }, + Event::MigrationAdvanced { index: 1, step: 0 }, + Event::MigrationCompleted { index: 1, took: 1 }, + Event::MigrationAdvanced { index: 2, step: 0 }, + Event::MigrationAdvanced { index: 2, step: 1 }, + Event::MigrationCompleted { index: 2, took: 2 }, + Event::UpgradeCompleted { migrations: 3 }, + ]); }); } #[test] fn historic_skipping_works() { - new_test_ext().execute_with(|| { - MIGRATIONS.with(|migrations| { - *migrations.borrow_mut() = vec![ - (SucceedAfter, 0), - (SucceedAfter, 0), // Will be skipped - (SucceedAfter, 1), - (SucceedAfter, 2), - (SucceedAfter, 1), // Will be skipped - ]; - }); + test_closure(|| { + MigrationsStorage::set(vec![ + (SucceedAfter, 0), + (SucceedAfter, 0), // duplicate + (SucceedAfter, 1), + (SucceedAfter, 2), + (SucceedAfter, 1), // duplicate + ]); System::set_block_number(1); Migrations::on_runtime_upgrade(); - run_to_block(10); - // Just three, since two were added twice. + // Just three historical ones, since two were added twice. assert_eq!( historic(), vec![ @@ -97,27 +80,26 @@ fn historic_skipping_works() { mocked_id(SucceedAfter, 2), ] ); - - assert_eq!( - System::events(), - vec![ - Event::UpgradeStarted.into_record(), - Event::MigrationCompleted { index: 0, took: 0 }.into_record(), - Event::MigrationSkippedHistoric { index: 1 }.into_record(), - Event::MigrationAdvanced { index: 2, step: 0 }.into_record(), - Event::MigrationCompleted { index: 2, took: 1 }.into_record(), - Event::MigrationAdvanced { index: 3, step: 0 }.into_record(), - Event::MigrationAdvanced { index: 3, step: 1 }.into_record(), - Event::MigrationCompleted { index: 3, took: 2 }.into_record(), - Event::MigrationSkippedHistoric { index: 4 }.into_record(), - Event::UpgradeCompleted { migrations: 5 }.into_record(), - ] - ); - + // Events received. + assert_events(vec![ + Event::UpgradeStarted, + Event::MigrationCompleted { index: 0, took: 0 }, + Event::MigrationSkippedHistoric { index: 1 }, + Event::MigrationAdvanced { index: 2, step: 0 }, + Event::MigrationCompleted { index: 2, took: 1 }, + Event::MigrationAdvanced { index: 3, step: 0 }, + Event::MigrationAdvanced { index: 3, step: 1 }, + Event::MigrationCompleted { index: 3, took: 2 }, + Event::MigrationSkippedHistoric { index: 4 }, + Event::UpgradeCompleted { migrations: 5 }, + ]); + + // Now go for another upgrade; just to make sure that it wont execute again. System::reset_events(); + Migrations::on_runtime_upgrade(); run_to_block(20); - assert!(System::events().is_empty()); + // Same historical ones as before. assert_eq!( historic(), vec![ @@ -126,16 +108,57 @@ fn historic_skipping_works() { mocked_id(SucceedAfter, 2), ] ); + + // Everything got skipped. + assert_events(vec![ + Event::UpgradeStarted, + Event::MigrationSkippedHistoric { index: 0 }, + Event::MigrationSkippedHistoric { index: 1 }, + Event::MigrationSkippedHistoric { index: 2 }, + Event::MigrationSkippedHistoric { index: 3 }, + Event::MigrationSkippedHistoric { index: 4 }, + Event::UpgradeCompleted { migrations: 5 }, + ]); }); } -trait IntoRecord { - fn into_record(self) -> EventRecord<::RuntimeEvent, H256>; +#[test] +#[cfg(debug_assertions)] +#[should_panic(expected = "Defensive")] +fn upgrade_fails_when_migration_active_err() { + upgrade_fails_when_migration_active(); +} + +#[test] +#[cfg(not(debug_assertions))] +fn upgrade_fails_when_migration_active_ok() { + upgrade_fails_when_migration_active(); } -impl IntoRecord for Event { - fn into_record(self) -> EventRecord<::RuntimeEvent, H256> { - let re: ::RuntimeEvent = self.into(); - EventRecord { phase: frame_system::Phase::Initialization, event: re, topics: vec![] } - } +/// When another upgrade happens while a migration is still running, it should stuck the chain. +// FAIL-CI we could still check the unique id and only fail if it changed... +fn upgrade_fails_when_migration_active() { + test_closure(|| { + MigrationsStorage::set(vec![(SucceedAfter, 10)]); + + System::set_block_number(1); + Migrations::on_runtime_upgrade(); + run_to_block(3); + + //assert_eq!( // TODO + // historic(), + // vec![mocked_id(SucceedAfter, 0)] + //); + // Events received. + assert_events(vec![ + Event::UpgradeStarted, + Event::MigrationAdvanced { index: 0, step: 0 }, + Event::MigrationAdvanced { index: 0, step: 1 }, + ]); + // Upgrade again. + Migrations::on_runtime_upgrade(); + // -- Defensive triggered -- + assert_eq!(Cursor::::get(), Some(MigrationCursor::Stuck)); + assert_events(vec![Event::UpgradeFailed]); + }); } From 4ae8fbfa3d513824f80b0d08b8d89a7af2314315 Mon Sep 17 00:00:00 2001 From: Oliver Tale-Yazdi Date: Thu, 1 Jun 2023 23:07:18 +0200 Subject: [PATCH 16/63] Fixes Signed-off-by: Oliver Tale-Yazdi --- bin/node/runtime/src/lib.rs | 9 +-- frame/migrations/src/benchmarking.rs | 22 ++++--- frame/migrations/src/lib.rs | 10 ++-- frame/migrations/src/weights.rs | 87 +++++++++++++++++----------- 4 files changed, 74 insertions(+), 54 deletions(-) diff --git a/bin/node/runtime/src/lib.rs b/bin/node/runtime/src/lib.rs index 0799b4a78c959..8b2e74cfff9aa 100644 --- a/bin/node/runtime/src/lib.rs +++ b/bin/node/runtime/src/lib.rs @@ -30,7 +30,6 @@ use frame_support::{ construct_runtime, dispatch::DispatchClass, instances::{Instance1, Instance2}, - migrations::SteppedMigration, ord_parameter_types, pallet_prelude::Get, parameter_types, @@ -1856,17 +1855,15 @@ impl pallet_statement::Config for Runtime { type MaxAllowedBytes = MaxAllowedBytes; } -type MbmCursor = BoundedVec>; - parameter_types! { pub MbmServiceWeight: Weight = Perbill::from_percent(80) * RuntimeBlockWeights::get().max_block; - pub const MbmMigrations: Vec>> = vec![]; } impl pallet_migrations::Config for Runtime { type RuntimeEvent = RuntimeEvent; - type Migrations = MbmMigrations; - type Cursor = MbmCursor; + type Migrations = (); + type Cursor = (); + type Identifier = (); type ServiceWeight = MbmServiceWeight; type WeightInfo = pallet_migrations::weights::SubstrateWeight; } diff --git a/frame/migrations/src/benchmarking.rs b/frame/migrations/src/benchmarking.rs index 60418b82cbf33..b9c56029eed12 100644 --- a/frame/migrations/src/benchmarking.rs +++ b/frame/migrations/src/benchmarking.rs @@ -20,7 +20,6 @@ use super::*; use frame_benchmarking::v2::*; -use frame_support::migrations::STEPPED_MIGRATION_CURSOR_LEN as CURSOR_LEN; use frame_system::{Pallet as System, RawOrigin}; #[benchmarks] @@ -40,7 +39,7 @@ mod benches { #[benchmark] fn on_init_base() { - assert!(!Cursor::::exists()); + Cursor::::set(Some(cursor::())); System::::set_block_number(1u32.into()); #[block] @@ -63,17 +62,22 @@ mod benches { /// Benchmarks the slowest path of `change_value`. #[benchmark] fn force_set_cursor() { - Cursor::::set(Some(cursor(0))); + Cursor::::set(Some(cursor::())); #[extrinsic_call] - _(RawOrigin::Root, Some(cursor(1))); + _(RawOrigin::Root, Some(cursor::())); } - fn cursor(i: u32) -> MigrationCursor { - MigrationCursor::Active( - u32::MAX - i, - Some(vec![1u8; CURSOR_LEN as usize].try_into().expect("Static length is good")), - ) + fn cursor() -> MigrationCursor { + // Note: The weight of a function can depend on the weight of reading the `inner_cursor`. + // `Cursor` is a user provided type. Now instead of requeuing something like `Cursor: + // From`, we instead rely on the fact that it is MEL and the PoV benchmarking will + // therefore already take the MEL bound, even when the cursor in storage is `None`. + MigrationCursor::Active(ActiveCursor { + index: u32::MAX, + inner_cursor: None, + started_at: 0u32.into(), + }) } // Implements a test for each benchmark. Execute with: diff --git a/frame/migrations/src/lib.rs b/frame/migrations/src/lib.rs index 1314deb6b4b0e..602eb8ece3765 100644 --- a/frame/migrations/src/lib.rs +++ b/frame/migrations/src/lib.rs @@ -140,7 +140,7 @@ pub mod pallet { impl Hooks> for Pallet { fn on_runtime_upgrade() -> Weight { if Cursor::::exists() { - defensive!("Code for ongoing migrations was deleted."); + defensive!("Ongoing migrations interrupted - chain stuck"); Self::deposit_event(Event::UpgradeFailed); Cursor::::set(Some(MigrationCursor::Stuck)); return T::WeightInfo::on_runtime_upgrade() @@ -150,6 +150,7 @@ pub mod pallet { Cursor::::set(Some(MigrationCursor::Active(ActiveCursor { index: 0, inner_cursor: None, + // TODO is this +1 correct? started_at: System::::block_number().saturating_add(1u32.into()), }))); Self::deposit_event(Event::UpgradeStarted); @@ -228,13 +229,12 @@ pub mod pallet { } } - #[pallet::call] + #[pallet::call(weight = T::WeightInfo)] impl Pallet { /// Allows root to set the cursor to any value. /// /// Should normally not be needed and is only in place as emergency measure. #[pallet::call_index(0)] - #[pallet::weight((0, DispatchClass::Mandatory))] pub fn force_set_cursor( origin: OriginFor, cursor: Option>, @@ -247,11 +247,11 @@ pub mod pallet { } #[pallet::call_index(1)] - #[pallet::weight(0)] + #[pallet::weight({0})] // FAIL-CI pub fn clear_historic(origin: OriginFor) -> DispatchResult { ensure_root(origin)?; - Historic::::clear(0, None); + let _ = Historic::::clear(0, None); // FAIL-CI think about limit + cursor Self::deposit_event(Event::HistoricCleared); Ok(()) diff --git a/frame/migrations/src/weights.rs b/frame/migrations/src/weights.rs index aef7891d60ff7..aea876d3a0cda 100644 --- a/frame/migrations/src/weights.rs +++ b/frame/migrations/src/weights.rs @@ -2,7 +2,7 @@ //! Autogenerated weights for pallet_migrations //! //! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 4.0.0-dev -//! DATE: 2023-05-31, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! DATE: 2023-06-01, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]` //! WORST CASE MAP SIZE: `1000000` //! HOSTNAME: `i9`, CPU: `13th Gen Intel(R) Core(TM) i9-13900K` //! EXECUTION: None, WASM-EXECUTION: Compiled, CHAIN: None, DB CACHE: 1024 @@ -34,8 +34,9 @@ use core::marker::PhantomData; /// Weight functions needed for pallet_migrations. pub trait WeightInfo { - fn on_runtime_upgrade_bail() -> Weight; fn on_runtime_upgrade() -> Weight; + fn on_init_base() -> Weight; + fn on_init_loop_base() -> Weight; fn force_set_cursor() -> Weight; } @@ -43,34 +44,43 @@ pub trait WeightInfo { pub struct SubstrateWeight(PhantomData); impl WeightInfo for SubstrateWeight { /// Storage: MultiBlockMigrations Cursor (r:1 w:0) - /// Proof: MultiBlockMigrations Cursor (max_values: Some(1), max_size: Some(1030), added: 1525, mode: MaxEncodedLen) - fn on_runtime_upgrade_bail() -> Weight { + /// Proof: MultiBlockMigrations Cursor (max_values: Some(1), max_size: Some(10), added: 505, mode: MaxEncodedLen) + fn on_runtime_upgrade() -> Weight { // Proof Size summary in bytes: - // Measured: `165` - // Estimated: `2515` - // Minimum execution time: 3_001_000 picoseconds. - Weight::from_parts(3_217_000, 2515) + // Measured: `109` + // Estimated: `1495` + // Minimum execution time: 1_048_000 picoseconds. + Weight::from_parts(1_124_000, 1495) .saturating_add(T::DbWeight::get().reads(1_u64)) } - /// Storage: MultiBlockMigrations Cursor (r:1 w:1) - /// Proof: MultiBlockMigrations Cursor (max_values: Some(1), max_size: Some(1030), added: 1525, mode: MaxEncodedLen) - fn on_runtime_upgrade() -> Weight { + /// Storage: MultiBlockMigrations Cursor (r:1 w:0) + /// Proof: MultiBlockMigrations Cursor (max_values: Some(1), max_size: Some(10), added: 505, mode: MaxEncodedLen) + fn on_init_base() -> Weight { // Proof Size summary in bytes: // Measured: `109` - // Estimated: `2515` - // Minimum execution time: 2_524_000 picoseconds. - Weight::from_parts(2_637_000, 2515) + // Estimated: `1495` + // Minimum execution time: 1_286_000 picoseconds. + Weight::from_parts(1_346_000, 1495) + .saturating_add(T::DbWeight::get().reads(1_u64)) + } + /// Storage: MultiBlockMigrations Cursor (r:1 w:0) + /// Proof: MultiBlockMigrations Cursor (max_values: Some(1), max_size: Some(10), added: 505, mode: MaxEncodedLen) + fn on_init_loop_base() -> Weight { + // Proof Size summary in bytes: + // Measured: `109` + // Estimated: `1495` + // Minimum execution time: 1_281_000 picoseconds. + Weight::from_parts(1_368_000, 1495) .saturating_add(T::DbWeight::get().reads(1_u64)) - .saturating_add(T::DbWeight::get().writes(1_u64)) } /// Storage: MultiBlockMigrations Cursor (r:0 w:1) - /// Proof: MultiBlockMigrations Cursor (max_values: Some(1), max_size: Some(1030), added: 1525, mode: MaxEncodedLen) + /// Proof: MultiBlockMigrations Cursor (max_values: Some(1), max_size: Some(10), added: 505, mode: MaxEncodedLen) fn force_set_cursor() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 1_035_000 picoseconds. - Weight::from_parts(1_183_000, 0) + // Minimum execution time: 827_000 picoseconds. + Weight::from_parts(927_000, 0) .saturating_add(T::DbWeight::get().writes(1_u64)) } } @@ -78,34 +88,43 @@ impl WeightInfo for SubstrateWeight { // For backwards compatibility and tests impl WeightInfo for () { /// Storage: MultiBlockMigrations Cursor (r:1 w:0) - /// Proof: MultiBlockMigrations Cursor (max_values: Some(1), max_size: Some(1030), added: 1525, mode: MaxEncodedLen) - fn on_runtime_upgrade_bail() -> Weight { + /// Proof: MultiBlockMigrations Cursor (max_values: Some(1), max_size: Some(10), added: 505, mode: MaxEncodedLen) + fn on_runtime_upgrade() -> Weight { // Proof Size summary in bytes: - // Measured: `165` - // Estimated: `2515` - // Minimum execution time: 3_001_000 picoseconds. - Weight::from_parts(3_217_000, 2515) + // Measured: `109` + // Estimated: `1495` + // Minimum execution time: 1_048_000 picoseconds. + Weight::from_parts(1_124_000, 1495) .saturating_add(RocksDbWeight::get().reads(1_u64)) } - /// Storage: MultiBlockMigrations Cursor (r:1 w:1) - /// Proof: MultiBlockMigrations Cursor (max_values: Some(1), max_size: Some(1030), added: 1525, mode: MaxEncodedLen) - fn on_runtime_upgrade() -> Weight { + /// Storage: MultiBlockMigrations Cursor (r:1 w:0) + /// Proof: MultiBlockMigrations Cursor (max_values: Some(1), max_size: Some(10), added: 505, mode: MaxEncodedLen) + fn on_init_base() -> Weight { // Proof Size summary in bytes: // Measured: `109` - // Estimated: `2515` - // Minimum execution time: 2_524_000 picoseconds. - Weight::from_parts(2_637_000, 2515) + // Estimated: `1495` + // Minimum execution time: 1_286_000 picoseconds. + Weight::from_parts(1_346_000, 1495) + .saturating_add(RocksDbWeight::get().reads(1_u64)) + } + /// Storage: MultiBlockMigrations Cursor (r:1 w:0) + /// Proof: MultiBlockMigrations Cursor (max_values: Some(1), max_size: Some(10), added: 505, mode: MaxEncodedLen) + fn on_init_loop_base() -> Weight { + // Proof Size summary in bytes: + // Measured: `109` + // Estimated: `1495` + // Minimum execution time: 1_281_000 picoseconds. + Weight::from_parts(1_368_000, 1495) .saturating_add(RocksDbWeight::get().reads(1_u64)) - .saturating_add(RocksDbWeight::get().writes(1_u64)) } /// Storage: MultiBlockMigrations Cursor (r:0 w:1) - /// Proof: MultiBlockMigrations Cursor (max_values: Some(1), max_size: Some(1030), added: 1525, mode: MaxEncodedLen) + /// Proof: MultiBlockMigrations Cursor (max_values: Some(1), max_size: Some(10), added: 505, mode: MaxEncodedLen) fn force_set_cursor() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 1_035_000 picoseconds. - Weight::from_parts(1_183_000, 0) + // Minimum execution time: 827_000 picoseconds. + Weight::from_parts(927_000, 0) .saturating_add(RocksDbWeight::get().writes(1_u64)) } } From e6beda5309a3839c7b1c8bb80b86339bd23250de Mon Sep 17 00:00:00 2001 From: Oliver Tale-Yazdi Date: Fri, 2 Jun 2023 11:56:43 +0200 Subject: [PATCH 17/63] Add status handling --- frame/migrations/src/lib.rs | 37 ++++++++++++++++++++++++++------- frame/migrations/src/mock.rs | 22 ++++++++++++++++++++ frame/migrations/src/tests.rs | 16 ++------------ frame/support/src/migrations.rs | 36 ++++++++++++++++++++++++++++++++ 4 files changed, 89 insertions(+), 22 deletions(-) diff --git a/frame/migrations/src/lib.rs b/frame/migrations/src/lib.rs index 602eb8ece3765..e45f6f587989a 100644 --- a/frame/migrations/src/lib.rs +++ b/frame/migrations/src/lib.rs @@ -54,6 +54,15 @@ pub struct ActiveCursor { started_at: BlockNumber, } +impl MigrationCursor { + pub fn as_active(&self) -> Option<&ActiveCursor> { + match self { + MigrationCursor::Active(active) => Some(active), + MigrationCursor::Stuck => None, + } + } +} + impl ActiveCursor { pub(crate) fn advance(&mut self, current_block: BlockNumber) { self.index.saturating_inc(); @@ -90,6 +99,9 @@ pub mod pallet { /// The identifier type that is shared across all migrations. type Identifier: codec::FullCodec + codec::MaxEncodedLen + scale_info::TypeInfo; + /// Notification handler for status updates regarding runtime upgrades. + type UpgradeStatusHandler: UpgradeStatusHandler; + /// The weight to spend each block to execute migrations. type ServiceWeight: Get; @@ -139,21 +151,26 @@ pub mod pallet { #[pallet::hooks] impl Hooks> for Pallet { fn on_runtime_upgrade() -> Weight { - if Cursor::::exists() { - defensive!("Ongoing migrations interrupted - chain stuck"); + use FailedUpgradeHandling::*; + + if let Some(cursor) = Cursor::::get() { + log::error!("Ongoing migrations interrupted - chain stuck"); Self::deposit_event(Event::UpgradeFailed); - Cursor::::set(Some(MigrationCursor::Stuck)); - return T::WeightInfo::on_runtime_upgrade() - } - if T::Migrations::get().len() > 0 { + let maybe_index = cursor.as_active().map(|c| c.index); + match T::UpgradeStatusHandler::failed(maybe_index) { + KeepStuck => Cursor::::set(Some(MigrationCursor::Stuck)), + ForceUnstuck => Cursor::::kill(), + } + } else if T::Migrations::get().len() > 0 { Cursor::::set(Some(MigrationCursor::Active(ActiveCursor { index: 0, inner_cursor: None, - // TODO is this +1 correct? + // +1 to get the step index. started_at: System::::block_number().saturating_add(1u32.into()), }))); Self::deposit_event(Event::UpgradeStarted); + T::UpgradeStatusHandler::started(); } T::WeightInfo::on_runtime_upgrade() @@ -161,6 +178,7 @@ pub mod pallet { fn on_initialize(n: T::BlockNumber) -> Weight { let mut meter = WeightMeter::from_limit(T::ServiceWeight::get()); + meter.defensive_saturating_accrue(T::WeightInfo::on_init_base()); let mut cursor = match Cursor::::get() { None => { @@ -175,11 +193,14 @@ pub mod pallet { }; debug_assert!(::is_suspended()); + meter.defensive_saturating_accrue(T::WeightInfo::on_init_loop_base()); let migrations = T::Migrations::get(); - for iteration in 0.. { + let mut iteration = 0; + while meter.check_accrue(T::WeightInfo::on_init_loop_base()) { // TODO let Some(migration) = migrations.get(cursor.index as usize) else { Self::deposit_event(Event::UpgradeCompleted { migrations: migrations.len() as u32 }); Cursor::::kill(); + T::UpgradeStatusHandler::completed(); return meter.consumed; }; if Historic::::contains_key(&migration.id()) { diff --git a/frame/migrations/src/mock.rs b/frame/migrations/src/mock.rs index 4e218b235b47c..a17ed9129e70e 100644 --- a/frame/migrations/src/mock.rs +++ b/frame/migrations/src/mock.rs @@ -162,12 +162,14 @@ impl crate::Config for Test { type Migrations = MigrationsStorage; type Cursor = MockedCursor; type Identifier = MockedIdentifier; + type UpgradeStatusHandler = LoggingUpgradeStatusHandler<()>; type ServiceWeight = ServiceWeight; type WeightInfo = (); } // Build genesis storage according to the mock runtime. pub fn new_test_ext() -> sp_io::TestExternalities { + sp_tracing::try_init_simple(); frame_system::GenesisConfig::default().build_storage::().unwrap().into() } @@ -226,3 +228,23 @@ pub fn assert_events(events: Vec) { assert_eq!(System::events(), events.into_records()); System::reset_events(); } + +/// Wraps an [`UpgradeStatusHandler`] and adds logging. +pub struct LoggingUpgradeStatusHandler(core::marker::PhantomData); +impl UpgradeStatusHandler for LoggingUpgradeStatusHandler { + fn started() { + log::info!("UpgradeStatusHandler started"); + T::started(); + } + + fn completed() { + log::info!("UpgradeStatusHandler completed"); + T::completed(); + } + + fn failed(migration: Option) -> FailedUpgradeHandling { + let res = T::failed(migration); + log::error!("UpgradeStatusHandler failed at: {migration:?}, handling as {res:?}"); + res + } +} diff --git a/frame/migrations/src/tests.rs b/frame/migrations/src/tests.rs index 7487cf0d526c8..249613a7403d7 100644 --- a/frame/migrations/src/tests.rs +++ b/frame/migrations/src/tests.rs @@ -122,21 +122,9 @@ fn historic_skipping_works() { }); } -#[test] -#[cfg(debug_assertions)] -#[should_panic(expected = "Defensive")] -fn upgrade_fails_when_migration_active_err() { - upgrade_fails_when_migration_active(); -} - -#[test] -#[cfg(not(debug_assertions))] -fn upgrade_fails_when_migration_active_ok() { - upgrade_fails_when_migration_active(); -} - /// When another upgrade happens while a migration is still running, it should stuck the chain. // FAIL-CI we could still check the unique id and only fail if it changed... +#[test] fn upgrade_fails_when_migration_active() { test_closure(|| { MigrationsStorage::set(vec![(SucceedAfter, 10)]); @@ -157,7 +145,7 @@ fn upgrade_fails_when_migration_active() { ]); // Upgrade again. Migrations::on_runtime_upgrade(); - // -- Defensive triggered -- + // -- Defensive path -- assert_eq!(Cursor::::get(), Some(MigrationCursor::Stuck)); assert_events(vec![Event::UpgradeFailed]); }); diff --git a/frame/support/src/migrations.rs b/frame/support/src/migrations.rs index 2351b6e35fa51..8a001a5b73843 100644 --- a/frame/support/src/migrations.rs +++ b/frame/support/src/migrations.rs @@ -288,3 +288,39 @@ impl ExtrinsicSuspenderQuery for () { false } } + +/// Notification handler for status updates regarding runtime upgrades. +pub trait UpgradeStatusHandler { + /// Notifies of the start of a runtime upgrade. + /// + /// Can be used to pause XCM etc. + fn started() {} + + /// Notifies of the completion of a runtime upgrade. + /// + /// Can be used to resume XCM etc. + fn completed() {} + + /// Infallibly handle a failed runtime upgrade. + /// + /// Gets optionally passed in the index of the migration that caused the failure. + fn failed(migration: Option) -> FailedUpgradeHandling; +} + +/// How to proceed after a runtime upgrade failed. +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum FailedUpgradeHandling { + /// Resume extrinsic processing of the chain. This will not resume the upgrade. + /// + /// This should be supplemented with additional measures to ensure that the broken chain state + /// does not get further messed up by user extrinsics. + ForceUnstuck, + /// Do nothing and keep blocking extrinsics. + KeepStuck, +} + +impl UpgradeStatusHandler for () { + fn failed(_migration: Option) -> FailedUpgradeHandling { + FailedUpgradeHandling::KeepStuck + } +} From fa204688fb84b8b45704ca2bd8f53ed23a91da3e Mon Sep 17 00:00:00 2001 From: Oliver Tale-Yazdi Date: Fri, 2 Jun 2023 12:45:28 +0200 Subject: [PATCH 18/63] Refactor Signed-off-by: Oliver Tale-Yazdi --- bin/node/runtime/src/lib.rs | 1 + frame/migrations/src/benchmarking.rs | 23 ++++- frame/migrations/src/lib.rs | 123 ++++++++++++++++----------- frame/migrations/src/weights.rs | 47 +++++----- 4 files changed, 123 insertions(+), 71 deletions(-) diff --git a/bin/node/runtime/src/lib.rs b/bin/node/runtime/src/lib.rs index 8b2e74cfff9aa..b940e5b0c9118 100644 --- a/bin/node/runtime/src/lib.rs +++ b/bin/node/runtime/src/lib.rs @@ -1864,6 +1864,7 @@ impl pallet_migrations::Config for Runtime { type Migrations = (); type Cursor = (); type Identifier = (); + type UpgradeStatusHandler = (); type ServiceWeight = MbmServiceWeight; type WeightInfo = pallet_migrations::weights::SubstrateWeight; } diff --git a/frame/migrations/src/benchmarking.rs b/frame/migrations/src/benchmarking.rs index b9c56029eed12..5a3ed667fc806 100644 --- a/frame/migrations/src/benchmarking.rs +++ b/frame/migrations/src/benchmarking.rs @@ -37,6 +37,17 @@ mod benches { } } + #[benchmark] + fn on_init_fast_path() { + Cursor::::set(Some(cursor::())); + System::::set_block_number(1u32.into()); + + #[block] + { + Pallet::::on_initialize(1u32.into()); + } + } + #[benchmark] fn on_init_base() { Cursor::::set(Some(cursor::())); @@ -49,7 +60,17 @@ mod benches { } #[benchmark] - fn on_init_loop_base() { + fn load_migrations() { + Pallet::::on_initialize(); + + #[block] + { + Pallet::::on_initialize(1u32.into()); + } + } + + #[benchmark] + fn on_init_loop() { System::::set_block_number(1u32.into()); Pallet::::on_runtime_upgrade(); diff --git a/frame/migrations/src/lib.rs b/frame/migrations/src/lib.rs index e45f6f587989a..d98dea938ee91 100644 --- a/frame/migrations/src/lib.rs +++ b/frame/migrations/src/lib.rs @@ -33,6 +33,7 @@ use frame_support::{ traits::Get, weights::{Weight, WeightMeter}, }; +use core::ops::ControlFlow; use frame_system::Pallet as System; use sp_runtime::Saturating; @@ -71,12 +72,15 @@ impl ActiveCursor { } } +pub type MigrationsOf = Vec::Cursor, Identifier = ::Identifier>>>; +pub type CursorOf = MigrationCursor<::Cursor, ::BlockNumber>; +pub type ActiveCursorOf = ActiveCursor<::Cursor, ::BlockNumber>; + #[frame_support::pallet] pub mod pallet { use super::*; use frame_support::pallet_prelude::*; use frame_system::pallet_prelude::*; - use sp_std::{boxed::Box, vec::Vec}; #[pallet::pallet] pub struct Pallet(_); @@ -89,9 +93,7 @@ pub mod pallet { /// /// Should only be updated in a runtime-upgrade once all the old ones have completed. (Check /// `Cursor` for `None`). - type Migrations: Get< - Vec>>, - >; + type Migrations: Get>; /// The cursor type that is shared across all migrations. type Cursor: codec::FullCodec + codec::MaxEncodedLen + scale_info::TypeInfo + Parameter; @@ -114,7 +116,7 @@ pub mod pallet { /// `None` indicates that no migration process is running. #[pallet::storage] pub type Cursor = - StorageValue<_, MigrationCursor, OptionQuery>; + StorageValue<_, CursorOf, OptionQuery>; /// Set of all successfully executed migrations. /// @@ -183,63 +185,31 @@ pub mod pallet { let mut cursor = match Cursor::::get() { None => { log::debug!(target: LOG_TARGET, "[Block {n:?}] Waiting for cursor to become `Some`."); - return meter.consumed + return meter.consumed; }, Some(MigrationCursor::Active(cursor)) => cursor, Some(MigrationCursor::Stuck) => { defensive!("Migration stuck. Governance intervention required."); - return meter.consumed + return meter.consumed; }, }; + debug_assert!(::is_suspended()); - meter.defensive_saturating_accrue(T::WeightInfo::on_init_loop_base()); let migrations = T::Migrations::get(); - let mut iteration = 0; - while meter.check_accrue(T::WeightInfo::on_init_loop_base()) { // TODO - let Some(migration) = migrations.get(cursor.index as usize) else { - Self::deposit_event(Event::UpgradeCompleted { migrations: migrations.len() as u32 }); - Cursor::::kill(); - T::UpgradeStatusHandler::completed(); - return meter.consumed; - }; - if Historic::::contains_key(&migration.id()) { - Self::deposit_event(Event::MigrationSkippedHistoric { index: cursor.index }); - cursor.advance(System::::block_number()); - continue + for i in 0.. { + if !meter.check_accrue(T::WeightInfo::on_init_base()) { + break; } - - let took = System::::block_number().saturating_sub(cursor.started_at); - match migration.transactional_step(cursor.inner_cursor.clone(), &mut meter) { - Ok(Some(next_cursor)) => { - Self::deposit_event(Event::MigrationAdvanced { - index: cursor.index, - step: took, - }); - cursor.inner_cursor = Some(next_cursor); - // A migration has to make maximal progress per step, we therefore break. - break - }, - Ok(None) => { - Self::deposit_event(Event::MigrationCompleted { - index: cursor.index, - took, - }); - Historic::::insert(&migration.id(), ()); - cursor.advance(System::::block_number()); - }, - Err(SteppedMigrationError::InsufficientWeight { required }) => { - if iteration == 0 || required.any_gt(meter.limit) { - Cursor::::set(Some(MigrationCursor::Stuck)); - Self::deposit_event(Event::UpgradeFailed); - } // else: Hope that it gets better next time. - return meter.consumed + + match Self::exec_migration(&mut meter, &migrations, cursor, i == 0) { + None => return meter.consumed, + Some(ControlFlow::Break(last_cursor)) => { + cursor = last_cursor; + break; }, - Err(SteppedMigrationError::Failed | SteppedMigrationError::Timeout) => { - Self::deposit_event(Event::MigrationFailed { index: cursor.index, took }); - Self::deposit_event(Event::UpgradeFailed); - Cursor::::set(Some(MigrationCursor::Stuck)); - return meter.consumed + Some(ControlFlow::Continue(next_cursor)) => { + cursor = next_cursor; }, } } @@ -280,6 +250,57 @@ pub mod pallet { } } +impl Pallet { + fn exec_migration(meter: &mut WeightMeter, migrations: &MigrationsOf, mut cursor: ActiveCursorOf, is_first: bool) -> Option, ActiveCursorOf>> { + let Some(migration) = migrations.get(cursor.index as usize) else { + Self::deposit_event(Event::UpgradeCompleted { migrations: migrations.len() as u32 }); + Cursor::::kill(); + T::UpgradeStatusHandler::completed(); + return None; + }; + if Historic::::contains_key(&migration.id()) { + Self::deposit_event(Event::MigrationSkippedHistoric { index: cursor.index }); + cursor.advance(System::::block_number()); + return Some(ControlFlow::Continue(cursor)); + } + + let took = System::::block_number().saturating_sub(cursor.started_at); + match migration.transactional_step(cursor.inner_cursor.clone(), meter) { + Ok(Some(next_cursor)) => { + Self::deposit_event(Event::MigrationAdvanced { + index: cursor.index, + step: took, + }); + cursor.inner_cursor = Some(next_cursor); + // A migration has to make maximal progress per step, we therefore break. + return Some(ControlFlow::Break(cursor)); + }, + Ok(None) => { + Self::deposit_event(Event::MigrationCompleted { + index: cursor.index, + took, + }); + Historic::::insert(&migration.id(), ()); + cursor.advance(System::::block_number()); + return Some(ControlFlow::Continue(cursor)); + }, + Err(SteppedMigrationError::InsufficientWeight { required }) => { + if is_first || required.any_gt(meter.limit) { + Cursor::::set(Some(MigrationCursor::Stuck)); + Self::deposit_event(Event::UpgradeFailed); + } // else: Hope that it gets better in the next block. + return None; + }, + Err(SteppedMigrationError::Failed | SteppedMigrationError::Timeout) => { + Self::deposit_event(Event::MigrationFailed { index: cursor.index, took }); + Self::deposit_event(Event::UpgradeFailed); + Cursor::::set(Some(MigrationCursor::Stuck)); + return None; + }, + } + } +} + impl ExtrinsicSuspenderQuery for Pallet { fn is_suspended() -> bool { Cursor::::exists() diff --git a/frame/migrations/src/weights.rs b/frame/migrations/src/weights.rs index aea876d3a0cda..3ecd45f792906 100644 --- a/frame/migrations/src/weights.rs +++ b/frame/migrations/src/weights.rs @@ -2,7 +2,7 @@ //! Autogenerated weights for pallet_migrations //! //! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 4.0.0-dev -//! DATE: 2023-06-01, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! DATE: 2023-06-02, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]` //! WORST CASE MAP SIZE: `1000000` //! HOSTNAME: `i9`, CPU: `13th Gen Intel(R) Core(TM) i9-13900K` //! EXECUTION: None, WASM-EXECUTION: Compiled, CHAIN: None, DB CACHE: 1024 @@ -36,6 +36,7 @@ use core::marker::PhantomData; pub trait WeightInfo { fn on_runtime_upgrade() -> Weight; fn on_init_base() -> Weight; + fn on_init_fast_path() -> Weight; fn on_init_loop_base() -> Weight; fn force_set_cursor() -> Weight; } @@ -43,25 +44,29 @@ pub trait WeightInfo { /// Weights for pallet_migrations using the Substrate node and recommended hardware. pub struct SubstrateWeight(PhantomData); impl WeightInfo for SubstrateWeight { + fn on_init_fast_path() -> Weight { + Weight::zero() + } /// Storage: MultiBlockMigrations Cursor (r:1 w:0) /// Proof: MultiBlockMigrations Cursor (max_values: Some(1), max_size: Some(10), added: 505, mode: MaxEncodedLen) fn on_runtime_upgrade() -> Weight { // Proof Size summary in bytes: // Measured: `109` // Estimated: `1495` - // Minimum execution time: 1_048_000 picoseconds. - Weight::from_parts(1_124_000, 1495) + // Minimum execution time: 1_097_000 picoseconds. + Weight::from_parts(1_183_000, 1495) .saturating_add(T::DbWeight::get().reads(1_u64)) } - /// Storage: MultiBlockMigrations Cursor (r:1 w:0) + /// Storage: MultiBlockMigrations Cursor (r:1 w:1) /// Proof: MultiBlockMigrations Cursor (max_values: Some(1), max_size: Some(10), added: 505, mode: MaxEncodedLen) fn on_init_base() -> Weight { // Proof Size summary in bytes: - // Measured: `109` + // Measured: `143` // Estimated: `1495` - // Minimum execution time: 1_286_000 picoseconds. - Weight::from_parts(1_346_000, 1495) + // Minimum execution time: 3_220_000 picoseconds. + Weight::from_parts(3_463_000, 1495) .saturating_add(T::DbWeight::get().reads(1_u64)) + .saturating_add(T::DbWeight::get().writes(1_u64)) } /// Storage: MultiBlockMigrations Cursor (r:1 w:0) /// Proof: MultiBlockMigrations Cursor (max_values: Some(1), max_size: Some(10), added: 505, mode: MaxEncodedLen) @@ -69,7 +74,7 @@ impl WeightInfo for SubstrateWeight { // Proof Size summary in bytes: // Measured: `109` // Estimated: `1495` - // Minimum execution time: 1_281_000 picoseconds. + // Minimum execution time: 1_307_000 picoseconds. Weight::from_parts(1_368_000, 1495) .saturating_add(T::DbWeight::get().reads(1_u64)) } @@ -79,8 +84,8 @@ impl WeightInfo for SubstrateWeight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 827_000 picoseconds. - Weight::from_parts(927_000, 0) + // Minimum execution time: 866_000 picoseconds. + Weight::from_parts(946_000, 0) .saturating_add(T::DbWeight::get().writes(1_u64)) } } @@ -93,19 +98,20 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `109` // Estimated: `1495` - // Minimum execution time: 1_048_000 picoseconds. - Weight::from_parts(1_124_000, 1495) + // Minimum execution time: 1_097_000 picoseconds. + Weight::from_parts(1_183_000, 1495) .saturating_add(RocksDbWeight::get().reads(1_u64)) } - /// Storage: MultiBlockMigrations Cursor (r:1 w:0) + /// Storage: MultiBlockMigrations Cursor (r:1 w:1) /// Proof: MultiBlockMigrations Cursor (max_values: Some(1), max_size: Some(10), added: 505, mode: MaxEncodedLen) fn on_init_base() -> Weight { // Proof Size summary in bytes: - // Measured: `109` + // Measured: `143` // Estimated: `1495` - // Minimum execution time: 1_286_000 picoseconds. - Weight::from_parts(1_346_000, 1495) + // Minimum execution time: 3_220_000 picoseconds. + Weight::from_parts(3_463_000, 1495) .saturating_add(RocksDbWeight::get().reads(1_u64)) + .saturating_add(RocksDbWeight::get().writes(1_u64)) } /// Storage: MultiBlockMigrations Cursor (r:1 w:0) /// Proof: MultiBlockMigrations Cursor (max_values: Some(1), max_size: Some(10), added: 505, mode: MaxEncodedLen) @@ -113,7 +119,7 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `109` // Estimated: `1495` - // Minimum execution time: 1_281_000 picoseconds. + // Minimum execution time: 1_307_000 picoseconds. Weight::from_parts(1_368_000, 1495) .saturating_add(RocksDbWeight::get().reads(1_u64)) } @@ -123,8 +129,11 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 827_000 picoseconds. - Weight::from_parts(927_000, 0) + // Minimum execution time: 866_000 picoseconds. + Weight::from_parts(946_000, 0) .saturating_add(RocksDbWeight::get().writes(1_u64)) } + fn on_init_fast_path() -> Weight { + Weight::zero() + } } From ad98b4332153cc307b6a4cf05c3f6d5dc9a5d125 Mon Sep 17 00:00:00 2001 From: Oliver Tale-Yazdi Date: Fri, 2 Jun 2023 13:32:17 +0200 Subject: [PATCH 19/63] Fixes Signed-off-by: Oliver Tale-Yazdi --- frame/migrations/src/benchmarking.rs | 20 +++---- frame/migrations/src/lib.rs | 88 ++++++++++++++++------------ frame/migrations/src/tests.rs | 1 - frame/support/src/migrations.rs | 6 +- 4 files changed, 63 insertions(+), 52 deletions(-) diff --git a/frame/migrations/src/benchmarking.rs b/frame/migrations/src/benchmarking.rs index 5a3ed667fc806..c006cd5c53442 100644 --- a/frame/migrations/src/benchmarking.rs +++ b/frame/migrations/src/benchmarking.rs @@ -59,16 +59,6 @@ mod benches { } } - #[benchmark] - fn load_migrations() { - Pallet::::on_initialize(); - - #[block] - { - Pallet::::on_initialize(1u32.into()); - } - } - #[benchmark] fn on_init_loop() { System::::set_block_number(1u32.into()); @@ -89,6 +79,16 @@ mod benches { _(RawOrigin::Root, Some(cursor::())); } + #[benchmark] + fn clear_historic(n: Linear<0, 1000>) { + //for i in 0..n { // TODO + // Historic::::insert(i.into(), ()); + //} + + #[extrinsic_call] + _(RawOrigin::Root, None, None); + } + fn cursor() -> MigrationCursor { // Note: The weight of a function can depend on the weight of reading the `inner_cursor`. // `Cursor` is a user provided type. Now instead of requeuing something like `Cursor: diff --git a/frame/migrations/src/lib.rs b/frame/migrations/src/lib.rs index d98dea938ee91..eed6712bfbf2f 100644 --- a/frame/migrations/src/lib.rs +++ b/frame/migrations/src/lib.rs @@ -26,16 +26,17 @@ pub mod weights; pub use weights::WeightInfo; -use codec::{Decode, Encode, MaxEncodedLen}; +use codec::{Decode, Encode, FullCodec, MaxEncodedLen}; +use core::ops::ControlFlow; use frame_support::{ defensive, migrations::*, traits::Get, weights::{Weight, WeightMeter}, }; -use core::ops::ControlFlow; use frame_system::Pallet as System; use sp_runtime::Saturating; +use sp_std::{boxed::Box, vec::Vec}; const LOG_TARGET: &'static str = "runtime::migrations"; @@ -72,9 +73,23 @@ impl ActiveCursor { } } -pub type MigrationsOf = Vec::Cursor, Identifier = ::Identifier>>>; -pub type CursorOf = MigrationCursor<::Cursor, ::BlockNumber>; -pub type ActiveCursorOf = ActiveCursor<::Cursor, ::BlockNumber>; +/// A collection of migrations that must be executed in order. +pub type MigrationsOf = Vec< + Box< + dyn SteppedMigration< + Cursor = ::Cursor, + Identifier = ::Identifier, + >, + >, +>; + +/// Convenience wrapper for [`MigrationCursor`]. +pub type CursorOf = + MigrationCursor<::Cursor, ::BlockNumber>; + +/// Convenience wrapper for [`ActiveCursor`]. +pub type ActiveCursorOf = + ActiveCursor<::Cursor, ::BlockNumber>; #[frame_support::pallet] pub mod pallet { @@ -96,12 +111,14 @@ pub mod pallet { type Migrations: Get>; /// The cursor type that is shared across all migrations. - type Cursor: codec::FullCodec + codec::MaxEncodedLen + scale_info::TypeInfo + Parameter; + type Cursor: FullCodec + MaxEncodedLen + TypeInfo + Parameter; /// The identifier type that is shared across all migrations. - type Identifier: codec::FullCodec + codec::MaxEncodedLen + scale_info::TypeInfo; + type Identifier: FullCodec + MaxEncodedLen + TypeInfo; /// Notification handler for status updates regarding runtime upgrades. + /// + /// Can be used to pause XCM etc. type UpgradeStatusHandler: UpgradeStatusHandler; /// The weight to spend each block to execute migrations. @@ -115,8 +132,7 @@ pub mod pallet { /// /// `None` indicates that no migration process is running. #[pallet::storage] - pub type Cursor = - StorageValue<_, CursorOf, OptionQuery>; + pub type Cursor = StorageValue<_, CursorOf, OptionQuery>; /// Set of all successfully executed migrations. /// @@ -147,7 +163,7 @@ pub mod pallet { /// This implies that the whole upgrade failed and governance intervention is required. MigrationFailed { index: u32, took: T::BlockNumber }, /// The list of historical migrations has been cleared. - HistoricCleared, + HistoricCleared { next_cursor: Option> }, } #[pallet::hooks] @@ -168,7 +184,6 @@ pub mod pallet { Cursor::::set(Some(MigrationCursor::Active(ActiveCursor { index: 0, inner_cursor: None, - // +1 to get the step index. started_at: System::::block_number().saturating_add(1u32.into()), }))); Self::deposit_event(Event::UpgradeStarted); @@ -185,28 +200,23 @@ pub mod pallet { let mut cursor = match Cursor::::get() { None => { log::debug!(target: LOG_TARGET, "[Block {n:?}] Waiting for cursor to become `Some`."); - return meter.consumed; + return meter.consumed }, Some(MigrationCursor::Active(cursor)) => cursor, Some(MigrationCursor::Stuck) => { defensive!("Migration stuck. Governance intervention required."); - return meter.consumed; + return meter.consumed }, }; - debug_assert!(::is_suspended()); let migrations = T::Migrations::get(); for i in 0.. { - if !meter.check_accrue(T::WeightInfo::on_init_base()) { - break; - } - match Self::exec_migration(&mut meter, &migrations, cursor, i == 0) { None => return meter.consumed, Some(ControlFlow::Break(last_cursor)) => { cursor = last_cursor; - break; + break }, Some(ControlFlow::Continue(next_cursor)) => { cursor = next_cursor; @@ -239,11 +249,18 @@ pub mod pallet { #[pallet::call_index(1)] #[pallet::weight({0})] // FAIL-CI - pub fn clear_historic(origin: OriginFor) -> DispatchResult { + pub fn clear_historic( + origin: OriginFor, + limit: Option, + map_cursor: Option>, + ) -> DispatchResult { ensure_root(origin)?; - let _ = Historic::::clear(0, None); // FAIL-CI think about limit + cursor - Self::deposit_event(Event::HistoricCleared); + let next = Historic::::clear( + limit.unwrap_or_default(), + map_cursor.as_ref().map(|c| c.as_slice()), + ); + Self::deposit_event(Event::HistoricCleared { next_cursor: next.maybe_cursor }); Ok(()) } @@ -251,7 +268,12 @@ pub mod pallet { } impl Pallet { - fn exec_migration(meter: &mut WeightMeter, migrations: &MigrationsOf, mut cursor: ActiveCursorOf, is_first: bool) -> Option, ActiveCursorOf>> { + fn exec_migration( + meter: &mut WeightMeter, + migrations: &MigrationsOf, + mut cursor: ActiveCursorOf, + is_first: bool, + ) -> Option, ActiveCursorOf>> { let Some(migration) = migrations.get(cursor.index as usize) else { Self::deposit_event(Event::UpgradeCompleted { migrations: migrations.len() as u32 }); Cursor::::kill(); @@ -261,41 +283,35 @@ impl Pallet { if Historic::::contains_key(&migration.id()) { Self::deposit_event(Event::MigrationSkippedHistoric { index: cursor.index }); cursor.advance(System::::block_number()); - return Some(ControlFlow::Continue(cursor)); + return Some(ControlFlow::Continue(cursor)) } let took = System::::block_number().saturating_sub(cursor.started_at); match migration.transactional_step(cursor.inner_cursor.clone(), meter) { Ok(Some(next_cursor)) => { - Self::deposit_event(Event::MigrationAdvanced { - index: cursor.index, - step: took, - }); + Self::deposit_event(Event::MigrationAdvanced { index: cursor.index, step: took }); cursor.inner_cursor = Some(next_cursor); // A migration has to make maximal progress per step, we therefore break. - return Some(ControlFlow::Break(cursor)); + return Some(ControlFlow::Break(cursor)) }, Ok(None) => { - Self::deposit_event(Event::MigrationCompleted { - index: cursor.index, - took, - }); + Self::deposit_event(Event::MigrationCompleted { index: cursor.index, took }); Historic::::insert(&migration.id(), ()); cursor.advance(System::::block_number()); - return Some(ControlFlow::Continue(cursor)); + return Some(ControlFlow::Continue(cursor)) }, Err(SteppedMigrationError::InsufficientWeight { required }) => { if is_first || required.any_gt(meter.limit) { Cursor::::set(Some(MigrationCursor::Stuck)); Self::deposit_event(Event::UpgradeFailed); } // else: Hope that it gets better in the next block. - return None; + return None }, Err(SteppedMigrationError::Failed | SteppedMigrationError::Timeout) => { Self::deposit_event(Event::MigrationFailed { index: cursor.index, took }); Self::deposit_event(Event::UpgradeFailed); Cursor::::set(Some(MigrationCursor::Stuck)); - return None; + return None }, } } diff --git a/frame/migrations/src/tests.rs b/frame/migrations/src/tests.rs index 249613a7403d7..944aba1ba766c 100644 --- a/frame/migrations/src/tests.rs +++ b/frame/migrations/src/tests.rs @@ -123,7 +123,6 @@ fn historic_skipping_works() { } /// When another upgrade happens while a migration is still running, it should stuck the chain. -// FAIL-CI we could still check the unique id and only fail if it changed... #[test] fn upgrade_fails_when_migration_active() { test_closure(|| { diff --git a/frame/support/src/migrations.rs b/frame/support/src/migrations.rs index 8a001a5b73843..3b2ad5be3d617 100644 --- a/frame/support/src/migrations.rs +++ b/frame/support/src/migrations.rs @@ -292,18 +292,14 @@ impl ExtrinsicSuspenderQuery for () { /// Notification handler for status updates regarding runtime upgrades. pub trait UpgradeStatusHandler { /// Notifies of the start of a runtime upgrade. - /// - /// Can be used to pause XCM etc. fn started() {} /// Notifies of the completion of a runtime upgrade. - /// - /// Can be used to resume XCM etc. fn completed() {} /// Infallibly handle a failed runtime upgrade. /// - /// Gets optionally passed in the index of the migration that caused the failure. + /// Gets passed in the optional index of the migration that caused the failure. fn failed(migration: Option) -> FailedUpgradeHandling; } From 5c3a90d484eb13deffa5274873555f8bfaa3379d Mon Sep 17 00:00:00 2001 From: Oliver Tale-Yazdi Date: Fri, 2 Jun 2023 15:15:44 +0200 Subject: [PATCH 20/63] Clippy Signed-off-by: Oliver Tale-Yazdi --- frame/migrations/src/lib.rs | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/frame/migrations/src/lib.rs b/frame/migrations/src/lib.rs index eed6712bfbf2f..4e50de24f7a20 100644 --- a/frame/migrations/src/lib.rs +++ b/frame/migrations/src/lib.rs @@ -256,10 +256,7 @@ pub mod pallet { ) -> DispatchResult { ensure_root(origin)?; - let next = Historic::::clear( - limit.unwrap_or_default(), - map_cursor.as_ref().map(|c| c.as_slice()), - ); + let next = Historic::::clear(limit.unwrap_or_default(), map_cursor.as_deref()); Self::deposit_event(Event::HistoricCleared { next_cursor: next.maybe_cursor }); Ok(()) From 357fa47bd77aff8885e9eca3d827ba5255dac6ca Mon Sep 17 00:00:00 2001 From: Oliver Tale-Yazdi Date: Fri, 2 Jun 2023 19:10:47 +0200 Subject: [PATCH 21/63] Update frame/support/src/migrations.rs Co-authored-by: Liam Aharon --- frame/support/src/migrations.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frame/support/src/migrations.rs b/frame/support/src/migrations.rs index 3b2ad5be3d617..c061ac0fe31b3 100644 --- a/frame/support/src/migrations.rs +++ b/frame/support/src/migrations.rs @@ -269,7 +269,7 @@ pub enum SteppedMigrationError { /// Can be resolved by calling with at least `required` weight. Note that calling it with /// exactly `required` weight could cause it to not make any progress. InsufficientWeight { required: Weight }, - // permanent errors: + // Permanent errors: /// The migration encountered a permanent error and cannot continue. /// /// This can happen if the storage is corrupted or an assumption got invalidated while the From da048768cb4d6a3e83c474fd77a7203e4de0e21a Mon Sep 17 00:00:00 2001 From: Oliver Tale-Yazdi Date: Fri, 2 Jun 2023 19:10:55 +0200 Subject: [PATCH 22/63] Update frame/migrations/src/mock.rs Co-authored-by: Liam Aharon --- frame/migrations/src/mock.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/frame/migrations/src/mock.rs b/frame/migrations/src/mock.rs index a17ed9129e70e..9695a069c1d02 100644 --- a/frame/migrations/src/mock.rs +++ b/frame/migrations/src/mock.rs @@ -202,7 +202,6 @@ pub fn run_to_block(n: u32) { } // Traits to make using events less insufferable: - pub trait IntoRecord { fn into_record(self) -> EventRecord<::RuntimeEvent, H256>; } From 2e8691559d50394351093db4f79c0f354f0e52ac Mon Sep 17 00:00:00 2001 From: Oliver Tale-Yazdi Date: Fri, 2 Jun 2023 19:12:28 +0200 Subject: [PATCH 23/63] Update frame/migrations/src/tests.rs Co-authored-by: Liam Aharon --- frame/migrations/src/tests.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frame/migrations/src/tests.rs b/frame/migrations/src/tests.rs index 944aba1ba766c..93b63d5d76db6 100644 --- a/frame/migrations/src/tests.rs +++ b/frame/migrations/src/tests.rs @@ -122,7 +122,7 @@ fn historic_skipping_works() { }); } -/// When another upgrade happens while a migration is still running, it should stuck the chain. +/// When another upgrade happens while a migration is still running, it should set the cursor to stuck. #[test] fn upgrade_fails_when_migration_active() { test_closure(|| { From 99691a3359efd2dedd663aac341a8640c27d9d3c Mon Sep 17 00:00:00 2001 From: Oliver Tale-Yazdi Date: Fri, 2 Jun 2023 19:27:23 +0200 Subject: [PATCH 24/63] fmt --- frame/migrations/src/tests.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/frame/migrations/src/tests.rs b/frame/migrations/src/tests.rs index 93b63d5d76db6..a10d78f4bbcc3 100644 --- a/frame/migrations/src/tests.rs +++ b/frame/migrations/src/tests.rs @@ -122,7 +122,8 @@ fn historic_skipping_works() { }); } -/// When another upgrade happens while a migration is still running, it should set the cursor to stuck. +/// When another upgrade happens while a migration is still running, it should set the cursor to +/// stuck. #[test] fn upgrade_fails_when_migration_active() { test_closure(|| { From e44c9025a842a8fc95682d8e5276b95b874b9be4 Mon Sep 17 00:00:00 2001 From: Oliver Tale-Yazdi Date: Sat, 3 Jun 2023 11:21:21 +0200 Subject: [PATCH 25/63] Add timeout checking Signed-off-by: Oliver Tale-Yazdi --- frame/migrations/src/lib.rs | 14 ++++++++-- frame/migrations/src/mock.rs | 49 +++++++++++++++++---------------- frame/support/src/migrations.rs | 14 ++++++---- 3 files changed, 45 insertions(+), 32 deletions(-) diff --git a/frame/migrations/src/lib.rs b/frame/migrations/src/lib.rs index 4e50de24f7a20..63c2c9cfd4366 100644 --- a/frame/migrations/src/lib.rs +++ b/frame/migrations/src/lib.rs @@ -288,8 +288,16 @@ impl Pallet { Ok(Some(next_cursor)) => { Self::deposit_event(Event::MigrationAdvanced { index: cursor.index, step: took }); cursor.inner_cursor = Some(next_cursor); - // A migration has to make maximal progress per step, we therefore break. - return Some(ControlFlow::Break(cursor)) + + if migration.max_steps().map_or(false, |max| took > max.into()) { + Self::deposit_event(Event::MigrationFailed { index: cursor.index, took }); + Self::deposit_event(Event::UpgradeFailed); + Cursor::::set(Some(MigrationCursor::Stuck)); + None + } else { + // A migration has to make maximal progress per step, we therefore break. + Some(ControlFlow::Break(cursor)) + } }, Ok(None) => { Self::deposit_event(Event::MigrationCompleted { index: cursor.index, took }); @@ -304,7 +312,7 @@ impl Pallet { } // else: Hope that it gets better in the next block. return None }, - Err(SteppedMigrationError::Failed | SteppedMigrationError::Timeout) => { + Err(SteppedMigrationError::InvalidCursor | SteppedMigrationError::Failed) => { Self::deposit_event(Event::MigrationFailed { index: cursor.index, took }); Self::deposit_event(Event::UpgradeFailed); Cursor::::set(Some(MigrationCursor::Stuck)); diff --git a/frame/migrations/src/mock.rs b/frame/migrations/src/mock.rs index 9695a069c1d02..4bffae2112480 100644 --- a/frame/migrations/src/mock.rs +++ b/frame/migrations/src/mock.rs @@ -65,11 +65,12 @@ pub enum MockedMigrationKind { } use MockedMigrationKind::*; // C style -pub struct MockedMigrate(MockedMigrationKind, u32); +/// A migration that succeeds or fails after a certain number of steps. +pub struct MockedMigration(MockedMigrationKind, u32); -impl SteppedMigration for MockedMigrate { - type Cursor = BoundedVec>; - type Identifier = BoundedVec>; +impl SteppedMigration for MockedMigration { + type Cursor = MockedCursor; + type Identifier = MockedIdentifier; fn id(&self) -> Self::Identifier { mocked_id(self.0, self.1) @@ -82,7 +83,7 @@ impl SteppedMigration for MockedMigrate { ) -> Result, SteppedMigrationError> { let mut count: u32 = cursor.as_ref().and_then(|c| Decode::decode(&mut &c[..]).ok()).unwrap_or(0); - log::debug!("MockedMigrate: Step {}", count); + log::debug!("MockedMigration: Step {}", count); if count != self.1 { count += 1; return Ok(Some(count.encode().try_into().unwrap())) @@ -90,11 +91,11 @@ impl SteppedMigration for MockedMigrate { match self.0 { SucceedAfter => { - log::debug!("MockedMigrate: Succeeded after {} steps", count); + log::debug!("MockedMigration: Succeeded after {} steps", count); Ok(None) }, FailAfter => { - log::debug!("MockedMigrate: Failed after {} steps", count); + log::debug!("MockedMigration: Failed after {} steps", count); Err(SteppedMigrationError::Failed) }, } @@ -102,16 +103,18 @@ impl SteppedMigration for MockedMigrate { } type MockedCursor = BoundedVec>; -type MockedIdentifier = BoundedVec>; +type MockedIdentifier = BoundedVec>; frame_support::parameter_types! { pub const ServiceWeight: Weight = Weight::MAX; } thread_local! { + /// The configs for the migrations to run. pub static MIGRATIONS: RefCell> = RefCell::new(vec![]); } +/// Dynamically set the migrations to run. pub struct MigrationsStorage; impl Get>>> for MigrationsStorage @@ -123,7 +126,7 @@ impl Get MockedIdentifier { - format!("MockedMigrate({:?}, {})", kind, steps) - .as_bytes() - .to_vec() - .try_into() - .unwrap() -} - -pub fn historic() -> Vec { - let mut historic = Historic::::iter_keys().collect::>(); - historic.sort(); - historic -} - impl crate::Config for Test { type RuntimeEvent = RuntimeEvent; type Migrations = MigrationsStorage; @@ -247,3 +236,17 @@ impl UpgradeStatusHandler for LoggingUpgradeStatusHandl res } } + +pub fn mocked_id(kind: MockedMigrationKind, steps: u32) -> MockedIdentifier { + format!("MockedMigration({:?}, {})", kind, steps) + .as_bytes() + .to_vec() + .try_into() + .unwrap() +} + +pub fn historic() -> Vec { + let mut historic = Historic::::iter_keys().collect::>(); + historic.sort(); + historic +} diff --git a/frame/support/src/migrations.rs b/frame/support/src/migrations.rs index c061ac0fe31b3..91f796add02c3 100644 --- a/frame/support/src/migrations.rs +++ b/frame/support/src/migrations.rs @@ -22,6 +22,7 @@ use crate::{ traits::{GetStorageVersion, NoStorageVersionSet, PalletInfoAccess, StorageVersion}, weights::{RuntimeDbWeight, Weight, WeightMeter}, }; +use codec::{Decode, Encode, MaxEncodedLen}; use impl_trait_for_tuples::impl_for_tuples; use sp_core::Get; use sp_io::{hashing::twox_128, storage::clear_prefix, KillStorageResult}; @@ -261,7 +262,7 @@ pub trait SteppedMigration { } } -#[derive(Debug)] +#[derive(Debug, Encode, Decode, MaxEncodedLen, scale_info::TypeInfo)] pub enum SteppedMigrationError { // Transient errors: /// The remaining weight is not enough to do anything. @@ -270,13 +271,14 @@ pub enum SteppedMigrationError { /// exactly `required` weight could cause it to not make any progress. InsufficientWeight { required: Weight }, // Permanent errors: - /// The migration encountered a permanent error and cannot continue. + /// The migration cannot decode its cursor and therefore not proceed. /// - /// This can happen if the storage is corrupted or an assumption got invalidated while the - /// migration was running. + /// This should not happen unless (1) the migration itself returned an invalid cursor in a + /// previous iteration, (2) the storage got corrupted or (3) there is a bug in the caller's + /// code. + InvalidCursor, + /// The migration encountered a permanent error and cannot continue. Failed, - /// The migration took longer that its [`SteppedMigration::max_steps`]. - Timeout, } pub trait ExtrinsicSuspenderQuery { From 5f512e67071bbe1cce90d1ae7bff7f63c2909efc Mon Sep 17 00:00:00 2001 From: Oliver Tale-Yazdi Date: Mon, 5 Jun 2023 12:12:32 +0200 Subject: [PATCH 26/63] Fix stuff Signed-off-by: Oliver Tale-Yazdi --- Cargo.lock | 1 + frame/executive/src/lib.rs | 4 +-- frame/migrations/Cargo.toml | 15 +++------ frame/migrations/src/lib.rs | 34 ++++++++++--------- frame/migrations/src/mock.rs | 17 +++++++--- frame/migrations/src/tests.rs | 60 +++++++++++++++++++++++++-------- frame/support/src/migrations.rs | 9 ++--- 7 files changed, 88 insertions(+), 52 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 38f3bb726808d..0b9244adeebad 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -6716,6 +6716,7 @@ dependencies = [ "impl-trait-for-tuples", "log", "parity-scale-codec", + "pretty_assertions", "scale-info", "sp-core", "sp-io", diff --git a/frame/executive/src/lib.rs b/frame/executive/src/lib.rs index d6fc8e38c28ba..6c6c6e4b292ca 100644 --- a/frame/executive/src/lib.rs +++ b/frame/executive/src/lib.rs @@ -587,9 +587,7 @@ impl< // Decode parameters and dispatch let dispatch_info = xt.get_dispatch_info(); // Check whether we need to error because extrinsics are paused. - let r = if dispatch_info.class != DispatchClass::Mandatory && - ExtrinsicSuspender::is_suspended() - { + let r = if ExtrinsicSuspender::is_suspended(dispatch_info.class) { // no refunds Err(DispatchError::Suspended.with_weight(dispatch_info.weight)) } else { diff --git a/frame/migrations/Cargo.toml b/frame/migrations/Cargo.toml index 3dc6cf8b3be6d..90ecbfb36f07c 100644 --- a/frame/migrations/Cargo.toml +++ b/frame/migrations/Cargo.toml @@ -27,20 +27,13 @@ sp-runtime = { version = "24.0.0", path = "../../primitives/runtime", default-fe sp-tracing = { version = "10.0.0", path = "../../primitives/tracing", features = [ "std" ] } sp-core = { version = "21.0.0", path = "../../primitives/core", features = [ "std" ] } sp-io = { version = "23.0.0", path = "../../primitives/io", features = [ "std" ] } +pretty_assertions = "1.3.0" [features] default = ["std"] -std = [ - "codec/std", - "frame-benchmarking?/std", - "frame-support/std", - "frame-system/std", - "scale-info/std", - "sp-std/std", - "sp-runtime/std", -] +std = ["codec/std", "frame-benchmarking?/std", "frame-support/std", "frame-system/std", "scale-info/std", "sp-std/std", "sp-runtime/std"] -runtime-benchmarks = ["frame-benchmarking/runtime-benchmarks", "frame-support/runtime-benchmarks", "frame-system/runtime-benchmarks", "sp-runtime/runtime-benchmarks",] +runtime-benchmarks = ["frame-benchmarking/runtime-benchmarks", "frame-support/runtime-benchmarks", "frame-system/runtime-benchmarks", "sp-runtime/runtime-benchmarks"] -try-runtime = ["frame-support/try-runtime", "sp-runtime/try-runtime",] +try-runtime = ["frame-support/try-runtime", "sp-runtime/try-runtime"] diff --git a/frame/migrations/src/lib.rs b/frame/migrations/src/lib.rs index 63c2c9cfd4366..ab7695c3eef42 100644 --- a/frame/migrations/src/lib.rs +++ b/frame/migrations/src/lib.rs @@ -29,7 +29,7 @@ pub use weights::WeightInfo; use codec::{Decode, Encode, FullCodec, MaxEncodedLen}; use core::ops::ControlFlow; use frame_support::{ - defensive, + dispatch::DispatchClass, migrations::*, traits::Get, weights::{Weight, WeightMeter}, @@ -155,13 +155,13 @@ pub mod pallet { /// Migration `index` was skipped, since it already executed in the past. MigrationSkippedHistoric { index: u32 }, /// Migration `index` made progress. - MigrationAdvanced { index: u32, step: T::BlockNumber }, + MigrationAdvanced { index: u32, blocks: T::BlockNumber }, /// Migration `index` completed. - MigrationCompleted { index: u32, took: T::BlockNumber }, + MigrationCompleted { index: u32, blocks: T::BlockNumber }, /// Migration `index` failed. /// /// This implies that the whole upgrade failed and governance intervention is required. - MigrationFailed { index: u32, took: T::BlockNumber }, + MigrationFailed { index: u32, blocks: T::BlockNumber }, /// The list of historical migrations has been cleared. HistoricCleared { next_cursor: Option> }, } @@ -184,7 +184,7 @@ pub mod pallet { Cursor::::set(Some(MigrationCursor::Active(ActiveCursor { index: 0, inner_cursor: None, - started_at: System::::block_number().saturating_add(1u32.into()), + started_at: System::::block_number(), }))); Self::deposit_event(Event::UpgradeStarted); T::UpgradeStatusHandler::started(); @@ -204,11 +204,11 @@ pub mod pallet { }, Some(MigrationCursor::Active(cursor)) => cursor, Some(MigrationCursor::Stuck) => { - defensive!("Migration stuck. Governance intervention required."); + log::error!("Migration stuck. Governance intervention required."); return meter.consumed }, }; - debug_assert!(::is_suspended()); + debug_assert!(::is_suspended(DispatchClass::Normal)); let migrations = T::Migrations::get(); for i in 0.. { @@ -283,14 +283,15 @@ impl Pallet { return Some(ControlFlow::Continue(cursor)) } - let took = System::::block_number().saturating_sub(cursor.started_at); + let blocks = System::::block_number().saturating_sub(cursor.started_at); match migration.transactional_step(cursor.inner_cursor.clone(), meter) { Ok(Some(next_cursor)) => { - Self::deposit_event(Event::MigrationAdvanced { index: cursor.index, step: took }); + Self::deposit_event(Event::MigrationAdvanced { index: cursor.index, blocks }); cursor.inner_cursor = Some(next_cursor); - if migration.max_steps().map_or(false, |max| took > max.into()) { - Self::deposit_event(Event::MigrationFailed { index: cursor.index, took }); + // We only do one step per block. + if migration.max_steps().map_or(false, |max| blocks > max.into()) { + Self::deposit_event(Event::MigrationFailed { index: cursor.index, blocks }); Self::deposit_event(Event::UpgradeFailed); Cursor::::set(Some(MigrationCursor::Stuck)); None @@ -300,7 +301,7 @@ impl Pallet { } }, Ok(None) => { - Self::deposit_event(Event::MigrationCompleted { index: cursor.index, took }); + Self::deposit_event(Event::MigrationCompleted { index: cursor.index, blocks }); Historic::::insert(&migration.id(), ()); cursor.advance(System::::block_number()); return Some(ControlFlow::Continue(cursor)) @@ -313,7 +314,7 @@ impl Pallet { return None }, Err(SteppedMigrationError::InvalidCursor | SteppedMigrationError::Failed) => { - Self::deposit_event(Event::MigrationFailed { index: cursor.index, took }); + Self::deposit_event(Event::MigrationFailed { index: cursor.index, blocks }); Self::deposit_event(Event::UpgradeFailed); Cursor::::set(Some(MigrationCursor::Stuck)); return None @@ -323,7 +324,10 @@ impl Pallet { } impl ExtrinsicSuspenderQuery for Pallet { - fn is_suspended() -> bool { - Cursor::::exists() + fn is_suspended(class: DispatchClass) -> bool { + match class { + DispatchClass::Mandatory => false, + DispatchClass::Normal | DispatchClass::Operational => Cursor::::exists(), + } } } diff --git a/frame/migrations/src/mock.rs b/frame/migrations/src/mock.rs index 4bffae2112480..452e66e15e061 100644 --- a/frame/migrations/src/mock.rs +++ b/frame/migrations/src/mock.rs @@ -23,6 +23,7 @@ use core::cell::RefCell; #[use_attr] use frame_support::derive_impl; use frame_support::{ + dispatch::DispatchClass, macro_magic::use_attr, migrations::*, traits::{OnFinalize, OnInitialize}, @@ -62,6 +63,7 @@ impl frame_system::Config for Test { pub enum MockedMigrationKind { SucceedAfter, FailAfter, + TimeoutAfter, } use MockedMigrationKind::*; // C style @@ -76,6 +78,10 @@ impl SteppedMigration for MockedMigration { mocked_id(self.0, self.1) } + fn max_steps(&self) -> Option { + matches!(self.0, TimeoutAfter).then(|| self.1) + } + fn step( &self, cursor: &Option, @@ -84,7 +90,7 @@ impl SteppedMigration for MockedMigration { let mut count: u32 = cursor.as_ref().and_then(|c| Decode::decode(&mut &c[..]).ok()).unwrap_or(0); log::debug!("MockedMigration: Step {}", count); - if count != self.1 { + if count != self.1 || matches!(self.0, TimeoutAfter) { count += 1; return Ok(Some(count.encode().try_into().unwrap())) } @@ -98,6 +104,7 @@ impl SteppedMigration for MockedMigration { log::debug!("MockedMigration: Failed after {} steps", count); Err(SteppedMigrationError::Failed) }, + TimeoutAfter => unreachable!(), } } } @@ -170,9 +177,9 @@ pub fn test_closure(f: impl FnOnce() -> R) -> R { pub struct LoggingSuspender(core::marker::PhantomData); impl ExtrinsicSuspenderQuery for LoggingSuspender { - fn is_suspended() -> bool { - let res = Inner::is_suspended(); - log::debug!("IsSuspended: {res}"); + fn is_suspended(class: DispatchClass) -> bool { + let res = Inner::is_suspended(class); + log::debug!("Is {class:?} suspended: {res}"); res } } @@ -213,7 +220,7 @@ impl IntoRecords for Vec { } pub fn assert_events(events: Vec) { - assert_eq!(System::events(), events.into_records()); + pretty_assertions::assert_eq!(events.into_records(), System::events()); System::reset_events(); } diff --git a/frame/migrations/src/tests.rs b/frame/migrations/src/tests.rs index a10d78f4bbcc3..9d67c90ab5ddf 100644 --- a/frame/migrations/src/tests.rs +++ b/frame/migrations/src/tests.rs @@ -45,12 +45,12 @@ fn basic_works() { // Check that we got all events. assert_events(vec![ Event::UpgradeStarted, - Event::MigrationCompleted { index: 0, took: 0 }, - Event::MigrationAdvanced { index: 1, step: 0 }, - Event::MigrationCompleted { index: 1, took: 1 }, - Event::MigrationAdvanced { index: 2, step: 0 }, - Event::MigrationAdvanced { index: 2, step: 1 }, - Event::MigrationCompleted { index: 2, took: 2 }, + Event::MigrationCompleted { index: 0, blocks: 1 }, + Event::MigrationAdvanced { index: 1, blocks: 0 }, + Event::MigrationCompleted { index: 1, blocks: 1 }, + Event::MigrationAdvanced { index: 2, blocks: 0 }, + Event::MigrationAdvanced { index: 2, blocks: 1 }, + Event::MigrationCompleted { index: 2, blocks: 2 }, Event::UpgradeCompleted { migrations: 3 }, ]); }); @@ -83,13 +83,13 @@ fn historic_skipping_works() { // Events received. assert_events(vec![ Event::UpgradeStarted, - Event::MigrationCompleted { index: 0, took: 0 }, + Event::MigrationCompleted { index: 0, blocks: 1 }, Event::MigrationSkippedHistoric { index: 1 }, - Event::MigrationAdvanced { index: 2, step: 0 }, - Event::MigrationCompleted { index: 2, took: 1 }, - Event::MigrationAdvanced { index: 3, step: 0 }, - Event::MigrationAdvanced { index: 3, step: 1 }, - Event::MigrationCompleted { index: 3, took: 2 }, + Event::MigrationAdvanced { index: 2, blocks: 0 }, + Event::MigrationCompleted { index: 2, blocks: 1 }, + Event::MigrationAdvanced { index: 3, blocks: 0 }, + Event::MigrationAdvanced { index: 3, blocks: 1 }, + Event::MigrationCompleted { index: 3, blocks: 2 }, Event::MigrationSkippedHistoric { index: 4 }, Event::UpgradeCompleted { migrations: 5 }, ]); @@ -140,8 +140,8 @@ fn upgrade_fails_when_migration_active() { // Events received. assert_events(vec![ Event::UpgradeStarted, - Event::MigrationAdvanced { index: 0, step: 0 }, - Event::MigrationAdvanced { index: 0, step: 1 }, + Event::MigrationAdvanced { index: 0, blocks: 1 }, + Event::MigrationAdvanced { index: 0, blocks: 2 }, ]); // Upgrade again. Migrations::on_runtime_upgrade(); @@ -150,3 +150,35 @@ fn upgrade_fails_when_migration_active() { assert_events(vec![Event::UpgradeFailed]); }); } + +#[test] +fn migration_timeout_errors() { + test_closure(|| { + MigrationsStorage::set(vec![(TimeoutAfter, 3)]); + + System::set_block_number(1); + Migrations::on_runtime_upgrade(); + run_to_block(5); + + // Times out after taking more than 3 steps. + assert_events(vec![ + Event::UpgradeStarted, + Event::MigrationAdvanced { index: 0, blocks: 1 }, + Event::MigrationAdvanced { index: 0, blocks: 2 }, + Event::MigrationAdvanced { index: 0, blocks: 3 }, + Event::MigrationAdvanced { index: 0, blocks: 4 }, + Event::MigrationFailed { index: 0, blocks: 4 }, + Event::UpgradeFailed, + ]); + + // Failed migrations are not black-listed. + assert!(historic().is_empty()); + assert_eq!(Cursor::::get(), Some(MigrationCursor::Stuck)); + + Migrations::on_runtime_upgrade(); + run_to_block(6); + + assert_events(vec![Event::UpgradeFailed]); + assert_eq!(Cursor::::get(), Some(MigrationCursor::Stuck)); + }); +} diff --git a/frame/support/src/migrations.rs b/frame/support/src/migrations.rs index 91f796add02c3..854e58e7d73e1 100644 --- a/frame/support/src/migrations.rs +++ b/frame/support/src/migrations.rs @@ -16,9 +16,9 @@ // limitations under the License. #[cfg(feature = "try-runtime")] -use crate::storage::unhashed::contains_prefixed_key; use crate::{ - storage::transactional::with_transaction_opaque_err, + dispatch::DispatchClass, + storage::{transactional::with_transaction_opaque_err, unhashed::contains_prefixed_key}, traits::{GetStorageVersion, NoStorageVersionSet, PalletInfoAccess, StorageVersion}, weights::{RuntimeDbWeight, Weight, WeightMeter}, }; @@ -281,12 +281,13 @@ pub enum SteppedMigrationError { Failed, } +/// Check whether transaction processing is suspended on the runtime level. pub trait ExtrinsicSuspenderQuery { - fn is_suspended() -> bool; + fn is_suspended(class: DispatchClass) -> bool; } impl ExtrinsicSuspenderQuery for () { - fn is_suspended() -> bool { + fn is_suspended(_class: DispatchClass) -> bool { false } } From c72c7b7483038d0d5bd1ada5b4b3f388da9023f2 Mon Sep 17 00:00:00 2001 From: Oliver Tale-Yazdi Date: Mon, 5 Jun 2023 13:33:12 +0200 Subject: [PATCH 27/63] Add docs --- Cargo.lock | 9 +- frame/migrations/Cargo.toml | 2 + frame/migrations/src/lib.rs | 142 +++++++++++++++++++++++++++++--- frame/migrations/src/tests.rs | 43 ++++++++-- frame/support/src/migrations.rs | 6 +- 5 files changed, 177 insertions(+), 25 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 0b9244adeebad..478f292e9ae17 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2036,18 +2036,18 @@ checksum = "fea41bba32d969b513997752735605054bc0dfa92b4c56bf1189f2e174be7a10" [[package]] name = "docify" -version = "0.1.13" +version = "0.1.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "18b972b74c30cbe838fc6a07665132ff94f257350e26fd01d80bc59ee7fcf129" +checksum = "15aa210b350ff62db3149ac5d0b2a0287c01ee91354e16290de344082b2b3ff6" dependencies = [ "docify_macros", ] [[package]] name = "docify_macros" -version = "0.1.13" +version = "0.1.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c93004d1011191c56df9e853dca42f2012e7488638bcd5078935f5ce43e06cf3" +checksum = "9bf3504ed030133996c59a9954669a7a4f869f93a7e74389e16149843db16f57" dependencies = [ "common-path", "derive-syn-parse", @@ -6710,6 +6710,7 @@ dependencies = [ name = "pallet-migrations" version = "1.0.0" dependencies = [ + "docify", "frame-benchmarking", "frame-support", "frame-system", diff --git a/frame/migrations/Cargo.toml b/frame/migrations/Cargo.toml index 90ecbfb36f07c..80cb63c091d8c 100644 --- a/frame/migrations/Cargo.toml +++ b/frame/migrations/Cargo.toml @@ -22,12 +22,14 @@ impl-trait-for-tuples = "0.2.2" log = "0.4.18" sp-std = { version = "8.0.0", path = "../../primitives/std", default-features = false } sp-runtime = { version = "24.0.0", path = "../../primitives/runtime", default-features = false } +docify = "0.1.14" [dev-dependencies] sp-tracing = { version = "10.0.0", path = "../../primitives/tracing", features = [ "std" ] } sp-core = { version = "21.0.0", path = "../../primitives/core", features = [ "std" ] } sp-io = { version = "23.0.0", path = "../../primitives/io", features = [ "std" ] } pretty_assertions = "1.3.0" +docify = "0.1.14" [features] default = ["std"] diff --git a/frame/migrations/src/lib.rs b/frame/migrations/src/lib.rs index ab7695c3eef42..e53c26cf43ba4 100644 --- a/frame/migrations/src/lib.rs +++ b/frame/migrations/src/lib.rs @@ -15,6 +15,114 @@ // See the License for the specific language governing permissions and // limitations under the License. +#![deny(rustdoc::broken_intra_doc_links)] + +//! # `pallet-migrations` +//! +//! Provides multi block migrations for FRAME runtimes. +//! +//! ## Overview +//! +//! The pallet takes care of execution a batch of multi-step migrations over multiple blocks. The +//! process starts on each runtime upgrade. Normal and operational transactions are pause while this +//! is on-going. +//! +//! ### Example +//! +//! This example demonstrates a simple mocked walk through the basis success path. The pallet is +//! configured with two migrations: one succeeding after just one step, and the second one +//! succeeding after two steps. A runtime upgrade is then enacted and the block number is advanced +//! until all migrations had time to execute. Afterwards the recorded historic migrations are +//! checked and the events are asserted. +#![doc = docify::embed!("frame/migrations/src/tests.rs", simple_works)] +//! +//! ## Pallet API +//! +//! See the [`pallet`] module for more information about the interfaces this pallet exposes, +//! including its configuration trait, dispatchables, storage items, events and errors. +//! +//! Otherwise noteworthy API of this pallet include its implementation of the +//! [`ExtrinsicSuspenderQuery`] trait. This can be plugged into `frame-executive` to check for +//! transaction suspension. +//! +//! ### Design Goals +//! +//! 1. Must automatically execute migrations over multiple blocks. +//! 2. Must prevent other (non-mandatory) transactions to execute in the meantime. +//! 3. Must respect pessimistic weight bounds of migrations. +//! 4. Must execute migrations in order. Skipping is not allowed; rather an all-or-nothing. +//! 5. Must prevent re-execution of migrations. +//! 6. Must provide transactional storage semantics for migrations. +//! 7. Must guarantee progress. +//! +//! ### Design +//! +//! Migrations are provided to the pallet via a `Get { Stuck, } +/// Points to the currently active migration and its inner cursor. #[derive(Debug, Clone, Eq, PartialEq, Encode, Decode, scale_info::TypeInfo, MaxEncodedLen)] pub struct ActiveCursor { index: u32, @@ -57,6 +166,7 @@ pub struct ActiveCursor { } impl MigrationCursor { + /// Maybe return self as an `ActiveCursor`. pub fn as_active(&self) -> Option<&ActiveCursor> { match self { MigrationCursor::Active(active) => Some(active), @@ -144,10 +254,13 @@ pub mod pallet { #[pallet::event] #[pallet::generate_deposit(pub(super) fn deposit_event)] pub enum Event { - /// Runtime upgrade started. - UpgradeStarted, + /// Runtime upgrade started with `migrations` in the queue. + /// + /// This can be used to design a progress indicator in combination with counting the + /// `MigrationCompleted` and `MigrationSkippedHistoric` events. + UpgradeStarted { migrations: u32 }, /// Runtime upgrade completed with `migrations`. - UpgradeCompleted { migrations: u32 }, + UpgradeCompleted, /// Runtime upgrade failed. /// /// This is very bad and will require governance intervention. @@ -163,7 +276,10 @@ pub mod pallet { /// This implies that the whole upgrade failed and governance intervention is required. MigrationFailed { index: u32, blocks: T::BlockNumber }, /// The list of historical migrations has been cleared. - HistoricCleared { next_cursor: Option> }, + HistoricCleared { + /// Should be passed to `clear_historic` in a successive call. + next_cursor: Option>, + }, } #[pallet::hooks] @@ -172,7 +288,7 @@ pub mod pallet { use FailedUpgradeHandling::*; if let Some(cursor) = Cursor::::get() { - log::error!("Ongoing migrations interrupted - chain stuck"); + log::error!(target: LOG, "Ongoing migrations interrupted - chain stuck"); Self::deposit_event(Event::UpgradeFailed); let maybe_index = cursor.as_active().map(|c| c.index); @@ -180,13 +296,17 @@ pub mod pallet { KeepStuck => Cursor::::set(Some(MigrationCursor::Stuck)), ForceUnstuck => Cursor::::kill(), } - } else if T::Migrations::get().len() > 0 { + return T::WeightInfo::on_runtime_upgrade() + } + + let migrations = T::Migrations::get().len() as u32; + if migrations > 0 { Cursor::::set(Some(MigrationCursor::Active(ActiveCursor { index: 0, inner_cursor: None, started_at: System::::block_number(), }))); - Self::deposit_event(Event::UpgradeStarted); + Self::deposit_event(Event::UpgradeStarted { migrations }); T::UpgradeStatusHandler::started(); } @@ -199,12 +319,12 @@ pub mod pallet { let mut cursor = match Cursor::::get() { None => { - log::debug!(target: LOG_TARGET, "[Block {n:?}] Waiting for cursor to become `Some`."); + log::debug!(target: LOG, "[Block {n:?}] Waiting for cursor to become `Some`."); return meter.consumed }, Some(MigrationCursor::Active(cursor)) => cursor, Some(MigrationCursor::Stuck) => { - log::error!("Migration stuck. Governance intervention required."); + log::error!(target: LOG, "Migration stuck. Governance intervention required."); return meter.consumed }, }; @@ -272,7 +392,7 @@ impl Pallet { is_first: bool, ) -> Option, ActiveCursorOf>> { let Some(migration) = migrations.get(cursor.index as usize) else { - Self::deposit_event(Event::UpgradeCompleted { migrations: migrations.len() as u32 }); + Self::deposit_event(Event::UpgradeCompleted); Cursor::::kill(); T::UpgradeStatusHandler::completed(); return None; diff --git a/frame/migrations/src/tests.rs b/frame/migrations/src/tests.rs index 9d67c90ab5ddf..3a022df54b35a 100644 --- a/frame/migrations/src/tests.rs +++ b/frame/migrations/src/tests.rs @@ -23,6 +23,33 @@ use crate::{ }; use frame_support::traits::OnRuntimeUpgrade; +#[test] +#[docify::export] +fn simple_works() { + use Event::*; + test_closure(|| { + // Add three migrations. Each taking one block longer. + MigrationsStorage::set(vec![(SucceedAfter, 1), (SucceedAfter, 2)]); + + System::set_block_number(1); + Migrations::on_runtime_upgrade(); + run_to_block(10); + + // Check that the executed migrations are recorded into `Historical`. + assert_eq!(historic(), vec![mocked_id(SucceedAfter, 1), mocked_id(SucceedAfter, 2),]); + // Check that we got all events. + assert_events(vec![ + UpgradeStarted { migrations: 2 }, + MigrationAdvanced { index: 0, blocks: 1 }, + MigrationCompleted { index: 0, blocks: 2 }, + MigrationAdvanced { index: 1, blocks: 0 }, + MigrationAdvanced { index: 1, blocks: 1 }, + MigrationCompleted { index: 1, blocks: 2 }, + UpgradeCompleted, + ]); + }); +} + #[test] fn basic_works() { test_closure(|| { @@ -44,14 +71,14 @@ fn basic_works() { ); // Check that we got all events. assert_events(vec![ - Event::UpgradeStarted, + Event::UpgradeStarted { migrations: 3 }, Event::MigrationCompleted { index: 0, blocks: 1 }, Event::MigrationAdvanced { index: 1, blocks: 0 }, Event::MigrationCompleted { index: 1, blocks: 1 }, Event::MigrationAdvanced { index: 2, blocks: 0 }, Event::MigrationAdvanced { index: 2, blocks: 1 }, Event::MigrationCompleted { index: 2, blocks: 2 }, - Event::UpgradeCompleted { migrations: 3 }, + Event::UpgradeCompleted, ]); }); } @@ -82,7 +109,7 @@ fn historic_skipping_works() { ); // Events received. assert_events(vec![ - Event::UpgradeStarted, + Event::UpgradeStarted { migrations: 5 }, Event::MigrationCompleted { index: 0, blocks: 1 }, Event::MigrationSkippedHistoric { index: 1 }, Event::MigrationAdvanced { index: 2, blocks: 0 }, @@ -91,7 +118,7 @@ fn historic_skipping_works() { Event::MigrationAdvanced { index: 3, blocks: 1 }, Event::MigrationCompleted { index: 3, blocks: 2 }, Event::MigrationSkippedHistoric { index: 4 }, - Event::UpgradeCompleted { migrations: 5 }, + Event::UpgradeCompleted, ]); // Now go for another upgrade; just to make sure that it wont execute again. @@ -111,13 +138,13 @@ fn historic_skipping_works() { // Everything got skipped. assert_events(vec![ - Event::UpgradeStarted, + Event::UpgradeStarted { migrations: 5 }, Event::MigrationSkippedHistoric { index: 0 }, Event::MigrationSkippedHistoric { index: 1 }, Event::MigrationSkippedHistoric { index: 2 }, Event::MigrationSkippedHistoric { index: 3 }, Event::MigrationSkippedHistoric { index: 4 }, - Event::UpgradeCompleted { migrations: 5 }, + Event::UpgradeCompleted, ]); }); } @@ -139,7 +166,7 @@ fn upgrade_fails_when_migration_active() { //); // Events received. assert_events(vec![ - Event::UpgradeStarted, + Event::UpgradeStarted { migrations: 1 }, Event::MigrationAdvanced { index: 0, blocks: 1 }, Event::MigrationAdvanced { index: 0, blocks: 2 }, ]); @@ -162,7 +189,7 @@ fn migration_timeout_errors() { // Times out after taking more than 3 steps. assert_events(vec![ - Event::UpgradeStarted, + Event::UpgradeStarted { migrations: 1 }, Event::MigrationAdvanced { index: 0, blocks: 1 }, Event::MigrationAdvanced { index: 0, blocks: 2 }, Event::MigrationAdvanced { index: 0, blocks: 3 }, diff --git a/frame/support/src/migrations.rs b/frame/support/src/migrations.rs index 854e58e7d73e1..51ee90ad1f06b 100644 --- a/frame/support/src/migrations.rs +++ b/frame/support/src/migrations.rs @@ -16,9 +16,10 @@ // limitations under the License. #[cfg(feature = "try-runtime")] +use crate::storage::unhashed::contains_prefixed_key; use crate::{ dispatch::DispatchClass, - storage::{transactional::with_transaction_opaque_err, unhashed::contains_prefixed_key}, + storage::transactional::with_transaction_opaque_err, traits::{GetStorageVersion, NoStorageVersionSet, PalletInfoAccess, StorageVersion}, weights::{RuntimeDbWeight, Weight, WeightMeter}, }; @@ -281,8 +282,9 @@ pub enum SteppedMigrationError { Failed, } -/// Check whether transaction processing is suspended on the runtime level. +/// Provides a way of checking whether transaction processing is suspended on the runtime level. pub trait ExtrinsicSuspenderQuery { + /// Check whether transactions with this dispatch class are suspended. fn is_suspended(class: DispatchClass) -> bool; } From 790914da33101c2180949e94ca86d6b63db12b48 Mon Sep 17 00:00:00 2001 From: Oliver Tale-Yazdi Date: Mon, 5 Jun 2023 15:36:57 +0200 Subject: [PATCH 28/63] Fix docs and kitchensink Signed-off-by: Oliver Tale-Yazdi --- Cargo.lock | 4 ++ bin/node/runtime/src/lib.rs | 1 + frame/migrations/Cargo.toml | 4 ++ frame/migrations/src/lib.rs | 88 ++++++++++++++++++++----------------- 4 files changed, 56 insertions(+), 41 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 478f292e9ae17..430bac6170762 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -6712,6 +6712,7 @@ version = "1.0.0" dependencies = [ "docify", "frame-benchmarking", + "frame-executive", "frame-support", "frame-system", "impl-trait-for-tuples", @@ -6719,11 +6720,14 @@ dependencies = [ "parity-scale-codec", "pretty_assertions", "scale-info", + "sp-api", + "sp-block-builder", "sp-core", "sp-io", "sp-runtime", "sp-std", "sp-tracing", + "sp-version", ] [[package]] diff --git a/bin/node/runtime/src/lib.rs b/bin/node/runtime/src/lib.rs index b940e5b0c9118..71ba7bfc41299 100644 --- a/bin/node/runtime/src/lib.rs +++ b/bin/node/runtime/src/lib.rs @@ -1989,6 +1989,7 @@ pub type Executive = frame_executive::Executive< Runtime, AllPalletsWithSystem, Migrations, + MultiBlockMigrations, >; // All migrations executed on runtime upgrade as a nested tuple of types implementing diff --git a/frame/migrations/Cargo.toml b/frame/migrations/Cargo.toml index 80cb63c091d8c..b751c4cf38bae 100644 --- a/frame/migrations/Cargo.toml +++ b/frame/migrations/Cargo.toml @@ -25,11 +25,15 @@ sp-runtime = { version = "24.0.0", path = "../../primitives/runtime", default-fe docify = "0.1.14" [dev-dependencies] +sp-api = { version = "4.0.0-dev", path = "../../primitives/api", features = [ "std" ] } sp-tracing = { version = "10.0.0", path = "../../primitives/tracing", features = [ "std" ] } sp-core = { version = "21.0.0", path = "../../primitives/core", features = [ "std" ] } +sp-version = { version = "22.0.0", path = "../../primitives/version", features = [ "std" ] } sp-io = { version = "23.0.0", path = "../../primitives/io", features = [ "std" ] } +sp-block-builder = { version = "4.0.0-dev", path = "../../primitives/block-builder", features = [ "std" ] } pretty_assertions = "1.3.0" docify = "0.1.14" +frame-executive = { version = "4.0.0-dev", path = "../executive" } [features] default = ["std"] diff --git a/frame/migrations/src/lib.rs b/frame/migrations/src/lib.rs index e53c26cf43ba4..7f04ea968d6da 100644 --- a/frame/migrations/src/lib.rs +++ b/frame/migrations/src/lib.rs @@ -57,34 +57,39 @@ //! //! ### Design //! -//! Migrations are provided to the pallet via a `Get Date: Mon, 5 Jun 2023 15:49:32 +0200 Subject: [PATCH 29/63] Format Cargo.toml Signed-off-by: Oliver Tale-Yazdi --- frame/migrations/Cargo.toml | 43 ++++++++++++++++++++++++++----------- 1 file changed, 30 insertions(+), 13 deletions(-) diff --git a/frame/migrations/Cargo.toml b/frame/migrations/Cargo.toml index b751c4cf38bae..76cb36c3c3e30 100644 --- a/frame/migrations/Cargo.toml +++ b/frame/migrations/Cargo.toml @@ -13,33 +13,50 @@ targets = ["x86_64-unknown-linux-gnu"] [dependencies] codec = { package = "parity-scale-codec", version = "3.0.0", default-features = false, features = [ "derive"] } +docify = "0.1.14" +impl-trait-for-tuples = "0.2.2" +log = "0.4.18" scale-info = { version = "2.0.0", default-features = false, features = ["derive"] } frame-benchmarking = { version = "4.0.0-dev", default-features = false, optional = true, path = "../benchmarking" } frame-support = { version = "4.0.0-dev", default-features = false, path = "../support" } frame-system = { version = "4.0.0-dev", default-features = false, path = "../system" } -impl-trait-for-tuples = "0.2.2" -log = "0.4.18" sp-std = { version = "8.0.0", path = "../../primitives/std", default-features = false } sp-runtime = { version = "24.0.0", path = "../../primitives/runtime", default-features = false } -docify = "0.1.14" [dev-dependencies] +frame-executive = { version = "4.0.0-dev", path = "../executive" } sp-api = { version = "4.0.0-dev", path = "../../primitives/api", features = [ "std" ] } -sp-tracing = { version = "10.0.0", path = "../../primitives/tracing", features = [ "std" ] } +sp-block-builder = { version = "4.0.0-dev", path = "../../primitives/block-builder", features = [ "std" ] } sp-core = { version = "21.0.0", path = "../../primitives/core", features = [ "std" ] } -sp-version = { version = "22.0.0", path = "../../primitives/version", features = [ "std" ] } sp-io = { version = "23.0.0", path = "../../primitives/io", features = [ "std" ] } -sp-block-builder = { version = "4.0.0-dev", path = "../../primitives/block-builder", features = [ "std" ] } -pretty_assertions = "1.3.0" +sp-tracing = { version = "10.0.0", path = "../../primitives/tracing", features = [ "std" ] } +sp-version = { version = "22.0.0", path = "../../primitives/version", features = [ "std" ] } + docify = "0.1.14" -frame-executive = { version = "4.0.0-dev", path = "../executive" } +pretty_assertions = "1.3.0" [features] default = ["std"] -std = ["codec/std", "frame-benchmarking?/std", "frame-support/std", "frame-system/std", "scale-info/std", "sp-std/std", "sp-runtime/std"] - -runtime-benchmarks = ["frame-benchmarking/runtime-benchmarks", "frame-support/runtime-benchmarks", "frame-system/runtime-benchmarks", "sp-runtime/runtime-benchmarks"] - -try-runtime = ["frame-support/try-runtime", "sp-runtime/try-runtime"] +std = [ + "codec/std", + "frame-benchmarking?/std", + "frame-support/std", + "frame-system/std", + "scale-info/std", + "sp-std/std", + "sp-runtime/std" +] + +runtime-benchmarks = [ + "frame-benchmarking/runtime-benchmarks", + "frame-support/runtime-benchmarks", + "frame-system/runtime-benchmarks", + "sp-runtime/runtime-benchmarks" +] + +try-runtime = [ + "frame-support/try-runtime", + "sp-runtime/try-runtime" +] From a2b31943da75464d6eae77bf485fd6dcefa66c23 Mon Sep 17 00:00:00 2001 From: Oliver Tale-Yazdi Date: Mon, 5 Jun 2023 15:52:55 +0200 Subject: [PATCH 30/63] Docs Signed-off-by: Oliver Tale-Yazdi --- frame/migrations/src/lib.rs | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/frame/migrations/src/lib.rs b/frame/migrations/src/lib.rs index 7f04ea968d6da..db1360420f556 100644 --- a/frame/migrations/src/lib.rs +++ b/frame/migrations/src/lib.rs @@ -131,13 +131,12 @@ #![cfg_attr(not(feature = "std"), no_std)] -pub use pallet::*; - mod benchmarking; mod mock; mod tests; pub mod weights; +pub use pallet::*; pub use weights::WeightInfo; use codec::{Decode, Encode, FullCodec, MaxEncodedLen}; @@ -182,6 +181,7 @@ impl MigrationCursor { } impl ActiveCursor { + /// Advance the cursor to the next migration. pub(crate) fn advance(&mut self, current_block: BlockNumber) { self.index.saturating_inc(); self.inner_cursor = None; @@ -218,6 +218,7 @@ pub mod pallet { #[pallet::config] pub trait Config: frame_system::Config { + /// The overarching event type of the runtime. type RuntimeEvent: From> + IsType<::RuntimeEvent>; /// All the multi-block migrations to run. @@ -373,6 +374,11 @@ pub mod pallet { Ok(()) } + /// Clear the entries of the `Historic` set. + /// + /// the `map_cursor` must be set to the last value that was returned by the + /// `HistoricCleared` event. The first time `None` can be used. `limit` must be chosen in a + /// way to result in a sensible weight. #[pallet::call_index(1)] #[pallet::weight({0})] // FAIL-CI pub fn clear_historic( @@ -391,6 +397,10 @@ pub mod pallet { } impl Pallet { + /// Try to make progress on the current migration. + /// + /// Returns whether processing should continue or break for this block. The `meter` contains the + /// remaining weight that can be consumed. fn exec_migration( meter: &mut WeightMeter, migrations: &MigrationsOf, From 60c1322761bea5176fb58669c4ce39e0651af110 Mon Sep 17 00:00:00 2001 From: Oliver Tale-Yazdi Date: Sun, 11 Jun 2023 13:33:28 +0200 Subject: [PATCH 31/63] =?UTF-8?q?Now=20it=20sounds=20like=20English=20?= =?UTF-8?q?=F0=9F=98=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Liam Aharon --- frame/migrations/src/benchmarking.rs | 2 +- frame/migrations/src/lib.rs | 78 ++++++++++++++-------------- frame/migrations/src/tests.rs | 6 +-- 3 files changed, 43 insertions(+), 43 deletions(-) diff --git a/frame/migrations/src/benchmarking.rs b/frame/migrations/src/benchmarking.rs index c006cd5c53442..352326979d971 100644 --- a/frame/migrations/src/benchmarking.rs +++ b/frame/migrations/src/benchmarking.rs @@ -91,7 +91,7 @@ mod benches { fn cursor() -> MigrationCursor { // Note: The weight of a function can depend on the weight of reading the `inner_cursor`. - // `Cursor` is a user provided type. Now instead of requeuing something like `Cursor: + // `Cursor` is a user provided type. Now instead of requiring something like `Cursor: // From`, we instead rely on the fact that it is MEL and the PoV benchmarking will // therefore already take the MEL bound, even when the cursor in storage is `None`. MigrationCursor::Active(ActiveCursor { diff --git a/frame/migrations/src/lib.rs b/frame/migrations/src/lib.rs index db1360420f556..05c6fbb3bde90 100644 --- a/frame/migrations/src/lib.rs +++ b/frame/migrations/src/lib.rs @@ -23,17 +23,17 @@ //! //! ## Overview //! -//! The pallet takes care of execution a batch of multi-step migrations over multiple blocks. The -//! process starts on each runtime upgrade. Normal and operational transactions are pause while this -//! is on-going. +//! The pallet takes care of executing a batch of multi-step migrations over multiple blocks. The +//! process starts on each runtime upgrade. Normal and operational transactions are paused while +//! migrations are on-going. //! //! ### Example //! -//! This example demonstrates a simple mocked walk through the basis success path. The pallet is +//! This example demonstrates a simple mocked walk through of a basic success scenario. The pallet is //! configured with two migrations: one succeeding after just one step, and the second one //! succeeding after two steps. A runtime upgrade is then enacted and the block number is advanced -//! until all migrations had time to execute. Afterwards the recorded historic migrations are -//! checked and the events are asserted. +//! until all migrations finish executing. Afterwards, the recorded historic migrations are +//! checked and events are asserted. #![doc = docify::embed!("frame/migrations/src/tests.rs", simple_works)] //! //! ## Pallet API @@ -50,7 +50,7 @@ //! 1. Must automatically execute migrations over multiple blocks. //! 2. Must prevent other (non-mandatory) transactions to execute in the meantime. //! 3. Must respect pessimistic weight bounds of migrations. -//! 4. Must execute migrations in order. Skipping is not allowed; rather an all-or-nothing. +//! 4. Must execute migrations in order. Skipping is not allowed; migrations are run on an all-or-nothing basis. //! 5. Must prevent re-execution of migrations. //! 6. Must provide transactional storage semantics for migrations. //! 7. Must guarantee progress. @@ -67,27 +67,27 @@ //! store its inner state and advance. Each time when the migration returns `Some(cursor)`, it //! signals the pallet that it is not done yet. //! The cursor is re-set on each runtime upgrade. This ensures that it starts to execute at the -//! first migration of the vector. The pallets cursor is only ever incremented and put into `Stuck` +//! first migration in the vector. The pallets cursor is only ever incremented or put into `Stuck` //! once it encounters an error (Goal 4). Once in the stuck state, the pallet will stay stuck until -//! it is resolved through manual intervention. -//! As soon as the cursor of the pallet becomes `Some(_)`; the transaction processing will be paused -//! by returning `true` from [`ExtrinsicSuspenderQuery::is_suspended`]. This ensures that no other -//! transactions are processed until the pallet is done (Goal 2). +//! it is fixed through manual governance intervention. +//! As soon as the cursor of the pallet becomes `Some(_)`; chain transaction processing is paused +//! by [`ExtrinsicSuspenderQuery::is_suspended`] returning `true`. This ensures that no other +//! transactions are processed until all migrations are complete (Goal 2). //! `on_initialize` the pallet will load the current migration and check whether it was already //! executed in the past by checking for membership of its ID in the `Historic` set. Historic -//! migrations are ignored without causing an error. Each successfully executed migration is added +//! migrations are skipped without causing an error. Each successfully executed migration is added //! to this set (Goal 5). -//! This proceeds until no more migrations can be loaded. This causes event `UpgradeCompleted` to be +//! This proceeds until no more migrations remain. At that point, the event `UpgradeCompleted` is //! emitted (Goal 1). -//! The execution of each migrations happens by calling [`SteppedMigration::transactional_step`]. +//! The execution of each migration happens by calling [`SteppedMigration::transactional_step`]. //! This function wraps the inner `step` function into a transactional layer to allow rollback in //! the error case (Goal 6). //! Weight limits must be checked by the migration itself. The pallet provides a [`WeightMeter`] for -//! that cause. The pallet may return [`SteppedMigrationError::InsufficientWeight`] at any point. -//! The pallet will react to this with a case decision: if that migration was exclusively execute in -//! this block, and therefore got the maximal amount of weight possible, the pallet becomes `Stuck`. -//! Otherwise one re-attempt is done with the same logic in the next block (Goal 3). -//! Progress of the pallet is guaranteed by providing once: a timeout for each migration via +//! that purpose. The pallet may return [`SteppedMigrationError::InsufficientWeight`] at any point. +//! In that scenario, the one of two things will happen: if that migration was exclusively executed in +//! this block, and therefore required more than the maximum amount of weight possible, the pallet becomes `Stuck`. +//! Otherwise one re-attempt is attempted with the same logic in the next block (Goal 3). +//! Progress through the migrations is guaranteed by providing a timeout for each migration via //! [`SteppedMigration::max_steps`]. The pallet **ONLY** guarantees progress if this is set to //! sensible limits (Goal 7). //! @@ -102,32 +102,32 @@ //! The standard procedure for a successful runtime upgrade can look like this: //! 1. Migrations are configured in the `Migrations` config item. All migrations expose `max_steps`, //! error tolerance, check their weight bounds and have a unique identifier. -//! 2. The runtime upgrade is enacted. Events `UpgradeStarted` is followed by lots of -//! `MigrationAdvanced`. Finally an `UpgradeCompleted` is emitted. -//! 3. Cleanup as described on the governance scenario can happen at any time. +//! 2. The runtime upgrade is enacted. `UpgradeStarted` events are followed by lots of +//! `MigrationAdvanced` events. Finally `UpgradeCompleted` is emitted. +//! 3. Cleanup as described in the governance scenario be executed at any time after the migration completes. //! //! ### Advice: Failed upgrades //! -//! Failed upgrade cannot automatically be handled but requires governance intervention. Set up -//! monitoring for event `UpgradeFailed` to act in this case. Hook [`UpgradeStatusHandler::failed`] +//! Failed upgrades cannot recovered from automatically and require governance intervention. Set up +//! monitoring for `UpgradeFailed` events to be made aware of any failures. The hook [`UpgradeStatusHandler::failed`] //! should be setup in a way that it allows governance to act, but still prevent other transactions //! from interacting with the inconsistent storage state. Note that this is paramount, since the -//! inconsistent state might contain faulty balance amount or similar that could cause great harm if +//! inconsistent state might contain a faulty balance amount or similar that could cause great harm if //! user transactions don't remain suspended. One way to implement this would be to use the //! `SafeMode` or `TxPause` pallets that can prevent most user interactions but still allow a //! whitelisted set of governance calls. //! //! ### Remark: Failed migrations //! -//! Failed migrations are not added to the `Historic` blacklist. This means that an erroneous -//! migration must be removed of fixed manually. This already applies - even before taking the -//! historic set into account. +//! Failed migrations are not added to the `Historic` set. This means that an erroneous +//! migration must be removed and fixed manually. This already applies, even before considering the +//! historic set. //! //! ### Remark: Transactional processing //! //! You can see the transactional semantics for migrational steps as mostly useless, since in the -//! stuck case the state is already messed up. This just prevents it from getting messed up even -//! more, but does not prevent it in the first place. +//! stuck case the state is already messed up. This just prevents it from becoming even more messed up +//! , but doesn't prevent it in the first place. #![cfg_attr(not(feature = "std"), no_std)] @@ -223,8 +223,8 @@ pub mod pallet { /// All the multi-block migrations to run. /// - /// Should only be updated in a runtime-upgrade once all the old ones have completed. (Check - /// `Cursor` for `None`). + /// Should only be updated in a runtime-upgrade once all the old migrations have completed. (Check + /// that `Cursor` is `None`). type Migrations: Get>; /// The cursor type that is shared across all migrations. @@ -247,7 +247,7 @@ pub mod pallet { /// The currently active migration to run and its cursor. /// - /// `None` indicates that no migration process is running. + /// `None` indicates that no migration is running. #[pallet::storage] pub type Cursor = StorageValue<_, CursorOf, OptionQuery>; @@ -374,11 +374,11 @@ pub mod pallet { Ok(()) } - /// Clear the entries of the `Historic` set. + /// Clears the `Historic` set. /// - /// the `map_cursor` must be set to the last value that was returned by the + /// `map_cursor` must be set to the last value that was returned by the /// `HistoricCleared` event. The first time `None` can be used. `limit` must be chosen in a - /// way to result in a sensible weight. + /// way that will result in a sensible weight. #[pallet::call_index(1)] #[pallet::weight({0})] // FAIL-CI pub fn clear_historic( @@ -425,14 +425,14 @@ impl Pallet { Self::deposit_event(Event::MigrationAdvanced { index: cursor.index, blocks }); cursor.inner_cursor = Some(next_cursor); - // We only do one step per block. + // We only progress one step per block. if migration.max_steps().map_or(false, |max| blocks > max.into()) { Self::deposit_event(Event::MigrationFailed { index: cursor.index, blocks }); Self::deposit_event(Event::UpgradeFailed); Cursor::::set(Some(MigrationCursor::Stuck)); None } else { - // A migration has to make maximal progress per step, we therefore break. + // A migration cannot progress more than one step per block, we therefore break. Some(ControlFlow::Break(cursor)) } }, diff --git a/frame/migrations/src/tests.rs b/frame/migrations/src/tests.rs index 3a022df54b35a..b65f5004a7f3a 100644 --- a/frame/migrations/src/tests.rs +++ b/frame/migrations/src/tests.rs @@ -28,14 +28,14 @@ use frame_support::traits::OnRuntimeUpgrade; fn simple_works() { use Event::*; test_closure(|| { - // Add three migrations. Each taking one block longer. - MigrationsStorage::set(vec![(SucceedAfter, 1), (SucceedAfter, 2)]); + // Add three migrations, each taking one block longer than the last. + MigrationsStorage::set(vec![(SucceedAfter, 0), (SucceedAfter, 1), (SucceedAfter, 2)]); System::set_block_number(1); Migrations::on_runtime_upgrade(); run_to_block(10); - // Check that the executed migrations are recorded into `Historical`. + // Check that the executed migrations are recorded in `Historical`. assert_eq!(historic(), vec![mocked_id(SucceedAfter, 1), mocked_id(SucceedAfter, 2),]); // Check that we got all events. assert_events(vec![ From 17be9274e4e2491fb31bb8b6289bab52eb688318 Mon Sep 17 00:00:00 2001 From: Oliver Tale-Yazdi Date: Sun, 11 Jun 2023 13:35:19 +0200 Subject: [PATCH 32/63] Fix test Signed-off-by: Oliver Tale-Yazdi --- frame/migrations/src/tests.rs | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/frame/migrations/src/tests.rs b/frame/migrations/src/tests.rs index b65f5004a7f3a..cc876b8548ff7 100644 --- a/frame/migrations/src/tests.rs +++ b/frame/migrations/src/tests.rs @@ -36,15 +36,16 @@ fn simple_works() { run_to_block(10); // Check that the executed migrations are recorded in `Historical`. - assert_eq!(historic(), vec![mocked_id(SucceedAfter, 1), mocked_id(SucceedAfter, 2),]); + assert_eq!(historic(), vec![mocked_id(SucceedAfter, 0), mocked_id(SucceedAfter, 1), mocked_id(SucceedAfter, 2),]); // Check that we got all events. assert_events(vec![ - UpgradeStarted { migrations: 2 }, - MigrationAdvanced { index: 0, blocks: 1 }, - MigrationCompleted { index: 0, blocks: 2 }, + UpgradeStarted { migrations: 3 }, + MigrationCompleted { index: 0, blocks: 1 }, MigrationAdvanced { index: 1, blocks: 0 }, - MigrationAdvanced { index: 1, blocks: 1 }, - MigrationCompleted { index: 1, blocks: 2 }, + MigrationCompleted { index: 1, blocks: 1 }, + MigrationAdvanced { index: 2, blocks: 0 }, + MigrationAdvanced { index: 2, blocks: 1 }, + MigrationCompleted { index: 2, blocks: 2 }, UpgradeCompleted, ]); }); From 741c476058cd87cf4367b6f52979ff2f72d2a97f Mon Sep 17 00:00:00 2001 From: Oliver Tale-Yazdi Date: Sun, 11 Jun 2023 13:35:30 +0200 Subject: [PATCH 33/63] fmt Signed-off-by: Oliver Tale-Yazdi --- frame/migrations/src/lib.rs | 45 ++++++++++++++++++----------------- frame/migrations/src/tests.rs | 9 ++++++- 2 files changed, 31 insertions(+), 23 deletions(-) diff --git a/frame/migrations/src/lib.rs b/frame/migrations/src/lib.rs index 05c6fbb3bde90..2ae3a695d98b8 100644 --- a/frame/migrations/src/lib.rs +++ b/frame/migrations/src/lib.rs @@ -29,8 +29,8 @@ //! //! ### Example //! -//! This example demonstrates a simple mocked walk through of a basic success scenario. The pallet is -//! configured with two migrations: one succeeding after just one step, and the second one +//! This example demonstrates a simple mocked walk through of a basic success scenario. The pallet +//! is configured with two migrations: one succeeding after just one step, and the second one //! succeeding after two steps. A runtime upgrade is then enacted and the block number is advanced //! until all migrations finish executing. Afterwards, the recorded historic migrations are //! checked and events are asserted. @@ -50,8 +50,8 @@ //! 1. Must automatically execute migrations over multiple blocks. //! 2. Must prevent other (non-mandatory) transactions to execute in the meantime. //! 3. Must respect pessimistic weight bounds of migrations. -//! 4. Must execute migrations in order. Skipping is not allowed; migrations are run on an all-or-nothing basis. -//! 5. Must prevent re-execution of migrations. +//! 4. Must execute migrations in order. Skipping is not allowed; migrations are run on an +//! all-or-nothing basis. 5. Must prevent re-execution of migrations. //! 6. Must provide transactional storage semantics for migrations. //! 7. Must guarantee progress. //! @@ -84,12 +84,12 @@ //! the error case (Goal 6). //! Weight limits must be checked by the migration itself. The pallet provides a [`WeightMeter`] for //! that purpose. The pallet may return [`SteppedMigrationError::InsufficientWeight`] at any point. -//! In that scenario, the one of two things will happen: if that migration was exclusively executed in -//! this block, and therefore required more than the maximum amount of weight possible, the pallet becomes `Stuck`. -//! Otherwise one re-attempt is attempted with the same logic in the next block (Goal 3). -//! Progress through the migrations is guaranteed by providing a timeout for each migration via -//! [`SteppedMigration::max_steps`]. The pallet **ONLY** guarantees progress if this is set to -//! sensible limits (Goal 7). +//! In that scenario, the one of two things will happen: if that migration was exclusively executed +//! in this block, and therefore required more than the maximum amount of weight possible, the +//! pallet becomes `Stuck`. Otherwise one re-attempt is attempted with the same logic in the next +//! block (Goal 3). Progress through the migrations is guaranteed by providing a timeout for each +//! migration via [`SteppedMigration::max_steps`]. The pallet **ONLY** guarantees progress if this +//! is set to sensible limits (Goal 7). //! //! ### Scenario: Governance cleanup //! @@ -104,18 +104,19 @@ //! error tolerance, check their weight bounds and have a unique identifier. //! 2. The runtime upgrade is enacted. `UpgradeStarted` events are followed by lots of //! `MigrationAdvanced` events. Finally `UpgradeCompleted` is emitted. -//! 3. Cleanup as described in the governance scenario be executed at any time after the migration completes. +//! 3. Cleanup as described in the governance scenario be executed at any time after the migration +//! completes. //! //! ### Advice: Failed upgrades //! //! Failed upgrades cannot recovered from automatically and require governance intervention. Set up -//! monitoring for `UpgradeFailed` events to be made aware of any failures. The hook [`UpgradeStatusHandler::failed`] -//! should be setup in a way that it allows governance to act, but still prevent other transactions -//! from interacting with the inconsistent storage state. Note that this is paramount, since the -//! inconsistent state might contain a faulty balance amount or similar that could cause great harm if -//! user transactions don't remain suspended. One way to implement this would be to use the -//! `SafeMode` or `TxPause` pallets that can prevent most user interactions but still allow a -//! whitelisted set of governance calls. +//! monitoring for `UpgradeFailed` events to be made aware of any failures. The hook +//! [`UpgradeStatusHandler::failed`] should be setup in a way that it allows governance to act, but +//! still prevent other transactions from interacting with the inconsistent storage state. Note that +//! this is paramount, since the inconsistent state might contain a faulty balance amount or similar +//! that could cause great harm if user transactions don't remain suspended. One way to implement +//! this would be to use the `SafeMode` or `TxPause` pallets that can prevent most user interactions +//! but still allow a whitelisted set of governance calls. //! //! ### Remark: Failed migrations //! @@ -126,8 +127,8 @@ //! ### Remark: Transactional processing //! //! You can see the transactional semantics for migrational steps as mostly useless, since in the -//! stuck case the state is already messed up. This just prevents it from becoming even more messed up -//! , but doesn't prevent it in the first place. +//! stuck case the state is already messed up. This just prevents it from becoming even more messed +//! up , but doesn't prevent it in the first place. #![cfg_attr(not(feature = "std"), no_std)] @@ -223,8 +224,8 @@ pub mod pallet { /// All the multi-block migrations to run. /// - /// Should only be updated in a runtime-upgrade once all the old migrations have completed. (Check - /// that `Cursor` is `None`). + /// Should only be updated in a runtime-upgrade once all the old migrations have completed. + /// (Check that `Cursor` is `None`). type Migrations: Get>; /// The cursor type that is shared across all migrations. diff --git a/frame/migrations/src/tests.rs b/frame/migrations/src/tests.rs index cc876b8548ff7..83d250bc1262d 100644 --- a/frame/migrations/src/tests.rs +++ b/frame/migrations/src/tests.rs @@ -36,7 +36,14 @@ fn simple_works() { run_to_block(10); // Check that the executed migrations are recorded in `Historical`. - assert_eq!(historic(), vec![mocked_id(SucceedAfter, 0), mocked_id(SucceedAfter, 1), mocked_id(SucceedAfter, 2),]); + assert_eq!( + historic(), + vec![ + mocked_id(SucceedAfter, 0), + mocked_id(SucceedAfter, 1), + mocked_id(SucceedAfter, 2), + ] + ); // Check that we got all events. assert_events(vec![ UpgradeStarted { migrations: 3 }, From 6a1438e5a03d4671c3021958f72a731ee8b68ad4 Mon Sep 17 00:00:00 2001 From: Oliver Tale-Yazdi Date: Sun, 11 Jun 2023 13:35:49 +0200 Subject: [PATCH 34/63] Benches still WIP Signed-off-by: Oliver Tale-Yazdi --- frame/migrations/src/benchmarking.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frame/migrations/src/benchmarking.rs b/frame/migrations/src/benchmarking.rs index 352326979d971..06c5b3489e6e2 100644 --- a/frame/migrations/src/benchmarking.rs +++ b/frame/migrations/src/benchmarking.rs @@ -49,7 +49,7 @@ mod benches { } #[benchmark] - fn on_init_base() { + fn on_init_base() { // FAIL-CI Cursor::::set(Some(cursor::())); System::::set_block_number(1u32.into()); From 0e4f8a622055d005db96211c788e6c51b0d4b568 Mon Sep 17 00:00:00 2001 From: Oliver Tale-Yazdi Date: Tue, 13 Jun 2023 13:18:02 +0200 Subject: [PATCH 35/63] Move code Signed-off-by: Oliver Tale-Yazdi --- bin/node/runtime/src/lib.rs | 2 +- frame/migrations/src/lib.rs | 164 ++++++++++++++++++-------------- frame/migrations/src/mock.rs | 14 +-- frame/support/src/migrations.rs | 4 +- 4 files changed, 101 insertions(+), 83 deletions(-) diff --git a/bin/node/runtime/src/lib.rs b/bin/node/runtime/src/lib.rs index 71ba7bfc41299..e89be58951a1d 100644 --- a/bin/node/runtime/src/lib.rs +++ b/bin/node/runtime/src/lib.rs @@ -1864,7 +1864,7 @@ impl pallet_migrations::Config for Runtime { type Migrations = (); type Cursor = (); type Identifier = (); - type UpgradeStatusHandler = (); + type UpgradeStatusNotify = (); type ServiceWeight = MbmServiceWeight; type WeightInfo = pallet_migrations::weights::SubstrateWeight; } diff --git a/frame/migrations/src/lib.rs b/frame/migrations/src/lib.rs index 2ae3a695d98b8..37421f08bffb6 100644 --- a/frame/migrations/src/lib.rs +++ b/frame/migrations/src/lib.rs @@ -111,7 +111,7 @@ //! //! Failed upgrades cannot recovered from automatically and require governance intervention. Set up //! monitoring for `UpgradeFailed` events to be made aware of any failures. The hook -//! [`UpgradeStatusHandler::failed`] should be setup in a way that it allows governance to act, but +//! [`UpgradeStatusNotify::failed`] should be setup in a way that it allows governance to act, but //! still prevent other transactions from interacting with the inconsistent storage state. Note that //! this is paramount, since the inconsistent state might contain a faulty balance amount or similar //! that could cause great harm if user transactions don't remain suspended. One way to implement @@ -163,14 +163,6 @@ pub enum MigrationCursor { Stuck, } -/// Points to the currently active migration and its inner cursor. -#[derive(Debug, Clone, Eq, PartialEq, Encode, Decode, scale_info::TypeInfo, MaxEncodedLen)] -pub struct ActiveCursor { - index: u32, - inner_cursor: Option, - started_at: BlockNumber, -} - impl MigrationCursor { /// Maybe return self as an `ActiveCursor`. pub fn as_active(&self) -> Option<&ActiveCursor> { @@ -181,6 +173,20 @@ impl MigrationCursor { } } +impl From> for MigrationCursor { + fn from(active: ActiveCursor) -> Self { + MigrationCursor::Active(active) + } +} + +/// Points to the currently active migration and its inner cursor. +#[derive(Debug, Clone, Eq, PartialEq, Encode, Decode, scale_info::TypeInfo, MaxEncodedLen)] +pub struct ActiveCursor { + index: u32, + inner_cursor: Option, + started_at: BlockNumber, +} + impl ActiveCursor { /// Advance the cursor to the next migration. pub(crate) fn advance(&mut self, current_block: BlockNumber) { @@ -200,11 +206,11 @@ pub type MigrationsOf = Vec< >, >; -/// Convenience wrapper for [`MigrationCursor`]. +/// Convenience alias for [`MigrationCursor`]. pub type CursorOf = MigrationCursor<::Cursor, ::BlockNumber>; -/// Convenience wrapper for [`ActiveCursor`]. +/// Convenience alias for [`ActiveCursor`]. pub type ActiveCursorOf = ActiveCursor<::Cursor, ::BlockNumber>; @@ -234,10 +240,10 @@ pub mod pallet { /// The identifier type that is shared across all migrations. type Identifier: FullCodec + MaxEncodedLen + TypeInfo; - /// Notification handler for status updates regarding runtime upgrades. + /// Notifications for status updates of a runtime upgrade. /// /// Can be used to pause XCM etc. - type UpgradeStatusHandler: UpgradeStatusHandler; + type UpgradeStatusNotify: UpgradeStatusNotify; /// The weight to spend each block to execute migrations. type ServiceWeight: Get; @@ -293,68 +299,11 @@ pub mod pallet { #[pallet::hooks] impl Hooks> for Pallet { fn on_runtime_upgrade() -> Weight { - use FailedUpgradeHandling::*; - - if let Some(cursor) = Cursor::::get() { - log::error!(target: LOG, "Ongoing migrations interrupted - chain stuck"); - Self::deposit_event(Event::UpgradeFailed); - - let maybe_index = cursor.as_active().map(|c| c.index); - match T::UpgradeStatusHandler::failed(maybe_index) { - KeepStuck => Cursor::::set(Some(MigrationCursor::Stuck)), - ForceUnstuck => Cursor::::kill(), - } - return T::WeightInfo::on_runtime_upgrade() - } - - let migrations = T::Migrations::get().len() as u32; - if migrations > 0 { - Cursor::::set(Some(MigrationCursor::Active(ActiveCursor { - index: 0, - inner_cursor: None, - started_at: System::::block_number(), - }))); - Self::deposit_event(Event::UpgradeStarted { migrations }); - T::UpgradeStatusHandler::started(); - } - - T::WeightInfo::on_runtime_upgrade() + Self::start_mbm() } fn on_initialize(n: T::BlockNumber) -> Weight { - let mut meter = WeightMeter::from_limit(T::ServiceWeight::get()); - meter.defensive_saturating_accrue(T::WeightInfo::on_init_base()); - - let mut cursor = match Cursor::::get() { - None => { - log::debug!(target: LOG, "[Block {n:?}] Waiting for cursor to become `Some`."); - return meter.consumed - }, - Some(MigrationCursor::Active(cursor)) => cursor, - Some(MigrationCursor::Stuck) => { - log::error!(target: LOG, "Migration stuck. Governance intervention required."); - return meter.consumed - }, - }; - debug_assert!(::is_suspended(DispatchClass::Normal)); - - let migrations = T::Migrations::get(); - for i in 0.. { - match Self::exec_migration(&mut meter, &migrations, cursor, i == 0) { - None => return meter.consumed, - Some(ControlFlow::Break(last_cursor)) => { - cursor = last_cursor; - break - }, - Some(ControlFlow::Continue(next_cursor)) => { - cursor = next_cursor; - }, - } - } - - Cursor::::set(Some(MigrationCursor::Active(cursor))); - - meter.consumed + Self::progress_mbm(n) } } @@ -398,6 +347,75 @@ pub mod pallet { } impl Pallet { + /// Starts the process of multi block migrations. + /// + /// Should only be called once all migrations completed. + fn start_mbm() -> Weight { + use FailedUpgradeHandling::*; + + if let Some(cursor) = Cursor::::get() { + log::error!(target: LOG, "Ongoing migrations interrupted - chain stuck"); + Self::deposit_event(Event::UpgradeFailed); + + let maybe_index = cursor.as_active().map(|c| c.index); + match T::UpgradeStatusNotify::failed(maybe_index) { + KeepStuck => Cursor::::set(Some(MigrationCursor::Stuck)), + ForceUnstuck => Cursor::::kill(), + } + return T::WeightInfo::on_runtime_upgrade() + } + + let migrations = T::Migrations::get().len() as u32; + if migrations > 0 { + Cursor::::set(Some(ActiveCursor { + index: 0, + inner_cursor: None, + started_at: System::::block_number(), + }.into())); + Self::deposit_event(Event::UpgradeStarted { migrations }); + T::UpgradeStatusNotify::started(); + } + + T::WeightInfo::on_runtime_upgrade() + } + + /// Tries to make progress on the multi block migrations. + fn progress_mbm(n: T::BlockNumber) -> Weight { + let mut meter = WeightMeter::from_limit(T::ServiceWeight::get()); + meter.defensive_saturating_accrue(T::WeightInfo::on_init_base()); + + let mut cursor = match Cursor::::get() { + None => { + log::debug!(target: LOG, "[Block {n:?}] Waiting for cursor to become `Some`."); + return meter.consumed + }, + Some(MigrationCursor::Active(cursor)) => cursor, + Some(MigrationCursor::Stuck) => { + log::error!(target: LOG, "Migration stuck. Governance intervention required."); + return meter.consumed + }, + }; + debug_assert!(::is_suspended(DispatchClass::Normal)); + + let migrations = T::Migrations::get(); + for i in 0.. { + match Self::exec_migration(&mut meter, &migrations, cursor, i == 0) { + None => return meter.consumed, + Some(ControlFlow::Break(last_cursor)) => { + cursor = last_cursor; + break + }, + Some(ControlFlow::Continue(next_cursor)) => { + cursor = next_cursor; + }, + } + } + + Cursor::::set(Some(cursor.into())); + + meter.consumed + } + /// Try to make progress on the current migration. /// /// Returns whether processing should continue or break for this block. The `meter` contains the @@ -411,7 +429,7 @@ impl Pallet { let Some(migration) = migrations.get(cursor.index as usize) else { Self::deposit_event(Event::UpgradeCompleted); Cursor::::kill(); - T::UpgradeStatusHandler::completed(); + T::UpgradeStatusNotify::completed(); return None; }; if Historic::::contains_key(&migration.id()) { diff --git a/frame/migrations/src/mock.rs b/frame/migrations/src/mock.rs index 452e66e15e061..19a5b1d111ac5 100644 --- a/frame/migrations/src/mock.rs +++ b/frame/migrations/src/mock.rs @@ -158,7 +158,7 @@ impl crate::Config for Test { type Migrations = MigrationsStorage; type Cursor = MockedCursor; type Identifier = MockedIdentifier; - type UpgradeStatusHandler = LoggingUpgradeStatusHandler<()>; + type UpgradeStatusNotify = LoggingUpgradeStatusNotify<()>; type ServiceWeight = ServiceWeight; type WeightInfo = (); } @@ -224,22 +224,22 @@ pub fn assert_events(events: Vec) { System::reset_events(); } -/// Wraps an [`UpgradeStatusHandler`] and adds logging. -pub struct LoggingUpgradeStatusHandler(core::marker::PhantomData); -impl UpgradeStatusHandler for LoggingUpgradeStatusHandler { +/// Wraps an [`UpgradeStatusNotify`] and adds logging. +pub struct LoggingUpgradeStatusNotify(core::marker::PhantomData); +impl UpgradeStatusNotify for LoggingUpgradeStatusNotify { fn started() { - log::info!("UpgradeStatusHandler started"); + log::info!("UpgradeStatusNotify started"); T::started(); } fn completed() { - log::info!("UpgradeStatusHandler completed"); + log::info!("UpgradeStatusNotify completed"); T::completed(); } fn failed(migration: Option) -> FailedUpgradeHandling { let res = T::failed(migration); - log::error!("UpgradeStatusHandler failed at: {migration:?}, handling as {res:?}"); + log::error!("UpgradeStatusNotify failed at: {migration:?}, handling as {res:?}"); res } } diff --git a/frame/support/src/migrations.rs b/frame/support/src/migrations.rs index 51ee90ad1f06b..eae8efd670af5 100644 --- a/frame/support/src/migrations.rs +++ b/frame/support/src/migrations.rs @@ -295,7 +295,7 @@ impl ExtrinsicSuspenderQuery for () { } /// Notification handler for status updates regarding runtime upgrades. -pub trait UpgradeStatusHandler { +pub trait UpgradeStatusNotify { /// Notifies of the start of a runtime upgrade. fn started() {} @@ -320,7 +320,7 @@ pub enum FailedUpgradeHandling { KeepStuck, } -impl UpgradeStatusHandler for () { +impl UpgradeStatusNotify for () { fn failed(_migration: Option) -> FailedUpgradeHandling { FailedUpgradeHandling::KeepStuck } From 97b58e14e4f972b99c4c2f3d0a3f215655e469c7 Mon Sep 17 00:00:00 2001 From: Oliver Tale-Yazdi Date: Tue, 13 Jun 2023 21:14:05 +0200 Subject: [PATCH 36/63] Docs review fixes Co-authored-by: Kian Paimani <5588131+kianenigma@users.noreply.github.com> Signed-off-by: Oliver Tale-Yazdi --- frame/migrations/src/benchmarking.rs | 3 ++- frame/migrations/src/lib.rs | 36 ++++++++++++++++------------ 2 files changed, 23 insertions(+), 16 deletions(-) diff --git a/frame/migrations/src/benchmarking.rs b/frame/migrations/src/benchmarking.rs index 06c5b3489e6e2..ca14c774e94c3 100644 --- a/frame/migrations/src/benchmarking.rs +++ b/frame/migrations/src/benchmarking.rs @@ -49,7 +49,8 @@ mod benches { } #[benchmark] - fn on_init_base() { // FAIL-CI + fn on_init_base() { + // FAIL-CI Cursor::::set(Some(cursor::())); System::::set_block_number(1u32.into()); diff --git a/frame/migrations/src/lib.rs b/frame/migrations/src/lib.rs index 37421f08bffb6..5b1b0a8f2e9ef 100644 --- a/frame/migrations/src/lib.rs +++ b/frame/migrations/src/lib.rs @@ -57,11 +57,11 @@ //! //! ### Design //! -//! Migrations are provided to the pallet through the storage item `Migrations` of type +//! Migrations are provided to the pallet through the associated type [`Config::Migrations`] of type //! `Get MigrationCursor { } } -impl From> for MigrationCursor { +impl From> + for MigrationCursor +{ fn from(active: ActiveCursor) -> Self { MigrationCursor::Active(active) } @@ -367,11 +370,14 @@ impl Pallet { let migrations = T::Migrations::get().len() as u32; if migrations > 0 { - Cursor::::set(Some(ActiveCursor { - index: 0, - inner_cursor: None, - started_at: System::::block_number(), - }.into())); + Cursor::::set(Some( + ActiveCursor { + index: 0, + inner_cursor: None, + started_at: System::::block_number(), + } + .into(), + )); Self::deposit_event(Event::UpgradeStarted { migrations }); T::UpgradeStatusNotify::started(); } From 87ac850b8f3f043f608de56dc02850a90ccb2017 Mon Sep 17 00:00:00 2001 From: Oliver Tale-Yazdi Date: Tue, 13 Jun 2023 21:54:41 +0200 Subject: [PATCH 37/63] Add tests Signed-off-by: Oliver Tale-Yazdi --- frame/migrations/src/lib.rs | 98 ++++++++++++++++++++++------------- frame/migrations/src/mock.rs | 27 +++++++--- frame/migrations/src/tests.rs | 71 ++++++++++++++++++++++++- 3 files changed, 153 insertions(+), 43 deletions(-) diff --git a/frame/migrations/src/lib.rs b/frame/migrations/src/lib.rs index 5b1b0a8f2e9ef..11b2f901b894a 100644 --- a/frame/migrations/src/lib.rs +++ b/frame/migrations/src/lib.rs @@ -15,6 +15,7 @@ // See the License for the specific language governing permissions and // limitations under the License. +#![deny(missing_docs)] #![deny(rustdoc::broken_intra_doc_links)] //! # `pallet-migrations` @@ -271,28 +272,53 @@ pub mod pallet { #[pallet::event] #[pallet::generate_deposit(pub(super) fn deposit_event)] pub enum Event { - /// Runtime upgrade started with `migrations` in the queue. + /// A Runtime upgrade started. /// - /// This can be used to design a progress indicator in combination with counting the - /// `MigrationCompleted` and `MigrationSkippedHistoric` events. - UpgradeStarted { migrations: u32 }, - /// Runtime upgrade completed with `migrations`. + /// Its end is indicated by `UpgradeCompleted` or `UpgradeFailed`. + UpgradeStarted { + /// The number of migrations that this upgrade contains. + /// + /// This can be used to design a progress indicator in combination with counting the + /// `MigrationCompleted` and `MigrationSkippedHistoric` events. + migrations: u32, + }, + /// The current runtime upgrade completed. + /// + /// This implies that all of its migrations completed successfully as well. UpgradeCompleted, /// Runtime upgrade failed. /// /// This is very bad and will require governance intervention. UpgradeFailed, - /// Migration `index` was skipped, since it already executed in the past. - MigrationSkippedHistoric { index: u32 }, - /// Migration `index` made progress. - MigrationAdvanced { index: u32, blocks: T::BlockNumber }, - /// Migration `index` completed. - MigrationCompleted { index: u32, blocks: T::BlockNumber }, - /// Migration `index` failed. + /// A migration was skipped since it was already executed in the past. + MigrationSkippedHistoric { + /// The index of the skipped migration within the [`Config::Migrations`] list. + index: u32, + }, + /// A migration progressed. + MigrationAdvanced { + /// The index of the migration within the [`Config::Migrations`] list. + index: u32, + /// The number of blocks that elapsed since the migration started. + blocks: T::BlockNumber, + }, + /// A Migration completed. + MigrationCompleted { + /// The index of the migration within the [`Config::Migrations`] list. + index: u32, + /// The number of blocks that elapsed since the migration started. + blocks: T::BlockNumber, + }, + /// A Migration failed. /// /// This implies that the whole upgrade failed and governance intervention is required. - MigrationFailed { index: u32, blocks: T::BlockNumber }, - /// The list of historical migrations has been cleared. + MigrationFailed { + /// The index of the migration within the [`Config::Migrations`] list. + index: u32, + /// The number of blocks that elapsed since the migration started. + blocks: T::BlockNumber, + }, + /// The set of historical migrations has been cleared. HistoricCleared { /// Should be passed to `clear_historic` in a successive call. next_cursor: Option>, @@ -302,11 +328,11 @@ pub mod pallet { #[pallet::hooks] impl Hooks> for Pallet { fn on_runtime_upgrade() -> Weight { - Self::start_mbm() + Self::onboard_new_mbms() } fn on_initialize(n: T::BlockNumber) -> Weight { - Self::progress_mbm(n) + Self::progress_mbms(n) } } @@ -350,21 +376,15 @@ pub mod pallet { } impl Pallet { - /// Starts the process of multi block migrations. + /// Onboard all new Multi-Block-Migrations and start the process of executing them. /// - /// Should only be called once all migrations completed. - fn start_mbm() -> Weight { - use FailedUpgradeHandling::*; - + /// Should only be called once all previous migrations completed. + fn onboard_new_mbms() -> Weight { if let Some(cursor) = Cursor::::get() { log::error!(target: LOG, "Ongoing migrations interrupted - chain stuck"); - Self::deposit_event(Event::UpgradeFailed); let maybe_index = cursor.as_active().map(|c| c.index); - match T::UpgradeStatusNotify::failed(maybe_index) { - KeepStuck => Cursor::::set(Some(MigrationCursor::Stuck)), - ForceUnstuck => Cursor::::kill(), - } + Self::upgrade_failed(maybe_index); return T::WeightInfo::on_runtime_upgrade() } @@ -385,14 +405,14 @@ impl Pallet { T::WeightInfo::on_runtime_upgrade() } - /// Tries to make progress on the multi block migrations. - fn progress_mbm(n: T::BlockNumber) -> Weight { + /// Tries to make progress on the Multi-Block-Migrations process. + fn progress_mbms(n: T::BlockNumber) -> Weight { let mut meter = WeightMeter::from_limit(T::ServiceWeight::get()); meter.defensive_saturating_accrue(T::WeightInfo::on_init_base()); let mut cursor = match Cursor::::get() { None => { - log::debug!(target: LOG, "[Block {n:?}] Waiting for cursor to become `Some`."); + log::trace!(target: LOG, "[Block {n:?}] Waiting for cursor to become `Some`."); return meter.consumed }, Some(MigrationCursor::Active(cursor)) => cursor, @@ -453,8 +473,7 @@ impl Pallet { // We only progress one step per block. if migration.max_steps().map_or(false, |max| blocks > max.into()) { Self::deposit_event(Event::MigrationFailed { index: cursor.index, blocks }); - Self::deposit_event(Event::UpgradeFailed); - Cursor::::set(Some(MigrationCursor::Stuck)); + Self::upgrade_failed(Some(cursor.index)); None } else { // A migration cannot progress more than one step per block, we therefore break. @@ -469,19 +488,28 @@ impl Pallet { }, Err(SteppedMigrationError::InsufficientWeight { required }) => { if is_first || required.any_gt(meter.limit) { - Cursor::::set(Some(MigrationCursor::Stuck)); - Self::deposit_event(Event::UpgradeFailed); + // Note: No `MigrationFailed` event since the migration did not fail. + Self::upgrade_failed(Some(cursor.index)); } // else: Hope that it gets better in the next block. return None }, Err(SteppedMigrationError::InvalidCursor | SteppedMigrationError::Failed) => { Self::deposit_event(Event::MigrationFailed { index: cursor.index, blocks }); - Self::deposit_event(Event::UpgradeFailed); - Cursor::::set(Some(MigrationCursor::Stuck)); + Self::upgrade_failed(Some(cursor.index)); return None }, } } + + fn upgrade_failed(migration: Option) { + use FailedUpgradeHandling::*; + Self::deposit_event(Event::UpgradeFailed); + + match T::UpgradeStatusNotify::failed(migration) { + KeepStuck => Cursor::::set(Some(MigrationCursor::Stuck)), + ForceUnstuck => Cursor::::kill(), + } + } } impl ExtrinsicSuspenderQuery for Pallet { diff --git a/frame/migrations/src/mock.rs b/frame/migrations/src/mock.rs index 19a5b1d111ac5..beb83112cde25 100644 --- a/frame/migrations/src/mock.rs +++ b/frame/migrations/src/mock.rs @@ -158,7 +158,7 @@ impl crate::Config for Test { type Migrations = MigrationsStorage; type Cursor = MockedCursor; type Identifier = MockedIdentifier; - type UpgradeStatusNotify = LoggingUpgradeStatusNotify<()>; + type UpgradeStatusNotify = MockedUpgradeStatusNotify; type ServiceWeight = ServiceWeight; type WeightInfo = (); } @@ -224,26 +224,39 @@ pub fn assert_events(events: Vec) { System::reset_events(); } -/// Wraps an [`UpgradeStatusNotify`] and adds logging. -pub struct LoggingUpgradeStatusNotify(core::marker::PhantomData); -impl UpgradeStatusNotify for LoggingUpgradeStatusNotify { +frame_support::parameter_types! { + pub static UpgradesStarted: u32 = 0; + pub static UpgradesCompleted: u32 = 0; + pub static UpgradesFailed: Vec> = vec![]; + + pub static FailedUpgradeResponse: FailedUpgradeHandling = FailedUpgradeHandling::KeepStuck; +} + +pub struct MockedUpgradeStatusNotify; +impl UpgradeStatusNotify for MockedUpgradeStatusNotify { fn started() { log::info!("UpgradeStatusNotify started"); - T::started(); + UpgradesStarted::mutate(|v| *v += 1); } fn completed() { log::info!("UpgradeStatusNotify completed"); - T::completed(); + UpgradesCompleted::mutate(|v| *v += 1); } fn failed(migration: Option) -> FailedUpgradeHandling { - let res = T::failed(migration); + UpgradesFailed::mutate(|v| v.push(migration)); + let res = FailedUpgradeResponse::get(); log::error!("UpgradeStatusNotify failed at: {migration:?}, handling as {res:?}"); res } } +/// Returns the number of `(started, completed, failed)` upgrades and resets their numbers. +pub fn upgrades_started_completed_failed() -> (u32, u32, u32) { + (UpgradesStarted::take(), UpgradesCompleted::take(), UpgradesFailed::take().len() as u32) +} + pub fn mocked_id(kind: MockedMigrationKind, steps: u32) -> MockedIdentifier { format!("MockedMigration({:?}, {})", kind, steps) .as_bytes() diff --git a/frame/migrations/src/tests.rs b/frame/migrations/src/tests.rs index 83d250bc1262d..bd8b6cc19e5dd 100644 --- a/frame/migrations/src/tests.rs +++ b/frame/migrations/src/tests.rs @@ -19,7 +19,7 @@ use crate::{ mock::{MockedMigrationKind::*, Test as T, *}, - Cursor, Event, MigrationCursor, + Cursor, Event, FailedUpgradeHandling, MigrationCursor, }; use frame_support::traits::OnRuntimeUpgrade; @@ -91,6 +91,68 @@ fn basic_works() { }); } +#[test] +fn failing_migration_keep_stuck_the_chain() { + test_closure(|| { + FailedUpgradeResponse::set(FailedUpgradeHandling::KeepStuck); + // Add three migrations. Each taking one block longer. + MigrationsStorage::set(vec![(FailAfter, 2)]); + + System::set_block_number(1); + Migrations::on_runtime_upgrade(); + run_to_block(10); + + // Failed migrations are not recorded in `Historical`. + assert!(historic().is_empty()); + // Check that we got all events. + assert_events(vec![ + Event::UpgradeStarted { migrations: 1 }, + Event::MigrationAdvanced { index: 0, blocks: 1 }, + Event::MigrationAdvanced { index: 0, blocks: 2 }, + Event::MigrationFailed { index: 0, blocks: 3 }, + Event::UpgradeFailed, + ]); + + // Check that the handler was called correctly. + assert_eq!(UpgradesStarted::take(), 1); + assert_eq!(UpgradesCompleted::take(), 0); + assert_eq!(UpgradesFailed::take(), vec![Some(0)]); + + assert_eq!(Cursor::::get(), Some(MigrationCursor::Stuck), "Must stuck the chain"); + }); +} + +#[test] +fn failing_migration_force_unstuck_the_chain() { + test_closure(|| { + FailedUpgradeResponse::set(FailedUpgradeHandling::ForceUnstuck); + // Add three migrations. Each taking one block longer. + MigrationsStorage::set(vec![(FailAfter, 2)]); + + System::set_block_number(1); + Migrations::on_runtime_upgrade(); + run_to_block(10); + + // Failed migrations are not recorded in `Historical`. + assert!(historic().is_empty()); + // Check that we got all events. + assert_events(vec![ + Event::UpgradeStarted { migrations: 1 }, + Event::MigrationAdvanced { index: 0, blocks: 1 }, + Event::MigrationAdvanced { index: 0, blocks: 2 }, + Event::MigrationFailed { index: 0, blocks: 3 }, + Event::UpgradeFailed, + ]); + + // Check that the handler was called correctly. + assert_eq!(UpgradesStarted::take(), 1); + assert_eq!(UpgradesCompleted::take(), 0); + assert_eq!(UpgradesFailed::take(), vec![Some(0)]); + + assert!(Cursor::::get().is_none(), "Must unstuck the chain"); + }); +} + #[test] fn historic_skipping_works() { test_closure(|| { @@ -128,6 +190,7 @@ fn historic_skipping_works() { Event::MigrationSkippedHistoric { index: 4 }, Event::UpgradeCompleted, ]); + assert_eq!(upgrades_started_completed_failed(), (1, 1, 0)); // Now go for another upgrade; just to make sure that it wont execute again. System::reset_events(); @@ -154,6 +217,7 @@ fn historic_skipping_works() { Event::MigrationSkippedHistoric { index: 4 }, Event::UpgradeCompleted, ]); + assert_eq!(upgrades_started_completed_failed(), (1, 1, 0)); }); } @@ -178,11 +242,14 @@ fn upgrade_fails_when_migration_active() { Event::MigrationAdvanced { index: 0, blocks: 1 }, Event::MigrationAdvanced { index: 0, blocks: 2 }, ]); + assert_eq!(upgrades_started_completed_failed(), (1, 0, 0)); + // Upgrade again. Migrations::on_runtime_upgrade(); // -- Defensive path -- assert_eq!(Cursor::::get(), Some(MigrationCursor::Stuck)); assert_events(vec![Event::UpgradeFailed]); + assert_eq!(upgrades_started_completed_failed(), (0, 0, 1)); }); } @@ -205,6 +272,7 @@ fn migration_timeout_errors() { Event::MigrationFailed { index: 0, blocks: 4 }, Event::UpgradeFailed, ]); + assert_eq!(upgrades_started_completed_failed(), (1, 0, 1)); // Failed migrations are not black-listed. assert!(historic().is_empty()); @@ -215,5 +283,6 @@ fn migration_timeout_errors() { assert_events(vec![Event::UpgradeFailed]); assert_eq!(Cursor::::get(), Some(MigrationCursor::Stuck)); + assert_eq!(upgrades_started_completed_failed(), (0, 0, 1)); }); } From b8c85593e19266a50667692991f4c7bd9d5d883b Mon Sep 17 00:00:00 2001 From: Oliver Tale-Yazdi Date: Tue, 13 Jun 2023 21:59:31 +0200 Subject: [PATCH 38/63] Rename to OnMigrationUpdate Signed-off-by: Oliver Tale-Yazdi --- bin/node/runtime/src/lib.rs | 2 +- frame/migrations/src/lib.rs | 10 +++++----- frame/migrations/src/mock.rs | 17 ++++++++++------- frame/support/src/migrations.rs | 4 ++-- 4 files changed, 18 insertions(+), 15 deletions(-) diff --git a/bin/node/runtime/src/lib.rs b/bin/node/runtime/src/lib.rs index e89be58951a1d..185c8a739ad74 100644 --- a/bin/node/runtime/src/lib.rs +++ b/bin/node/runtime/src/lib.rs @@ -1864,7 +1864,7 @@ impl pallet_migrations::Config for Runtime { type Migrations = (); type Cursor = (); type Identifier = (); - type UpgradeStatusNotify = (); + type OnMigrationUpdate = (); type ServiceWeight = MbmServiceWeight; type WeightInfo = pallet_migrations::weights::SubstrateWeight; } diff --git a/frame/migrations/src/lib.rs b/frame/migrations/src/lib.rs index 11b2f901b894a..9e4bd1a54ce58 100644 --- a/frame/migrations/src/lib.rs +++ b/frame/migrations/src/lib.rs @@ -113,7 +113,7 @@ //! //! Failed upgrades cannot recovered from automatically and require governance intervention. Set up //! monitoring for `UpgradeFailed` events to be made aware of any failures. The hook -//! [`UpgradeStatusNotify::failed`] should be setup in a way that it allows governance to act, but +//! [`OnMigrationUpdate::failed`] should be setup in a way that it allows governance to act, but //! still prevent other transactions from interacting with the inconsistent storage state. Note that //! this is paramount, since the inconsistent state might contain a faulty balance amount or similar //! that could cause great harm if user transactions don't remain suspended. One way to implement @@ -247,7 +247,7 @@ pub mod pallet { /// Notifications for status updates of a runtime upgrade. /// /// Can be used to pause XCM etc. - type UpgradeStatusNotify: UpgradeStatusNotify; + type OnMigrationUpdate: OnMigrationUpdate; /// The weight to spend each block to execute migrations. type ServiceWeight: Get; @@ -399,7 +399,7 @@ impl Pallet { .into(), )); Self::deposit_event(Event::UpgradeStarted { migrations }); - T::UpgradeStatusNotify::started(); + T::OnMigrationUpdate::started(); } T::WeightInfo::on_runtime_upgrade() @@ -455,7 +455,7 @@ impl Pallet { let Some(migration) = migrations.get(cursor.index as usize) else { Self::deposit_event(Event::UpgradeCompleted); Cursor::::kill(); - T::UpgradeStatusNotify::completed(); + T::OnMigrationUpdate::completed(); return None; }; if Historic::::contains_key(&migration.id()) { @@ -505,7 +505,7 @@ impl Pallet { use FailedUpgradeHandling::*; Self::deposit_event(Event::UpgradeFailed); - match T::UpgradeStatusNotify::failed(migration) { + match T::OnMigrationUpdate::failed(migration) { KeepStuck => Cursor::::set(Some(MigrationCursor::Stuck)), ForceUnstuck => Cursor::::kill(), } diff --git a/frame/migrations/src/mock.rs b/frame/migrations/src/mock.rs index beb83112cde25..038d99bba3a11 100644 --- a/frame/migrations/src/mock.rs +++ b/frame/migrations/src/mock.rs @@ -158,7 +158,7 @@ impl crate::Config for Test { type Migrations = MigrationsStorage; type Cursor = MockedCursor; type Identifier = MockedIdentifier; - type UpgradeStatusNotify = MockedUpgradeStatusNotify; + type OnMigrationUpdate = MockedOnMigrationUpdate; type ServiceWeight = ServiceWeight; type WeightInfo = (); } @@ -225,29 +225,32 @@ pub fn assert_events(events: Vec) { } frame_support::parameter_types! { + /// The number of started upgrades. pub static UpgradesStarted: u32 = 0; + /// The number of completed upgrades. pub static UpgradesCompleted: u32 = 0; + /// The migrations that failed. pub static UpgradesFailed: Vec> = vec![]; - + /// Return value of `MockedOnMigrationUpdate::failed`. pub static FailedUpgradeResponse: FailedUpgradeHandling = FailedUpgradeHandling::KeepStuck; } -pub struct MockedUpgradeStatusNotify; -impl UpgradeStatusNotify for MockedUpgradeStatusNotify { +pub struct MockedOnMigrationUpdate; +impl OnMigrationUpdate for MockedOnMigrationUpdate { fn started() { - log::info!("UpgradeStatusNotify started"); + log::info!("OnMigrationUpdate started"); UpgradesStarted::mutate(|v| *v += 1); } fn completed() { - log::info!("UpgradeStatusNotify completed"); + log::info!("OnMigrationUpdate completed"); UpgradesCompleted::mutate(|v| *v += 1); } fn failed(migration: Option) -> FailedUpgradeHandling { UpgradesFailed::mutate(|v| v.push(migration)); let res = FailedUpgradeResponse::get(); - log::error!("UpgradeStatusNotify failed at: {migration:?}, handling as {res:?}"); + log::error!("OnMigrationUpdate failed at: {migration:?}, handling as {res:?}"); res } } diff --git a/frame/support/src/migrations.rs b/frame/support/src/migrations.rs index eae8efd670af5..425f170466a8b 100644 --- a/frame/support/src/migrations.rs +++ b/frame/support/src/migrations.rs @@ -295,7 +295,7 @@ impl ExtrinsicSuspenderQuery for () { } /// Notification handler for status updates regarding runtime upgrades. -pub trait UpgradeStatusNotify { +pub trait OnMigrationUpdate { /// Notifies of the start of a runtime upgrade. fn started() {} @@ -320,7 +320,7 @@ pub enum FailedUpgradeHandling { KeepStuck, } -impl UpgradeStatusNotify for () { +impl OnMigrationUpdate for () { fn failed(_migration: Option) -> FailedUpgradeHandling { FailedUpgradeHandling::KeepStuck } From 3b27c4f05aa4ae844fc74394369ccf527f4bb956 Mon Sep 17 00:00:00 2001 From: Oliver Tale-Yazdi Date: Wed, 14 Jun 2023 14:26:37 +0200 Subject: [PATCH 39/63] Fix bugs Signed-off-by: Oliver Tale-Yazdi --- frame/migrations/src/lib.rs | 12 +++++--- frame/migrations/src/mock.rs | 9 ++++-- frame/migrations/src/tests.rs | 56 +++++++++++++++++++++++++++++++++++ 3 files changed, 71 insertions(+), 6 deletions(-) diff --git a/frame/migrations/src/lib.rs b/frame/migrations/src/lib.rs index 9e4bd1a54ce58..5c969ab40ea4a 100644 --- a/frame/migrations/src/lib.rs +++ b/frame/migrations/src/lib.rs @@ -487,11 +487,14 @@ impl Pallet { return Some(ControlFlow::Continue(cursor)) }, Err(SteppedMigrationError::InsufficientWeight { required }) => { - if is_first || required.any_gt(meter.limit) { - // Note: No `MigrationFailed` event since the migration did not fail. + if !is_first || required.any_gt(meter.limit) { + Self::deposit_event(Event::MigrationFailed { index: cursor.index, blocks }); Self::upgrade_failed(Some(cursor.index)); - } // else: Hope that it gets better in the next block. - return None + None + } else { + // else: Hope that it gets better in the next block. + Some(ControlFlow::Continue(cursor)) + } }, Err(SteppedMigrationError::InvalidCursor | SteppedMigrationError::Failed) => { Self::deposit_event(Event::MigrationFailed { index: cursor.index, blocks }); @@ -501,6 +504,7 @@ impl Pallet { } } + /// Fail the current runtime upgrade. fn upgrade_failed(migration: Option) { use FailedUpgradeHandling::*; Self::deposit_event(Event::UpgradeFailed); diff --git a/frame/migrations/src/mock.rs b/frame/migrations/src/mock.rs index 038d99bba3a11..af1db5682982e 100644 --- a/frame/migrations/src/mock.rs +++ b/frame/migrations/src/mock.rs @@ -64,10 +64,11 @@ pub enum MockedMigrationKind { SucceedAfter, FailAfter, TimeoutAfter, + HightWeightAfter, } use MockedMigrationKind::*; // C style -/// A migration that succeeds or fails after a certain number of steps. +/// A migration that does something after a certain number of steps. pub struct MockedMigration(MockedMigrationKind, u32); impl SteppedMigration for MockedMigration { @@ -100,6 +101,10 @@ impl SteppedMigration for MockedMigration { log::debug!("MockedMigration: Succeeded after {} steps", count); Ok(None) }, + HightWeightAfter => { + log::debug!("MockedMigration: Not enough weight after {} steps", count); + Err(SteppedMigrationError::InsufficientWeight { required: Weight::from_all(100) }) + }, FailAfter => { log::debug!("MockedMigration: Failed after {} steps", count); Err(SteppedMigrationError::Failed) @@ -113,7 +118,7 @@ type MockedCursor = BoundedVec>; type MockedIdentifier = BoundedVec>; frame_support::parameter_types! { - pub const ServiceWeight: Weight = Weight::MAX; + pub const ServiceWeight: Weight = Weight::MAX.div(10); } thread_local! { diff --git a/frame/migrations/src/tests.rs b/frame/migrations/src/tests.rs index bd8b6cc19e5dd..a5567a495972b 100644 --- a/frame/migrations/src/tests.rs +++ b/frame/migrations/src/tests.rs @@ -153,6 +153,62 @@ fn failing_migration_force_unstuck_the_chain() { }); } +/// A migration that reports of not getting enough weight is retried once, if it is not the first +/// one to run in a block. +#[test] +fn high_weight_migration_retries_once() { + test_closure(|| { + // Add three migrations. Each taking one block longer. + MigrationsStorage::set(vec![(SucceedAfter, 0), (HightWeightAfter, 0)]); + + System::set_block_number(1); + Migrations::on_runtime_upgrade(); + run_to_block(10); + + assert_eq!(historic(), vec![mocked_id(SucceedAfter, 0)]); + // Check that we got all events. + assert_events::>(vec![ + Event::UpgradeStarted { migrations: 2 }, + Event::MigrationCompleted { index: 0, blocks: 1 }, + Event::MigrationFailed { index: 1, blocks: 0 }, + Event::UpgradeFailed, + ]); + + // Check that the handler was called correctly. + assert_eq!(upgrades_started_completed_failed(), (1, 0, 1)); + assert_eq!(Cursor::::get(), Some(MigrationCursor::Stuck)); + }); +} + +/// A migration that reports not getting enough weight errors if it is the first one to run in that +/// block. +#[test] +fn high_weight_migration_singular_fails() { + test_closure(|| { + // Add three migrations. Each taking one block longer. + MigrationsStorage::set(vec![(HightWeightAfter, 2)]); + + System::set_block_number(1); + Migrations::on_runtime_upgrade(); + run_to_block(10); + + // Failed migrations are not recorded in `Historical`. + assert!(historic().is_empty()); + // Check that we got all events. + assert_events(vec![ + Event::UpgradeStarted { migrations: 1 }, + Event::MigrationAdvanced { index: 0, blocks: 1 }, + Event::MigrationAdvanced { index: 0, blocks: 2 }, + Event::MigrationFailed { index: 0, blocks: 3 }, + Event::UpgradeFailed, + ]); + + // Check that the handler was called correctly. + assert_eq!(upgrades_started_completed_failed(), (1, 0, 1)); + assert_eq!(Cursor::::get(), Some(MigrationCursor::Stuck)); + }); +} + #[test] fn historic_skipping_works() { test_closure(|| { From 07f68897040d7a0f2210c5664d03d8eb514382c9 Mon Sep 17 00:00:00 2001 From: Oliver Tale-Yazdi Date: Wed, 14 Jun 2023 15:13:20 +0200 Subject: [PATCH 40/63] More fixes Signed-off-by: Oliver Tale-Yazdi --- frame/migrations/src/lib.rs | 6 ++-- frame/migrations/src/mock.rs | 6 ++-- frame/migrations/src/tests.rs | 62 +++++++++++++++++++++++++---------- 3 files changed, 50 insertions(+), 24 deletions(-) diff --git a/frame/migrations/src/lib.rs b/frame/migrations/src/lib.rs index 5c969ab40ea4a..d2b3578dd2ab4 100644 --- a/frame/migrations/src/lib.rs +++ b/frame/migrations/src/lib.rs @@ -487,13 +487,13 @@ impl Pallet { return Some(ControlFlow::Continue(cursor)) }, Err(SteppedMigrationError::InsufficientWeight { required }) => { - if !is_first || required.any_gt(meter.limit) { + if is_first || required.any_gt(meter.limit) { Self::deposit_event(Event::MigrationFailed { index: cursor.index, blocks }); Self::upgrade_failed(Some(cursor.index)); None } else { - // else: Hope that it gets better in the next block. - Some(ControlFlow::Continue(cursor)) + // Hope that it gets better in the next block. + Some(ControlFlow::Break(cursor)) } }, Err(SteppedMigrationError::InvalidCursor | SteppedMigrationError::Failed) => { diff --git a/frame/migrations/src/mock.rs b/frame/migrations/src/mock.rs index af1db5682982e..de227732c3b59 100644 --- a/frame/migrations/src/mock.rs +++ b/frame/migrations/src/mock.rs @@ -64,7 +64,7 @@ pub enum MockedMigrationKind { SucceedAfter, FailAfter, TimeoutAfter, - HightWeightAfter, + HightWeightAfter(Weight), } use MockedMigrationKind::*; // C style @@ -101,9 +101,9 @@ impl SteppedMigration for MockedMigration { log::debug!("MockedMigration: Succeeded after {} steps", count); Ok(None) }, - HightWeightAfter => { + HightWeightAfter(required) => { log::debug!("MockedMigration: Not enough weight after {} steps", count); - Err(SteppedMigrationError::InsufficientWeight { required: Weight::from_all(100) }) + Err(SteppedMigrationError::InsufficientWeight { required }) }, FailAfter => { log::debug!("MockedMigration: Failed after {} steps", count); diff --git a/frame/migrations/src/tests.rs b/frame/migrations/src/tests.rs index a5567a495972b..d97a55af3371f 100644 --- a/frame/migrations/src/tests.rs +++ b/frame/migrations/src/tests.rs @@ -22,6 +22,7 @@ use crate::{ Cursor, Event, FailedUpgradeHandling, MigrationCursor, }; use frame_support::traits::OnRuntimeUpgrade; +use frame_support::pallet_prelude::Weight; #[test] #[docify::export] @@ -61,7 +62,6 @@ fn simple_works() { #[test] fn basic_works() { test_closure(|| { - // Add three migrations. Each taking one block longer. MigrationsStorage::set(vec![(SucceedAfter, 0), (SucceedAfter, 1), (SucceedAfter, 2)]); System::set_block_number(1); @@ -95,7 +95,6 @@ fn basic_works() { fn failing_migration_keep_stuck_the_chain() { test_closure(|| { FailedUpgradeResponse::set(FailedUpgradeHandling::KeepStuck); - // Add three migrations. Each taking one block longer. MigrationsStorage::set(vec![(FailAfter, 2)]); System::set_block_number(1); @@ -126,7 +125,6 @@ fn failing_migration_keep_stuck_the_chain() { fn failing_migration_force_unstuck_the_chain() { test_closure(|| { FailedUpgradeResponse::set(FailedUpgradeHandling::ForceUnstuck); - // Add three migrations. Each taking one block longer. MigrationsStorage::set(vec![(FailAfter, 2)]); System::set_block_number(1); @@ -153,13 +151,40 @@ fn failing_migration_force_unstuck_the_chain() { }); } +/// A migration that reports not getting enough weight errors if it is the first one to run in that +/// block. +#[test] +fn high_weight_migration_singular_fails() { + test_closure(|| { + MigrationsStorage::set(vec![(HightWeightAfter(Weight::zero()), 2)]); + + System::set_block_number(1); + Migrations::on_runtime_upgrade(); + run_to_block(10); + + // Failed migrations are not recorded in `Historical`. + assert!(historic().is_empty()); + // Check that we got all events. + assert_events(vec![ + Event::UpgradeStarted { migrations: 1 }, + Event::MigrationAdvanced { index: 0, blocks: 1 }, + Event::MigrationAdvanced { index: 0, blocks: 2 }, + Event::MigrationFailed { index: 0, blocks: 3 }, + Event::UpgradeFailed, + ]); + + // Check that the handler was called correctly. + assert_eq!(upgrades_started_completed_failed(), (1, 0, 1)); + assert_eq!(Cursor::::get(), Some(MigrationCursor::Stuck)); + }); +} + /// A migration that reports of not getting enough weight is retried once, if it is not the first /// one to run in a block. #[test] fn high_weight_migration_retries_once() { test_closure(|| { - // Add three migrations. Each taking one block longer. - MigrationsStorage::set(vec![(SucceedAfter, 0), (HightWeightAfter, 0)]); + MigrationsStorage::set(vec![(SucceedAfter, 0), (HightWeightAfter(Weight::zero()), 0)]); System::set_block_number(1); Migrations::on_runtime_upgrade(); @@ -170,7 +195,8 @@ fn high_weight_migration_retries_once() { assert_events::>(vec![ Event::UpgradeStarted { migrations: 2 }, Event::MigrationCompleted { index: 0, blocks: 1 }, - Event::MigrationFailed { index: 1, blocks: 0 }, + // `blocks=1` means that it was retried once. + Event::MigrationFailed { index: 1, blocks: 1 }, Event::UpgradeFailed, ]); @@ -180,26 +206,26 @@ fn high_weight_migration_retries_once() { }); } -/// A migration that reports not getting enough weight errors if it is the first one to run in that -/// block. +/// If a migration uses more weight than the limit, then it will not retry but fail even when it is +/// not the first one in the block. +// Note: Same as `high_weight_migration_retries_once` but with different required weight for the +// migration. #[test] -fn high_weight_migration_singular_fails() { +fn high_weight_migration_permanently_overweight_fails() { test_closure(|| { - // Add three migrations. Each taking one block longer. - MigrationsStorage::set(vec![(HightWeightAfter, 2)]); + MigrationsStorage::set(vec![(SucceedAfter, 0), (HightWeightAfter(Weight::MAX), 0)]); System::set_block_number(1); Migrations::on_runtime_upgrade(); run_to_block(10); - // Failed migrations are not recorded in `Historical`. - assert!(historic().is_empty()); + assert_eq!(historic(), vec![mocked_id(SucceedAfter, 0)]); // Check that we got all events. - assert_events(vec![ - Event::UpgradeStarted { migrations: 1 }, - Event::MigrationAdvanced { index: 0, blocks: 1 }, - Event::MigrationAdvanced { index: 0, blocks: 2 }, - Event::MigrationFailed { index: 0, blocks: 3 }, + assert_events::>(vec![ + Event::UpgradeStarted { migrations: 2 }, + Event::MigrationCompleted { index: 0, blocks: 1 }, + // `blocks=0` means that it was not retried. + Event::MigrationFailed { index: 1, blocks: 0 }, Event::UpgradeFailed, ]); From f202a801909d12a7b5908848a12536867d416c6e Mon Sep 17 00:00:00 2001 From: Oliver Tale-Yazdi Date: Wed, 14 Jun 2023 15:25:40 +0200 Subject: [PATCH 41/63] Fixes Signed-off-by: Oliver Tale-Yazdi --- frame/executive/src/lib.rs | 11 +++-------- frame/migrations/src/lib.rs | 21 ++++++++++++--------- frame/migrations/src/mock.rs | 2 +- frame/support/src/migrations.rs | 16 ++++++++++------ 4 files changed, 26 insertions(+), 24 deletions(-) diff --git a/frame/executive/src/lib.rs b/frame/executive/src/lib.rs index 6c6c6e4b292ca..f5f47ec5537e4 100644 --- a/frame/executive/src/lib.rs +++ b/frame/executive/src/lib.rs @@ -191,7 +191,7 @@ impl< + OnFinalize + OffchainWorker, COnRuntimeUpgrade: OnRuntimeUpgrade, - ExtrinsicSuspender: frame_support::migrations::ExtrinsicSuspenderQuery, + ExtrinsicSuspender: frame_support::migrations::UpgradeStatusQuery, > ExecuteBlock for Executive< System, @@ -391,7 +391,7 @@ impl< + OnFinalize + OffchainWorker, COnRuntimeUpgrade: OnRuntimeUpgrade, - ExtrinsicSuspender: frame_support::migrations::ExtrinsicSuspenderQuery, + ExtrinsicSuspender: frame_support::migrations::UpgradeStatusQuery, > Executive< System, @@ -587,12 +587,7 @@ impl< // Decode parameters and dispatch let dispatch_info = xt.get_dispatch_info(); // Check whether we need to error because extrinsics are paused. - let r = if ExtrinsicSuspender::is_suspended(dispatch_info.class) { - // no refunds - Err(DispatchError::Suspended.with_weight(dispatch_info.weight)) - } else { - Applyable::apply::(xt, &dispatch_info, encoded_len)? - }; + let r = Applyable::apply::(xt, &dispatch_info, encoded_len)?; // Mandatory(inherents) are not allowed to fail. // diff --git a/frame/migrations/src/lib.rs b/frame/migrations/src/lib.rs index d2b3578dd2ab4..0eec28c063fd2 100644 --- a/frame/migrations/src/lib.rs +++ b/frame/migrations/src/lib.rs @@ -43,7 +43,7 @@ //! including its configuration trait, dispatchables, storage items, events and errors. //! //! Otherwise noteworthy API of this pallet include its implementation of the -//! [`ExtrinsicSuspenderQuery`] trait. This can be plugged into `frame-executive` to check for +//! [`UpgradeStatusQuery`] trait. This can be plugged into `frame-executive` to check for //! transaction suspension. //! //! ### Design Goals @@ -72,7 +72,7 @@ //! once it encounters an error (Goal 4). Once in the stuck state, the pallet will stay stuck until //! it is fixed through manual governance intervention. //! As soon as the cursor of the pallet becomes `Some(_)`; chain transaction processing is paused -//! by [`ExtrinsicSuspenderQuery::is_suspended`] returning `true`. This ensures that no other +//! by [`UpgradeStatusQuery::is_suspended`] returning `true`. This ensures that no other //! transactions are processed until all migrations are complete (Goal 2). //! `on_initialize` the pallet will load the current migration and check whether it was already //! executed in the past by checking for membership of its ID in the `Historic` set. Historic @@ -421,7 +421,7 @@ impl Pallet { return meter.consumed }, }; - debug_assert!(::is_suspended(DispatchClass::Normal)); + debug_assert!(::is_upgrading()); let migrations = T::Migrations::get(); for i in 0.. { @@ -516,11 +516,14 @@ impl Pallet { } } -impl ExtrinsicSuspenderQuery for Pallet { - fn is_suspended(class: DispatchClass) -> bool { - match class { - DispatchClass::Mandatory => false, - DispatchClass::Normal | DispatchClass::Operational => Cursor::::exists(), - } +impl UpgradeStatusQuery for Pallet { + fn is_upgrading() -> bool { + Cursor::::exists() + } +} + +impl MultiStepMigrator for Pallet { + fn step() -> Weight { + Self::progress_mbms(System::::block_number()) } } diff --git a/frame/migrations/src/mock.rs b/frame/migrations/src/mock.rs index de227732c3b59..7bc61a6335c80 100644 --- a/frame/migrations/src/mock.rs +++ b/frame/migrations/src/mock.rs @@ -181,7 +181,7 @@ pub fn test_closure(f: impl FnOnce() -> R) -> R { } pub struct LoggingSuspender(core::marker::PhantomData); -impl ExtrinsicSuspenderQuery for LoggingSuspender { +impl UpgradeStatusQuery for LoggingSuspender { fn is_suspended(class: DispatchClass) -> bool { let res = Inner::is_suspended(class); log::debug!("Is {class:?} suspended: {res}"); diff --git a/frame/support/src/migrations.rs b/frame/support/src/migrations.rs index 425f170466a8b..cf5cc28a9064e 100644 --- a/frame/support/src/migrations.rs +++ b/frame/support/src/migrations.rs @@ -282,14 +282,12 @@ pub enum SteppedMigrationError { Failed, } -/// Provides a way of checking whether transaction processing is suspended on the runtime level. -pub trait ExtrinsicSuspenderQuery { - /// Check whether transactions with this dispatch class are suspended. - fn is_suspended(class: DispatchClass) -> bool; +pub trait UpgradeStatusQuery { + fn is_upgrading() -> bool; } -impl ExtrinsicSuspenderQuery for () { - fn is_suspended(_class: DispatchClass) -> bool { +impl UpgradeStatusQuery for () { + fn is_upgrading() -> bool { false } } @@ -325,3 +323,9 @@ impl OnMigrationUpdate for () { FailedUpgradeHandling::KeepStuck } } + +/// Something that can do multi step migrations. +pub trait MultiStepMigrator { + /// Do the next step in the MBM process. + fn step() -> Weight; +} From 1249955c56de5bd51e6182779d34453489ab68a6 Mon Sep 17 00:00:00 2001 From: Oliver Tale-Yazdi Date: Wed, 14 Jun 2023 15:27:34 +0200 Subject: [PATCH 42/63] Undo changes to frame-executive Signed-off-by: Oliver Tale-Yazdi --- frame/executive/src/lib.rs | 6 ++---- frame/migrations/src/lib.rs | 1 - frame/migrations/src/mock.rs | 11 +++++------ frame/migrations/src/tests.rs | 3 +-- frame/support/src/migrations.rs | 1 - 5 files changed, 8 insertions(+), 14 deletions(-) diff --git a/frame/executive/src/lib.rs b/frame/executive/src/lib.rs index f5f47ec5537e4..df04d3508729d 100644 --- a/frame/executive/src/lib.rs +++ b/frame/executive/src/lib.rs @@ -118,9 +118,7 @@ use codec::{Codec, Encode}; use frame_support::{ - dispatch::{ - DispatchClass, DispatchInfo, GetDispatchInfo, PostDispatchInfo, WithPostDispatchInfo, - }, + dispatch::{DispatchClass, DispatchInfo, GetDispatchInfo, PostDispatchInfo}, pallet_prelude::InvalidTransaction, traits::{ EnsureInherentsAreFirst, ExecuteBlock, OffchainWorker, OnFinalize, OnIdle, OnInitialize, @@ -135,7 +133,7 @@ use sp_runtime::{ ValidateUnsigned, Zero, }, transaction_validity::{TransactionSource, TransactionValidity}, - ApplyExtrinsicResult, DispatchError, + ApplyExtrinsicResult, }; use sp_std::{marker::PhantomData, prelude::*}; diff --git a/frame/migrations/src/lib.rs b/frame/migrations/src/lib.rs index 0eec28c063fd2..8c6048bc177c6 100644 --- a/frame/migrations/src/lib.rs +++ b/frame/migrations/src/lib.rs @@ -145,7 +145,6 @@ pub use weights::WeightInfo; use codec::{Decode, Encode, FullCodec, MaxEncodedLen}; use core::ops::ControlFlow; use frame_support::{ - dispatch::DispatchClass, migrations::*, traits::Get, weights::{Weight, WeightMeter}, diff --git a/frame/migrations/src/mock.rs b/frame/migrations/src/mock.rs index 7bc61a6335c80..c21afa8d54405 100644 --- a/frame/migrations/src/mock.rs +++ b/frame/migrations/src/mock.rs @@ -23,7 +23,6 @@ use core::cell::RefCell; #[use_attr] use frame_support::derive_impl; use frame_support::{ - dispatch::DispatchClass, macro_magic::use_attr, migrations::*, traits::{OnFinalize, OnInitialize}, @@ -180,11 +179,11 @@ pub fn test_closure(f: impl FnOnce() -> R) -> R { ext.execute_with(f) } -pub struct LoggingSuspender(core::marker::PhantomData); -impl UpgradeStatusQuery for LoggingSuspender { - fn is_suspended(class: DispatchClass) -> bool { - let res = Inner::is_suspended(class); - log::debug!("Is {class:?} suspended: {res}"); +pub struct LoggingQuery(core::marker::PhantomData); +impl UpgradeStatusQuery for LoggingQuery { + fn is_upgrading() -> bool { + let res = Inner::is_upgrading(); + log::debug!("Is upgrading: {res}"); res } } diff --git a/frame/migrations/src/tests.rs b/frame/migrations/src/tests.rs index d97a55af3371f..f64deb5548d3b 100644 --- a/frame/migrations/src/tests.rs +++ b/frame/migrations/src/tests.rs @@ -21,8 +21,7 @@ use crate::{ mock::{MockedMigrationKind::*, Test as T, *}, Cursor, Event, FailedUpgradeHandling, MigrationCursor, }; -use frame_support::traits::OnRuntimeUpgrade; -use frame_support::pallet_prelude::Weight; +use frame_support::{pallet_prelude::Weight, traits::OnRuntimeUpgrade}; #[test] #[docify::export] diff --git a/frame/support/src/migrations.rs b/frame/support/src/migrations.rs index cf5cc28a9064e..1769081553ade 100644 --- a/frame/support/src/migrations.rs +++ b/frame/support/src/migrations.rs @@ -18,7 +18,6 @@ #[cfg(feature = "try-runtime")] use crate::storage::unhashed::contains_prefixed_key; use crate::{ - dispatch::DispatchClass, storage::transactional::with_transaction_opaque_err, traits::{GetStorageVersion, NoStorageVersionSet, PalletInfoAccess, StorageVersion}, weights::{RuntimeDbWeight, Weight, WeightMeter}, From 717b30e98c8314b707302c70627c54063fd22c0f Mon Sep 17 00:00:00 2001 From: Oliver Tale-Yazdi Date: Wed, 14 Jun 2023 20:22:44 +0200 Subject: [PATCH 43/63] Fix executive Signed-off-by: Oliver Tale-Yazdi --- frame/executive/src/lib.rs | 91 +++++++++++++------ frame/migrations/src/lib.rs | 10 +- frame/migrations/src/mock.rs | 9 -- .../src/construct_runtime/expand/inherent.rs | 18 ++-- frame/support/src/migrations.rs | 21 +++-- frame/support/src/traits/misc.rs | 6 +- frame/system/benchmarking/src/lib.rs | 1 + .../proc-macro/src/mock_impl_runtime_apis.rs | 4 +- primitives/api/src/lib.rs | 2 +- test-utils/runtime/src/lib.rs | 2 +- 10 files changed, 94 insertions(+), 70 deletions(-) diff --git a/frame/executive/src/lib.rs b/frame/executive/src/lib.rs index df04d3508729d..68c1aecccbb09 100644 --- a/frame/executive/src/lib.rs +++ b/frame/executive/src/lib.rs @@ -165,7 +165,7 @@ pub struct Executive< UnsignedValidator, AllPalletsWithSystem, OnRuntimeUpgrade = (), - ExtrinsicSuspender = (), + MultiStepMigrator = (), >( PhantomData<( System, @@ -174,7 +174,7 @@ pub struct Executive< UnsignedValidator, AllPalletsWithSystem, OnRuntimeUpgrade, - ExtrinsicSuspender, + MultiStepMigrator, )>, ); @@ -189,7 +189,7 @@ impl< + OnFinalize + OffchainWorker, COnRuntimeUpgrade: OnRuntimeUpgrade, - ExtrinsicSuspender: frame_support::migrations::UpgradeStatusQuery, + MultiStepMigrator: frame_support::migrations::MultiStepMigrator, > ExecuteBlock for Executive< System, @@ -198,7 +198,7 @@ impl< UnsignedValidator, AllPalletsWithSystem, COnRuntimeUpgrade, - ExtrinsicSuspender, + MultiStepMigrator, > where Block::Extrinsic: Checkable + Codec, CheckedOf: Applyable + GetDispatchInfo, @@ -215,7 +215,7 @@ impl< UnsignedValidator, AllPalletsWithSystem, COnRuntimeUpgrade, - ExtrinsicSuspender, + MultiStepMigrator, >::execute_block(block); } } @@ -389,7 +389,7 @@ impl< + OnFinalize + OffchainWorker, COnRuntimeUpgrade: OnRuntimeUpgrade, - ExtrinsicSuspender: frame_support::migrations::UpgradeStatusQuery, + MultiStepMigrator: frame_support::migrations::MultiStepMigrator, > Executive< System, @@ -398,7 +398,7 @@ impl< UnsignedValidator, AllPalletsWithSystem, COnRuntimeUpgrade, - ExtrinsicSuspender, + MultiStepMigrator, > where Block::Extrinsic: Checkable + Codec, CheckedOf: Applyable + GetDispatchInfo, @@ -474,7 +474,8 @@ impl< } } - fn initial_checks(block: &Block) { + /// Returns the index of the first extrinsic in the block. + fn initial_checks(block: &Block) -> u32 { sp_tracing::enter_span!(sp_tracing::Level::TRACE, "initial_checks"); let header = block.header(); @@ -487,8 +488,9 @@ impl< "Parent hash should be valid.", ); - if let Err(i) = System::ensure_inherents_are_first(block) { - panic!("Invalid inherent position for extrinsic at index {}", i); + match System::ensure_inherents_are_first(block) { + Ok(first_extrinsic_index) => first_extrinsic_index, + Err(i) => panic!("Invalid inherent position for extrinsic at index {}", i), } } @@ -497,53 +499,76 @@ impl< sp_io::init_tracing(); sp_tracing::within_span! { sp_tracing::info_span!("execute_block", ?block); - + // `on_runtime_upgrade` and `on_initialize`. Self::initialize_block(block.header()); - // any initial checks - Self::initial_checks(&block); + // Check the block and panic if invalid. + let first_extrinsic_index = Self::initial_checks(&block); + let (header, dispatchables) = block.deconstruct(); + + // Process inherents (if any). + let num_inherents = first_extrinsic_index as usize; + Self::execute_dispatchables(dispatchables.iter().take(num_inherents)); + + let is_upgrading = MultiStepMigrator::is_upgrading(); + if is_upgrading { + if num_inherents < dispatchables.len() { + panic!("Extrinsics are not allowed during Multi-Block-Migrations"); + } + + let used_weight = MultiStepMigrator::step(); + >::register_extra_weight_unchecked( + used_weight, + DispatchClass::Mandatory, + ); + } else { + // TODO `poll` hook - // execute extrinsics - let (header, extrinsics) = block.deconstruct(); - Self::execute_extrinsics_with_book_keeping(extrinsics, *header.number()); + // Process extrinsics. + Self::execute_dispatchables(dispatchables.iter().skip(num_inherents)); + } + // Dispatchable processing is done now. + >::note_finished_extrinsics(); + + if !is_upgrading { + Self::on_idle_hook(*header.number()); + } + + Self::on_finalize_hook(*header.number()); // any final checks Self::final_checks(&header); } } - /// Execute given extrinsics and take care of post-extrinsics book-keeping. - fn execute_extrinsics_with_book_keeping( - extrinsics: Vec, - block_number: NumberFor, - ) { - extrinsics.into_iter().for_each(|e| { - if let Err(e) = Self::apply_extrinsic(e) { + /// Execute given extrinsics. + fn execute_dispatchables<'a>(dispatchables: impl Iterator) { + dispatchables.into_iter().for_each(|e| { + if let Err(e) = Self::apply_extrinsic(e.clone()) { let err: &'static str = e.into(); panic!("{}", err) } }); - - // post-extrinsics book-keeping - >::note_finished_extrinsics(); - - Self::idle_and_finalize_hook(block_number); } /// Finalize the block - it is up the caller to ensure that all header fields are valid /// except state-root. + // Note: This is only used by the block builder - not Executive itself. pub fn finalize_block() -> System::Header { sp_io::init_tracing(); sp_tracing::enter_span!(sp_tracing::Level::TRACE, "finalize_block"); >::note_finished_extrinsics(); let block_number = >::block_number(); - Self::idle_and_finalize_hook(block_number); + if !MultiStepMigrator::is_upgrading() { + Self::on_idle_hook(block_number); + } + Self::on_finalize_hook(block_number); >::finalize() } - fn idle_and_finalize_hook(block_number: NumberFor) { + fn on_idle_hook(block_number: NumberFor) { let weight = >::block_weight(); let max_weight = >::get().max_block; let remaining_weight = max_weight.saturating_sub(weight.total()); @@ -558,7 +583,9 @@ impl< DispatchClass::Mandatory, ); } + } + fn on_finalize_hook(block_number: NumberFor) { >::on_finalize(block_number); } @@ -584,6 +611,10 @@ impl< // Decode parameters and dispatch let dispatch_info = xt.get_dispatch_info(); + if dispatch_info.class != DispatchClass::Mandatory && MultiStepMigrator::is_upgrading() { + // This function is also used by the block builder. So it should respect this check. + panic!("Only Mandatory extrinsics are allowed during Multi-Block-Migrations"); + } // Check whether we need to error because extrinsics are paused. let r = Applyable::apply::(xt, &dispatch_info, encoded_len)?; diff --git a/frame/migrations/src/lib.rs b/frame/migrations/src/lib.rs index 8c6048bc177c6..2a5aa962238c8 100644 --- a/frame/migrations/src/lib.rs +++ b/frame/migrations/src/lib.rs @@ -43,7 +43,7 @@ //! including its configuration trait, dispatchables, storage items, events and errors. //! //! Otherwise noteworthy API of this pallet include its implementation of the -//! [`UpgradeStatusQuery`] trait. This can be plugged into `frame-executive` to check for +//! [`MultiStepMigrator`] trait. This can be plugged into `frame-executive` to check for //! transaction suspension. //! //! ### Design Goals @@ -72,7 +72,7 @@ //! once it encounters an error (Goal 4). Once in the stuck state, the pallet will stay stuck until //! it is fixed through manual governance intervention. //! As soon as the cursor of the pallet becomes `Some(_)`; chain transaction processing is paused -//! by [`UpgradeStatusQuery::is_suspended`] returning `true`. This ensures that no other +//! by [`MultiStepMigrator::is_suspended`] returning `true`. This ensures that no other //! transactions are processed until all migrations are complete (Goal 2). //! `on_initialize` the pallet will load the current migration and check whether it was already //! executed in the past by checking for membership of its ID in the `Historic` set. Historic @@ -420,7 +420,7 @@ impl Pallet { return meter.consumed }, }; - debug_assert!(::is_upgrading()); + debug_assert!(::is_upgrading()); let migrations = T::Migrations::get(); for i in 0.. { @@ -515,13 +515,11 @@ impl Pallet { } } -impl UpgradeStatusQuery for Pallet { +impl MultiStepMigrator for Pallet { fn is_upgrading() -> bool { Cursor::::exists() } -} -impl MultiStepMigrator for Pallet { fn step() -> Weight { Self::progress_mbms(System::::block_number()) } diff --git a/frame/migrations/src/mock.rs b/frame/migrations/src/mock.rs index c21afa8d54405..119c3bfd064e9 100644 --- a/frame/migrations/src/mock.rs +++ b/frame/migrations/src/mock.rs @@ -179,15 +179,6 @@ pub fn test_closure(f: impl FnOnce() -> R) -> R { ext.execute_with(f) } -pub struct LoggingQuery(core::marker::PhantomData); -impl UpgradeStatusQuery for LoggingQuery { - fn is_upgrading() -> bool { - let res = Inner::is_upgrading(); - log::debug!("Is upgrading: {res}"); - res - } -} - pub fn run_to_block(n: u32) { while System::block_number() < n { if System::block_number() > 1 { diff --git a/frame/support/procedural/src/construct_runtime/expand/inherent.rs b/frame/support/procedural/src/construct_runtime/expand/inherent.rs index 52586bd691d4e..8d6beb9cfcd5a 100644 --- a/frame/support/procedural/src/construct_runtime/expand/inherent.rs +++ b/frame/support/procedural/src/construct_runtime/expand/inherent.rs @@ -178,12 +178,12 @@ pub fn expand_outer_inherent( } impl #scrate::traits::EnsureInherentsAreFirst<#block> for #runtime { - fn ensure_inherents_are_first(block: &#block) -> Result<(), u32> { + fn ensure_inherents_are_first(block: &#block) -> Result { use #scrate::inherent::ProvideInherent; use #scrate::traits::{IsSubType, ExtrinsicCall}; use #scrate::sp_runtime::traits::Block as _; - let mut first_signed_observed = false; + let mut first_signed_index = None; for (i, xt) in block.extrinsics().iter().enumerate() { let is_signed = #scrate::inherent::Extrinsic::is_signed(xt).unwrap_or(false); @@ -207,16 +207,16 @@ pub fn expand_outer_inherent( is_inherent }; - if !is_inherent { - first_signed_observed = true; - } - - if first_signed_observed && is_inherent { - return Err(i as u32) + if is_inherent { + if first_signed_index.is_some() { + return Err(i as u32) + } + } else if first_signed_index.is_none() { + first_signed_index = Some(i as u32); } } - Ok(()) + Ok(first_signed_index.unwrap_or(0)) } } } diff --git a/frame/support/src/migrations.rs b/frame/support/src/migrations.rs index 1769081553ade..7ef87b2500214 100644 --- a/frame/support/src/migrations.rs +++ b/frame/support/src/migrations.rs @@ -281,16 +281,6 @@ pub enum SteppedMigrationError { Failed, } -pub trait UpgradeStatusQuery { - fn is_upgrading() -> bool; -} - -impl UpgradeStatusQuery for () { - fn is_upgrading() -> bool { - false - } -} - /// Notification handler for status updates regarding runtime upgrades. pub trait OnMigrationUpdate { /// Notifies of the start of a runtime upgrade. @@ -325,6 +315,17 @@ impl OnMigrationUpdate for () { /// Something that can do multi step migrations. pub trait MultiStepMigrator { + fn is_upgrading() -> bool; /// Do the next step in the MBM process. fn step() -> Weight; } + +impl MultiStepMigrator for () { + fn is_upgrading() -> bool { + false + } + + fn step() -> Weight { + Weight::zero() + } +} diff --git a/frame/support/src/traits/misc.rs b/frame/support/src/traits/misc.rs index a6f8c46d63951..e3a51aa2a678d 100644 --- a/frame/support/src/traits/misc.rs +++ b/frame/support/src/traits/misc.rs @@ -880,8 +880,10 @@ pub trait GetBacking { pub trait EnsureInherentsAreFirst { /// Ensure the position of inherent is correct, i.e. they are before non-inherents. /// - /// On error return the index of the inherent with invalid position (counting from 0). - fn ensure_inherents_are_first(block: &Block) -> Result<(), u32>; + /// On error return the index of the inherent with invalid position (counting from 0). On + /// success it returns the index of the last inherent. `0` therefore means that there are no + /// inherents. + fn ensure_inherents_are_first(block: &Block) -> Result; } /// An extrinsic on which we can get access to call. diff --git a/frame/system/benchmarking/src/lib.rs b/frame/system/benchmarking/src/lib.rs index 1cd7b1bac6bd5..ccd3bacf62ec6 100644 --- a/frame/system/benchmarking/src/lib.rs +++ b/frame/system/benchmarking/src/lib.rs @@ -18,6 +18,7 @@ // Benchmarks for Utility Pallet #![cfg_attr(not(feature = "std"), no_std)] +#![cfg(feature = "runtime-benchmarks")] use codec::Encode; use frame_benchmarking::v1::{benchmarks, whitelisted_caller}; diff --git a/primitives/api/proc-macro/src/mock_impl_runtime_apis.rs b/primitives/api/proc-macro/src/mock_impl_runtime_apis.rs index be8c8ca0f8527..e86db2c7b8940 100644 --- a/primitives/api/proc-macro/src/mock_impl_runtime_apis.rs +++ b/primitives/api/proc-macro/src/mock_impl_runtime_apis.rs @@ -170,7 +170,7 @@ fn implement_common_api_traits(block_type: TypePath, self_ty: Type) -> Result::Hash, _: &<#block_type as #crate_::BlockT>::Header, - ) -> std::result::Result<(), #crate_::ApiError> { + ) -> std::result::Result<#crate_::bool, #crate_::ApiError> { unimplemented!("`Core::initialize_block` not implemented for runtime api mocks") } @@ -179,7 +179,7 @@ fn implement_common_api_traits(block_type: TypePath, self_ty: Type) -> Result::Hash, _: #crate_::ExecutionContext, _: &<#block_type as #crate_::BlockT>::Header, - ) -> std::result::Result<(), #crate_::ApiError> { + ) -> std::result::Result<#crate_::bool, #crate_::ApiError> { unimplemented!("`Core::initialize_block` not implemented for runtime api mocks") } } diff --git a/primitives/api/src/lib.rs b/primitives/api/src/lib.rs index 02770280f7b90..8b2773700e32f 100644 --- a/primitives/api/src/lib.rs +++ b/primitives/api/src/lib.rs @@ -293,7 +293,7 @@ pub use sp_api_proc_macro::decl_runtime_apis; /// # unimplemented!() /// # } /// # fn execute_block(_block: Block) {} -/// # fn initialize_block(_header: &::Header) {} +/// # fn initialize_block(_header: &::Header) -> bool { todo!() } /// # } /// /// impl self::Balance for Runtime { diff --git a/test-utils/runtime/src/lib.rs b/test-utils/runtime/src/lib.rs index 8d77439f16455..cb819d3238048 100644 --- a/test-utils/runtime/src/lib.rs +++ b/test-utils/runtime/src/lib.rs @@ -480,7 +480,7 @@ impl_runtime_apis! { fn initialize_block(header: &::Header) { log::trace!(target: LOG_TARGET, "initialize_block: {header:#?}"); - Executive::initialize_block(header); + Executive::initialize_block(header) } } From c7e8384c895ba25d51adf016b698e1b0bf8e2c56 Mon Sep 17 00:00:00 2001 From: Oliver Tale-Yazdi Date: Wed, 14 Jun 2023 20:34:21 +0200 Subject: [PATCH 44/63] Fix Signed-off-by: Oliver Tale-Yazdi --- frame/executive/src/lib.rs | 20 ++++++++++++++++--- .../proc-macro/src/mock_impl_runtime_apis.rs | 4 ++-- 2 files changed, 19 insertions(+), 5 deletions(-) diff --git a/frame/executive/src/lib.rs b/frame/executive/src/lib.rs index 68c1aecccbb09..2c3d4bba04895 100644 --- a/frame/executive/src/lib.rs +++ b/frame/executive/src/lib.rs @@ -233,8 +233,17 @@ impl< + OffchainWorker + frame_support::traits::TryState, COnRuntimeUpgrade: OnRuntimeUpgrade, - > Executive -where + MultiStepMigrator: frame_support::migrations::MultiStepMigrator, + > + Executive< + System, + Block, + Context, + UnsignedValidator, + AllPalletsWithSystem, + COnRuntimeUpgrade, + MultiStepMigrator, + > where Block::Extrinsic: Checkable + Codec, CheckedOf: Applyable + GetDispatchInfo, CallOf: @@ -304,7 +313,12 @@ where // post-extrinsics book-keeping >::note_finished_extrinsics(); - Self::idle_and_finalize_hook(*header.number()); + let is_upgrading = MultiStepMigrator::is_upgrading(); // FAIL-CI fix this properly + if !is_upgrading { + Self::on_idle_hook(*header.number()); + } + + Self::on_finalize_hook(*header.number()); // run the try-state checks of all pallets, ensuring they don't alter any state. let _guard = frame_support::StorageNoopGuard::default(); diff --git a/primitives/api/proc-macro/src/mock_impl_runtime_apis.rs b/primitives/api/proc-macro/src/mock_impl_runtime_apis.rs index e86db2c7b8940..be8c8ca0f8527 100644 --- a/primitives/api/proc-macro/src/mock_impl_runtime_apis.rs +++ b/primitives/api/proc-macro/src/mock_impl_runtime_apis.rs @@ -170,7 +170,7 @@ fn implement_common_api_traits(block_type: TypePath, self_ty: Type) -> Result::Hash, _: &<#block_type as #crate_::BlockT>::Header, - ) -> std::result::Result<#crate_::bool, #crate_::ApiError> { + ) -> std::result::Result<(), #crate_::ApiError> { unimplemented!("`Core::initialize_block` not implemented for runtime api mocks") } @@ -179,7 +179,7 @@ fn implement_common_api_traits(block_type: TypePath, self_ty: Type) -> Result::Hash, _: #crate_::ExecutionContext, _: &<#block_type as #crate_::BlockT>::Header, - ) -> std::result::Result<#crate_::bool, #crate_::ApiError> { + ) -> std::result::Result<(), #crate_::ApiError> { unimplemented!("`Core::initialize_block` not implemented for runtime api mocks") } } From a1589a3a87922cb44bbb8fb89d39ba1c73fe0129 Mon Sep 17 00:00:00 2001 From: Oliver Tale-Yazdi Date: Thu, 15 Jun 2023 11:40:45 +0200 Subject: [PATCH 45/63] Fix docs Signed-off-by: Oliver Tale-Yazdi --- frame/executive/src/lib.rs | 2 +- frame/migrations/src/lib.rs | 2 +- primitives/api/src/lib.rs | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/frame/executive/src/lib.rs b/frame/executive/src/lib.rs index 2c3d4bba04895..c7ba0385aa100 100644 --- a/frame/executive/src/lib.rs +++ b/frame/executive/src/lib.rs @@ -567,7 +567,7 @@ impl< /// Finalize the block - it is up the caller to ensure that all header fields are valid /// except state-root. - // Note: This is only used by the block builder - not Executive itself. + // Note: Only used by the block builder - not Executive itself. pub fn finalize_block() -> System::Header { sp_io::init_tracing(); sp_tracing::enter_span!(sp_tracing::Level::TRACE, "finalize_block"); diff --git a/frame/migrations/src/lib.rs b/frame/migrations/src/lib.rs index 2a5aa962238c8..d88e072a5eb49 100644 --- a/frame/migrations/src/lib.rs +++ b/frame/migrations/src/lib.rs @@ -72,7 +72,7 @@ //! once it encounters an error (Goal 4). Once in the stuck state, the pallet will stay stuck until //! it is fixed through manual governance intervention. //! As soon as the cursor of the pallet becomes `Some(_)`; chain transaction processing is paused -//! by [`MultiStepMigrator::is_suspended`] returning `true`. This ensures that no other +//! by [`MultiStepMigrator::is_upgrading`] returning `true`. This ensures that no other //! transactions are processed until all migrations are complete (Goal 2). //! `on_initialize` the pallet will load the current migration and check whether it was already //! executed in the past by checking for membership of its ID in the `Historic` set. Historic diff --git a/primitives/api/src/lib.rs b/primitives/api/src/lib.rs index 8b2773700e32f..8ab9e5dce4530 100644 --- a/primitives/api/src/lib.rs +++ b/primitives/api/src/lib.rs @@ -293,7 +293,7 @@ pub use sp_api_proc_macro::decl_runtime_apis; /// # unimplemented!() /// # } /// # fn execute_block(_block: Block) {} -/// # fn initialize_block(_header: &::Header) -> bool { todo!() } +/// # fn initialize_block(_header: &::Header) { } /// # } /// /// impl self::Balance for Runtime { From 4fd5d9368f269c5c9ae45db0269abb0fb9909d81 Mon Sep 17 00:00:00 2001 From: Oliver Tale-Yazdi Date: Thu, 15 Jun 2023 12:03:55 +0200 Subject: [PATCH 46/63] Add log target to sc-basic-authorship Now '-lwarn,basic-authorship=trace' can be used for debugging. Signed-off-by: Oliver Tale-Yazdi --- .../basic-authorship/src/basic_authorship.rs | 38 ++++++++++--------- 1 file changed, 21 insertions(+), 17 deletions(-) diff --git a/client/basic-authorship/src/basic_authorship.rs b/client/basic-authorship/src/basic_authorship.rs index 642900d2f35d8..1ac59716be9b7 100644 --- a/client/basic-authorship/src/basic_authorship.rs +++ b/client/basic-authorship/src/basic_authorship.rs @@ -57,6 +57,8 @@ pub const DEFAULT_BLOCK_SIZE_LIMIT: usize = 4 * 1024 * 1024 + 512; const DEFAULT_SOFT_DEADLINE_PERCENT: Percent = Percent::from_percent(50); +const LOG_TARGET: &'static str = "basic-authorship"; + /// [`Proposer`] factory. pub struct ProposerFactory { spawn_handle: Box, @@ -302,7 +304,7 @@ where .propose_with(inherent_data, inherent_digests, deadline, block_size_limit) .await; if tx.send(res).is_err() { - trace!("Could not send block production result to proposer!"); + trace!(target: LOG_TARGET, "Could not send block production result to proposer!"); } }), ); @@ -358,7 +360,7 @@ where for inherent in inherents { match block_builder.push(inherent) { Err(ApplyExtrinsicFailed(Validity(e))) if e.exhausted_resources() => { - warn!("⚠️ Dropping non-mandatory inherent from overweight block.") + warn!(target: LOG_TARGET, "⚠️ Dropping non-mandatory inherent from overweight block.") }, Err(ApplyExtrinsicFailed(Validity(e))) if e.was_mandatory() => { error!( @@ -367,12 +369,14 @@ where return Err(ApplyExtrinsicFailed(Validity(e))) }, Err(e) => { - warn!("❗️ Inherent extrinsic returned unexpected error: {}. Dropping.", e); + warn!(target: LOG_TARGET, "❗️ Inherent extrinsic returned unexpected error: {}. Dropping.", e); }, Ok(_) => {}, } } + // TODO `poll` hook + // proceed with transactions // We calculate soft deadline used only in case we start skipping transactions. let now = (self.now)(); @@ -391,7 +395,7 @@ where let mut pending_iterator = select! { res = t1 => res, _ = t2 => { - log::warn!( + log::warn!(target: LOG_TARGET, "Timeout fired waiting for transaction pool at block #{}. \ Proceeding with production.", self.parent_number, @@ -402,8 +406,8 @@ where let block_size_limit = block_size_limit.unwrap_or(self.default_block_size_limit); - debug!("Attempting to push transactions from the pool."); - debug!("Pool status: {:?}", self.transaction_pool.status()); + debug!(target: LOG_TARGET, "Attempting to push transactions from the pool."); + debug!(target: LOG_TARGET, "Pool status: {:?}", self.transaction_pool.status()); let mut transaction_pushed = false; let end_reason = loop { @@ -415,7 +419,7 @@ where let now = (self.now)(); if now > deadline { - debug!( + debug!(target: LOG_TARGET, "Consensus deadline reached when pushing block transactions, \ proceeding with proposing." ); @@ -431,59 +435,59 @@ where pending_iterator.report_invalid(&pending_tx); if skipped < MAX_SKIPPED_TRANSACTIONS { skipped += 1; - debug!( + debug!(target: LOG_TARGET, "Transaction would overflow the block size limit, \ but will try {} more transactions before quitting.", MAX_SKIPPED_TRANSACTIONS - skipped, ); continue } else if now < soft_deadline { - debug!( + debug!(target: LOG_TARGET, "Transaction would overflow the block size limit, \ but we still have time before the soft deadline, so \ we will try a bit more." ); continue } else { - debug!("Reached block size limit, proceeding with proposing."); + debug!(target: LOG_TARGET, "Reached block size limit, proceeding with proposing."); break EndProposingReason::HitBlockSizeLimit } } - trace!("[{:?}] Pushing to the block.", pending_tx_hash); + trace!(target: LOG_TARGET, "[{:?}] Pushing to the block.", pending_tx_hash); match sc_block_builder::BlockBuilder::push(&mut block_builder, pending_tx_data) { Ok(()) => { transaction_pushed = true; - debug!("[{:?}] Pushed to the block.", pending_tx_hash); + debug!(target: LOG_TARGET, "[{:?}] Pushed to the block.", pending_tx_hash); }, Err(ApplyExtrinsicFailed(Validity(e))) if e.exhausted_resources() => { pending_iterator.report_invalid(&pending_tx); if skipped < MAX_SKIPPED_TRANSACTIONS { skipped += 1; - debug!( + debug!(target: LOG_TARGET, "Block seems full, but will try {} more transactions before quitting.", MAX_SKIPPED_TRANSACTIONS - skipped, ); } else if (self.now)() < soft_deadline { - debug!( + debug!(target: LOG_TARGET, "Block seems full, but we still have time before the soft deadline, \ so we will try a bit more before quitting." ); } else { - debug!("Reached block weight limit, proceeding with proposing."); + debug!(target: LOG_TARGET, "Reached block weight limit, proceeding with proposing."); break EndProposingReason::HitBlockWeightLimit } }, Err(e) => { pending_iterator.report_invalid(&pending_tx); - debug!("[{:?}] Invalid transaction: {}", pending_tx_hash, e); + debug!(target: LOG_TARGET, "[{:?}] Invalid transaction: {}", pending_tx_hash, e); unqueue_invalid.push(pending_tx_hash); }, } }; if matches!(end_reason, EndProposingReason::HitBlockSizeLimit) && !transaction_pushed { - warn!( + warn!(target: LOG_TARGET, "Hit block size limit of `{}` without including any transaction!", block_size_limit, ); From 37cf9b838075fe5e164d18dd300dcf6f95ee316f Mon Sep 17 00:00:00 2001 From: Oliver Tale-Yazdi Date: Thu, 15 Jun 2023 12:21:44 +0200 Subject: [PATCH 47/63] Add mock_helpers Signed-off-by: Oliver Tale-Yazdi --- frame/migrations/Cargo.toml | 3 +- frame/migrations/src/lib.rs | 1 + frame/migrations/src/mock.rs | 76 ++------------------- frame/migrations/src/mock_helpers.rs | 98 ++++++++++++++++++++++++++++ frame/migrations/src/tests.rs | 3 +- 5 files changed, 107 insertions(+), 74 deletions(-) create mode 100644 frame/migrations/src/mock_helpers.rs diff --git a/frame/migrations/Cargo.toml b/frame/migrations/Cargo.toml index 76cb36c3c3e30..bc3e67ff1efbe 100644 --- a/frame/migrations/Cargo.toml +++ b/frame/migrations/Cargo.toml @@ -21,6 +21,7 @@ scale-info = { version = "2.0.0", default-features = false, features = ["derive" frame-benchmarking = { version = "4.0.0-dev", default-features = false, optional = true, path = "../benchmarking" } frame-support = { version = "4.0.0-dev", default-features = false, path = "../support" } frame-system = { version = "4.0.0-dev", default-features = false, path = "../system" } +sp-core = { version = "21.0.0", path = "../../primitives/core", default-features = false } sp-std = { version = "8.0.0", path = "../../primitives/std", default-features = false } sp-runtime = { version = "24.0.0", path = "../../primitives/runtime", default-features = false } @@ -28,7 +29,6 @@ sp-runtime = { version = "24.0.0", path = "../../primitives/runtime", default-fe frame-executive = { version = "4.0.0-dev", path = "../executive" } sp-api = { version = "4.0.0-dev", path = "../../primitives/api", features = [ "std" ] } sp-block-builder = { version = "4.0.0-dev", path = "../../primitives/block-builder", features = [ "std" ] } -sp-core = { version = "21.0.0", path = "../../primitives/core", features = [ "std" ] } sp-io = { version = "23.0.0", path = "../../primitives/io", features = [ "std" ] } sp-tracing = { version = "10.0.0", path = "../../primitives/tracing", features = [ "std" ] } sp-version = { version = "22.0.0", path = "../../primitives/version", features = [ "std" ] } @@ -45,6 +45,7 @@ std = [ "frame-support/std", "frame-system/std", "scale-info/std", + "sp-core/std", "sp-std/std", "sp-runtime/std" ] diff --git a/frame/migrations/src/lib.rs b/frame/migrations/src/lib.rs index d88e072a5eb49..048275a1ab969 100644 --- a/frame/migrations/src/lib.rs +++ b/frame/migrations/src/lib.rs @@ -136,6 +136,7 @@ mod benchmarking; mod mock; +pub mod mock_helpers; mod tests; pub mod weights; diff --git a/frame/migrations/src/mock.rs b/frame/migrations/src/mock.rs index 119c3bfd064e9..de51491d531ce 100644 --- a/frame/migrations/src/mock.rs +++ b/frame/migrations/src/mock.rs @@ -17,8 +17,8 @@ #![cfg(test)] -use crate::{Event, Historic}; -use codec::{Decode, Encode}; +use crate::{mock_helpers::*, Event, Historic}; + use core::cell::RefCell; #[use_attr] use frame_support::derive_impl; @@ -26,11 +26,10 @@ use frame_support::{ macro_magic::use_attr, migrations::*, traits::{OnFinalize, OnInitialize}, - weights::{Weight, WeightMeter}, + weights::Weight, }; use frame_system::EventRecord; -use sp_core::{ConstU32, Get, H256}; -use sp_runtime::BoundedVec; +use sp_core::{Get, H256}; type UncheckedExtrinsic = frame_system::mocking::MockUncheckedExtrinsic; type Block = frame_system::mocking::MockBlock; @@ -57,65 +56,6 @@ impl frame_system::Config for Test { type OnSetCode = (); } -#[derive(Debug, Clone, Copy)] -#[allow(dead_code)] -pub enum MockedMigrationKind { - SucceedAfter, - FailAfter, - TimeoutAfter, - HightWeightAfter(Weight), -} -use MockedMigrationKind::*; // C style - -/// A migration that does something after a certain number of steps. -pub struct MockedMigration(MockedMigrationKind, u32); - -impl SteppedMigration for MockedMigration { - type Cursor = MockedCursor; - type Identifier = MockedIdentifier; - - fn id(&self) -> Self::Identifier { - mocked_id(self.0, self.1) - } - - fn max_steps(&self) -> Option { - matches!(self.0, TimeoutAfter).then(|| self.1) - } - - fn step( - &self, - cursor: &Option, - _meter: &mut WeightMeter, - ) -> Result, SteppedMigrationError> { - let mut count: u32 = - cursor.as_ref().and_then(|c| Decode::decode(&mut &c[..]).ok()).unwrap_or(0); - log::debug!("MockedMigration: Step {}", count); - if count != self.1 || matches!(self.0, TimeoutAfter) { - count += 1; - return Ok(Some(count.encode().try_into().unwrap())) - } - - match self.0 { - SucceedAfter => { - log::debug!("MockedMigration: Succeeded after {} steps", count); - Ok(None) - }, - HightWeightAfter(required) => { - log::debug!("MockedMigration: Not enough weight after {} steps", count); - Err(SteppedMigrationError::InsufficientWeight { required }) - }, - FailAfter => { - log::debug!("MockedMigration: Failed after {} steps", count); - Err(SteppedMigrationError::Failed) - }, - TimeoutAfter => unreachable!(), - } - } -} - -type MockedCursor = BoundedVec>; -type MockedIdentifier = BoundedVec>; - frame_support::parameter_types! { pub const ServiceWeight: Weight = Weight::MAX.div(10); } @@ -255,14 +195,6 @@ pub fn upgrades_started_completed_failed() -> (u32, u32, u32) { (UpgradesStarted::take(), UpgradesCompleted::take(), UpgradesFailed::take().len() as u32) } -pub fn mocked_id(kind: MockedMigrationKind, steps: u32) -> MockedIdentifier { - format!("MockedMigration({:?}, {})", kind, steps) - .as_bytes() - .to_vec() - .try_into() - .unwrap() -} - pub fn historic() -> Vec { let mut historic = Historic::::iter_keys().collect::>(); historic.sort(); diff --git a/frame/migrations/src/mock_helpers.rs b/frame/migrations/src/mock_helpers.rs new file mode 100644 index 0000000000000..631aeef7f382d --- /dev/null +++ b/frame/migrations/src/mock_helpers.rs @@ -0,0 +1,98 @@ +// This file is part of Substrate. + +// Copyright (C) Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: Apache-2.0 + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +//! Helpers for std and no-std testing. Can be re-used by other crates. + +use super::*; + +use sp_core::ConstU32; +use sp_runtime::BoundedVec; + +/// An opaque cursor of a migration. +pub type MockedCursor = BoundedVec>; +/// An opaque identifier of a migration. +pub type MockedIdentifier = BoundedVec>; + +/// How a [`MockedMigration`] should behave. +#[derive(Debug, Clone, Copy)] +#[allow(dead_code)] +pub enum MockedMigrationKind { + /// Succeed after its number of steps elapsed. + SucceedAfter, + /// Fail after its number of steps elapsed. + FailAfter, + /// Never terminate. + TimeoutAfter, + /// Cause an [`InsufficientWeight`] error after its number of steps elapsed. + HightWeightAfter(Weight), +} +use MockedMigrationKind::*; // C style + +/// A migration that does something after a certain number of steps. +pub struct MockedMigration(pub MockedMigrationKind, pub u32); + +impl SteppedMigration for MockedMigration { + type Cursor = MockedCursor; + type Identifier = MockedIdentifier; + + fn id(&self) -> Self::Identifier { + mocked_id(self.0, self.1) + } + + fn max_steps(&self) -> Option { + matches!(self.0, TimeoutAfter).then(|| self.1) + } + + fn step( + &self, + cursor: &Option, + _meter: &mut WeightMeter, + ) -> Result, SteppedMigrationError> { + let mut count: u32 = + cursor.as_ref().and_then(|c| Decode::decode(&mut &c[..]).ok()).unwrap_or(0); + log::debug!("MockedMigration: Step {}", count); + if count != self.1 || matches!(self.0, TimeoutAfter) { + count += 1; + return Ok(Some(count.encode().try_into().unwrap())) + } + + match self.0 { + SucceedAfter => { + log::debug!("MockedMigration: Succeeded after {} steps", count); + Ok(None) + }, + HightWeightAfter(required) => { + log::debug!("MockedMigration: Not enough weight after {} steps", count); + Err(SteppedMigrationError::InsufficientWeight { required }) + }, + FailAfter => { + log::debug!("MockedMigration: Failed after {} steps", count); + Err(SteppedMigrationError::Failed) + }, + TimeoutAfter => unreachable!(), + } + } +} + +/// Calculate the identifier of a mocked migration. +pub fn mocked_id(kind: MockedMigrationKind, steps: u32) -> MockedIdentifier { + format!("MockedMigration({:?}, {})", kind, steps) + .as_bytes() + .to_vec() + .try_into() + .unwrap() +} diff --git a/frame/migrations/src/tests.rs b/frame/migrations/src/tests.rs index f64deb5548d3b..d2a6e5202d600 100644 --- a/frame/migrations/src/tests.rs +++ b/frame/migrations/src/tests.rs @@ -18,7 +18,8 @@ #![cfg(test)] use crate::{ - mock::{MockedMigrationKind::*, Test as T, *}, + mock::{Test as T, *}, + mock_helpers::{MockedMigrationKind::*, *}, Cursor, Event, FailedUpgradeHandling, MigrationCursor, }; use frame_support::{pallet_prelude::Weight, traits::OnRuntimeUpgrade}; From 69fddc3a1fe18a17f448d641cf91ad1cb108c03d Mon Sep 17 00:00:00 2001 From: Oliver Tale-Yazdi Date: Thu, 15 Jun 2023 13:56:59 +0200 Subject: [PATCH 48/63] Add progress_mbms to BlockBuilder Signed-off-by: Oliver Tale-Yazdi --- bin/node-template/runtime/src/lib.rs | 2 +- .../basic-authorship/src/basic_authorship.rs | 2 ++ client/block-builder/src/lib.rs | 21 ++++++++++-- frame/executive/src/lib.rs | 32 +++++++++++++++---- frame/migrations/src/lib.rs | 13 +++++--- frame/migrations/src/mock_helpers.rs | 9 ++---- frame/support/src/migrations.rs | 3 ++ frame/support/test/tests/runtime_metadata.rs | 2 +- frame/system/src/lib.rs | 3 ++ primitives/api/src/lib.rs | 6 ++-- primitives/api/test/tests/decl_and_impl.rs | 2 +- .../ui/impl_incorrect_method_signature.rs | 2 +- .../api/test/tests/ui/impl_missing_version.rs | 2 +- .../test/tests/ui/missing_versioned_method.rs | 2 +- .../missing_versioned_method_multiple_vers.rs | 2 +- .../ui/positive_cases/custom_where_bound.rs | 2 +- .../tests/ui/positive_cases/default_impls.rs | 2 +- ...ype_reference_in_impl_runtime_apis_call.rs | 2 +- primitives/block-builder/src/lib.rs | 9 +++++- primitives/runtime/src/lib.rs | 7 ++++ test-utils/runtime/src/lib.rs | 2 +- 21 files changed, 93 insertions(+), 34 deletions(-) diff --git a/bin/node-template/runtime/src/lib.rs b/bin/node-template/runtime/src/lib.rs index 22fb01b62d0f0..a16edff3e639c 100644 --- a/bin/node-template/runtime/src/lib.rs +++ b/bin/node-template/runtime/src/lib.rs @@ -354,7 +354,7 @@ impl_runtime_apis! { Executive::execute_block(block); } - fn initialize_block(header: &::Header) { + fn initialize_block(header: &::Header) -> sp_runtime::RuntimeMbmMode { Executive::initialize_block(header) } } diff --git a/client/basic-authorship/src/basic_authorship.rs b/client/basic-authorship/src/basic_authorship.rs index 1ac59716be9b7..957f2fef01329 100644 --- a/client/basic-authorship/src/basic_authorship.rs +++ b/client/basic-authorship/src/basic_authorship.rs @@ -375,6 +375,8 @@ where } } + // Try to progress any ongoing MBMs. No-OP if there are none. + block_builder.progress_mbms()?; // TODO `poll` hook // proceed with transactions diff --git a/client/block-builder/src/lib.rs b/client/block-builder/src/lib.rs index f055d4688822a..5bb2e548b34ff 100644 --- a/client/block-builder/src/lib.rs +++ b/client/block-builder/src/lib.rs @@ -36,7 +36,7 @@ use sp_core::ExecutionContext; use sp_runtime::{ legacy, traits::{Block as BlockT, Hash, HashFor, Header as HeaderT, NumberFor, One}, - Digest, + Digest, RuntimeMbmMode, }; use sc_client_api::backend; @@ -139,6 +139,7 @@ pub struct BlockBuilder<'a, Block: BlockT, A: ProvideRuntimeApi, B> { backend: &'a B, /// The estimated size of the block header. estimated_header_size: usize, + mbm_mode: RuntimeMbmMode, } impl<'a, Block, A, B> BlockBuilder<'a, Block, A, B> @@ -178,7 +179,7 @@ where api.record_proof(); } - api.initialize_block_with_context( + let mbm_mode = api.initialize_block_with_context( parent_hash, ExecutionContext::BlockConstruction, &header, @@ -195,9 +196,25 @@ where version, backend, estimated_header_size, + mbm_mode, }) } + pub fn progress_mbms(&self) -> Result<(), Error> { + match self.mbm_mode { + RuntimeMbmMode::NotMigrating => Ok(()), + RuntimeMbmMode::Migrating => { + // FAIL-CI why 'with_context'?! + self.api + .progress_mbms_with_context( + self.parent_hash, + ExecutionContext::BlockConstruction, + ) + .map_err(Into::into) + }, + } + } + /// Push onto the block's list of extrinsics. /// /// This will ensure the extrinsic can be validly executed (by executing it). diff --git a/frame/executive/src/lib.rs b/frame/executive/src/lib.rs index c7ba0385aa100..c56e5429a7c07 100644 --- a/frame/executive/src/lib.rs +++ b/frame/executive/src/lib.rs @@ -118,6 +118,7 @@ use codec::{Codec, Encode}; use frame_support::{ + defensive_assert, dispatch::{DispatchClass, DispatchInfo, GetDispatchInfo, PostDispatchInfo}, pallet_prelude::InvalidTransaction, traits::{ @@ -133,7 +134,7 @@ use sp_runtime::{ ValidateUnsigned, Zero, }, transaction_validity::{TransactionSource, TransactionValidity}, - ApplyExtrinsicResult, + ApplyExtrinsicResult, RuntimeMbmMode, }; use sp_std::{marker::PhantomData, prelude::*}; @@ -427,11 +428,17 @@ impl< } /// Start the execution of a particular block. - pub fn initialize_block(header: &System::Header) { + pub fn initialize_block(header: &System::Header) -> RuntimeMbmMode { sp_io::init_tracing(); sp_tracing::enter_span!(sp_tracing::Level::TRACE, "init_block"); let digests = Self::extract_pre_digest(header); Self::initialize_block_impl(header.number(), header.parent_hash(), &digests); + + if MultiStepMigrator::is_upgrading() { + RuntimeMbmMode::Migrating + } else { + RuntimeMbmMode::NotMigrating + } } fn extract_pre_digest(header: &System::Header) -> Digest { @@ -530,11 +537,7 @@ impl< panic!("Extrinsics are not allowed during Multi-Block-Migrations"); } - let used_weight = MultiStepMigrator::step(); - >::register_extra_weight_unchecked( - used_weight, - DispatchClass::Mandatory, - ); + Self::progress_mbms(); } else { // TODO `poll` hook @@ -555,6 +558,21 @@ impl< } } + /// Progress ongoing MBM migrations. + // Used by the block builder and Executive. + pub fn progress_mbms() { + defensive_assert!( + MultiStepMigrator::is_upgrading(), + "Tried to progress MBMs although \ + none were ongoing. Not a hard error but unexpected." + ); + let used_weight = MultiStepMigrator::step(); + >::register_extra_weight_unchecked( + used_weight, + DispatchClass::Mandatory, + ); + } + /// Execute given extrinsics. fn execute_dispatchables<'a>(dispatchables: impl Iterator) { dispatchables.into_iter().for_each(|e| { diff --git a/frame/migrations/src/lib.rs b/frame/migrations/src/lib.rs index 048275a1ab969..8beb672f47aba 100644 --- a/frame/migrations/src/lib.rs +++ b/frame/migrations/src/lib.rs @@ -331,9 +331,10 @@ pub mod pallet { Self::onboard_new_mbms() } - fn on_initialize(n: T::BlockNumber) -> Weight { - Self::progress_mbms(n) - } + // This is done by frame-executive. + //fn on_initialize(n: T::BlockNumber) -> Weight { + // Self::progress_mbms(n) + //} } #[pallet::call(weight = T::WeightInfo)] @@ -389,6 +390,7 @@ impl Pallet { } let migrations = T::Migrations::get().len() as u32; + log::info!(target: LOG, "Onboarding {migrations} MBM migrations"); if migrations > 0 { Cursor::::set(Some( ActiveCursor { @@ -415,7 +417,10 @@ impl Pallet { log::trace!(target: LOG, "[Block {n:?}] Waiting for cursor to become `Some`."); return meter.consumed }, - Some(MigrationCursor::Active(cursor)) => cursor, + Some(MigrationCursor::Active(cursor)) => { + log::info!(target: LOG, "Progressing MBM migration #{}", cursor.index); + cursor + }, Some(MigrationCursor::Stuck) => { log::error!(target: LOG, "Migration stuck. Governance intervention required."); return meter.consumed diff --git a/frame/migrations/src/mock_helpers.rs b/frame/migrations/src/mock_helpers.rs index 631aeef7f382d..a7c739eb288c6 100644 --- a/frame/migrations/src/mock_helpers.rs +++ b/frame/migrations/src/mock_helpers.rs @@ -19,6 +19,7 @@ use super::*; +use codec::Encode; use sp_core::ConstU32; use sp_runtime::BoundedVec; @@ -28,7 +29,7 @@ pub type MockedCursor = BoundedVec>; pub type MockedIdentifier = BoundedVec>; /// How a [`MockedMigration`] should behave. -#[derive(Debug, Clone, Copy)] +#[derive(Debug, Clone, Copy, Encode)] #[allow(dead_code)] pub enum MockedMigrationKind { /// Succeed after its number of steps elapsed. @@ -90,9 +91,5 @@ impl SteppedMigration for MockedMigration { /// Calculate the identifier of a mocked migration. pub fn mocked_id(kind: MockedMigrationKind, steps: u32) -> MockedIdentifier { - format!("MockedMigration({:?}, {})", kind, steps) - .as_bytes() - .to_vec() - .try_into() - .unwrap() + (b"MockedMigration", kind, steps).encode().try_into().unwrap() } diff --git a/frame/support/src/migrations.rs b/frame/support/src/migrations.rs index 7ef87b2500214..43ac25618f492 100644 --- a/frame/support/src/migrations.rs +++ b/frame/support/src/migrations.rs @@ -315,8 +315,11 @@ impl OnMigrationUpdate for () { /// Something that can do multi step migrations. pub trait MultiStepMigrator { + /// Hint for whether [`step`] should be called. fn is_upgrading() -> bool; /// Do the next step in the MBM process. + /// + /// Must gracefully handle the case that it is currently not upgrading. fn step() -> Weight; } diff --git a/frame/support/test/tests/runtime_metadata.rs b/frame/support/test/tests/runtime_metadata.rs index 70ca307d4428c..3b4a44996cc8d 100644 --- a/frame/support/test/tests/runtime_metadata.rs +++ b/frame/support/test/tests/runtime_metadata.rs @@ -107,7 +107,7 @@ sp_api::impl_runtime_apis! { fn execute_block(_: Block) { unimplemented!() } - fn initialize_block(_: &::Header) { + fn initialize_block(_: &::Header) -> sp_runtime::RuntimeMbmMode { unimplemented!() } } diff --git a/frame/system/src/lib.rs b/frame/system/src/lib.rs index cbda3d55cc68c..d095b6700c436 100644 --- a/frame/system/src/lib.rs +++ b/frame/system/src/lib.rs @@ -444,6 +444,9 @@ pub mod pallet { } /// Set the new runtime code without doing any checks of the given `code`. + /// + /// Note that runtime upgrades will not run if this is called with a not-increasing spec + /// version! #[pallet::call_index(3)] #[pallet::weight((T::SystemWeightInfo::set_code(), DispatchClass::Operational))] pub fn set_code_without_checks( diff --git a/primitives/api/src/lib.rs b/primitives/api/src/lib.rs index e04029bf097b6..a16b35630c325 100644 --- a/primitives/api/src/lib.rs +++ b/primitives/api/src/lib.rs @@ -93,7 +93,7 @@ pub use sp_runtime::{ generic::BlockId, traits::{Block as BlockT, Hash as HashT, HashFor, Header as HeaderT, NumberFor}, transaction_validity::TransactionValidity, - RuntimeString, TransactionOutcome, + RuntimeMbmMode, RuntimeString, TransactionOutcome, }; #[doc(hidden)] #[cfg(feature = "std")] @@ -715,7 +715,7 @@ pub fn deserialize_runtime_api_info(bytes: [u8; RUNTIME_API_INFO_SIZE]) -> ([u8; decl_runtime_apis! { /// The `Core` runtime api that every Substrate runtime needs to implement. #[core_trait] - #[api_version(4)] + #[api_version(5)] pub trait Core { /// Returns the version of the runtime. fn version() -> RuntimeVersion; @@ -723,7 +723,7 @@ decl_runtime_apis! { fn execute_block(block: Block); /// Initialize a block with the given header. #[renamed("initialise_block", 2)] - fn initialize_block(header: &::Header); + fn initialize_block(header: &::Header) -> RuntimeMbmMode; } /// The `Metadata` api trait that returns metadata for the runtime. diff --git a/primitives/api/test/tests/decl_and_impl.rs b/primitives/api/test/tests/decl_and_impl.rs index 274f80bd1b465..911955c851d03 100644 --- a/primitives/api/test/tests/decl_and_impl.rs +++ b/primitives/api/test/tests/decl_and_impl.rs @@ -89,7 +89,7 @@ impl_runtime_apis! { fn execute_block(_: Block) { unimplemented!() } - fn initialize_block(_: &::Header) { + fn initialize_block(_: &::Header) -> sp_runtime::RuntimeMbmMode { unimplemented!() } } diff --git a/primitives/api/test/tests/ui/impl_incorrect_method_signature.rs b/primitives/api/test/tests/ui/impl_incorrect_method_signature.rs index 32501be7855c6..c2db5f8e5f538 100644 --- a/primitives/api/test/tests/ui/impl_incorrect_method_signature.rs +++ b/primitives/api/test/tests/ui/impl_incorrect_method_signature.rs @@ -23,7 +23,7 @@ sp_api::impl_runtime_apis! { fn execute_block(_: Block) { unimplemented!() } - fn initialize_block(_: &::Header) { + fn initialize_block(_: &::Header) -> sp_runtime::RuntimeMbmMode { unimplemented!() } } diff --git a/primitives/api/test/tests/ui/impl_missing_version.rs b/primitives/api/test/tests/ui/impl_missing_version.rs index 8fd40a400922f..32b6f74f44c2b 100644 --- a/primitives/api/test/tests/ui/impl_missing_version.rs +++ b/primitives/api/test/tests/ui/impl_missing_version.rs @@ -28,7 +28,7 @@ sp_api::impl_runtime_apis! { fn execute_block(_: Block) { unimplemented!() } - fn initialize_block(_: &::Header) { + fn initialize_block(_: &::Header) -> sp_runtime::RuntimeMbmMode { unimplemented!() } } diff --git a/primitives/api/test/tests/ui/missing_versioned_method.rs b/primitives/api/test/tests/ui/missing_versioned_method.rs index 919cef055fe62..662b1ef5d729e 100644 --- a/primitives/api/test/tests/ui/missing_versioned_method.rs +++ b/primitives/api/test/tests/ui/missing_versioned_method.rs @@ -27,7 +27,7 @@ sp_api::impl_runtime_apis! { fn execute_block(_: Block) { unimplemented!() } - fn initialize_block(_: &::Header) { + fn initialize_block(_: &::Header) -> sp_runtime::RuntimeMbmMode { unimplemented!() } } diff --git a/primitives/api/test/tests/ui/missing_versioned_method_multiple_vers.rs b/primitives/api/test/tests/ui/missing_versioned_method_multiple_vers.rs index 036bba417f57d..71914825f5bdb 100644 --- a/primitives/api/test/tests/ui/missing_versioned_method_multiple_vers.rs +++ b/primitives/api/test/tests/ui/missing_versioned_method_multiple_vers.rs @@ -30,7 +30,7 @@ sp_api::impl_runtime_apis! { fn execute_block(_: Block) { unimplemented!() } - fn initialize_block(_: &::Header) { + fn initialize_block(_: &::Header) -> sp_runtime::RuntimeMbmMode { unimplemented!() } } diff --git a/primitives/api/test/tests/ui/positive_cases/custom_where_bound.rs b/primitives/api/test/tests/ui/positive_cases/custom_where_bound.rs index b572a3bc30d5d..efc8f55bd6be3 100644 --- a/primitives/api/test/tests/ui/positive_cases/custom_where_bound.rs +++ b/primitives/api/test/tests/ui/positive_cases/custom_where_bound.rs @@ -34,7 +34,7 @@ sp_api::impl_runtime_apis! { fn execute_block(_: Block) { unimplemented!() } - fn initialize_block(_: &::Header) { + fn initialize_block(_: &::Header) -> sp_runtime::RuntimeMbmMode { unimplemented!() } } diff --git a/primitives/api/test/tests/ui/positive_cases/default_impls.rs b/primitives/api/test/tests/ui/positive_cases/default_impls.rs index 58192feb9ecac..bd9280a790b47 100644 --- a/primitives/api/test/tests/ui/positive_cases/default_impls.rs +++ b/primitives/api/test/tests/ui/positive_cases/default_impls.rs @@ -29,7 +29,7 @@ sp_api::impl_runtime_apis! { fn execute_block(_: Block) { unimplemented!() } - fn initialize_block(_: &::Header) { + fn initialize_block(_: &::Header) -> sp_runtime::RuntimeMbmMode { unimplemented!() } } diff --git a/primitives/api/test/tests/ui/type_reference_in_impl_runtime_apis_call.rs b/primitives/api/test/tests/ui/type_reference_in_impl_runtime_apis_call.rs index 14a8fa4d4e0b1..560fffc193e59 100644 --- a/primitives/api/test/tests/ui/type_reference_in_impl_runtime_apis_call.rs +++ b/primitives/api/test/tests/ui/type_reference_in_impl_runtime_apis_call.rs @@ -25,7 +25,7 @@ sp_api::impl_runtime_apis! { fn execute_block(_: Block) { unimplemented!() } - fn initialize_block(_: &::Header) { + fn initialize_block(_: &::Header) -> sp_runtime::RuntimeMbmMode { unimplemented!() } } diff --git a/primitives/block-builder/src/lib.rs b/primitives/block-builder/src/lib.rs index 29e04857f463e..48674967c434c 100644 --- a/primitives/block-builder/src/lib.rs +++ b/primitives/block-builder/src/lib.rs @@ -24,7 +24,7 @@ use sp_runtime::{traits::Block as BlockT, ApplyExtrinsicResult}; sp_api::decl_runtime_apis! { /// The `BlockBuilder` api trait that provides the required functionality for building a block. - #[api_version(6)] + #[api_version(7)] pub trait BlockBuilder { /// Apply the given extrinsic. /// @@ -48,5 +48,12 @@ sp_api::decl_runtime_apis! { /// Check that the inherents are valid. The inherent data will vary from chain to chain. fn check_inherents(block: Block, data: InherentData) -> CheckInherentsResult; + + /// Progress Multi-Block-Migrations. + /// + /// This is safe to call even when `initialize_block` returned + /// `RuntimeMbmMode::NotMigrating`. In this case it will just waste some time so the block + /// builder is advised not to do that. + fn progress_mbms(); } } diff --git a/primitives/runtime/src/lib.rs b/primitives/runtime/src/lib.rs index 921d79536508d..e098c785d92bc 100644 --- a/primitives/runtime/src/lib.rs +++ b/primitives/runtime/src/lib.rs @@ -938,6 +938,13 @@ impl TransactionOutcome { } } +// FAIL-CI find a good spot for this +#[derive(Debug, Clone, Copy, PartialEq, Eq, Encode, Decode, scale_info::TypeInfo)] +pub enum RuntimeMbmMode { + NotMigrating, + Migrating, +} + #[cfg(test)] mod tests { use crate::traits::BlakeTwo256; diff --git a/test-utils/runtime/src/lib.rs b/test-utils/runtime/src/lib.rs index cb819d3238048..15df5ecf5d8c6 100644 --- a/test-utils/runtime/src/lib.rs +++ b/test-utils/runtime/src/lib.rs @@ -478,7 +478,7 @@ impl_runtime_apis! { Executive::execute_block(block); } - fn initialize_block(header: &::Header) { + fn initialize_block(header: &::Header) -> sp_runtime::RuntimeMbmMode { log::trace!(target: LOG_TARGET, "initialize_block: {header:#?}"); Executive::initialize_block(header) } From 7435788840b701a366956758c7bebf9239e58a38 Mon Sep 17 00:00:00 2001 From: Oliver Tale-Yazdi Date: Fri, 16 Jun 2023 13:22:58 +0200 Subject: [PATCH 49/63] Add log target Signed-off-by: Oliver Tale-Yazdi --- .../basic-authorship/src/basic_authorship.rs | 36 ++++++++++--------- 1 file changed, 19 insertions(+), 17 deletions(-) diff --git a/client/basic-authorship/src/basic_authorship.rs b/client/basic-authorship/src/basic_authorship.rs index 642900d2f35d8..5194adabda930 100644 --- a/client/basic-authorship/src/basic_authorship.rs +++ b/client/basic-authorship/src/basic_authorship.rs @@ -57,6 +57,8 @@ pub const DEFAULT_BLOCK_SIZE_LIMIT: usize = 4 * 1024 * 1024 + 512; const DEFAULT_SOFT_DEADLINE_PERCENT: Percent = Percent::from_percent(50); +const LOG_TARGET: &'static str = "basic-authorship"; + /// [`Proposer`] factory. pub struct ProposerFactory { spawn_handle: Box, @@ -302,7 +304,7 @@ where .propose_with(inherent_data, inherent_digests, deadline, block_size_limit) .await; if tx.send(res).is_err() { - trace!("Could not send block production result to proposer!"); + trace!(target: LOG_TARGET, "Could not send block production result to proposer!"); } }), ); @@ -358,7 +360,7 @@ where for inherent in inherents { match block_builder.push(inherent) { Err(ApplyExtrinsicFailed(Validity(e))) if e.exhausted_resources() => { - warn!("⚠️ Dropping non-mandatory inherent from overweight block.") + warn!(target: LOG_TARGET, "⚠️ Dropping non-mandatory inherent from overweight block.") }, Err(ApplyExtrinsicFailed(Validity(e))) if e.was_mandatory() => { error!( @@ -367,7 +369,7 @@ where return Err(ApplyExtrinsicFailed(Validity(e))) }, Err(e) => { - warn!("❗️ Inherent extrinsic returned unexpected error: {}. Dropping.", e); + warn!(target: LOG_TARGET, "❗️ Inherent extrinsic returned unexpected error: {}. Dropping.", e); }, Ok(_) => {}, } @@ -391,7 +393,7 @@ where let mut pending_iterator = select! { res = t1 => res, _ = t2 => { - log::warn!( + log::warn!(target: LOG_TARGET, "Timeout fired waiting for transaction pool at block #{}. \ Proceeding with production.", self.parent_number, @@ -402,8 +404,8 @@ where let block_size_limit = block_size_limit.unwrap_or(self.default_block_size_limit); - debug!("Attempting to push transactions from the pool."); - debug!("Pool status: {:?}", self.transaction_pool.status()); + debug!(target: LOG_TARGET, "Attempting to push transactions from the pool."); + debug!(target: LOG_TARGET, "Pool status: {:?}", self.transaction_pool.status()); let mut transaction_pushed = false; let end_reason = loop { @@ -415,7 +417,7 @@ where let now = (self.now)(); if now > deadline { - debug!( + debug!(target: LOG_TARGET, "Consensus deadline reached when pushing block transactions, \ proceeding with proposing." ); @@ -431,59 +433,59 @@ where pending_iterator.report_invalid(&pending_tx); if skipped < MAX_SKIPPED_TRANSACTIONS { skipped += 1; - debug!( + debug!(target: LOG_TARGET, "Transaction would overflow the block size limit, \ but will try {} more transactions before quitting.", MAX_SKIPPED_TRANSACTIONS - skipped, ); continue } else if now < soft_deadline { - debug!( + debug!(target: LOG_TARGET, "Transaction would overflow the block size limit, \ but we still have time before the soft deadline, so \ we will try a bit more." ); continue } else { - debug!("Reached block size limit, proceeding with proposing."); + debug!(target: LOG_TARGET, "Reached block size limit, proceeding with proposing."); break EndProposingReason::HitBlockSizeLimit } } - trace!("[{:?}] Pushing to the block.", pending_tx_hash); + trace!(target: LOG_TARGET, "[{:?}] Pushing to the block.", pending_tx_hash); match sc_block_builder::BlockBuilder::push(&mut block_builder, pending_tx_data) { Ok(()) => { transaction_pushed = true; - debug!("[{:?}] Pushed to the block.", pending_tx_hash); + debug!(target: LOG_TARGET, "[{:?}] Pushed to the block.", pending_tx_hash); }, Err(ApplyExtrinsicFailed(Validity(e))) if e.exhausted_resources() => { pending_iterator.report_invalid(&pending_tx); if skipped < MAX_SKIPPED_TRANSACTIONS { skipped += 1; - debug!( + debug!(target: LOG_TARGET, "Block seems full, but will try {} more transactions before quitting.", MAX_SKIPPED_TRANSACTIONS - skipped, ); } else if (self.now)() < soft_deadline { - debug!( + debug!(target: LOG_TARGET, "Block seems full, but we still have time before the soft deadline, \ so we will try a bit more before quitting." ); } else { - debug!("Reached block weight limit, proceeding with proposing."); + debug!(target: LOG_TARGET, "Reached block weight limit, proceeding with proposing."); break EndProposingReason::HitBlockWeightLimit } }, Err(e) => { pending_iterator.report_invalid(&pending_tx); - debug!("[{:?}] Invalid transaction: {}", pending_tx_hash, e); + debug!(target: LOG_TARGET, "[{:?}] Invalid transaction: {}", pending_tx_hash, e); unqueue_invalid.push(pending_tx_hash); }, } }; if matches!(end_reason, EndProposingReason::HitBlockSizeLimit) && !transaction_pushed { - warn!( + warn!(target: LOG_TARGET, "Hit block size limit of `{}` without including any transaction!", block_size_limit, ); From 6b1d6b173a3d9c4b87f15d54c7467c77a28a1160 Mon Sep 17 00:00:00 2001 From: Oliver Tale-Yazdi Date: Fri, 16 Jun 2023 13:27:20 +0200 Subject: [PATCH 50/63] Factor out apply_inherents Signed-off-by: Oliver Tale-Yazdi --- .../basic-authorship/src/basic_authorship.rs | 68 +++++++++++-------- 1 file changed, 39 insertions(+), 29 deletions(-) diff --git a/client/basic-authorship/src/basic_authorship.rs b/client/basic-authorship/src/basic_authorship.rs index 5194adabda930..0e9c8fab8ccc5 100644 --- a/client/basic-authorship/src/basic_authorship.rs +++ b/client/basic-authorship/src/basic_authorship.rs @@ -345,35 +345,7 @@ where let mut block_builder = self.client.new_block_at(self.parent_hash, inherent_digests, PR::ENABLED)?; - let create_inherents_start = time::Instant::now(); - let inherents = block_builder.create_inherents(inherent_data)?; - let create_inherents_end = time::Instant::now(); - - self.metrics.report(|metrics| { - metrics.create_inherents_time.observe( - create_inherents_end - .saturating_duration_since(create_inherents_start) - .as_secs_f64(), - ); - }); - - for inherent in inherents { - match block_builder.push(inherent) { - Err(ApplyExtrinsicFailed(Validity(e))) if e.exhausted_resources() => { - warn!(target: LOG_TARGET, "⚠️ Dropping non-mandatory inherent from overweight block.") - }, - Err(ApplyExtrinsicFailed(Validity(e))) if e.was_mandatory() => { - error!( - "❌️ Mandatory inherent extrinsic returned error. Block cannot be produced." - ); - return Err(ApplyExtrinsicFailed(Validity(e))) - }, - Err(e) => { - warn!(target: LOG_TARGET, "❗️ Inherent extrinsic returned unexpected error: {}. Dropping.", e); - }, - Ok(_) => {}, - } - } + self.apply_inherents(&mut block_builder, inherent_data)?; // proceed with transactions // We calculate soft deadline used only in case we start skipping transactions. @@ -535,6 +507,44 @@ where Ok(Proposal { block, proof, storage_changes }) } + + /// Apply all inherents to the block. + fn apply_inherents( + &self, + block_builder: &mut sc_block_builder::BlockBuilder<'_, Block, C, B>, + inherent_data: InherentData, + ) -> Result<(), sp_blockchain::Error> { + let create_inherents_start = time::Instant::now(); + let inherents = block_builder.create_inherents(inherent_data)?; + let create_inherents_end = time::Instant::now(); + + self.metrics.report(|metrics| { + metrics.create_inherents_time.observe( + create_inherents_end + .saturating_duration_since(create_inherents_start) + .as_secs_f64(), + ); + }); + + for inherent in inherents { + match block_builder.push(inherent) { + Err(ApplyExtrinsicFailed(Validity(e))) if e.exhausted_resources() => { + warn!(target: LOG_TARGET, "⚠️ Dropping non-mandatory inherent from overweight block.") + }, + Err(ApplyExtrinsicFailed(Validity(e))) if e.was_mandatory() => { + error!( + "❌️ Mandatory inherent extrinsic returned error. Block cannot be produced." + ); + return Err(ApplyExtrinsicFailed(Validity(e))) + }, + Err(e) => { + warn!(target: LOG_TARGET, "❗️ Inherent extrinsic returned unexpected error: {}. Dropping.", e); + }, + Ok(_) => {}, + } + } + Ok(()) + } } #[cfg(test)] From 0b7dddcb3c5dd866c14912b4b498e637d6a7e7d6 Mon Sep 17 00:00:00 2001 From: Oliver Tale-Yazdi Date: Fri, 16 Jun 2023 13:36:59 +0200 Subject: [PATCH 51/63] Factor out apply_extrinsics Signed-off-by: Oliver Tale-Yazdi --- .../basic-authorship/src/basic_authorship.rs | 175 ++++++++++-------- 1 file changed, 93 insertions(+), 82 deletions(-) diff --git a/client/basic-authorship/src/basic_authorship.rs b/client/basic-authorship/src/basic_authorship.rs index 0e9c8fab8ccc5..eda943d525219 100644 --- a/client/basic-authorship/src/basic_authorship.rs +++ b/client/basic-authorship/src/basic_authorship.rs @@ -347,6 +347,97 @@ where self.apply_inherents(&mut block_builder, inherent_data)?; + let block_timer = time::Instant::now(); + let end_reason = + self.apply_extrinsics(&mut block_builder, deadline, block_size_limit).await?; + + let (block, storage_changes, proof) = block_builder.build()?.into_inner(); + + self.metrics.report(|metrics| { + metrics.number_of_transactions.set(block.extrinsics().len() as u64); + metrics.block_constructed.observe(block_timer.elapsed().as_secs_f64()); + + metrics.report_end_proposing_reason(end_reason); + }); + + info!( + "🎁 Prepared block for proposing at {} ({} ms) [hash: {:?}; parent_hash: {}; extrinsics ({}): [{}]]", + block.header().number(), + block_timer.elapsed().as_millis(), + ::Hash::from(block.header().hash()), + block.header().parent_hash(), + block.extrinsics().len(), + block.extrinsics() + .iter() + .map(|xt| BlakeTwo256::hash_of(xt).to_string()) + .collect::>() + .join(", ") + ); + telemetry!( + self.telemetry; + CONSENSUS_INFO; + "prepared_block_for_proposing"; + "number" => ?block.header().number(), + "hash" => ?::Hash::from(block.header().hash()), + ); + + let proof = + PR::into_proof(proof).map_err(|e| sp_blockchain::Error::Application(Box::new(e)))?; + + let propose_with_end = time::Instant::now(); + self.metrics.report(|metrics| { + metrics.create_block_proposal_time.observe( + propose_with_end.saturating_duration_since(propose_with_start).as_secs_f64(), + ); + }); + + Ok(Proposal { block, proof, storage_changes }) + } + + /// Apply all inherents to the block. + fn apply_inherents( + &self, + block_builder: &mut sc_block_builder::BlockBuilder<'_, Block, C, B>, + inherent_data: InherentData, + ) -> Result<(), sp_blockchain::Error> { + let create_inherents_start = time::Instant::now(); + let inherents = block_builder.create_inherents(inherent_data)?; + let create_inherents_end = time::Instant::now(); + + self.metrics.report(|metrics| { + metrics.create_inherents_time.observe( + create_inherents_end + .saturating_duration_since(create_inherents_start) + .as_secs_f64(), + ); + }); + + for inherent in inherents { + match block_builder.push(inherent) { + Err(ApplyExtrinsicFailed(Validity(e))) if e.exhausted_resources() => { + warn!(target: LOG_TARGET, "⚠️ Dropping non-mandatory inherent from overweight block.") + }, + Err(ApplyExtrinsicFailed(Validity(e))) if e.was_mandatory() => { + error!( + "❌️ Mandatory inherent extrinsic returned error. Block cannot be produced." + ); + return Err(ApplyExtrinsicFailed(Validity(e))) + }, + Err(e) => { + warn!(target: LOG_TARGET, "❗️ Inherent extrinsic returned unexpected error: {}. Dropping.", e); + }, + Ok(_) => {}, + } + } + Ok(()) + } + + async fn apply_extrinsics( + &self, + block_builder: &mut sc_block_builder::BlockBuilder<'_, Block, C, B>, + deadline: time::Instant, + block_size_limit: Option, + ) -> Result { // proceed with transactions // We calculate soft deadline used only in case we start skipping transactions. let now = (self.now)(); @@ -354,7 +445,6 @@ where let left_micros: u64 = left.as_micros().saturated_into(); let soft_deadline = now + time::Duration::from_micros(self.soft_deadline_percent.mul_floor(left_micros)); - let block_timer = time::Instant::now(); let mut skipped = 0; let mut unqueue_invalid = Vec::new(); @@ -425,7 +515,7 @@ where } trace!(target: LOG_TARGET, "[{:?}] Pushing to the block.", pending_tx_hash); - match sc_block_builder::BlockBuilder::push(&mut block_builder, pending_tx_data) { + match sc_block_builder::BlockBuilder::push(block_builder, pending_tx_data) { Ok(()) => { transaction_pushed = true; debug!(target: LOG_TARGET, "[{:?}] Pushed to the block.", pending_tx_hash); @@ -464,86 +554,7 @@ where } self.transaction_pool.remove_invalid(&unqueue_invalid); - - let (block, storage_changes, proof) = block_builder.build()?.into_inner(); - - self.metrics.report(|metrics| { - metrics.number_of_transactions.set(block.extrinsics().len() as u64); - metrics.block_constructed.observe(block_timer.elapsed().as_secs_f64()); - - metrics.report_end_proposing_reason(end_reason); - }); - - info!( - "🎁 Prepared block for proposing at {} ({} ms) [hash: {:?}; parent_hash: {}; extrinsics ({}): [{}]]", - block.header().number(), - block_timer.elapsed().as_millis(), - ::Hash::from(block.header().hash()), - block.header().parent_hash(), - block.extrinsics().len(), - block.extrinsics() - .iter() - .map(|xt| BlakeTwo256::hash_of(xt).to_string()) - .collect::>() - .join(", ") - ); - telemetry!( - self.telemetry; - CONSENSUS_INFO; - "prepared_block_for_proposing"; - "number" => ?block.header().number(), - "hash" => ?::Hash::from(block.header().hash()), - ); - - let proof = - PR::into_proof(proof).map_err(|e| sp_blockchain::Error::Application(Box::new(e)))?; - - let propose_with_end = time::Instant::now(); - self.metrics.report(|metrics| { - metrics.create_block_proposal_time.observe( - propose_with_end.saturating_duration_since(propose_with_start).as_secs_f64(), - ); - }); - - Ok(Proposal { block, proof, storage_changes }) - } - - /// Apply all inherents to the block. - fn apply_inherents( - &self, - block_builder: &mut sc_block_builder::BlockBuilder<'_, Block, C, B>, - inherent_data: InherentData, - ) -> Result<(), sp_blockchain::Error> { - let create_inherents_start = time::Instant::now(); - let inherents = block_builder.create_inherents(inherent_data)?; - let create_inherents_end = time::Instant::now(); - - self.metrics.report(|metrics| { - metrics.create_inherents_time.observe( - create_inherents_end - .saturating_duration_since(create_inherents_start) - .as_secs_f64(), - ); - }); - - for inherent in inherents { - match block_builder.push(inherent) { - Err(ApplyExtrinsicFailed(Validity(e))) if e.exhausted_resources() => { - warn!(target: LOG_TARGET, "⚠️ Dropping non-mandatory inherent from overweight block.") - }, - Err(ApplyExtrinsicFailed(Validity(e))) if e.was_mandatory() => { - error!( - "❌️ Mandatory inherent extrinsic returned error. Block cannot be produced." - ); - return Err(ApplyExtrinsicFailed(Validity(e))) - }, - Err(e) => { - warn!(target: LOG_TARGET, "❗️ Inherent extrinsic returned unexpected error: {}. Dropping.", e); - }, - Ok(_) => {}, - } - } - Ok(()) + Ok(end_reason) } } From 648e005e3412487351e8f06f8d89d8d9f115a722 Mon Sep 17 00:00:00 2001 From: Oliver Tale-Yazdi Date: Fri, 16 Jun 2023 13:40:19 +0200 Subject: [PATCH 52/63] Factor out print_summary Signed-off-by: Oliver Tale-Yazdi --- .../basic-authorship/src/basic_authorship.rs | 64 +++++++++++-------- 1 file changed, 37 insertions(+), 27 deletions(-) diff --git a/client/basic-authorship/src/basic_authorship.rs b/client/basic-authorship/src/basic_authorship.rs index eda943d525219..442b5b54fb3c7 100644 --- a/client/basic-authorship/src/basic_authorship.rs +++ b/client/basic-authorship/src/basic_authorship.rs @@ -353,33 +353,7 @@ where let (block, storage_changes, proof) = block_builder.build()?.into_inner(); - self.metrics.report(|metrics| { - metrics.number_of_transactions.set(block.extrinsics().len() as u64); - metrics.block_constructed.observe(block_timer.elapsed().as_secs_f64()); - - metrics.report_end_proposing_reason(end_reason); - }); - - info!( - "🎁 Prepared block for proposing at {} ({} ms) [hash: {:?}; parent_hash: {}; extrinsics ({}): [{}]]", - block.header().number(), - block_timer.elapsed().as_millis(), - ::Hash::from(block.header().hash()), - block.header().parent_hash(), - block.extrinsics().len(), - block.extrinsics() - .iter() - .map(|xt| BlakeTwo256::hash_of(xt).to_string()) - .collect::>() - .join(", ") - ); - telemetry!( - self.telemetry; - CONSENSUS_INFO; - "prepared_block_for_proposing"; - "number" => ?block.header().number(), - "hash" => ?::Hash::from(block.header().hash()), - ); + self.print_summary(&block, end_reason, &block_timer); let proof = PR::into_proof(proof).map_err(|e| sp_blockchain::Error::Application(Box::new(e)))?; @@ -556,6 +530,42 @@ where self.transaction_pool.remove_invalid(&unqueue_invalid); Ok(end_reason) } + + /// Prints a summary and reports telemetry. + fn print_summary( + &self, + block: &Block, + end_reason: EndProposingReason, + block_timer: &time::Instant, + ) { + self.metrics.report(|metrics| { + metrics.number_of_transactions.set(block.extrinsics().len() as u64); + metrics.block_constructed.observe(block_timer.elapsed().as_secs_f64()); + + metrics.report_end_proposing_reason(end_reason); + }); + + info!( + "🎁 Prepared block for proposing at {} ({} ms) [hash: {:?}; parent_hash: {}; extrinsics ({}): [{}]]", + block.header().number(), + block_timer.elapsed().as_millis(), + ::Hash::from(block.header().hash()), + block.header().parent_hash(), + block.extrinsics().len(), + block.extrinsics() + .iter() + .map(|xt| BlakeTwo256::hash_of(xt).to_string()) + .collect::>() + .join(", ") + ); + telemetry!( + self.telemetry; + CONSENSUS_INFO; + "prepared_block_for_proposing"; + "number" => ?block.header().number(), + "hash" => ?::Hash::from(block.header().hash()), + ); + } } #[cfg(test)] From 24c8bf69b37a02a4d11a95c4354104e2bc546020 Mon Sep 17 00:00:00 2001 From: Oliver Tale-Yazdi Date: Fri, 16 Jun 2023 13:45:57 +0200 Subject: [PATCH 53/63] Pimp print_summary Signed-off-by: Oliver Tale-Yazdi --- .../basic-authorship/src/basic_authorship.rs | 33 ++++++++++++------- 1 file changed, 21 insertions(+), 12 deletions(-) diff --git a/client/basic-authorship/src/basic_authorship.rs b/client/basic-authorship/src/basic_authorship.rs index 442b5b54fb3c7..cc559483e76dd 100644 --- a/client/basic-authorship/src/basic_authorship.rs +++ b/client/basic-authorship/src/basic_authorship.rs @@ -353,7 +353,7 @@ where let (block, storage_changes, proof) = block_builder.build()?.into_inner(); - self.print_summary(&block, end_reason, &block_timer); + self.print_summary(&block, end_reason, block_timer.elapsed()); let proof = PR::into_proof(proof).map_err(|e| sp_blockchain::Error::Application(Box::new(e)))?; @@ -536,27 +536,36 @@ where &self, block: &Block, end_reason: EndProposingReason, - block_timer: &time::Instant, + block_took: time::Duration, ) { + let extrinsics = block.extrinsics(); self.metrics.report(|metrics| { - metrics.number_of_transactions.set(block.extrinsics().len() as u64); - metrics.block_constructed.observe(block_timer.elapsed().as_secs_f64()); + metrics.number_of_transactions.set(extrinsics.len() as u64); + metrics.block_constructed.observe(block_took.as_secs_f64()); metrics.report_end_proposing_reason(end_reason); }); + let extrinsics_summary = if extrinsics.is_empty() { + "no extrinsics".to_string() + } else { + format!( + "extrinsics ({}): [{}]", + extrinsics.len(), + extrinsics + .iter() + .map(|xt| BlakeTwo256::hash_of(xt).to_string()) + .collect::>() + .join(", ") + ) + }; + info!( - "🎁 Prepared block for proposing at {} ({} ms) [hash: {:?}; parent_hash: {}; extrinsics ({}): [{}]]", + "🎁 Prepared block for proposing at {} ({} ms) [hash: {:?}; parent_hash: {}; {extrinsics_summary}", block.header().number(), - block_timer.elapsed().as_millis(), + block_took.as_millis(), ::Hash::from(block.header().hash()), block.header().parent_hash(), - block.extrinsics().len(), - block.extrinsics() - .iter() - .map(|xt| BlakeTwo256::hash_of(xt).to_string()) - .collect::>() - .join(", ") ); telemetry!( self.telemetry; From 4b99c6e3b573b84758623d2f675549ac080004ca Mon Sep 17 00:00:00 2001 From: Oliver Tale-Yazdi Date: Fri, 16 Jun 2023 13:53:00 +0200 Subject: [PATCH 54/63] Cleanup Signed-off-by: Oliver Tale-Yazdi --- .../basic-authorship/src/basic_authorship.rs | 24 ++++++++----------- 1 file changed, 10 insertions(+), 14 deletions(-) diff --git a/client/basic-authorship/src/basic_authorship.rs b/client/basic-authorship/src/basic_authorship.rs index cc559483e76dd..d9f88e91a37d8 100644 --- a/client/basic-authorship/src/basic_authorship.rs +++ b/client/basic-authorship/src/basic_authorship.rs @@ -341,30 +341,25 @@ where block_size_limit: Option, ) -> Result, PR::Proof>, sp_blockchain::Error> { - let propose_with_start = time::Instant::now(); + let propose_with_timer = time::Instant::now(); let mut block_builder = self.client.new_block_at(self.parent_hash, inherent_digests, PR::ENABLED)?; self.apply_inherents(&mut block_builder, inherent_data)?; + // TODO call `after_inherents` and check if we should apply extrinsincs here + // + let block_timer = time::Instant::now(); let end_reason = self.apply_extrinsics(&mut block_builder, deadline, block_size_limit).await?; - let (block, storage_changes, proof) = block_builder.build()?.into_inner(); - - self.print_summary(&block, end_reason, block_timer.elapsed()); + let block_took = block_timer.elapsed(); let proof = PR::into_proof(proof).map_err(|e| sp_blockchain::Error::Application(Box::new(e)))?; - let propose_with_end = time::Instant::now(); - self.metrics.report(|metrics| { - metrics.create_block_proposal_time.observe( - propose_with_end.saturating_duration_since(propose_with_start).as_secs_f64(), - ); - }); - + self.print_summary(&block, end_reason, block_took, propose_with_timer.elapsed()); Ok(Proposal { block, proof, storage_changes }) } @@ -429,7 +424,7 @@ where let mut pending_iterator = select! { res = t1 => res, _ = t2 => { - log::warn!(target: LOG_TARGET, + warn!(target: LOG_TARGET, "Timeout fired waiting for transaction pool at block #{}. \ Proceeding with production.", self.parent_number, @@ -531,19 +526,20 @@ where Ok(end_reason) } - /// Prints a summary and reports telemetry. + /// Prints a summary and does telemetry + metrics. fn print_summary( &self, block: &Block, end_reason: EndProposingReason, block_took: time::Duration, + propose_with_took: time::Duration, ) { let extrinsics = block.extrinsics(); self.metrics.report(|metrics| { metrics.number_of_transactions.set(extrinsics.len() as u64); metrics.block_constructed.observe(block_took.as_secs_f64()); - metrics.report_end_proposing_reason(end_reason); + metrics.create_block_proposal_time.observe(propose_with_took.as_secs_f64()); }); let extrinsics_summary = if extrinsics.is_empty() { From 93f0d32f4a66ce21444870b72172d73ca4f2d695 Mon Sep 17 00:00:00 2001 From: Oliver Tale-Yazdi Date: Fri, 16 Jun 2023 14:08:02 +0200 Subject: [PATCH 55/63] Fixes Signed-off-by: Oliver Tale-Yazdi --- client/basic-authorship/src/basic_authorship.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/client/basic-authorship/src/basic_authorship.rs b/client/basic-authorship/src/basic_authorship.rs index d9f88e91a37d8..9241ff218feeb 100644 --- a/client/basic-authorship/src/basic_authorship.rs +++ b/client/basic-authorship/src/basic_authorship.rs @@ -57,7 +57,7 @@ pub const DEFAULT_BLOCK_SIZE_LIMIT: usize = 4 * 1024 * 1024 + 512; const DEFAULT_SOFT_DEADLINE_PERCENT: Percent = Percent::from_percent(50); -const LOG_TARGET: &'static str = "basic-authorship"; +const LOG_TARGET: &'static str = "sc-basic-authorship"; /// [`Proposer`] factory. pub struct ProposerFactory { @@ -401,6 +401,7 @@ where Ok(()) } + /// Apply as many extrinsics as possible to the block. async fn apply_extrinsics( &self, block_builder: &mut sc_block_builder::BlockBuilder<'_, Block, C, B>, From c57bbf5194cbe8fa711f8121467aed9c212c14ff Mon Sep 17 00:00:00 2001 From: Oliver Tale-Yazdi Date: Fri, 16 Jun 2023 21:45:54 +0200 Subject: [PATCH 56/63] Backup Signed-off-by: Oliver Tale-Yazdi --- bin/node-template/runtime/src/lib.rs | 2 +- .../basic-authorship/src/basic_authorship.rs | 3 +- client/block-builder/src/lib.rs | 24 +++------ frame/executive/src/lib.rs | 51 ++++++++----------- frame/support/test/tests/runtime_metadata.rs | 2 +- primitives/api/src/lib.rs | 4 +- primitives/api/test/tests/decl_and_impl.rs | 2 +- .../ui/impl_incorrect_method_signature.rs | 2 +- .../api/test/tests/ui/impl_missing_version.rs | 2 +- .../test/tests/ui/missing_versioned_method.rs | 2 +- .../missing_versioned_method_multiple_vers.rs | 2 +- .../ui/positive_cases/custom_where_bound.rs | 2 +- .../tests/ui/positive_cases/default_impls.rs | 2 +- ...ype_reference_in_impl_runtime_apis_call.rs | 2 +- primitives/block-builder/src/lib.rs | 10 ++-- primitives/runtime/src/lib.rs | 12 +++-- test-utils/runtime/src/lib.rs | 6 ++- 17 files changed, 56 insertions(+), 74 deletions(-) diff --git a/bin/node-template/runtime/src/lib.rs b/bin/node-template/runtime/src/lib.rs index a16edff3e639c..22fb01b62d0f0 100644 --- a/bin/node-template/runtime/src/lib.rs +++ b/bin/node-template/runtime/src/lib.rs @@ -354,7 +354,7 @@ impl_runtime_apis! { Executive::execute_block(block); } - fn initialize_block(header: &::Header) -> sp_runtime::RuntimeMbmMode { + fn initialize_block(header: &::Header) { Executive::initialize_block(header) } } diff --git a/client/basic-authorship/src/basic_authorship.rs b/client/basic-authorship/src/basic_authorship.rs index 957f2fef01329..e3d78264fb51f 100644 --- a/client/basic-authorship/src/basic_authorship.rs +++ b/client/basic-authorship/src/basic_authorship.rs @@ -376,8 +376,7 @@ where } // Try to progress any ongoing MBMs. No-OP if there are none. - block_builder.progress_mbms()?; - // TODO `poll` hook + let mode = block_builder.after_inherents()?; // proceed with transactions // We calculate soft deadline used only in case we start skipping transactions. diff --git a/client/block-builder/src/lib.rs b/client/block-builder/src/lib.rs index 5bb2e548b34ff..12e95a613399c 100644 --- a/client/block-builder/src/lib.rs +++ b/client/block-builder/src/lib.rs @@ -36,7 +36,7 @@ use sp_core::ExecutionContext; use sp_runtime::{ legacy, traits::{Block as BlockT, Hash, HashFor, Header as HeaderT, NumberFor, One}, - Digest, RuntimeMbmMode, + Digest, }; use sc_client_api::backend; @@ -139,7 +139,6 @@ pub struct BlockBuilder<'a, Block: BlockT, A: ProvideRuntimeApi, B> { backend: &'a B, /// The estimated size of the block header. estimated_header_size: usize, - mbm_mode: RuntimeMbmMode, } impl<'a, Block, A, B> BlockBuilder<'a, Block, A, B> @@ -179,7 +178,7 @@ where api.record_proof(); } - let mbm_mode = api.initialize_block_with_context( + api.initialize_block_with_context( parent_hash, ExecutionContext::BlockConstruction, &header, @@ -196,23 +195,14 @@ where version, backend, estimated_header_size, - mbm_mode, }) } - pub fn progress_mbms(&self) -> Result<(), Error> { - match self.mbm_mode { - RuntimeMbmMode::NotMigrating => Ok(()), - RuntimeMbmMode::Migrating => { - // FAIL-CI why 'with_context'?! - self.api - .progress_mbms_with_context( - self.parent_hash, - ExecutionContext::BlockConstruction, - ) - .map_err(Into::into) - }, - } + pub fn after_inherents(&self) -> Result<(), Error> { + // FAIL-CI why 'with_context'?! + self.api + .after_inherents_with_context(self.parent_hash, ExecutionContext::BlockConstruction); + Ok(()) } /// Push onto the block's list of extrinsics. diff --git a/frame/executive/src/lib.rs b/frame/executive/src/lib.rs index c56e5429a7c07..d06e2a3a5604a 100644 --- a/frame/executive/src/lib.rs +++ b/frame/executive/src/lib.rs @@ -118,7 +118,6 @@ use codec::{Codec, Encode}; use frame_support::{ - defensive_assert, dispatch::{DispatchClass, DispatchInfo, GetDispatchInfo, PostDispatchInfo}, pallet_prelude::InvalidTransaction, traits::{ @@ -134,7 +133,7 @@ use sp_runtime::{ ValidateUnsigned, Zero, }, transaction_validity::{TransactionSource, TransactionValidity}, - ApplyExtrinsicResult, RuntimeMbmMode, + ApplyExtrinsicResult, BlockAfterInherentsMode, }; use sp_std::{marker::PhantomData, prelude::*}; @@ -428,17 +427,11 @@ impl< } /// Start the execution of a particular block. - pub fn initialize_block(header: &System::Header) -> RuntimeMbmMode { + pub fn initialize_block(header: &System::Header) { sp_io::init_tracing(); sp_tracing::enter_span!(sp_tracing::Level::TRACE, "init_block"); let digests = Self::extract_pre_digest(header); Self::initialize_block_impl(header.number(), header.parent_hash(), &digests); - - if MultiStepMigrator::is_upgrading() { - RuntimeMbmMode::Migrating - } else { - RuntimeMbmMode::NotMigrating - } } fn extract_pre_digest(header: &System::Header) -> Digest { @@ -531,24 +524,21 @@ impl< let num_inherents = first_extrinsic_index as usize; Self::execute_dispatchables(dispatchables.iter().take(num_inherents)); - let is_upgrading = MultiStepMigrator::is_upgrading(); - if is_upgrading { - if num_inherents < dispatchables.len() { - panic!("Extrinsics are not allowed during Multi-Block-Migrations"); - } - - Self::progress_mbms(); - } else { - // TODO `poll` hook - - // Process extrinsics. - Self::execute_dispatchables(dispatchables.iter().skip(num_inherents)); + match Self::after_inherents() { + BlockAfterInherentsMode::NoExtrinsics => { + if num_inherents < dispatchables.len() { + panic!("Extrinsics are not allowed"); + } + }, + BlockAfterInherentsMode::CanPushExtrinsics => { + Self::execute_dispatchables(dispatchables.iter().skip(num_inherents)); + }, } // Dispatchable processing is done now. >::note_finished_extrinsics(); - - if !is_upgrading { + // TODO this could be optimized to be oneless storage read. + if !MultiStepMigrator::is_upgrading() { Self::on_idle_hook(*header.number()); } @@ -560,17 +550,18 @@ impl< /// Progress ongoing MBM migrations. // Used by the block builder and Executive. - pub fn progress_mbms() { - defensive_assert!( - MultiStepMigrator::is_upgrading(), - "Tried to progress MBMs although \ - none were ongoing. Not a hard error but unexpected." - ); + pub fn after_inherents() -> BlockAfterInherentsMode { let used_weight = MultiStepMigrator::step(); >::register_extra_weight_unchecked( used_weight, DispatchClass::Mandatory, ); + // TODO `poll` hook goes here . + if MultiStepMigrator::is_upgrading() { + BlockAfterInherentsMode::NoExtrinsics + } else { + BlockAfterInherentsMode::CanPushExtrinsics + } } /// Execute given extrinsics. @@ -644,7 +635,7 @@ impl< // Decode parameters and dispatch let dispatch_info = xt.get_dispatch_info(); if dispatch_info.class != DispatchClass::Mandatory && MultiStepMigrator::is_upgrading() { - // This function is also used by the block builder. So it should respect this check. + // The block builder respects this by using the mode returned by `after_inherents`. panic!("Only Mandatory extrinsics are allowed during Multi-Block-Migrations"); } // Check whether we need to error because extrinsics are paused. diff --git a/frame/support/test/tests/runtime_metadata.rs b/frame/support/test/tests/runtime_metadata.rs index 3b4a44996cc8d..70ca307d4428c 100644 --- a/frame/support/test/tests/runtime_metadata.rs +++ b/frame/support/test/tests/runtime_metadata.rs @@ -107,7 +107,7 @@ sp_api::impl_runtime_apis! { fn execute_block(_: Block) { unimplemented!() } - fn initialize_block(_: &::Header) -> sp_runtime::RuntimeMbmMode { + fn initialize_block(_: &::Header) { unimplemented!() } } diff --git a/primitives/api/src/lib.rs b/primitives/api/src/lib.rs index a16b35630c325..20b292ae29ed0 100644 --- a/primitives/api/src/lib.rs +++ b/primitives/api/src/lib.rs @@ -93,7 +93,7 @@ pub use sp_runtime::{ generic::BlockId, traits::{Block as BlockT, Hash as HashT, HashFor, Header as HeaderT, NumberFor}, transaction_validity::TransactionValidity, - RuntimeMbmMode, RuntimeString, TransactionOutcome, + RuntimeString, TransactionOutcome, }; #[doc(hidden)] #[cfg(feature = "std")] @@ -723,7 +723,7 @@ decl_runtime_apis! { fn execute_block(block: Block); /// Initialize a block with the given header. #[renamed("initialise_block", 2)] - fn initialize_block(header: &::Header) -> RuntimeMbmMode; + fn initialize_block(header: &::Header); } /// The `Metadata` api trait that returns metadata for the runtime. diff --git a/primitives/api/test/tests/decl_and_impl.rs b/primitives/api/test/tests/decl_and_impl.rs index 911955c851d03..274f80bd1b465 100644 --- a/primitives/api/test/tests/decl_and_impl.rs +++ b/primitives/api/test/tests/decl_and_impl.rs @@ -89,7 +89,7 @@ impl_runtime_apis! { fn execute_block(_: Block) { unimplemented!() } - fn initialize_block(_: &::Header) -> sp_runtime::RuntimeMbmMode { + fn initialize_block(_: &::Header) { unimplemented!() } } diff --git a/primitives/api/test/tests/ui/impl_incorrect_method_signature.rs b/primitives/api/test/tests/ui/impl_incorrect_method_signature.rs index c2db5f8e5f538..32501be7855c6 100644 --- a/primitives/api/test/tests/ui/impl_incorrect_method_signature.rs +++ b/primitives/api/test/tests/ui/impl_incorrect_method_signature.rs @@ -23,7 +23,7 @@ sp_api::impl_runtime_apis! { fn execute_block(_: Block) { unimplemented!() } - fn initialize_block(_: &::Header) -> sp_runtime::RuntimeMbmMode { + fn initialize_block(_: &::Header) { unimplemented!() } } diff --git a/primitives/api/test/tests/ui/impl_missing_version.rs b/primitives/api/test/tests/ui/impl_missing_version.rs index 32b6f74f44c2b..8fd40a400922f 100644 --- a/primitives/api/test/tests/ui/impl_missing_version.rs +++ b/primitives/api/test/tests/ui/impl_missing_version.rs @@ -28,7 +28,7 @@ sp_api::impl_runtime_apis! { fn execute_block(_: Block) { unimplemented!() } - fn initialize_block(_: &::Header) -> sp_runtime::RuntimeMbmMode { + fn initialize_block(_: &::Header) { unimplemented!() } } diff --git a/primitives/api/test/tests/ui/missing_versioned_method.rs b/primitives/api/test/tests/ui/missing_versioned_method.rs index 662b1ef5d729e..919cef055fe62 100644 --- a/primitives/api/test/tests/ui/missing_versioned_method.rs +++ b/primitives/api/test/tests/ui/missing_versioned_method.rs @@ -27,7 +27,7 @@ sp_api::impl_runtime_apis! { fn execute_block(_: Block) { unimplemented!() } - fn initialize_block(_: &::Header) -> sp_runtime::RuntimeMbmMode { + fn initialize_block(_: &::Header) { unimplemented!() } } diff --git a/primitives/api/test/tests/ui/missing_versioned_method_multiple_vers.rs b/primitives/api/test/tests/ui/missing_versioned_method_multiple_vers.rs index 71914825f5bdb..036bba417f57d 100644 --- a/primitives/api/test/tests/ui/missing_versioned_method_multiple_vers.rs +++ b/primitives/api/test/tests/ui/missing_versioned_method_multiple_vers.rs @@ -30,7 +30,7 @@ sp_api::impl_runtime_apis! { fn execute_block(_: Block) { unimplemented!() } - fn initialize_block(_: &::Header) -> sp_runtime::RuntimeMbmMode { + fn initialize_block(_: &::Header) { unimplemented!() } } diff --git a/primitives/api/test/tests/ui/positive_cases/custom_where_bound.rs b/primitives/api/test/tests/ui/positive_cases/custom_where_bound.rs index efc8f55bd6be3..b572a3bc30d5d 100644 --- a/primitives/api/test/tests/ui/positive_cases/custom_where_bound.rs +++ b/primitives/api/test/tests/ui/positive_cases/custom_where_bound.rs @@ -34,7 +34,7 @@ sp_api::impl_runtime_apis! { fn execute_block(_: Block) { unimplemented!() } - fn initialize_block(_: &::Header) -> sp_runtime::RuntimeMbmMode { + fn initialize_block(_: &::Header) { unimplemented!() } } diff --git a/primitives/api/test/tests/ui/positive_cases/default_impls.rs b/primitives/api/test/tests/ui/positive_cases/default_impls.rs index bd9280a790b47..58192feb9ecac 100644 --- a/primitives/api/test/tests/ui/positive_cases/default_impls.rs +++ b/primitives/api/test/tests/ui/positive_cases/default_impls.rs @@ -29,7 +29,7 @@ sp_api::impl_runtime_apis! { fn execute_block(_: Block) { unimplemented!() } - fn initialize_block(_: &::Header) -> sp_runtime::RuntimeMbmMode { + fn initialize_block(_: &::Header) { unimplemented!() } } diff --git a/primitives/api/test/tests/ui/type_reference_in_impl_runtime_apis_call.rs b/primitives/api/test/tests/ui/type_reference_in_impl_runtime_apis_call.rs index 560fffc193e59..14a8fa4d4e0b1 100644 --- a/primitives/api/test/tests/ui/type_reference_in_impl_runtime_apis_call.rs +++ b/primitives/api/test/tests/ui/type_reference_in_impl_runtime_apis_call.rs @@ -25,7 +25,7 @@ sp_api::impl_runtime_apis! { fn execute_block(_: Block) { unimplemented!() } - fn initialize_block(_: &::Header) -> sp_runtime::RuntimeMbmMode { + fn initialize_block(_: &::Header) { unimplemented!() } } diff --git a/primitives/block-builder/src/lib.rs b/primitives/block-builder/src/lib.rs index 48674967c434c..8681953778330 100644 --- a/primitives/block-builder/src/lib.rs +++ b/primitives/block-builder/src/lib.rs @@ -20,7 +20,7 @@ #![cfg_attr(not(feature = "std"), no_std)] use sp_inherents::{CheckInherentsResult, InherentData}; -use sp_runtime::{traits::Block as BlockT, ApplyExtrinsicResult}; +use sp_runtime::{traits::Block as BlockT, ApplyExtrinsicResult, BlockAfterInherentsMode}; sp_api::decl_runtime_apis! { /// The `BlockBuilder` api trait that provides the required functionality for building a block. @@ -49,11 +49,7 @@ sp_api::decl_runtime_apis! { /// Check that the inherents are valid. The inherent data will vary from chain to chain. fn check_inherents(block: Block, data: InherentData) -> CheckInherentsResult; - /// Progress Multi-Block-Migrations. - /// - /// This is safe to call even when `initialize_block` returned - /// `RuntimeMbmMode::NotMigrating`. In this case it will just waste some time so the block - /// builder is advised not to do that. - fn progress_mbms(); + /// Called after inherents are done but before extrinsic processing. + fn after_inherents() -> BlockAfterInherentsMode; } } diff --git a/primitives/runtime/src/lib.rs b/primitives/runtime/src/lib.rs index e098c785d92bc..d90c067b49774 100644 --- a/primitives/runtime/src/lib.rs +++ b/primitives/runtime/src/lib.rs @@ -938,11 +938,13 @@ impl TransactionOutcome { } } -// FAIL-CI find a good spot for this -#[derive(Debug, Clone, Copy, PartialEq, Eq, Encode, Decode, scale_info::TypeInfo)] -pub enum RuntimeMbmMode { - NotMigrating, - Migrating, +/// The mode of a block after inherents were applied. +#[derive(Debug, PartialEq, Eq, Clone, Copy, Encode, Decode, TypeInfo)] +pub enum BlockAfterInherentsMode { + /// No extrinsics should be pushed to the block. + NoExtrinsics, + /// Can push extrinsics to the block. + CanPushExtrinsics, } #[cfg(test)] diff --git a/test-utils/runtime/src/lib.rs b/test-utils/runtime/src/lib.rs index 15df5ecf5d8c6..bacc4a2d4aa67 100644 --- a/test-utils/runtime/src/lib.rs +++ b/test-utils/runtime/src/lib.rs @@ -478,7 +478,7 @@ impl_runtime_apis! { Executive::execute_block(block); } - fn initialize_block(header: &::Header) -> sp_runtime::RuntimeMbmMode { + fn initialize_block(header: &::Header) { log::trace!(target: LOG_TARGET, "initialize_block: {header:#?}"); Executive::initialize_block(header) } @@ -526,6 +526,10 @@ impl_runtime_apis! { fn check_inherents(_block: Block, _data: InherentData) -> CheckInherentsResult { CheckInherentsResult::new() } + + fn after_inherents() -> sp_runtime::BlockAfterInherentsMode { + Executive::after_inherents() + } } impl frame_system_rpc_runtime_api::AccountNonceApi for Runtime { From 4101e82de8bef1e2ea4328c13e2d32c3febb408b Mon Sep 17 00:00:00 2001 From: Oliver Tale-Yazdi Date: Fri, 16 Jun 2023 22:11:03 +0200 Subject: [PATCH 57/63] Fix stuff Signed-off-by: Oliver Tale-Yazdi --- .../basic-authorship/src/basic_authorship.rs | 21 ++++++++++++------- client/block-builder/src/lib.rs | 9 ++++---- client/proposer-metrics/src/lib.rs | 4 ++++ frame/executive/src/lib.rs | 14 +++++++------ frame/migrations/src/lib.rs | 5 ----- frame/migrations/src/mock.rs | 2 ++ primitives/runtime/src/lib.rs | 4 ++-- 7 files changed, 35 insertions(+), 24 deletions(-) diff --git a/client/basic-authorship/src/basic_authorship.rs b/client/basic-authorship/src/basic_authorship.rs index 9241ff218feeb..c90926eba73bb 100644 --- a/client/basic-authorship/src/basic_authorship.rs +++ b/client/basic-authorship/src/basic_authorship.rs @@ -39,7 +39,7 @@ use sp_core::traits::SpawnNamed; use sp_inherents::InherentData; use sp_runtime::{ traits::{BlakeTwo256, Block as BlockT, Hash as HashT, Header as HeaderT}, - Digest, Percent, SaturatedConversion, + BlockAfterInherentsMode, Digest, Percent, SaturatedConversion, }; use std::{marker::PhantomData, pin::Pin, sync::Arc, time}; @@ -347,12 +347,14 @@ where self.apply_inherents(&mut block_builder, inherent_data)?; - // TODO call `after_inherents` and check if we should apply extrinsincs here - // - let block_timer = time::Instant::now(); - let end_reason = - self.apply_extrinsics(&mut block_builder, deadline, block_size_limit).await?; + let mode = block_builder.after_inherents()?; + let end_reason = match mode { + BlockAfterInherentsMode::ExtrinsicsAllowed => + self.apply_extrinsics(&mut block_builder, deadline, block_size_limit).await?, + BlockAfterInherentsMode::ExtrinsicsForbidden => EndProposingReason::ExtrinsicsForbidden, + }; + let (block, storage_changes, proof) = block_builder.build()?.into_inner(); let block_took = block_timer.elapsed(); @@ -544,7 +546,12 @@ where }); let extrinsics_summary = if extrinsics.is_empty() { - "no extrinsics".to_string() + if end_reason == EndProposingReason::ExtrinsicsForbidden { + "extrinsics forbidden" + } else { + "no extrinsics" + } + .to_string() } else { format!( "extrinsics ({}): [{}]", diff --git a/client/block-builder/src/lib.rs b/client/block-builder/src/lib.rs index 12e95a613399c..37d290f3220c6 100644 --- a/client/block-builder/src/lib.rs +++ b/client/block-builder/src/lib.rs @@ -36,7 +36,7 @@ use sp_core::ExecutionContext; use sp_runtime::{ legacy, traits::{Block as BlockT, Hash, HashFor, Header as HeaderT, NumberFor, One}, - Digest, + BlockAfterInherentsMode, Digest, }; use sc_client_api::backend; @@ -198,11 +198,12 @@ where }) } - pub fn after_inherents(&self) -> Result<(), Error> { + /// Called after inherents but before extrinsics have been applied. + pub fn after_inherents(&self) -> Result { // FAIL-CI why 'with_context'?! self.api - .after_inherents_with_context(self.parent_hash, ExecutionContext::BlockConstruction); - Ok(()) + .after_inherents_with_context(self.parent_hash, ExecutionContext::BlockConstruction) + .map_err(Into::into) } /// Push onto the block's list of extrinsics. diff --git a/client/proposer-metrics/src/lib.rs b/client/proposer-metrics/src/lib.rs index 012e8ca769a96..3e8c6388f3800 100644 --- a/client/proposer-metrics/src/lib.rs +++ b/client/proposer-metrics/src/lib.rs @@ -44,11 +44,14 @@ impl MetricsLink { } /// The reason why proposing a block ended. +#[derive(Clone, Copy, PartialEq, Eq)] pub enum EndProposingReason { NoMoreTransactions, HitDeadline, HitBlockSizeLimit, HitBlockWeightLimit, + /// No extrinsics are allowed in the block. + ExtrinsicsForbidden, } /// Authorship metrics. @@ -112,6 +115,7 @@ impl Metrics { EndProposingReason::NoMoreTransactions => "no_more_transactions", EndProposingReason::HitBlockSizeLimit => "hit_block_size_limit", EndProposingReason::HitBlockWeightLimit => "hit_block_weight_limit", + EndProposingReason::ExtrinsicsForbidden => "no_extrinsics_allowed", }; self.end_proposing_reason.with_label_values(&[reason]).inc(); diff --git a/frame/executive/src/lib.rs b/frame/executive/src/lib.rs index d06e2a3a5604a..d610f6a067fe5 100644 --- a/frame/executive/src/lib.rs +++ b/frame/executive/src/lib.rs @@ -525,19 +525,19 @@ impl< Self::execute_dispatchables(dispatchables.iter().take(num_inherents)); match Self::after_inherents() { - BlockAfterInherentsMode::NoExtrinsics => { + BlockAfterInherentsMode::ExtrinsicsForbidden => { if num_inherents < dispatchables.len() { panic!("Extrinsics are not allowed"); } }, - BlockAfterInherentsMode::CanPushExtrinsics => { + BlockAfterInherentsMode::ExtrinsicsAllowed => { Self::execute_dispatchables(dispatchables.iter().skip(num_inherents)); }, } // Dispatchable processing is done now. >::note_finished_extrinsics(); - // TODO this could be optimized to be oneless storage read. + // TODO this could be optimized to be one less storage read. if !MultiStepMigrator::is_upgrading() { Self::on_idle_hook(*header.number()); } @@ -556,11 +556,13 @@ impl< used_weight, DispatchClass::Mandatory, ); - // TODO `poll` hook goes here . + + // TODO `poll` hook goes here. + if MultiStepMigrator::is_upgrading() { - BlockAfterInherentsMode::NoExtrinsics + BlockAfterInherentsMode::ExtrinsicsForbidden } else { - BlockAfterInherentsMode::CanPushExtrinsics + BlockAfterInherentsMode::ExtrinsicsAllowed } } diff --git a/frame/migrations/src/lib.rs b/frame/migrations/src/lib.rs index 8beb672f47aba..d905bce7248aa 100644 --- a/frame/migrations/src/lib.rs +++ b/frame/migrations/src/lib.rs @@ -330,11 +330,6 @@ pub mod pallet { fn on_runtime_upgrade() -> Weight { Self::onboard_new_mbms() } - - // This is done by frame-executive. - //fn on_initialize(n: T::BlockNumber) -> Weight { - // Self::progress_mbms(n) - //} } #[pallet::call(weight = T::WeightInfo)] diff --git a/frame/migrations/src/mock.rs b/frame/migrations/src/mock.rs index de51491d531ce..7d9a4cdc203fb 100644 --- a/frame/migrations/src/mock.rs +++ b/frame/migrations/src/mock.rs @@ -129,6 +129,8 @@ pub fn run_to_block(n: u32) { System::set_block_number(System::block_number() + 1); System::on_initialize(System::block_number()); Migrations::on_initialize(System::block_number()); + // Executive calls this: + ::step(); } } diff --git a/primitives/runtime/src/lib.rs b/primitives/runtime/src/lib.rs index d90c067b49774..e96b4172689e3 100644 --- a/primitives/runtime/src/lib.rs +++ b/primitives/runtime/src/lib.rs @@ -942,9 +942,9 @@ impl TransactionOutcome { #[derive(Debug, PartialEq, Eq, Clone, Copy, Encode, Decode, TypeInfo)] pub enum BlockAfterInherentsMode { /// No extrinsics should be pushed to the block. - NoExtrinsics, + ExtrinsicsForbidden, /// Can push extrinsics to the block. - CanPushExtrinsics, + ExtrinsicsAllowed, } #[cfg(test)] From 8f7bda1e9359c42e16042dac5363edafb0dd4af2 Mon Sep 17 00:00:00 2001 From: Oliver Tale-Yazdi Date: Fri, 16 Jun 2023 22:29:09 +0200 Subject: [PATCH 58/63] Works Signed-off-by: Oliver Tale-Yazdi --- bin/node/runtime/src/lib.rs | 26 ++++++++++++++++++++++---- client/proposer-metrics/src/lib.rs | 2 +- frame/executive/src/lib.rs | 15 +++++++++------ 3 files changed, 32 insertions(+), 11 deletions(-) diff --git a/bin/node/runtime/src/lib.rs b/bin/node/runtime/src/lib.rs index b57f196bf40da..6bb1026bd3c41 100644 --- a/bin/node/runtime/src/lib.rs +++ b/bin/node/runtime/src/lib.rs @@ -143,7 +143,7 @@ pub const VERSION: RuntimeVersion = RuntimeVersion { // and set impl_version to 0. If only runtime // implementation changes and behavior does not, then leave spec_version as // is and increment impl_version. - spec_version: 268, + spec_version: 269, impl_version: 0, apis: RUNTIME_API_VERSIONS, transaction_version: 2, @@ -1862,13 +1862,27 @@ impl pallet_statement::Config for Runtime { parameter_types! { pub MbmServiceWeight: Weight = Perbill::from_percent(80) * RuntimeBlockWeights::get().max_block; + pub Mbms: pallet_migrations::MigrationsOf = vec![ + Box::new(pallet_migrations::mock_helpers::MockedMigration( + pallet_migrations::mock_helpers::MockedMigrationKind::SucceedAfter, 0 + )), + Box::new(pallet_migrations::mock_helpers::MockedMigration( + pallet_migrations::mock_helpers::MockedMigrationKind::SucceedAfter, 2 + )), + Box::new(pallet_migrations::mock_helpers::MockedMigration( + pallet_migrations::mock_helpers::MockedMigrationKind::SucceedAfter, 3 + )), + Box::new(pallet_migrations::mock_helpers::MockedMigration( + pallet_migrations::mock_helpers::MockedMigrationKind::SucceedAfter, 20 + )) + ]; } impl pallet_migrations::Config for Runtime { type RuntimeEvent = RuntimeEvent; - type Migrations = (); - type Cursor = (); - type Identifier = (); + type Migrations = Mbms; + type Cursor = pallet_migrations::mock_helpers::MockedCursor; + type Identifier = pallet_migrations::mock_helpers::MockedIdentifier; type OnMigrationUpdate = (); type ServiceWeight = MbmServiceWeight; type WeightInfo = pallet_migrations::weights::SubstrateWeight; @@ -2128,6 +2142,10 @@ impl_runtime_apis! { fn check_inherents(block: Block, data: InherentData) -> CheckInherentsResult { data.check_extrinsics(&block) } + + fn after_inherents() -> sp_runtime::BlockAfterInherentsMode { + Executive::after_inherents() + } } impl sp_transaction_pool::runtime_api::TaggedTransactionQueue for Runtime { diff --git a/client/proposer-metrics/src/lib.rs b/client/proposer-metrics/src/lib.rs index 3e8c6388f3800..a9aa5a0e0e302 100644 --- a/client/proposer-metrics/src/lib.rs +++ b/client/proposer-metrics/src/lib.rs @@ -115,7 +115,7 @@ impl Metrics { EndProposingReason::NoMoreTransactions => "no_more_transactions", EndProposingReason::HitBlockSizeLimit => "hit_block_size_limit", EndProposingReason::HitBlockWeightLimit => "hit_block_weight_limit", - EndProposingReason::ExtrinsicsForbidden => "no_extrinsics_allowed", + EndProposingReason::ExtrinsicsForbidden => "extrinsics_forbidden", }; self.end_proposing_reason.with_label_values(&[reason]).inc(); diff --git a/frame/executive/src/lib.rs b/frame/executive/src/lib.rs index d610f6a067fe5..751ca871dfbb8 100644 --- a/frame/executive/src/lib.rs +++ b/frame/executive/src/lib.rs @@ -551,15 +551,18 @@ impl< /// Progress ongoing MBM migrations. // Used by the block builder and Executive. pub fn after_inherents() -> BlockAfterInherentsMode { - let used_weight = MultiStepMigrator::step(); - >::register_extra_weight_unchecked( - used_weight, - DispatchClass::Mandatory, - ); + let is_upgrading = MultiStepMigrator::is_upgrading(); + if is_upgrading { + let used_weight = MultiStepMigrator::step(); + >::register_extra_weight_unchecked( + used_weight, + DispatchClass::Mandatory, + ); + } // TODO `poll` hook goes here. - if MultiStepMigrator::is_upgrading() { + if is_upgrading { BlockAfterInherentsMode::ExtrinsicsForbidden } else { BlockAfterInherentsMode::ExtrinsicsAllowed From e430d57ca98165950bb31e9dad522aa142c60a99 Mon Sep 17 00:00:00 2001 From: Oliver Tale-Yazdi Date: Sat, 17 Jun 2023 18:23:04 +0200 Subject: [PATCH 59/63] Fix template Signed-off-by: Oliver Tale-Yazdi --- bin/node-template/runtime/src/lib.rs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/bin/node-template/runtime/src/lib.rs b/bin/node-template/runtime/src/lib.rs index 22fb01b62d0f0..f2dc1099fdbb4 100644 --- a/bin/node-template/runtime/src/lib.rs +++ b/bin/node-template/runtime/src/lib.rs @@ -392,6 +392,10 @@ impl_runtime_apis! { ) -> sp_inherents::CheckInherentsResult { data.check_extrinsics(&block) } + + fn after_inherents() -> sp_runtime::BlockAfterInherentsMode { + Executive::after_inherents() + } } impl sp_transaction_pool::runtime_api::TaggedTransactionQueue for Runtime { From 89313b3a67cae821b500eaa457f6e71648f633eb Mon Sep 17 00:00:00 2001 From: Oliver Tale-Yazdi Date: Sun, 18 Jun 2023 11:25:57 +0200 Subject: [PATCH 60/63] Fix test --- bin/node/runtime/src/lib.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/bin/node/runtime/src/lib.rs b/bin/node/runtime/src/lib.rs index 6bb1026bd3c41..c5d9ddacb0f31 100644 --- a/bin/node/runtime/src/lib.rs +++ b/bin/node/runtime/src/lib.rs @@ -143,7 +143,7 @@ pub const VERSION: RuntimeVersion = RuntimeVersion { // and set impl_version to 0. If only runtime // implementation changes and behavior does not, then leave spec_version as // is and increment impl_version. - spec_version: 269, + spec_version: 268, impl_version: 0, apis: RUNTIME_API_VERSIONS, transaction_version: 2, @@ -1862,6 +1862,7 @@ impl pallet_statement::Config for Runtime { parameter_types! { pub MbmServiceWeight: Weight = Perbill::from_percent(80) * RuntimeBlockWeights::get().max_block; + // FAIL-CI remove pub Mbms: pallet_migrations::MigrationsOf = vec![ Box::new(pallet_migrations::mock_helpers::MockedMigration( pallet_migrations::mock_helpers::MockedMigrationKind::SucceedAfter, 0 From 999b0f0c09dba75385c8428d0f761a302acc6dcc Mon Sep 17 00:00:00 2001 From: Oliver Tale-Yazdi Date: Tue, 27 Jun 2023 09:20:14 +0200 Subject: [PATCH 61/63] WIP --- frame/migrations/src/lib.rs | 26 +--- frame/migrations/src/mock_helpers.rs | 1 - frame/support/src/migrations.rs | 225 ++++++++++++++++++++++++++- 3 files changed, 227 insertions(+), 25 deletions(-) diff --git a/frame/migrations/src/lib.rs b/frame/migrations/src/lib.rs index d905bce7248aa..0ca69f2debc44 100644 --- a/frame/migrations/src/lib.rs +++ b/frame/migrations/src/lib.rs @@ -136,7 +136,7 @@ mod benchmarking; mod mock; -pub mod mock_helpers; +//pub mod mock_helpers; mod tests; pub mod weights; @@ -200,16 +200,6 @@ impl ActiveCursor { } } -/// A collection of migrations that must be executed in order. -pub type MigrationsOf = Vec< - Box< - dyn SteppedMigration< - Cursor = ::Cursor, - Identifier = ::Identifier, - >, - >, ->; - /// Convenience alias for [`MigrationCursor`]. pub type CursorOf = MigrationCursor<::Cursor, ::BlockNumber>; @@ -236,7 +226,7 @@ pub mod pallet { /// /// Should only be updated in a runtime-upgrade once all the old migrations have completed. /// (Check that `Cursor` is `None`). - type Migrations: Get>; + type Migrations: SteppedMigrations; /// The cursor type that is shared across all migrations. type Cursor: FullCodec + MaxEncodedLen + TypeInfo + Parameter; @@ -384,7 +374,7 @@ impl Pallet { return T::WeightInfo::on_runtime_upgrade() } - let migrations = T::Migrations::get().len() as u32; + let migrations = T::Migrations::len(); log::info!(target: LOG, "Onboarding {migrations} MBM migrations"); if migrations > 0 { Cursor::::set(Some( @@ -423,9 +413,8 @@ impl Pallet { }; debug_assert!(::is_upgrading()); - let migrations = T::Migrations::get(); for i in 0.. { - match Self::exec_migration(&mut meter, &migrations, cursor, i == 0) { + match Self::exec_migration(&mut meter, cursor, i == 0) { None => return meter.consumed, Some(ControlFlow::Break(last_cursor)) => { cursor = last_cursor; @@ -448,24 +437,23 @@ impl Pallet { /// remaining weight that can be consumed. fn exec_migration( meter: &mut WeightMeter, - migrations: &MigrationsOf, mut cursor: ActiveCursorOf, is_first: bool, ) -> Option, ActiveCursorOf>> { - let Some(migration) = migrations.get(cursor.index as usize) else { + let Some(id) = T::Migrations::nth_id(cursor.index) else { Self::deposit_event(Event::UpgradeCompleted); Cursor::::kill(); T::OnMigrationUpdate::completed(); return None; }; - if Historic::::contains_key(&migration.id()) { + if Historic::::contains_key(&id) { Self::deposit_event(Event::MigrationSkippedHistoric { index: cursor.index }); cursor.advance(System::::block_number()); return Some(ControlFlow::Continue(cursor)) } let blocks = System::::block_number().saturating_sub(cursor.started_at); - match migration.transactional_step(cursor.inner_cursor.clone(), meter) { + match T::Migrations::nth_transactional_step(cursor.index, cursor.inner_cursor.clone(), meter) { Ok(Some(next_cursor)) => { Self::deposit_event(Event::MigrationAdvanced { index: cursor.index, blocks }); cursor.inner_cursor = Some(next_cursor); diff --git a/frame/migrations/src/mock_helpers.rs b/frame/migrations/src/mock_helpers.rs index a7c739eb288c6..70863e1d5fa5f 100644 --- a/frame/migrations/src/mock_helpers.rs +++ b/frame/migrations/src/mock_helpers.rs @@ -59,7 +59,6 @@ impl SteppedMigration for MockedMigration { } fn step( - &self, cursor: &Option, _meter: &mut WeightMeter, ) -> Result, SteppedMigrationError> { diff --git a/frame/support/src/migrations.rs b/frame/support/src/migrations.rs index 43ac25618f492..43b5ee9016717 100644 --- a/frame/support/src/migrations.rs +++ b/frame/support/src/migrations.rs @@ -223,13 +223,13 @@ pub trait SteppedMigration { /// The unique identifier of this migration. /// /// If two migrations have the same identifier, then they are assumed to be identical. - fn id(&self) -> Self::Identifier; + fn id() -> Self::Identifier; /// The maximum number of steps that this migration can take at most. /// /// This can be used to enforce progress and prevent migrations to be stuck forever. A migration /// that exceeds its max steps is treated as failed. `None` means that there is no limit. - fn max_steps(&self) -> Option { + fn max_steps() -> Option { None } @@ -240,18 +240,16 @@ pub trait SteppedMigration { /// provided as convenience for a caller. A cursor of `None` implies that the migration is at /// its end. TODO: Think about iterator `fuse` requirement. fn step( - &self, cursor: &Option, meter: &mut WeightMeter, ) -> Result, SteppedMigrationError>; /// Same as [`Self::step`], but rolls back pending changes in the error case. fn transactional_step( - &self, mut cursor: Option, meter: &mut WeightMeter, ) -> Result, SteppedMigrationError> { - with_transaction_opaque_err(move || match self.step(&cursor, meter) { + with_transaction_opaque_err(move || match Self::step(&cursor, meter) { Ok(new_cursor) => { cursor = new_cursor; sp_api::TransactionOutcome::Commit(Ok(cursor)) @@ -332,3 +330,220 @@ impl MultiStepMigrator for () { Weight::zero() } } + +impl SteppedMigration for (Left, Right) { + type Cursor = Either, Option>; + type Identifier = (Left::Identifier, Right::Identifier); + + fn id() -> Self::Identifier { + (Left::id(), Right::id()) + } + + /// # SECURITY + /// + /// Never returns `Some(Right(None))` as cursor. + fn step( + cursor: &Option, + meter: &mut WeightMeter, + ) -> Result, SteppedMigrationError> { + let new_cursor = match cursor { + None => Some(Either::Left(Left::step(&None, meter)?)), + Some(Either::Left(inner @ Some(_))) => Some(Either::Left(Left::step(&inner, meter)?)), + // Left None, need to go right now. + Some(Either::Right(inner)) => { + match Right::step(&inner, meter)? { + None => None, + new_inner @ Some(_) => Some(Either::Right(new_inner)), + } + } + _ => todo!(), + }; + Ok(new_cursor) + } +} + +/// Multiple [`SteppedMigration`]. +pub trait SteppedMigrations { + type Cursor: codec::FullCodec + codec::MaxEncodedLen; + type Identifier: codec::FullCodec + codec::MaxEncodedLen; + + fn len() -> u32; + + fn nth_id(n: u32) -> Option; + + fn nth_step( + n: u32, + cursor: &Option, + meter: &mut WeightMeter, + ) -> Option, SteppedMigrationError>>; + + fn nth_transactional_step( + n: u32, + cursor: Option, + meter: &mut WeightMeter, + ) -> Option, SteppedMigrationError>>; +} + +pub trait NonAggregatedMigration {} + +impl + NonAggregatedMigration> SteppedMigrations for T { + type Cursor = Cursor; + type Identifier = Identifier; + + fn len() -> u32 { + 1 + } + + fn nth_id(n: u32) -> Option { + match n { + 0 => Some(T::id()), + _ => None, + } + } + + fn nth_step( + n: u32, + cursor: &Option, + meter: &mut WeightMeter, + ) -> Option, SteppedMigrationError>> { + match n { + 0 => Some(T::step(cursor, meter)), + _ => None, + } + } + + fn nth_transactional_step( + n: u32, + cursor: Option, + meter: &mut WeightMeter, + ) -> Option, SteppedMigrationError>> { + match n { + 0 => Some(T::transactional_step(cursor, meter)), + _ => None, + } + } +} + +impl, Right: SteppedMigrations, Cursor: codec::FullCodec + codec::MaxEncodedLen + Clone, Identifier: codec::FullCodec + codec::MaxEncodedLen> SteppedMigrations for (Left, Right) { + type Cursor = Cursor; + type Identifier = Identifier; + + fn len() -> u32 { + Left::len() + Right::len() + } + + fn nth_id(n: u32) -> Option { + match Left::nth_id(n) { + None if n > 0 => Right::nth_id(n - 1), + other => other, + } + } + + fn nth_step( + n: u32, + cursor: &Option, + meter: &mut WeightMeter, + ) -> Option, SteppedMigrationError>> { + match Left::nth_step(n, cursor, meter) { + None if n > 0 => Right::nth_step(n - 1, cursor, meter), + other => other, + } + } + + fn nth_transactional_step( + n: u32, + cursor: Option, + meter: &mut WeightMeter, + ) -> Option, SteppedMigrationError>> { + match Left::nth_transactional_step(n, cursor.clone(), meter) { + None if n > 0 => Right::nth_transactional_step(n - 1, cursor, meter), + other => other, + } + } +} + +#[derive(Decode, Encode, MaxEncodedLen, Eq, PartialEq)] +pub enum Either { + Left(L), + Right(R), +} + +pub struct M0; +impl SteppedMigration for M0 { + type Cursor = (); + type Identifier = u8; + + fn id() -> Self::Identifier { + 0 + } + + fn step( + _cursor: &Option, + _meter: &mut WeightMeter, + ) -> Result, SteppedMigrationError> { + log::info!("M0"); + Ok(None) + } +} + +pub struct M1; +impl SteppedMigration for M1 { + type Cursor = (); + type Identifier = u8; + + fn id() -> Self::Identifier { + 1 + } + + fn step( + _cursor: &Option, + _meter: &mut WeightMeter, + ) -> Result, SteppedMigrationError> { + log::info!("M1"); + Ok(None) + } +} + +pub struct M2; +impl SteppedMigration for M2 { + type Cursor = (); + type Identifier = u8; + + fn id() -> Self::Identifier { + 2 + } + + fn step( + _cursor: &Option, + _meter: &mut WeightMeter, + ) -> Result, SteppedMigrationError> { + log::info!("M2"); + Ok(None) + } +} + +// mark +impl NonAggregatedMigration for M0 {} +impl NonAggregatedMigration for M1 {} +impl NonAggregatedMigration for M2 {} + +#[test] +fn templates_work() { + sp_tracing::init_for_tests(); + // Three migrations combined to execute in order: + type Triple = (M0, (M1, M2)); + assert_eq!(::len(), 3); + // Six migrations, just concatenating the ones from before: + type Hextuple = (Triple, Triple); + assert_eq!(::len(), 6); + + // Check the IDs. The index specific functions all return an Option, + // to account for the out-of-range case. + assert_eq!(::nth_id(0), Some(0)); + assert_eq!(::nth_id(1), Some(1)); + assert_eq!(::nth_id(2), Some(2)); + + for n in 0 .. 3 { + ::nth_step(n, &Default::default(), &mut WeightMeter::max_limit()); + } +} From f87a8e4f1154e1ab60c0f6fa8ead89732ae8ce6a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bastian=20K=C3=B6cher?= Date: Thu, 29 Jun 2023 17:00:50 +0200 Subject: [PATCH 62/63] Use impl trait for tuples --- frame/support/src/migrations.rs | 184 ++++++++++++++------------------ 1 file changed, 82 insertions(+), 102 deletions(-) diff --git a/frame/support/src/migrations.rs b/frame/support/src/migrations.rs index 43b5ee9016717..6e8ead6d45791 100644 --- a/frame/support/src/migrations.rs +++ b/frame/support/src/migrations.rs @@ -240,7 +240,7 @@ pub trait SteppedMigration { /// provided as convenience for a caller. A cursor of `None` implies that the migration is at /// its end. TODO: Think about iterator `fuse` requirement. fn step( - cursor: &Option, + cursor: Option, meter: &mut WeightMeter, ) -> Result, SteppedMigrationError>; @@ -249,7 +249,7 @@ pub trait SteppedMigration { mut cursor: Option, meter: &mut WeightMeter, ) -> Result, SteppedMigrationError> { - with_transaction_opaque_err(move || match Self::step(&cursor, meter) { + with_transaction_opaque_err(move || match Self::step(cursor, meter) { Ok(new_cursor) => { cursor = new_cursor; sp_api::TransactionOutcome::Commit(Ok(cursor)) @@ -331,134 +331,115 @@ impl MultiStepMigrator for () { } } -impl SteppedMigration for (Left, Right) { - type Cursor = Either, Option>; - type Identifier = (Left::Identifier, Right::Identifier); - - fn id() -> Self::Identifier { - (Left::id(), Right::id()) - } - - /// # SECURITY - /// - /// Never returns `Some(Right(None))` as cursor. - fn step( - cursor: &Option, - meter: &mut WeightMeter, - ) -> Result, SteppedMigrationError> { - let new_cursor = match cursor { - None => Some(Either::Left(Left::step(&None, meter)?)), - Some(Either::Left(inner @ Some(_))) => Some(Either::Left(Left::step(&inner, meter)?)), - // Left None, need to go right now. - Some(Either::Right(inner)) => { - match Right::step(&inner, meter)? { - None => None, - new_inner @ Some(_) => Some(Either::Right(new_inner)), - } - } - _ => todo!(), - }; - Ok(new_cursor) - } -} - /// Multiple [`SteppedMigration`]. pub trait SteppedMigrations { - type Cursor: codec::FullCodec + codec::MaxEncodedLen; - type Identifier: codec::FullCodec + codec::MaxEncodedLen; - fn len() -> u32; - fn nth_id(n: u32) -> Option; + fn nth_id(n: u32) -> Option>; fn nth_step( n: u32, - cursor: &Option, + cursor: Option>, meter: &mut WeightMeter, - ) -> Option, SteppedMigrationError>>; + ) -> Option>, SteppedMigrationError>>; fn nth_transactional_step( n: u32, - cursor: Option, + cursor: Option>, meter: &mut WeightMeter, - ) -> Option, SteppedMigrationError>>; + ) -> Option>, SteppedMigrationError>>; } -pub trait NonAggregatedMigration {} - -impl + NonAggregatedMigration> SteppedMigrations for T { - type Cursor = Cursor; - type Identifier = Identifier; - +impl SteppedMigrations for T { fn len() -> u32 { 1 } - fn nth_id(n: u32) -> Option { - match n { - 0 => Some(T::id()), - _ => None, - } + fn nth_id(_: u32) -> Option> { + Some(T::id().encode()) } fn nth_step( - n: u32, - cursor: &Option, + _: u32, + cursor: Option>, meter: &mut WeightMeter, - ) -> Option, SteppedMigrationError>> { - match n { - 0 => Some(T::step(cursor, meter)), - _ => None, - } + ) -> Option>, SteppedMigrationError>> { + Some( + T::step(cursor.map(|c| Decode::decode(&mut &c[..]).unwrap()), meter) + .map(|v| v.map(|v| v.encode())), + ) } fn nth_transactional_step( - n: u32, - cursor: Option, + _: u32, + cursor: Option>, meter: &mut WeightMeter, - ) -> Option, SteppedMigrationError>> { - match n { - 0 => Some(T::transactional_step(cursor, meter)), - _ => None, - } + ) -> Option>, SteppedMigrationError>> { + Some( + T::transactional_step(cursor.map(|c| Decode::decode(&mut &c[..]).unwrap()), meter) + .map(|v| v.map(|v| v.encode())), + ) } } -impl, Right: SteppedMigrations, Cursor: codec::FullCodec + codec::MaxEncodedLen + Clone, Identifier: codec::FullCodec + codec::MaxEncodedLen> SteppedMigrations for (Left, Right) { - type Cursor = Cursor; - type Identifier = Identifier; - +#[impl_trait_for_tuples::impl_for_tuples(1, 30)] +impl SteppedMigrations for Tuple { fn len() -> u32 { - Left::len() + Right::len() + let mut i = 0; + + for_tuples!( #( i += Tuple::len(); )* ); + + i } - fn nth_id(n: u32) -> Option { - match Left::nth_id(n) { - None if n > 0 => Right::nth_id(n - 1), - other => other, - } + fn nth_id(n: u32) -> Option> { + let mut i = 0; + + for_tuples!( #( + if (i + Tuple::len()) > n { + return Tuple::nth_id(n - i) + } + + i += Tuple::len(); + )* ); + + None } fn nth_step( n: u32, - cursor: &Option, + cursor: Option>, meter: &mut WeightMeter, - ) -> Option, SteppedMigrationError>> { - match Left::nth_step(n, cursor, meter) { - None if n > 0 => Right::nth_step(n - 1, cursor, meter), - other => other, - } + ) -> Option>, SteppedMigrationError>> { + let mut i = 0; + + for_tuples!( #( + if (i + Tuple::len()) > n { + return Tuple::nth_step(n - i, cursor, meter) + } + + i += Tuple::len(); + )* ); + + None } fn nth_transactional_step( n: u32, - cursor: Option, + cursor: Option>, meter: &mut WeightMeter, - ) -> Option, SteppedMigrationError>> { - match Left::nth_transactional_step(n, cursor.clone(), meter) { - None if n > 0 => Right::nth_transactional_step(n - 1, cursor, meter), - other => other, - } + ) -> Option>, SteppedMigrationError>> { + let mut i = 0; + + for_tuples! ( #( + if (i + Tuple::len()) > n { + return Tuple::nth_transactional_step(n - i, cursor, meter) + } + + i += Tuple::len(); + )* ); + + None } } @@ -478,7 +459,7 @@ impl SteppedMigration for M0 { } fn step( - _cursor: &Option, + _cursor: Option, _meter: &mut WeightMeter, ) -> Result, SteppedMigrationError> { log::info!("M0"); @@ -496,7 +477,7 @@ impl SteppedMigration for M1 { } fn step( - _cursor: &Option, + _cursor: Option, _meter: &mut WeightMeter, ) -> Result, SteppedMigrationError> { log::info!("M1"); @@ -514,7 +495,7 @@ impl SteppedMigration for M2 { } fn step( - _cursor: &Option, + _cursor: Option, _meter: &mut WeightMeter, ) -> Result, SteppedMigrationError> { log::info!("M2"); @@ -522,11 +503,6 @@ impl SteppedMigration for M2 { } } -// mark -impl NonAggregatedMigration for M0 {} -impl NonAggregatedMigration for M1 {} -impl NonAggregatedMigration for M2 {} - #[test] fn templates_work() { sp_tracing::init_for_tests(); @@ -539,11 +515,15 @@ fn templates_work() { // Check the IDs. The index specific functions all return an Option, // to account for the out-of-range case. - assert_eq!(::nth_id(0), Some(0)); - assert_eq!(::nth_id(1), Some(1)); - assert_eq!(::nth_id(2), Some(2)); - - for n in 0 .. 3 { - ::nth_step(n, &Default::default(), &mut WeightMeter::max_limit()); + assert_eq!(::nth_id(0), Some(0u8.encode())); + assert_eq!(::nth_id(1), Some(1u8.encode())); + assert_eq!(::nth_id(2), Some(2u8.encode())); + + for n in 0..3 { + ::nth_step( + n, + Default::default(), + &mut WeightMeter::max_limit(), + ); } } From f3f76b62690d66a0361aa01eb68dc09ad85ec1ec Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bastian=20K=C3=B6cher?= Date: Thu, 29 Jun 2023 17:22:47 +0200 Subject: [PATCH 63/63] Simplify --- frame/support/src/migrations.rs | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/frame/support/src/migrations.rs b/frame/support/src/migrations.rs index 6e8ead6d45791..ad7c311e7914f 100644 --- a/frame/support/src/migrations.rs +++ b/frame/support/src/migrations.rs @@ -385,11 +385,7 @@ impl SteppedMigrations for T { #[impl_trait_for_tuples::impl_for_tuples(1, 30)] impl SteppedMigrations for Tuple { fn len() -> u32 { - let mut i = 0; - - for_tuples!( #( i += Tuple::len(); )* ); - - i + for_tuples!( #( Tuple::len() )+* ) } fn nth_id(n: u32) -> Option> {