1#![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;
28pub 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 #[pallet::config]
69 pub trait Config:
70 polkadot_sdk::frame_system::Config
71 + pallet_ismp::Config
72 + cumulus_pallet_parachain_system::Config
73 {
74 type IsmpHost: IsmpHost + Default;
76 type WeightInfo: WeightInfo;
78 type RootOrigin: EnsureOrigin<Self::RuntimeOrigin>;
80 }
81
82 #[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 #[pallet::storage]
92 pub type ConsensusUpdated<T: Config> = StorageValue<_, bool>;
93
94 #[pallet::storage]
96 pub type Parachains<T: Config> = StorageMap<_, Identity, u32, u64>;
97
98 #[pallet::event]
100 #[pallet::generate_deposit(pub(super) fn deposit_event)]
101 pub enum Event<T: Config> {
102 ParachainsAdded {
104 para_ids: Vec<ParachainData>,
106 },
107 ParachainsRemoved {
109 para_ids: Vec<u32>,
111 },
112 }
113
114 #[pallet::error]
115 pub enum Error<T> {
116 InvalidConsensusStateId,
118 ConsensusAlreadyUpdated,
120 }
121
122 #[pallet::call]
123 impl<T: Config> Pallet<T> {
124 #[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 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 #[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 ¶_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 #[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 ¶_ids {
187 Parachains::<T>::remove(id);
188 }
189
190 Self::deposit_event(Event::ParachainsRemoved { para_ids });
191
192 Ok(())
193 }
194 }
195
196 #[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 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 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 #[pallet::genesis_config]
247 #[derive(frame_support::DefaultNoBound)]
248 pub struct GenesisConfig<T> {
249 pub parachains: Vec<ParachainData>,
251 #[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 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 pub fn para_ids() -> Vec<u32> {
287 Parachains::<T>::iter_keys().collect()
288 }
289
290 pub fn current_relay_chain_state() -> RelayChainState {
292 RelaychainDataProvider::<T>::current_relay_chain_state()
293 }
294
295 pub fn initialize() {
299 let host = T::IsmpHost::default();
300 let host_state_machine = host.host_state_machine();
301 let message = CreateConsensusState {
302 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
315pub trait RelayChainOracle {
317 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#[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 pub id: u32,
346 pub slot_duration: u64,
348}
349
350pub 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}