ismp_parachain/
lib.rs

1// Copyright (C) Polytope Labs Ltd.
2// SPDX-License-Identifier: Apache-2.0
3
4// Licensed under the Apache License, Version 2.0 (the "License");
5// you may not use this file except in compliance with the License.
6// You may obtain a copy of the License at
7//
8// 	http://www.apache.org/licenses/LICENSE-2.0
9//
10// Unless required by applicable law or agreed to in writing, software
11// distributed under the License is distributed on an "AS IS" BASIS,
12// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13// See the License for the specific language governing permissions and
14// limitations under the License.
15
16#![doc = include_str!("../README.md")]
17#![cfg_attr(not(feature = "std"), no_std)]
18#![deny(missing_docs)]
19
20extern crate alloc;
21extern crate core;
22
23pub mod consensus;
24mod migration;
25
26#[cfg(feature = "runtime-benchmarks")]
27mod benchmarking;
28/// weights trait crate
29pub mod weights;
30
31pub use consensus::*;
32use polkadot_sdk::*;
33
34use alloc::{vec, vec::Vec};
35use codec::{Decode, DecodeWithMemTracking, Encode, MaxEncodedLen};
36use cumulus_pallet_parachain_system::{
37	RelayChainState, RelaychainDataProvider, RelaychainStateProvider,
38};
39use cumulus_primitives_core::relay_chain;
40use ismp::{
41	consensus::ConsensusStateId,
42	handlers,
43	host::{IsmpHost, StateMachine},
44	messaging::CreateConsensusState,
45};
46pub use pallet::*;
47pub use weights::WeightInfo;
48
49#[frame_support::pallet]
50pub mod pallet {
51	use super::*;
52	use cumulus_primitives_core::relay_chain;
53	use frame_support::pallet_prelude::*;
54	use frame_system::pallet_prelude::*;
55	use ismp::{
56		consensus::StateMachineId,
57		host::StateMachine,
58		messaging::{ConsensusMessage, Message},
59	};
60	use migration::StorageV0;
61
62	const STORAGE_VERSION: StorageVersion = StorageVersion::new(1);
63	#[pallet::pallet]
64	#[pallet::storage_version(STORAGE_VERSION)]
65	pub struct Pallet<T>(_);
66
67	/// The config trait
68	#[pallet::config]
69	pub trait Config:
70		polkadot_sdk::frame_system::Config
71		+ pallet_ismp::Config
72		+ cumulus_pallet_parachain_system::Config
73	{
74		/// The underlying [`IsmpHost`] implementation
75		type IsmpHost: IsmpHost + Default;
76		/// WeightInfo
77		type WeightInfo: WeightInfo;
78		/// Origin for privileged actions
79		type RootOrigin: EnsureOrigin<Self::RuntimeOrigin>;
80	}
81
82	/// Mapping of relay chain heights to it's state commitment. The state commitment of the parent
83	/// relay block is inserted at every block in `on_finalize`. This commitment is gotten from
84	/// parachain-system.
85	#[pallet::storage]
86	#[pallet::getter(fn relay_chain_state)]
87	pub type RelayChainStateCommitments<T: Config> =
88		StorageMap<_, Blake2_128Concat, relay_chain::BlockNumber, relay_chain::Hash, OptionQuery>;
89
90	/// Tracks whether we've already seen the `update_parachain_consensus` inherent
91	#[pallet::storage]
92	pub type ConsensusUpdated<T: Config> = StorageValue<_, bool>;
93
94	/// List of parachains that this state machine is interested in.
95	#[pallet::storage]
96	pub type Parachains<T: Config> = StorageMap<_, Identity, u32, u64>;
97
98	/// Events emitted by this pallet
99	#[pallet::event]
100	#[pallet::generate_deposit(pub(super) fn deposit_event)]
101	pub enum Event<T: Config> {
102		/// Parachains with the `para_ids` have been added to the whitelist
103		ParachainsAdded {
104			/// The parachains in question
105			para_ids: Vec<ParachainData>,
106		},
107		/// Parachains with the `para_ids` have been removed from the whitelist
108		ParachainsRemoved {
109			/// The parachains in question
110			para_ids: Vec<u32>,
111		},
112	}
113
114	#[pallet::error]
115	pub enum Error<T> {
116		/// Only Parachain Consensus updates should be passed in the inherents.
117		InvalidConsensusStateId,
118		/// ValidationData must be updated only once in a block.
119		ConsensusAlreadyUpdated,
120	}
121
122	#[pallet::call]
123	impl<T: Config> Pallet<T> {
124		/// This allows block builders submit parachain consensus proofs as inherents. If the
125		/// provided [`ConsensusMessage`] is not for a parachain, this call will fail.
126		#[pallet::call_index(0)]
127		#[pallet::weight(<T as pallet::Config>::WeightInfo::update_parachain_consensus())]
128		pub fn update_parachain_consensus(
129			origin: OriginFor<T>,
130			data: ConsensusMessage,
131		) -> DispatchResultWithPostInfo {
132			ensure_none(origin)?;
133
134			ensure!(!ConsensusUpdated::<T>::exists(), Error::<T>::ConsensusAlreadyUpdated);
135
136			let host = <T::IsmpHost>::default();
137
138			ensure!(
139				data.consensus_state_id == parachain_consensus_state_id(host.host_state_machine()),
140				Error::<T>::InvalidConsensusStateId
141			);
142
143			// Handling error will prevent this inherent from breaking block production if there's a
144			// reorg and it's no longer valid
145			if let Err(err) = pallet_ismp::Pallet::<T>::execute(vec![Message::Consensus(data)]) {
146				log::trace!(target: "ismp", "Parachain inherent consensus update failed {err:?}");
147			} else {
148				ConsensusUpdated::<T>::put(true);
149			}
150
151			Ok(Pays::No.into())
152		}
153
154		/// Add some new parachains to the parachains whitelist
155		#[pallet::call_index(1)]
156		#[pallet::weight(<T as pallet::Config>::WeightInfo::add_parachain(para_ids.len() as u32))]
157		pub fn add_parachain(origin: OriginFor<T>, para_ids: Vec<ParachainData>) -> DispatchResult {
158			T::RootOrigin::ensure_origin(origin)?;
159			let host = <T::IsmpHost>::default();
160			for para in &para_ids {
161				let state_id = match host.host_state_machine() {
162					StateMachine::Kusama(_) => StateMachine::Kusama(para.id),
163					StateMachine::Polkadot(_) => StateMachine::Polkadot(para.id),
164					_ => continue,
165				};
166				Parachains::<T>::insert(para.id, para.slot_duration);
167				let _ = host.store_challenge_period(
168					StateMachineId {
169						state_id,
170						consensus_state_id: parachain_consensus_state_id(host.host_state_machine()),
171					},
172					0,
173				);
174			}
175
176			Self::deposit_event(Event::ParachainsAdded { para_ids });
177
178			Ok(())
179		}
180
181		/// Removes some parachains from the parachains whitelist
182		#[pallet::call_index(2)]
183		#[pallet::weight(<T as pallet::Config>::WeightInfo::remove_parachain(para_ids.len() as u32))]
184		pub fn remove_parachain(origin: OriginFor<T>, para_ids: Vec<u32>) -> DispatchResult {
185			T::RootOrigin::ensure_origin(origin)?;
186			for id in &para_ids {
187				Parachains::<T>::remove(id);
188			}
189
190			Self::deposit_event(Event::ParachainsRemoved { para_ids });
191
192			Ok(())
193		}
194	}
195
196	// Pallet implements [`Hooks`] trait to define some logic to execute in some context.
197	#[pallet::hooks]
198	impl<T: Config> Hooks<BlockNumberFor<T>> for Pallet<T> {
199		fn on_finalize(_n: BlockNumberFor<T>) {
200			let state = RelaychainDataProvider::<T>::current_relay_chain_state();
201			if !RelayChainStateCommitments::<T>::contains_key(state.number) {
202				RelayChainStateCommitments::<T>::insert(state.number, state.state_root);
203			}
204		}
205
206		fn on_initialize(_n: BlockNumberFor<T>) -> Weight {
207			// kill the storage, since this is the beginning of a new block.
208			ConsensusUpdated::<T>::kill();
209			let host = T::IsmpHost::default();
210			if let Err(_) =
211				host.consensus_state(parachain_consensus_state_id(host.host_state_machine()))
212			{
213				Pallet::<T>::initialize();
214			}
215
216			Weight::from_parts(0, 0)
217		}
218
219		fn on_runtime_upgrade() -> Weight {
220			StorageV0::migrate_to_v1::<T>()
221		}
222	}
223
224	/// The identifier for the parachain consensus update inherent.
225	pub const INHERENT_IDENTIFIER: InherentIdentifier = *b"paraismp";
226
227	#[pallet::inherent]
228	impl<T: Config> ProvideInherent for Pallet<T> {
229		type Call = Call<T>;
230		type Error = sp_inherents::MakeFatalError<()>;
231		const INHERENT_IDENTIFIER: InherentIdentifier = INHERENT_IDENTIFIER;
232
233		fn create_inherent(data: &InherentData) -> Option<Self::Call> {
234			let data: ConsensusMessage =
235				data.get_data(&Self::INHERENT_IDENTIFIER).ok().flatten()?;
236
237			Some(Call::update_parachain_consensus { data })
238		}
239
240		fn is_inherent(call: &Self::Call) -> bool {
241			matches!(call, Call::update_parachain_consensus { .. })
242		}
243	}
244
245	/// The genesis config
246	#[pallet::genesis_config]
247	#[derive(frame_support::DefaultNoBound)]
248	pub struct GenesisConfig<T> {
249		/// List of parachains to track at genesis
250		pub parachains: Vec<ParachainData>,
251		/// phantom data
252		#[serde(skip)]
253		pub _marker: PhantomData<T>,
254	}
255
256	#[pallet::genesis_build]
257	impl<T: Config> BuildGenesisConfig for GenesisConfig<T> {
258		fn build(&self) {
259			Pallet::<T>::initialize();
260			let host = <T::IsmpHost>::default();
261			let host_state_machine = host.host_state_machine();
262
263			// insert the parachain ids
264			for para in &self.parachains {
265				Parachains::<T>::insert(para.id, para.slot_duration);
266				let state_id = match host.host_state_machine() {
267					StateMachine::Kusama(_) => StateMachine::Kusama(para.id),
268					StateMachine::Polkadot(_) => StateMachine::Polkadot(para.id),
269					_ => continue,
270				};
271				let _ = host.store_challenge_period(
272					StateMachineId {
273						state_id,
274						consensus_state_id: parachain_consensus_state_id(host_state_machine),
275					},
276					0,
277				);
278			}
279		}
280	}
281}
282
283impl<T: Config> Pallet<T> {
284	/// Returns the list of parachains who's consensus updates will be inserted by the inherent
285	/// data provider
286	pub fn para_ids() -> Vec<u32> {
287		Parachains::<T>::iter_keys().collect()
288	}
289
290	/// Returns the current relay chain state
291	pub fn current_relay_chain_state() -> RelayChainState {
292		RelaychainDataProvider::<T>::current_relay_chain_state()
293	}
294
295	/// Initializes the parachain consensus state. Rather than requiring a seperate
296	/// `create_consensus_state` call, simply including this pallet in your runtime will create the
297	/// ismp parachain client consensus state, either through `genesis_build` or `on_initialize`.
298	pub fn initialize() {
299		let host = T::IsmpHost::default();
300		let host_state_machine = host.host_state_machine();
301		let message = CreateConsensusState {
302			// insert empty bytes
303			consensus_state: vec![],
304			unbonding_period: u64::MAX,
305			challenge_periods: Default::default(),
306			consensus_state_id: parachain_consensus_state_id(host_state_machine),
307			consensus_client_id: PARACHAIN_CONSENSUS_ID,
308			state_machine_commitments: vec![],
309		};
310		handlers::create_client(&host, message)
311			.expect("Failed to initialize parachain consensus client");
312	}
313}
314
315/// Interface that exposes the relay chain state roots.
316pub trait RelayChainOracle {
317	/// Returns the state root for a given height if it exists.
318	fn state_root(height: relay_chain::BlockNumber) -> Option<relay_chain::Hash>;
319}
320
321impl<T: Config> RelayChainOracle for Pallet<T> {
322	fn state_root(height: relay_chain::BlockNumber) -> Option<relay_chain::Hash> {
323		RelayChainStateCommitments::<T>::get(height)
324	}
325}
326
327/// Data provided when registering a parachain to be tracked by hyperbridge consensus client
328#[derive(
329	Debug,
330	Clone,
331	Copy,
332	Encode,
333	Decode,
334	DecodeWithMemTracking,
335	scale_info::TypeInfo,
336	PartialEq,
337	Hash,
338	Eq,
339	MaxEncodedLen,
340	serde::Deserialize,
341	serde::Serialize,
342)]
343pub struct ParachainData {
344	/// parachain id
345	pub id: u32,
346	/// parachain slot duration type
347	pub slot_duration: u64,
348}
349
350/// Returns the consensus state id for The relay chain
351pub fn parachain_consensus_state_id(host: StateMachine) -> ConsensusStateId {
352	match host {
353		StateMachine::Kusama(_) => PASEO_CONSENSUS_ID,
354		StateMachine::Polkadot(_) => POLKADOT_CONSENSUS_ID,
355		_ => POLKADOT_CONSENSUS_ID,
356	}
357}