Skip to content

Commit

Permalink
Multi-Block-Migrations, poll hook and new System callbacks (parityt…
Browse files Browse the repository at this point in the history
…ech#1781)

This MR is the merge of
paritytech/substrate#14414 and
paritytech/substrate#14275. It implements
[RFC#13](polkadot-fellows/RFCs#13), closes
paritytech#198.

----- 

This Merge request introduces three major topicals:

1. Multi-Block-Migrations
1. New pallet `poll` hook for periodic service work
1. Replacement hooks for `on_initialize` and `on_finalize` in cases
where `poll` cannot be used

and some more general changes to FRAME.  
The changes for each topical span over multiple crates. They are listed
in topical order below.

# 1.) Multi-Block-Migrations

Multi-Block-Migrations are facilitated by creating `pallet_migrations`
and configuring `System::Config::MultiBlockMigrator` to point to it.
Executive picks this up and triggers one step of the migrations pallet
per block.
The chain is in lockdown mode for as long as an MBM is ongoing.
Executive does this by polling `MultiBlockMigrator::ongoing` and not
allowing any transaction in a block, if true.

A MBM is defined through trait `SteppedMigration`. A condensed version
looks like this:
```rust
/// A migration that can proceed in multiple steps.
pub trait SteppedMigration {
	type Cursor: FullCodec + MaxEncodedLen;
	type Identifier: FullCodec + MaxEncodedLen;

	fn id() -> Self::Identifier;

	fn max_steps() -> Option<u32>;

	fn step(
		cursor: Option<Self::Cursor>,
		meter: &mut WeightMeter,
	) -> Result<Option<Self::Cursor>, SteppedMigrationError>;
}
```

`pallet_migrations` can be configured with an aggregated tuple of these
migrations. It then starts to migrate them one-by-one on the next
runtime upgrade.
Two things are important here:
- 1. Doing another runtime upgrade while MBMs are ongoing is not a good
idea and can lead to messed up state.
- 2. **Pallet Migrations MUST BE CONFIGURED IN `System::Config`,
otherwise it is not used.**

The pallet supports an `UpgradeStatusHandler` that can be used to notify
external logic of upgrade start/finish (for example to pause XCM
dispatch).

Error recovery is very limited in the case that a migration errors or
times out (exceeds its `max_steps`). Currently the runtime dev can
decide in `FailedMigrationHandler::failed` how to handle this. One
follow-up would be to pair this with the `SafeMode` pallet and enact
safe mode when an upgrade fails, to allow governance to rescue the
chain. This is currently not possible, since governance is not
`Mandatory`.

## Runtime API

- `Core`: `initialize_block` now returns `ExtrinsicInclusionMode` to
inform the Block Author whether they can push transactions.

### Integration

Add it to your runtime implementation of `Core` and `BlockBuilder`:
```patch
diff --git a/runtime/src/lib.rs b/runtime/src/lib.rs
@@ impl_runtime_apis! {
	impl sp_block_builder::Core<Block> for Runtime {
-		fn initialize_block(header: &<Block as BlockT>::Header) {
+		fn initialize_block(header: &<Block as BlockT>::Header) -> RuntimeExecutiveMode {
			Executive::initialize_block(header)
		}

		...
	}
```

# 2.) `poll` hook

A new pallet hook is introduced: `poll`. `Poll` is intended to replace
mostly all usage of `on_initialize`.
The reason for this is that any code that can be called from
`on_initialize` cannot be migrated through an MBM. Currently there is no
way to statically check this; the implication is to use `on_initialize`
as rarely as possible.
Failing to do so can result in broken storage invariants.

The implementation of the poll hook depends on the `Runtime API` changes
that are explained above.

# 3.) Hard-Deadline callbacks

Three new callbacks are introduced and configured on `System::Config`:
`PreInherents`, `PostInherents` and `PostTransactions`.
These hooks are meant as replacement for `on_initialize` and
`on_finalize` in cases where the code that runs cannot be moved to
`poll`.
The reason for this is to make the usage of HD-code (hard deadline) more
explicit - again to prevent broken invariants by MBMs.

# 4.) FRAME (general changes)

## `frame_system` pallet

A new memorize storage item `InherentsApplied` is added. It is used by
executive to track whether inherents have already been applied.
Executive and can then execute the MBMs directly between inherents and
transactions.

The `Config` gets five new items:
- `SingleBlockMigrations` this is the new way of configuring migrations
that run in a single block. Previously they were defined as last generic
argument of `Executive`. This shift is brings all central configuration
about migrations closer into view of the developer (migrations that are
configured in `Executive` will still work for now but is deprecated).
- `MultiBlockMigrator` this can be configured to an engine that drives
MBMs. One example would be the `pallet_migrations`. Note that this is
only the engine; the exact MBMs are injected into the engine.
- `PreInherents` a callback that executes after `on_initialize` but
before inherents.
- `PostInherents` a callback that executes after all inherents ran
(including MBMs and `poll`).
- `PostTransactions` in symmetry to `PreInherents`, this one is called
before `on_finalize` but after all transactions.

A sane default is to set all of these to `()`. Example diff suitable for
any chain:
```patch
@@ impl frame_system::Config for Test {
 	type MaxConsumers = ConstU32<16>;
+	type SingleBlockMigrations = ();
+	type MultiBlockMigrator = ();
+	type PreInherents = ();
+	type PostInherents = ();
+	type PostTransactions = ();
 }
```

An overview of how the block execution now looks like is here. The same
graph is also in the rust doc.

<details><summary>Block Execution Flow</summary>
<p>

![Screenshot 2023-12-04 at 19 11
29](https://github.com/paritytech/polkadot-sdk/assets/10380170/e88a80c4-ef11-4faa-8df5-8b33a724c054)

</p>
</details> 

## Inherent Order

Moved to paritytech#2154

---------------


## TODO

- [ ] Check that `try-runtime` still works
- [ ] Ensure backwards compatibility with old Runtime APIs
- [x] Consume weight correctly
- [x] Cleanup

---------

Signed-off-by: Oliver Tale-Yazdi <oliver.tale-yazdi@parity.io>
Co-authored-by: Liam Aharon <liam.aharon@hotmail.com>
Co-authored-by: Juan Girini <juangirini@gmail.com>
Co-authored-by: command-bot <>
Co-authored-by: Francisco Aguirre <franciscoaguirreperez@gmail.com>
Co-authored-by: Gavin Wood <gavin@parity.io>
Co-authored-by: Bastian Köcher <git@kchr.de>
  • Loading branch information
6 people authored Feb 28, 2024
1 parent ba97338 commit f3544bb
Show file tree
Hide file tree
Showing 47 changed files with 4,647 additions and 1,081 deletions.
5 changes: 3 additions & 2 deletions substrate/bin/minimal/runtime/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,8 @@ use frame::{
prelude::*,
runtime::{
apis::{
self, impl_runtime_apis, ApplyExtrinsicResult, CheckInherentsResult, OpaqueMetadata,
self, impl_runtime_apis, ApplyExtrinsicResult, CheckInherentsResult,
ExtrinsicInclusionMode, OpaqueMetadata,
},
prelude::*,
},
Expand Down Expand Up @@ -121,7 +122,7 @@ impl_runtime_apis! {
RuntimeExecutive::execute_block(block)
}

fn initialize_block(header: &Header) {
fn initialize_block(header: &Header) -> ExtrinsicInclusionMode {
RuntimeExecutive::initialize_block(header)
}
}
Expand Down
2 changes: 1 addition & 1 deletion substrate/bin/node-template/runtime/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -331,7 +331,7 @@ impl_runtime_apis! {
Executive::execute_block(block);
}

fn initialize_block(header: &<Block as BlockT>::Header) {
fn initialize_block(header: &<Block as BlockT>::Header) -> sp_runtime::ExtrinsicInclusionMode {
Executive::initialize_block(header)
}
}
Expand Down
4 changes: 4 additions & 0 deletions substrate/bin/node/runtime/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,7 @@ pallet-election-provider-support-benchmarking = { path = "../../../frame/electio
pallet-elections-phragmen = { path = "../../../frame/elections-phragmen", default-features = false }
pallet-example-tasks = { path = "../../../frame/examples/tasks", default-features = false }
pallet-fast-unstake = { path = "../../../frame/fast-unstake", default-features = false }
pallet-migrations = { path = "../../../frame/migrations", default-features = false }
pallet-nis = { path = "../../../frame/nis", default-features = false }
pallet-grandpa = { path = "../../../frame/grandpa", default-features = false }
pallet-im-online = { path = "../../../frame/im-online", default-features = false }
Expand Down Expand Up @@ -198,6 +199,7 @@ std = [
"pallet-lottery/std",
"pallet-membership/std",
"pallet-message-queue/std",
"pallet-migrations/std",
"pallet-mixnet/std",
"pallet-mmr/std",
"pallet-multisig/std",
Expand Down Expand Up @@ -302,6 +304,7 @@ runtime-benchmarks = [
"pallet-lottery/runtime-benchmarks",
"pallet-membership/runtime-benchmarks",
"pallet-message-queue/runtime-benchmarks",
"pallet-migrations/runtime-benchmarks",
"pallet-mixnet/runtime-benchmarks",
"pallet-mmr/runtime-benchmarks",
"pallet-multisig/runtime-benchmarks",
Expand Down Expand Up @@ -381,6 +384,7 @@ try-runtime = [
"pallet-lottery/try-runtime",
"pallet-membership/try-runtime",
"pallet-message-queue/try-runtime",
"pallet-migrations/try-runtime",
"pallet-mixnet/try-runtime",
"pallet-mmr/try-runtime",
"pallet-multisig/try-runtime",
Expand Down
24 changes: 23 additions & 1 deletion substrate/bin/node/runtime/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -310,6 +310,7 @@ impl frame_system::Config for Runtime {
type SystemWeightInfo = frame_system::weights::SubstrateWeight<Runtime>;
type SS58Prefix = ConstU16<42>;
type MaxConsumers = ConstU32<16>;
type MultiBlockMigrator = MultiBlockMigrations;
}

impl pallet_insecure_randomness_collective_flip::Config for Runtime {}
Expand Down Expand Up @@ -2007,6 +2008,25 @@ impl pallet_statement::Config for Runtime {
type MaxAllowedBytes = MaxAllowedBytes;
}

parameter_types! {
pub MbmServiceWeight: Weight = Perbill::from_percent(80) * RuntimeBlockWeights::get().max_block;
}

impl pallet_migrations::Config for Runtime {
type RuntimeEvent = RuntimeEvent;
#[cfg(not(feature = "runtime-benchmarks"))]
type Migrations = ();
// Benchmarks need mocked migrations to guarantee that they succeed.
#[cfg(feature = "runtime-benchmarks")]
type Migrations = pallet_migrations::mock_helpers::MockedMigrations;
type CursorMaxLen = ConstU32<65_536>;
type IdentifierMaxLen = ConstU32<256>;
type MigrationStatusHandler = ();
type FailedMigrationHandler = frame_support::migrations::FreezeChainOnFailedMigration;
type MaxServiceWeight = MbmServiceWeight;
type WeightInfo = pallet_migrations::weights::SubstrateWeight<Runtime>;
}

parameter_types! {
pub const BrokerPalletId: PalletId = PalletId(*b"py/broke");
}
Expand Down Expand Up @@ -2241,6 +2261,7 @@ construct_runtime!(
TxPause: pallet_tx_pause,
SafeMode: pallet_safe_mode,
Statement: pallet_statement,
MultiBlockMigrations: pallet_migrations,
Broker: pallet_broker,
TasksExample: pallet_example_tasks,
Mixnet: pallet_mixnet,
Expand Down Expand Up @@ -2372,6 +2393,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::<Runtime>]
Expand Down Expand Up @@ -2417,7 +2439,7 @@ impl_runtime_apis! {
Executive::execute_block(block);
}

fn initialize_block(header: &<Block as BlockT>::Header) {
fn initialize_block(header: &<Block as BlockT>::Header) -> sp_runtime::ExtrinsicInclusionMode {
Executive::initialize_block(header)
}
}
Expand Down
13 changes: 7 additions & 6 deletions substrate/client/basic-authorship/src/basic_authorship.rs
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,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,
Digest, ExtrinsicInclusionMode, Percent, SaturatedConversion,
};
use std::{marker::PhantomData, pin::Pin, sync::Arc, time};

Expand Down Expand Up @@ -335,11 +335,12 @@ where

self.apply_inherents(&mut block_builder, inherent_data)?;

// TODO call `after_inherents` and check if we should apply extrinsincs here
// <https://github.com/paritytech/substrate/pull/14275/>

let end_reason =
self.apply_extrinsics(&mut block_builder, deadline, block_size_limit).await?;
let mode = block_builder.extrinsic_inclusion_mode();
let end_reason = match mode {
ExtrinsicInclusionMode::AllExtrinsics =>
self.apply_extrinsics(&mut block_builder, deadline, block_size_limit).await?,
ExtrinsicInclusionMode::OnlyInherents => EndProposingReason::TransactionForbidden,
};
let (block, storage_changes, proof) = block_builder.build()?.into_inner();
let block_took = block_timer.elapsed();

Expand Down
26 changes: 22 additions & 4 deletions substrate/client/block-builder/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ use sp_core::traits::CallContext;
use sp_runtime::{
legacy,
traits::{Block as BlockT, Hash, HashingFor, Header as HeaderT, NumberFor, One},
Digest,
Digest, ExtrinsicInclusionMode,
};
use std::marker::PhantomData;

Expand Down Expand Up @@ -198,10 +198,12 @@ pub struct BlockBuilder<'a, Block: BlockT, C: ProvideRuntimeApi<Block> + 'a> {
extrinsics: Vec<Block::Extrinsic>,
api: ApiRef<'a, C::Api>,
call_api_at: &'a C,
/// Version of the [`BlockBuilderApi`] runtime API.
version: u32,
parent_hash: Block::Hash,
/// The estimated size of the block header.
estimated_header_size: usize,
extrinsic_inclusion_mode: ExtrinsicInclusionMode,
}

impl<'a, Block, C> BlockBuilder<'a, Block, C>
Expand Down Expand Up @@ -244,22 +246,38 @@ where

api.set_call_context(CallContext::Onchain);

api.initialize_block(parent_hash, &header)?;
let core_version = api
.api_version::<dyn Core<Block>>(parent_hash)?
.ok_or_else(|| Error::VersionInvalid("Core".to_string()))?;

let version = api
let extrinsic_inclusion_mode = if core_version >= 5 {
api.initialize_block(parent_hash, &header)?
} else {
#[allow(deprecated)]
api.initialize_block_before_version_5(parent_hash, &header)?;
ExtrinsicInclusionMode::AllExtrinsics
};

let bb_version = api
.api_version::<dyn BlockBuilderApi<Block>>(parent_hash)?
.ok_or_else(|| Error::VersionInvalid("BlockBuilderApi".to_string()))?;

Ok(Self {
parent_hash,
extrinsics: Vec::new(),
api,
version,
version: bb_version,
estimated_header_size,
call_api_at,
extrinsic_inclusion_mode,
})
}

/// The extrinsic inclusion mode of the runtime for this block.
pub fn extrinsic_inclusion_mode(&self) -> ExtrinsicInclusionMode {
self.extrinsic_inclusion_mode
}

/// Push onto the block's list of extrinsics.
///
/// This will ensure the extrinsic can be validly executed (by executing it).
Expand Down
4 changes: 4 additions & 0 deletions substrate/client/proposer-metrics/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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 transactions are allowed in the block.
TransactionForbidden,
}

/// Authorship metrics.
Expand Down Expand Up @@ -112,6 +115,7 @@ impl Metrics {
EndProposingReason::NoMoreTransactions => "no_more_transactions",
EndProposingReason::HitBlockSizeLimit => "hit_block_size_limit",
EndProposingReason::HitBlockWeightLimit => "hit_block_weight_limit",
EndProposingReason::TransactionForbidden => "transactions_forbidden",
};

self.end_proposing_reason.with_label_values(&[reason]).inc();
Expand Down
6 changes: 3 additions & 3 deletions substrate/client/rpc-spec-v2/src/chain_head/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -242,12 +242,12 @@ async fn follow_with_runtime() {
let event: FollowEvent<String> = get_next_event(&mut sub).await;

// it is basically json-encoded substrate_test_runtime_client::runtime::VERSION
let runtime_str = "{\"specName\":\"test\",\"implName\":\"parity-test\",\"authoringVersion\":0,\
\"specVersion\":2,\"implVersion\":2,\"apis\":[[\"0xdf6acb689907609b\",4],\
let runtime_str = "{\"specName\":\"test\",\"implName\":\"parity-test\",\"authoringVersion\":1,\
\"specVersion\":2,\"implVersion\":2,\"apis\":[[\"0xdf6acb689907609b\",5],\
[\"0x37e397fc7c91f5e4\",2],[\"0xd2bc9897eed08f15\",3],[\"0x40fe3ad401f8959a\",6],\
[\"0xbc9d89904f5b923f\",1],[\"0xc6e9a76309f39b09\",2],[\"0xdd718d5cc53262d4\",1],\
[\"0xcbca25e39f142387\",2],[\"0xf78b278be53f454c\",2],[\"0xab3c0572291feb8b\",1],\
[\"0xed99c5acb25eedf5\",3],[\"0xfbc577b9d747efd6\",1]],\"transactionVersion\":1,\"stateVersion\":0}";
[\"0xed99c5acb25eedf5\",3],[\"0xfbc577b9d747efd6\",1]],\"transactionVersion\":1,\"stateVersion\":1}";

let runtime: RuntimeVersion = serde_json::from_str(runtime_str).unwrap();

Expand Down
2 changes: 1 addition & 1 deletion substrate/client/rpc/src/state/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -475,7 +475,7 @@ async fn should_return_runtime_version() {

// it is basically json-encoded substrate_test_runtime_client::runtime::VERSION
let result = "{\"specName\":\"test\",\"implName\":\"parity-test\",\"authoringVersion\":1,\
\"specVersion\":2,\"implVersion\":2,\"apis\":[[\"0xdf6acb689907609b\",4],\
\"specVersion\":2,\"implVersion\":2,\"apis\":[[\"0xdf6acb689907609b\",5],\
[\"0x37e397fc7c91f5e4\",2],[\"0xd2bc9897eed08f15\",3],[\"0x40fe3ad401f8959a\",6],\
[\"0xbc9d89904f5b923f\",1],[\"0xc6e9a76309f39b09\",2],[\"0xdd718d5cc53262d4\",1],\
[\"0xcbca25e39f142387\",2],[\"0xf78b278be53f454c\",2],[\"0xab3c0572291feb8b\",1],\
Expand Down
2 changes: 2 additions & 0 deletions substrate/frame/executive/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ workspace = true
targets = ["x86_64-unknown-linux-gnu"]

[dependencies]
aquamarine = "0.3.2"
codec = { package = "parity-scale-codec", version = "3.6.1", default-features = false, features = [
"derive",
] }
Expand Down Expand Up @@ -44,6 +45,7 @@ default = ["std"]
with-tracing = ["sp-tracing/with-tracing"]
std = [
"codec/std",
"frame-support/experimental",
"frame-support/std",
"frame-system/std",
"frame-try-runtime/std",
Expand Down
Loading

0 comments on commit f3544bb

Please sign in to comment.