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
//! Pallet that allows block authors to include their identity in a block via an inherent.
18
//! Currently the author does not _prove_ their identity, just states it. So it should not be used,
19
//! for things like equivocation slashing that require authenticated authorship information.
20

            
21
#![cfg_attr(not(feature = "std"), no_std)]
22

            
23
use frame_support::traits::{FindAuthor, Get};
24
use nimbus_primitives::{
25
	AccountLookup, CanAuthor, NimbusId, SlotBeacon, INHERENT_IDENTIFIER, NIMBUS_ENGINE_ID,
26
};
27
use parity_scale_codec::{Decode, Encode, FullCodec};
28
use sp_inherents::{InherentIdentifier, IsFatalError};
29
use sp_runtime::{ConsensusEngineId, RuntimeString};
30

            
31
pub use crate::weights::WeightInfo;
32
pub use exec::BlockExecutor;
33
pub use pallet::*;
34

            
35
#[cfg(any(test, feature = "runtime-benchmarks"))]
36
mod benchmarks;
37

            
38
pub mod weights;
39

            
40
mod exec;
41

            
42
#[cfg(test)]
43
mod mock;
44
#[cfg(test)]
45
mod tests;
46

            
47
#[frame_support::pallet]
48
pub mod pallet {
49
	use super::*;
50
	use frame_support::pallet_prelude::*;
51
	use frame_system::pallet_prelude::*;
52

            
53
	/// The Author Inherent pallet. The core of the nimbus consensus framework's runtime presence.
54
	#[pallet::pallet]
55
	pub struct Pallet<T>(PhantomData<T>);
56

            
57
	#[pallet::config]
58
	pub trait Config: frame_system::Config {
59
		/// Type used to refer to a block author.
60
		type AuthorId: sp_std::fmt::Debug + PartialEq + Clone + FullCodec + TypeInfo + MaxEncodedLen;
61

            
62
		/// A type to convert between NimbusId and AuthorId. This is useful when you want to associate
63
		/// Block authoring behavior with an AuthorId for rewards or slashing. If you do not need to
64
		/// hold an AuthorId responsible for authoring use `()` which acts as an identity mapping.
65
		type AccountLookup: AccountLookup<Self::AuthorId>;
66

            
67
		/// The final word on whether the reported author can author at this height.
68
		/// This will be used when executing the inherent. This check is often stricter than the
69
		/// Preliminary check, because it can use more data.
70
		/// If the pallet that implements this trait depends on an inherent, that inherent **must**
71
		/// be included before this one.
72
		type CanAuthor: CanAuthor<Self::AuthorId>;
73

            
74
		/// Some way of determining the current slot for purposes of verifying the author's eligibility
75
		type SlotBeacon: SlotBeacon;
76

            
77
		type WeightInfo: WeightInfo;
78
	}
79

            
80
	impl<T> sp_runtime::BoundToRuntimeAppPublic for Pallet<T> {
81
		type Public = NimbusId;
82
	}
83

            
84
	#[pallet::error]
85
	pub enum Error<T> {
86
		/// Author already set in block.
87
		AuthorAlreadySet,
88
		/// No AccountId was found to be associated with this author
89
		NoAccountId,
90
		/// The author in the inherent is not an eligible author.
91
		CannotBeAuthor,
92
	}
93

            
94
	/// Author of current block.
95
	#[pallet::storage]
96
	pub type Author<T: Config> = StorageValue<_, T::AuthorId, OptionQuery>;
97

            
98
	/// Check if the inherent was included
99
	#[pallet::storage]
100
	pub type InherentIncluded<T: Config> = StorageValue<_, bool, ValueQuery>;
101

            
102
	#[pallet::hooks]
103
	impl<T: Config> Hooks<BlockNumberFor<T>> for Pallet<T> {
104
		fn on_initialize(_: BlockNumberFor<T>) -> Weight {
105
			// Now extract the author from the digest
106
			let digest = <frame_system::Pallet<T>>::digest();
107
			let pre_runtime_digests = digest.logs.iter().filter_map(|d| d.as_pre_runtime());
108
			if let Some(author) = Self::find_author(pre_runtime_digests) {
109
				// Store the author so we can confirm eligibility after the inherents have executed
110
				<Author<T>>::put(&author);
111
			}
112

            
113
			// on_initialize: 1 write
114
			// on_finalize: 1 read + 1 write
115
			T::DbWeight::get().reads_writes(1, 2)
116
		}
117
		fn on_finalize(_: BlockNumberFor<T>) {
118
			// According to parity, the only way to ensure that a mandatory inherent is included
119
			// is by checking on block finalization that the inherent set a particular storage item:
120
			// https://github.com/paritytech/polkadot-sdk/issues/2841#issuecomment-1876040854
121
			assert!(
122
				InherentIncluded::<T>::take(),
123
				"Block invalid, missing inherent `kick_off_authorship_validation`"
124
			);
125
		}
126
	}
127

            
128
	#[pallet::call]
129
	impl<T: Config> Pallet<T> {
130
		/// This inherent is a workaround to run code after the "real" inherents have executed,
131
		/// but before transactions are executed.
132
		// This should go into on_post_inherents when it is ready https://github.com/paritytech/substrate/pull/10128
133
		// TODO better weight. For now we just set a somewhat conservative fudge factor
134
		#[pallet::call_index(0)]
135
		#[pallet::weight((T::WeightInfo::kick_off_authorship_validation(), DispatchClass::Mandatory))]
136
		pub fn kick_off_authorship_validation(origin: OriginFor<T>) -> DispatchResultWithPostInfo {
137
			ensure_none(origin)?;
138

            
139
			// First check that the slot number is valid (greater than the previous highest)
140
			let new_slot = T::SlotBeacon::slot();
141

            
142
			// Now check that the author is valid in this slot
143
			assert!(
144
				T::CanAuthor::can_author(&Self::get(), &new_slot),
145
				"Block invalid, supplied author is not eligible."
146
			);
147

            
148
			InherentIncluded::<T>::put(true);
149

            
150
			Ok(Pays::No.into())
151
		}
152
	}
153

            
154
	#[pallet::inherent]
155
	impl<T: Config> ProvideInherent for Pallet<T> {
156
		type Call = Call<T>;
157
		type Error = InherentError;
158
		const INHERENT_IDENTIFIER: InherentIdentifier = INHERENT_IDENTIFIER;
159

            
160
		fn is_inherent_required(_: &InherentData) -> Result<Option<Self::Error>, Self::Error> {
161
			// Return Ok(Some(_)) unconditionally because this inherent is required in every block
162
			// If it is not found, throw an AuthorInherentRequired error.
163
			Ok(Some(InherentError::Other(
164
				sp_runtime::RuntimeString::Borrowed(
165
					"Inherent required to manually initiate author validation",
166
				),
167
			)))
168
		}
169

            
170
		// Regardless of whether the client is still supplying the author id,
171
		// we will create the new empty-payload inherent extrinsic.
172
		fn create_inherent(_data: &InherentData) -> Option<Self::Call> {
173
			Some(Call::kick_off_authorship_validation {})
174
		}
175

            
176
		fn is_inherent(call: &Self::Call) -> bool {
177
			matches!(call, Call::kick_off_authorship_validation { .. })
178
		}
179
	}
180

            
181
	impl<T: Config> FindAuthor<T::AuthorId> for Pallet<T> {
182
		fn find_author<'a, I>(digests: I) -> Option<T::AuthorId>
183
		where
184
			I: 'a + IntoIterator<Item = (ConsensusEngineId, &'a [u8])>,
185
		{
186
			for (id, mut data) in digests.into_iter() {
187
				if id == NIMBUS_ENGINE_ID {
188
					let author_id = NimbusId::decode(&mut data)
189
						.expect("NimbusId encoded in preruntime digest must be valid");
190

            
191
					let author_account = T::AccountLookup::lookup_account(&author_id)
192
						.expect("No Account Mapped to this NimbusId");
193

            
194
					return Some(author_account);
195
				}
196
			}
197

            
198
			None
199
		}
200
	}
