referrerpolicy=no-referrer-when-downgrade

cumulus_pallet_aura_ext/
consensus_hook.rs

1// Copyright (C) Parity Technologies (UK) Ltd.
2// This file is part of Cumulus.
3// SPDX-License-Identifier: Apache-2.0
4
5// Licensed under the Apache License, Version 2.0 (the "License");
6// you may not use this file except in compliance with the License.
7// You may obtain a copy of the License at
8//
9// 	http://www.apache.org/licenses/LICENSE-2.0
10//
11// Unless required by applicable law or agreed to in writing, software
12// distributed under the License is distributed on an "AS IS" BASIS,
13// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14// See the License for the specific language governing permissions and
15// limitations under the License.
16
17//! The definition of a [`FixedVelocityConsensusHook`] for consensus logic to manage
18//! block velocity.
19use super::{pallet, Aura};
20use core::{marker::PhantomData, num::NonZeroU32};
21use cumulus_pallet_parachain_system::{
22	self as parachain_system,
23	consensus_hook::{ConsensusHook, UnincludedSegmentCapacity},
24	relay_state_snapshot::RelayChainStateProof,
25};
26use frame_support::pallet_prelude::*;
27use sp_consensus_aura::{Slot, SlotDuration};
28
29/// A consensus hook that enforces fixed block production velocity and unincluded segment capacity.
30///
31/// It keeps track of relay chain slot information and parachain blocks authored per relay chain
32/// slot.
33///
34/// # Type Parameters
35/// - `T` - The runtime configuration trait
36/// - `RELAY_CHAIN_SLOT_DURATION_MILLIS` - Duration of relay chain slots in milliseconds
37/// - `V` - Maximum number of blocks that can be authored per relay chain parent (velocity)
38/// - `C` - Maximum capacity of unincluded segment
39///
40/// # Example Configuration
41/// ```ignore
42/// type ConsensusHook = FixedVelocityConsensusHook<Runtime, 6000, 2, 8>;
43/// ```
44/// This configures:
45/// - 6 second relay chain slots
46/// - Maximum 2 blocks per slot
47/// - Maximum 8 blocks in unincluded segment
48pub struct FixedVelocityConsensusHook<
49	T,
50	const RELAY_CHAIN_SLOT_DURATION_MILLIS: u32,
51	const V: u32,
52	const C: u32,
53>(PhantomData<T>);
54
55impl<
56		T: pallet::Config,
57		const RELAY_CHAIN_SLOT_DURATION_MILLIS: u32,
58		const V: u32,
59		const C: u32,
60	> ConsensusHook for FixedVelocityConsensusHook<T, RELAY_CHAIN_SLOT_DURATION_MILLIS, V, C>
61where
62	<T as pallet_timestamp::Config>::Moment: Into<u64>,
63{
64	/// Consensus hook that performs validations on the provided relay chain state
65	/// proof:
66	/// - Ensures blocks are not produced faster than the specified velocity `V`
67	/// - Verifies parachain slot alignment with relay chain slot
68	///
69	/// # Panics
70	/// - When the relay chain slot from the state is smaller than the slot from the proof
71	/// - When the number of authored blocks exceeds velocity limit
72	/// - When parachain slot is ahead of the calculated slot from relay chain
73	fn on_state_proof(state_proof: &RelayChainStateProof) -> (Weight, UnincludedSegmentCapacity) {
74		// Ensure velocity is non-zero.
75		let velocity = V.max(1);
76		let relay_chain_slot = state_proof.read_slot().expect("failed to read relay chain slot");
77
78		let (relay_chain_slot, authored_in_relay) = match pallet::RelaySlotInfo::<T>::get() {
79			Some((slot, authored)) if slot == relay_chain_slot => (slot, authored),
80			Some((slot, _)) if slot < relay_chain_slot => (relay_chain_slot, 0),
81			Some((slot, _)) => {
82				panic!("Slot moved backwards: stored_slot={slot:?}, relay_chain_slot={relay_chain_slot:?}")
83			},
84			None => (relay_chain_slot, 0),
85		};
86
87		// We need to allow one additional block to be built to fill the unincluded segment.
88		if authored_in_relay > velocity {
89			panic!("authored blocks limit is reached for the slot: relay_chain_slot={relay_chain_slot:?}, authored={authored_in_relay:?}, velocity={velocity:?}");
90		}
91
92		pallet::RelaySlotInfo::<T>::put((relay_chain_slot, authored_in_relay + 1));
93
94		let para_slot = pallet_aura::CurrentSlot::<T>::get();
95
96		// Convert relay chain timestamp.
97		let relay_chain_timestamp =
98			u64::from(RELAY_CHAIN_SLOT_DURATION_MILLIS).saturating_mul(*relay_chain_slot);
99
100		let para_slot_duration = SlotDuration::from_millis(Aura::<T>::slot_duration().into());
101		let para_slot_from_relay =
102			Slot::from_timestamp(relay_chain_timestamp.into(), para_slot_duration);
103
104		if *para_slot > *para_slot_from_relay {
105			panic!(
106				"Parachain slot is too far in the future: parachain_slot={:?}, derived_from_relay_slot={:?} velocity={:?}, relay_chain_slot={:?}",
107				para_slot,
108				para_slot_from_relay,
109				velocity,
110				relay_chain_slot
111			);
112		}
113
114		let weight = T::DbWeight::get().reads(1);
115
116		(
117			weight,
118			NonZeroU32::new(core::cmp::max(C, 1))
119				.expect("1 is the minimum value and non-zero; qed")
120				.into(),
121		)
122	}
123}
124
125impl<
126		T: pallet::Config + parachain_system::Config,
127		const RELAY_CHAIN_SLOT_DURATION_MILLIS: u32,
128		const V: u32,
129		const C: u32,
130	> FixedVelocityConsensusHook<T, RELAY_CHAIN_SLOT_DURATION_MILLIS, V, C>
131{
132	/// Whether it is legal to extend the chain, assuming the given block is the most
133	/// recently included one as-of the relay parent that will be built against, and
134	/// the given slot.
135	///
136	/// This should be consistent with the logic the runtime uses when validating blocks to
137	/// avoid issues.
138	///
139	/// When the unincluded segment is empty, i.e. `included_hash == at`, where at is the block
140	/// whose state we are querying against, this must always return `true` as long as the slot
141	/// is more recent than the included block itself.
142	pub fn can_build_upon(included_hash: T::Hash, new_slot: Slot) -> bool {
143		let velocity = V.max(1);
144		let (last_slot, authored_so_far) = match pallet::RelaySlotInfo::<T>::get() {
145			None => return true,
146			Some(x) => x,
147		};
148
149		let size_after_included =
150			parachain_system::Pallet::<T>::unincluded_segment_size_after(included_hash);
151
152		// can never author when the unincluded segment is full.
153		if size_after_included >= C {
154			return false
155		}
156
157		// Check that we have not authored more than `V + 1` parachain blocks in the current relay
158		// chain slot.
159		if last_slot == new_slot {
160			authored_so_far < velocity + 1
161		} else {
162			// disallow slot from moving backwards.
163			last_slot < new_slot
164		}
165	}
166}