1
// Copyright Moonsong Labs
2
// This file is part of Moonkit.
3

            
4
// Moonkit is free software: you can redistribute it and/or modify
5
// it under the terms of the GNU General Public License as published by
6
// the Free Software Foundation, either version 3 of the License, or
7
// (at your option) any later version.
8

            
9
// Moonkit is distributed in the hope that it will be useful,
10
// but WITHOUT ANY WARRANTY; without even the implied warranty of
11
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12
// GNU General Public License for more details.
13

            
14
// You should have received a copy of the GNU General Public License
15
// along with Moonkit.  If not, see <http://www.gnu.org/licenses/>.
16

            
17
//! The definition of a [`FixedVelocityConsensusHook`] for consensus logic to manage
18
//! block velocity.
19
//!
20
//! The velocity `V` refers to the rate of block processing by the relay chain.
21

            
22
use crate::*;
23
use cumulus_pallet_parachain_system::{
24
	self as parachain_system,
25
	consensus_hook::{ConsensusHook, UnincludedSegmentCapacity},
26
	relay_state_snapshot::RelayChainStateProof,
27
};
28
use frame_support::pallet_prelude::*;
29
use sp_consensus_slots::Slot;
30
use sp_std::{marker::PhantomData, num::NonZeroU32};
31

            
32
#[cfg(tests)]
33
type RelayChainStateProof = crate::mock::FakeRelayChainStateProof;
34

            
35
/// A consensus hook for a fixed block processing velocity and unincluded segment capacity.
36
///
37
/// Relay chain slot duration must be provided in milliseconds.
38
pub struct FixedVelocityConsensusHook<T, const V: u32, const C: u32>(PhantomData<T>);
39

            
40
impl<T: pallet::Config, const V: u32, const C: u32> ConsensusHook
41
	for FixedVelocityConsensusHook<T, V, C>
42
where
43
	<T as pallet_timestamp::Config>::Moment: Into<u64>,
44
{
45
	// Validates the number of authored blocks within the slot with respect to the `V + 1` limit.
46
	fn on_state_proof(state_proof: &RelayChainStateProof) -> (Weight, UnincludedSegmentCapacity) {
47
		let relay_chain_slot = state_proof
48
			.read_slot()
49
			.expect("failed to read relay chain slot");
50

            
51
		Self::on_state_proof_inner(relay_chain_slot)
52
	}
53
}
54

            
55
impl<T: pallet::Config, const V: u32, const C: u32> FixedVelocityConsensusHook<T, V, C>
56
where
57
	<T as pallet_timestamp::Config>::Moment: Into<u64>,
58
{
59
	pub(crate) fn on_state_proof_inner(
60
		relay_chain_slot: cumulus_primitives_core::relay_chain::Slot,
61
	) -> (Weight, UnincludedSegmentCapacity) {
62
		// Ensure velocity is non-zero.
63
		let velocity = V.max(1);
64

            
65
		// Get and verify the parachain slot
66
		let new_slot = T::GetAndVerifySlot::get_and_verify_slot(&relay_chain_slot)
67
			.expect("slot number mismatch");
68

            
69
		// Update Slot Info
70
		let authored = match SlotInfo::<T>::get() {
71
			Some((slot, authored)) if slot == new_slot => {
72
				if !T::AllowMultipleBlocksPerSlot::get() {
73
					panic!("Block invalid; Supplied slot number is not high enough");
74
				}
75
				authored + 1
76
			}
77
			Some((slot, _)) if slot < new_slot => 1,
78
			Some(..) => {
79
				panic!("slot moved backwards")
80
			}
81
			None => 1,
82
		};
83

            
84
		// Perform checks.
85
		if authored > velocity + 1 {
86
			panic!("authored blocks limit is reached for the slot")
87
		}
88

            
89
		// Store new slot info
90
		SlotInfo::<T>::put((new_slot, authored));
91

            
92
		// Account weights
93
		let weight = T::DbWeight::get().reads_writes(1, 1);
94

            
95
		// Return weight and unincluded segment capacity
96
		(
97
			weight,
98
			NonZeroU32::new(sp_std::cmp::max(C, 1))
99
				.expect("1 is the minimum value and non-zero; qed")
100
				.into(),
101
		)
102
	}
103
}
104

            
105
impl<T: pallet::Config + parachain_system::Config, const V: u32, const C: u32>
106
	FixedVelocityConsensusHook<T, V, C>
107
{
108
	/// Whether it is legal to extend the chain, assuming the given block is the most
109
	/// recently included one as-of the relay parent that will be built against, and
110
	/// the given slot.
111
	///
112
	/// This should be consistent with the logic the runtime uses when validating blocks to
113
	/// avoid issues.
114
	///
115
	/// When the unincluded segment is empty, i.e. `included_hash == at`, where at is the block
116
	/// whose state we are querying against, this must always return `true` as long as the slot
117
	/// is more recent than the included block itself.
118
	pub fn can_build_upon(included_hash: T::Hash, new_slot: Slot) -> bool {
119
		let velocity = V.max(1);
120
		let (last_slot, authored_so_far) = match pallet::Pallet::<T>::slot_info() {
121
			None => return true,
122
			Some(x) => x,
123
		};
124

            
125
		let size_after_included =
126
			parachain_system::Pallet::<T>::unincluded_segment_size_after(included_hash);
127

            
128
		// can never author when the unincluded segment is full.
129
		if size_after_included >= C {
130
			return false;
131
		}
132

            
133
		if last_slot == new_slot {
134
			authored_so_far < velocity + 1
135
		} else {
136
			// disallow slot from moving backwards.
137
			last_slot < new_slot
138
		}
139
	}
140
}