201

            
202
	impl<T: Config> Get<T::AuthorId> for Pallet<T> {
203
		fn get() -> T::AuthorId {
204
			Author::<T>::get().expect("Block author not inserted into Author Inherent Pallet")
205
		}
206
	}
207

            
208
	/// To learn whether a given NimbusId can author, as opposed to an account id, you
209
	/// can ask this pallet directly. It will do the mapping for you.
210
	impl<T: Config> CanAuthor<NimbusId> for Pallet<T> {
211
		fn can_author(author: &NimbusId, slot: &u32) -> bool {
212
			let account = match T::AccountLookup::lookup_account(author) {
213
				Some(account) => account,
214
				// Authors whose account lookups fail will not be eligible
215
				None => {
216
					return false;
217
				}
218
			};
219

            
220
			T::CanAuthor::can_author(&account, slot)
221
		}
222
		#[cfg(feature = "runtime-benchmarks")]
223
		fn set_eligible_author(slot: &u32) {
224
			let eligible_authors = T::CanAuthor::get_authors(slot);
225
			if let Some(author) = eligible_authors.first() {
226
				Author::<T>::put(author)
227
			}
228
		}
229
	}
230
}
231

            
232
#[derive(Encode)]
233
#[cfg_attr(feature = "std", derive(Debug, Decode))]
234
pub enum InherentError {
235
	Other(RuntimeString),
236
}
237

            
238
impl IsFatalError for InherentError {
239
	fn is_fatal_error(&self) -> bool {
240
		match *self {
241
			InherentError::Other(_) => true,
242
		}
243
	}
244
}
245

            
246
impl InherentError {
247
	/// Try to create an instance ouf of the given identifier and data.
248
	#[cfg(feature = "std")]
249
	pub fn try_from(id: &InherentIdentifier, data: &[u8]) -> Option<Self> {
250
		if id == &INHERENT_IDENTIFIER {
251
			<InherentError as parity_scale_codec::Decode>::decode(&mut &data[..]).ok()
252
		} else {
253
			None
254
		}
255
	}
256
}