1use crate::{
111 configuration,
112 inclusion::{QueueFootprinter, UmpQueueId},
113 initializer::SessionChangeNotification,
114 shared,
115};
116use alloc::{collections::btree_set::BTreeSet, vec::Vec};
117use bitvec::{order::Lsb0 as BitOrderLsb0, vec::BitVec};
118use codec::{Decode, Encode};
119use core::{cmp, mem};
120use frame_support::{
121 dispatch::PostDispatchInfo,
122 pallet_prelude::*,
123 traits::{EnsureOriginWithArg, EstimateNextSessionRotation},
124 DefaultNoBound,
125};
126use frame_system::pallet_prelude::*;
127use polkadot_primitives::{
128 ConsensusLog, HeadData, Id as ParaId, PvfCheckStatement, SessionIndex, UpgradeGoAhead,
129 UpgradeRestriction, ValidationCode, ValidationCodeHash, ValidatorSignature, MIN_CODE_SIZE,
130};
131use scale_info::{Type, TypeInfo};
132use sp_core::RuntimeDebug;
133use sp_runtime::{
134 traits::{AppVerify, One, Saturating},
135 DispatchResult, SaturatedConversion,
136};
137
138use serde::{Deserialize, Serialize};
139
140pub use crate::Origin as ParachainOrigin;
141
142#[cfg(feature = "runtime-benchmarks")]
143pub mod benchmarking;
144
145#[cfg(test)]
146pub(crate) mod tests;
147
148pub use pallet::*;
149
150const LOG_TARGET: &str = "runtime::paras";
151
152#[derive(Default, Encode, Decode, TypeInfo)]
154#[cfg_attr(test, derive(Debug, Clone, PartialEq))]
155pub struct ReplacementTimes<N> {
156 expected_at: N,
160 activated_at: N,
164}
165
166#[derive(Default, Encode, Decode, TypeInfo)]
169#[cfg_attr(test, derive(Debug, Clone, PartialEq))]
170pub struct ParaPastCodeMeta<N> {
171 upgrade_times: Vec<ReplacementTimes<N>>,
177 last_pruned: Option<N>,
180}
181
182#[derive(PartialEq, Eq, Clone, Encode, Decode, RuntimeDebug, TypeInfo)]
188pub enum ParaLifecycle {
189 Onboarding,
191 Parathread,
193 Parachain,
195 UpgradingParathread,
197 DowngradingParachain,
199 OffboardingParathread,
201 OffboardingParachain,
203}
204
205impl ParaLifecycle {
206 pub fn is_onboarding(&self) -> bool {
210 matches!(self, ParaLifecycle::Onboarding)
211 }
212
213 pub fn is_stable(&self) -> bool {
216 matches!(self, ParaLifecycle::Parathread | ParaLifecycle::Parachain)
217 }
218
219 pub fn is_parachain(&self) -> bool {
223 matches!(
224 self,
225 ParaLifecycle::Parachain |
226 ParaLifecycle::DowngradingParachain |
227 ParaLifecycle::OffboardingParachain
228 )
229 }
230
231 pub fn is_parathread(&self) -> bool {
235 matches!(
236 self,
237 ParaLifecycle::Parathread |
238 ParaLifecycle::UpgradingParathread |
239 ParaLifecycle::OffboardingParathread
240 )
241 }
242
243 pub fn is_offboarding(&self) -> bool {
245 matches!(self, ParaLifecycle::OffboardingParathread | ParaLifecycle::OffboardingParachain)
246 }
247
248 pub fn is_transitioning(&self) -> bool {
250 !Self::is_stable(self)
251 }
252}
253
254impl<N: Ord + Copy + PartialEq> ParaPastCodeMeta<N> {
255 pub(crate) fn note_replacement(&mut self, expected_at: N, activated_at: N) {
257 self.upgrade_times.push(ReplacementTimes { expected_at, activated_at })
258 }
259
260 fn is_empty(&self) -> bool {
262 self.upgrade_times.is_empty()
263 }
264
265 #[cfg(test)]
268 fn most_recent_change(&self) -> Option<N> {
269 self.upgrade_times.last().map(|x| x.expected_at)
270 }
271
272 fn prune_up_to(&'_ mut self, max: N) -> impl Iterator<Item = N> + '_ {
283 let to_prune = self.upgrade_times.iter().take_while(|t| t.activated_at <= max).count();
284 let drained = if to_prune == 0 {
285 self.upgrade_times.drain(self.upgrade_times.len()..)
287 } else {
288 self.last_pruned = Some(self.upgrade_times[to_prune - 1].activated_at);
290 self.upgrade_times.drain(..to_prune)
291 };
292
293 drained.map(|times| times.expected_at)
294 }
295}
296
297#[derive(
299 PartialEq,
300 Eq,
301 Clone,
302 Encode,
303 Decode,
304 DecodeWithMemTracking,
305 RuntimeDebug,
306 TypeInfo,
307 Serialize,
308 Deserialize,
309)]
310pub struct ParaGenesisArgs {
311 pub genesis_head: HeadData,
313 pub validation_code: ValidationCode,
315 #[serde(rename = "parachain")]
317 pub para_kind: ParaKind,
318}
319
320#[derive(DecodeWithMemTracking, PartialEq, Eq, Clone, RuntimeDebug)]
322pub enum ParaKind {
323 Parathread,
324 Parachain,
325}
326
327impl Serialize for ParaKind {
328 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
329 where
330 S: serde::Serializer,
331 {
332 match self {
333 ParaKind::Parachain => serializer.serialize_bool(true),
334 ParaKind::Parathread => serializer.serialize_bool(false),
335 }
336 }
337}
338
339impl<'de> Deserialize<'de> for ParaKind {
340 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
341 where
342 D: serde::Deserializer<'de>,
343 {
344 match serde::de::Deserialize::deserialize(deserializer) {
345 Ok(true) => Ok(ParaKind::Parachain),
346 Ok(false) => Ok(ParaKind::Parathread),
347 _ => Err(serde::de::Error::custom("invalid ParaKind serde representation")),
348 }
349 }
350}
351
352impl Encode for ParaKind {
355 fn size_hint(&self) -> usize {
356 true.size_hint()
357 }
358
359 fn using_encoded<R, F: FnOnce(&[u8]) -> R>(&self, f: F) -> R {
360 match self {
361 ParaKind::Parachain => true.using_encoded(f),
362 ParaKind::Parathread => false.using_encoded(f),
363 }
364 }
365}
366
367impl Decode for ParaKind {
368 fn decode<I: codec::Input>(input: &mut I) -> Result<Self, codec::Error> {
369 match bool::decode(input) {
370 Ok(true) => Ok(ParaKind::Parachain),
371 Ok(false) => Ok(ParaKind::Parathread),
372 _ => Err("Invalid ParaKind representation".into()),
373 }
374 }
375}
376
377impl TypeInfo for ParaKind {
378 type Identity = bool;
379 fn type_info() -> Type {
380 bool::type_info()
381 }
382}
383
384#[derive(Debug, Encode, Decode, TypeInfo)]
387pub(crate) enum PvfCheckCause<BlockNumber> {
388 Onboarding(ParaId),
390 Upgrade {
392 id: ParaId,
395 included_at: BlockNumber,
404 upgrade_strategy: UpgradeStrategy,
409 },
410}
411
412#[derive(Debug, Copy, Clone, PartialEq, TypeInfo, Decode, Encode)]
420pub enum UpgradeStrategy {
421 SetGoAheadSignal,
426 ApplyAtExpectedBlock,
430}
431
432impl<BlockNumber> PvfCheckCause<BlockNumber> {
433 fn para_id(&self) -> ParaId {
435 match *self {
436 PvfCheckCause::Onboarding(id) => id,
437 PvfCheckCause::Upgrade { id, .. } => id,
438 }
439 }
440}
441
442#[derive(Copy, Clone, Encode, Decode, RuntimeDebug, TypeInfo)]
444enum PvfCheckOutcome {
445 Accepted,
446 Rejected,
447}
448
449#[derive(Encode, Decode, TypeInfo)]
451pub(crate) struct PvfCheckActiveVoteState<BlockNumber> {
452 votes_accept: BitVec<u8, BitOrderLsb0>,
458 votes_reject: BitVec<u8, BitOrderLsb0>,
459
460 age: SessionIndex,
463 created_at: BlockNumber,
465 causes: Vec<PvfCheckCause<BlockNumber>>,
467}
468
469impl<BlockNumber> PvfCheckActiveVoteState<BlockNumber> {
470 fn new(now: BlockNumber, n_validators: usize, cause: PvfCheckCause<BlockNumber>) -> Self {
473 let mut causes = Vec::with_capacity(1);
474 causes.push(cause);
475 Self {
476 created_at: now,
477 votes_accept: bitvec::bitvec![u8, BitOrderLsb0; 0; n_validators],
478 votes_reject: bitvec::bitvec![u8, BitOrderLsb0; 0; n_validators],
479 age: 0,
480 causes,
481 }
482 }
483
484 fn reinitialize_ballots(&mut self, n_validators: usize) {
487 let clear_and_resize = |v: &mut BitVec<_, _>| {
488 v.clear();
489 v.resize(n_validators, false);
490 };
491 clear_and_resize(&mut self.votes_accept);
492 clear_and_resize(&mut self.votes_reject);
493 }
494
495 fn has_vote(&self, validator_index: usize) -> Option<bool> {
498 let accept_vote = self.votes_accept.get(validator_index)?;
499 let reject_vote = self.votes_reject.get(validator_index)?;
500 Some(*accept_vote || *reject_vote)
501 }
502
503 fn quorum(&self, n_validators: usize) -> Option<PvfCheckOutcome> {
505 let accept_threshold = polkadot_primitives::supermajority_threshold(n_validators);
506 let reject_threshold = n_validators - accept_threshold;
508
509 if self.votes_accept.count_ones() >= accept_threshold {
510 Some(PvfCheckOutcome::Accepted)
511 } else if self.votes_reject.count_ones() > reject_threshold {
512 Some(PvfCheckOutcome::Rejected)
513 } else {
514 None
515 }
516 }
517
518 #[cfg(test)]
519 pub(crate) fn causes(&self) -> &[PvfCheckCause<BlockNumber>] {
520 self.causes.as_slice()
521 }
522}
523
524pub trait OnNewHead {
526 fn on_new_head(id: ParaId, head: &HeadData) -> Weight;
529}
530
531#[impl_trait_for_tuples::impl_for_tuples(30)]
532impl OnNewHead for Tuple {
533 fn on_new_head(id: ParaId, head: &HeadData) -> Weight {
534 let mut weight: Weight = Default::default();
535 for_tuples!( #( weight.saturating_accrue(Tuple::on_new_head(id, head)); )* );
536 weight
537 }
538}
539
540pub trait AssignCoretime {
545 fn assign_coretime(id: ParaId) -> DispatchResult;
547}
548
549impl AssignCoretime for () {
550 fn assign_coretime(_: ParaId) -> DispatchResult {
551 Ok(())
552 }
553}
554
555#[derive(Debug, Encode, Decode, DecodeWithMemTracking, TypeInfo)]
557#[cfg_attr(test, derive(PartialEq))]
558pub struct AuthorizedCodeHashAndExpiry<T> {
559 code_hash: ValidationCodeHash,
560 expire_at: T,
561}
562impl<T> From<(ValidationCodeHash, T)> for AuthorizedCodeHashAndExpiry<T> {
563 fn from(value: (ValidationCodeHash, T)) -> Self {
564 AuthorizedCodeHashAndExpiry { code_hash: value.0, expire_at: value.1 }
565 }
566}
567
568pub trait WeightInfo {
569 fn force_set_current_code(c: u32) -> Weight;
570 fn force_set_current_head(s: u32) -> Weight;
571 fn force_set_most_recent_context() -> Weight;
572 fn force_schedule_code_upgrade(c: u32) -> Weight;
573 fn force_note_new_head(s: u32) -> Weight;
574 fn force_queue_action() -> Weight;
575 fn add_trusted_validation_code(c: u32) -> Weight;
576 fn poke_unused_validation_code() -> Weight;
577 fn remove_upgrade_cooldown() -> Weight;
578
579 fn include_pvf_check_statement_finalize_upgrade_accept() -> Weight;
580 fn include_pvf_check_statement_finalize_upgrade_reject() -> Weight;
581 fn include_pvf_check_statement_finalize_onboarding_accept() -> Weight;
582 fn include_pvf_check_statement_finalize_onboarding_reject() -> Weight;
583 fn include_pvf_check_statement() -> Weight;
584 fn authorize_force_set_current_code_hash() -> Weight;
585 fn apply_authorized_force_set_current_code(c: u32) -> Weight;
586}
587
588pub struct TestWeightInfo;
589impl WeightInfo for TestWeightInfo {
590 fn force_set_current_code(_c: u32) -> Weight {
591 Weight::MAX
592 }
593 fn force_set_current_head(_s: u32) -> Weight {
594 Weight::MAX
595 }
596 fn force_set_most_recent_context() -> Weight {
597 Weight::MAX
598 }
599 fn force_schedule_code_upgrade(_c: u32) -> Weight {
600 Weight::MAX
601 }
602 fn force_note_new_head(_s: u32) -> Weight {
603 Weight::MAX
604 }
605 fn force_queue_action() -> Weight {
606 Weight::MAX
607 }
608 fn add_trusted_validation_code(_c: u32) -> Weight {
609 Weight::zero()
611 }
612 fn poke_unused_validation_code() -> Weight {
613 Weight::MAX
614 }
615 fn include_pvf_check_statement_finalize_upgrade_accept() -> Weight {
616 Weight::MAX
617 }
618 fn include_pvf_check_statement_finalize_upgrade_reject() -> Weight {
619 Weight::MAX
620 }
621 fn include_pvf_check_statement_finalize_onboarding_accept() -> Weight {
622 Weight::MAX
623 }
624 fn include_pvf_check_statement_finalize_onboarding_reject() -> Weight {
625 Weight::MAX
626 }
627 fn include_pvf_check_statement() -> Weight {
628 Weight::MAX - Weight::from_parts(1, 1)
630 }
631 fn remove_upgrade_cooldown() -> Weight {
632 Weight::MAX
633 }
634 fn authorize_force_set_current_code_hash() -> Weight {
635 Weight::MAX
636 }
637 fn apply_authorized_force_set_current_code(_c: u32) -> Weight {
638 Weight::MAX
639 }
640}
641
642#[frame_support::pallet]
643pub mod pallet {
644 use super::*;
645 use frame_support::traits::{
646 fungible::{Inspect, Mutate},
647 tokens::{Fortitude, Precision, Preservation},
648 };
649 use sp_runtime::transaction_validity::{
650 InvalidTransaction, TransactionPriority, TransactionSource, TransactionValidity,
651 ValidTransaction,
652 };
653
654 type BalanceOf<T> = <<T as Config>::Fungible as Inspect<AccountIdFor<T>>>::Balance;
655
656 #[pallet::pallet]
657 #[pallet::without_storage_info]
658 pub struct Pallet<T>(_);
659
660 #[pallet::config]
661 pub trait Config:
662 frame_system::Config
663 + configuration::Config
664 + shared::Config
665 + frame_system::offchain::CreateBare<Call<Self>>
666 {
667 #[allow(deprecated)]
668 type RuntimeEvent: From<Event<Self>> + IsType<<Self as frame_system::Config>::RuntimeEvent>;
669
670 #[pallet::constant]
671 type UnsignedPriority: Get<TransactionPriority>;
672
673 type NextSessionRotation: EstimateNextSessionRotation<BlockNumberFor<Self>>;
674
675 type QueueFootprinter: QueueFootprinter<Origin = UmpQueueId>;
680
681 type OnNewHead: OnNewHead;
683
684 type WeightInfo: WeightInfo;
686
687 type AssignCoretime: AssignCoretime;
693
694 type Fungible: Mutate<Self::AccountId, Balance: From<BlockNumberFor<Self>>>;
696
697 type CooldownRemovalMultiplier: Get<BalanceOf<Self>>;
707
708 type AuthorizeCurrentCodeOrigin: EnsureOriginWithArg<Self::RuntimeOrigin, ParaId>;
710 }
711
712 #[pallet::event]
713 #[pallet::generate_deposit(pub(super) fn deposit_event)]
714 pub enum Event<T: Config> {
715 CurrentCodeUpdated(ParaId),
717 CurrentHeadUpdated(ParaId),
719 CodeUpgradeScheduled(ParaId),
721 NewHeadNoted(ParaId),
723 ActionQueued(ParaId, SessionIndex),
725 PvfCheckStarted(ValidationCodeHash, ParaId),
728 PvfCheckAccepted(ValidationCodeHash, ParaId),
731 PvfCheckRejected(ValidationCodeHash, ParaId),
734 UpgradeCooldownRemoved {
736 para_id: ParaId,
738 },
739 CodeAuthorized {
741 para_id: ParaId,
743 code_hash: ValidationCodeHash,
745 expire_at: BlockNumberFor<T>,
747 },
748 }
749
750 #[pallet::error]
751 pub enum Error<T> {
752 NotRegistered,
754 CannotOnboard,
756 CannotOffboard,
758 CannotUpgrade,
760 CannotDowngrade,
762 PvfCheckStatementStale,
764 PvfCheckStatementFuture,
766 PvfCheckValidatorIndexOutOfBounds,
768 PvfCheckInvalidSignature,
770 PvfCheckDoubleVote,
772 PvfCheckSubjectInvalid,
774 CannotUpgradeCode,
776 InvalidCode,
778 NothingAuthorized,
780 Unauthorized,
782 InvalidBlockNumber,
784 }
785
786 #[pallet::storage]
791 pub(super) type PvfActiveVoteMap<T: Config> = StorageMap<
792 _,
793 Twox64Concat,
794 ValidationCodeHash,
795 PvfCheckActiveVoteState<BlockNumberFor<T>>,
796 OptionQuery,
797 >;
798
799 #[pallet::storage]
801 pub(super) type PvfActiveVoteList<T: Config> =
802 StorageValue<_, Vec<ValidationCodeHash>, ValueQuery>;
803
804 #[pallet::storage]
809 pub type Parachains<T: Config> = StorageValue<_, Vec<ParaId>, ValueQuery>;
810
811 #[pallet::storage]
813 pub(super) type ParaLifecycles<T: Config> = StorageMap<_, Twox64Concat, ParaId, ParaLifecycle>;
814
815 #[pallet::storage]
817 pub type Heads<T: Config> = StorageMap<_, Twox64Concat, ParaId, HeadData>;
818
819 #[pallet::storage]
821 pub type MostRecentContext<T: Config> = StorageMap<_, Twox64Concat, ParaId, BlockNumberFor<T>>;
822
823 #[pallet::storage]
827 pub type CurrentCodeHash<T: Config> = StorageMap<_, Twox64Concat, ParaId, ValidationCodeHash>;
828
829 #[pallet::storage]
834 pub(super) type PastCodeHash<T: Config> =
835 StorageMap<_, Twox64Concat, (ParaId, BlockNumberFor<T>), ValidationCodeHash>;
836
837 #[pallet::storage]
841 pub type PastCodeMeta<T: Config> =
842 StorageMap<_, Twox64Concat, ParaId, ParaPastCodeMeta<BlockNumberFor<T>>, ValueQuery>;
843
844 #[pallet::storage]
851 pub(super) type PastCodePruning<T: Config> =
852 StorageValue<_, Vec<(ParaId, BlockNumberFor<T>)>, ValueQuery>;
853
854 #[pallet::storage]
859 pub type FutureCodeUpgrades<T: Config> = StorageMap<_, Twox64Concat, ParaId, BlockNumberFor<T>>;
860
861 #[pallet::storage]
870 pub(super) type FutureCodeUpgradesAt<T: Config> =
871 StorageValue<_, Vec<(ParaId, BlockNumberFor<T>)>, ValueQuery>;
872
873 #[pallet::storage]
877 pub type FutureCodeHash<T: Config> = StorageMap<_, Twox64Concat, ParaId, ValidationCodeHash>;
878
879 #[pallet::storage]
881 pub type AuthorizedCodeHash<T: Config> =
882 StorageMap<_, Twox64Concat, ParaId, AuthorizedCodeHashAndExpiry<BlockNumberFor<T>>>;
883
884 #[pallet::storage]
895 pub(super) type UpgradeGoAheadSignal<T: Config> =
896 StorageMap<_, Twox64Concat, ParaId, UpgradeGoAhead>;
897
898 #[pallet::storage]
908 pub type UpgradeRestrictionSignal<T: Config> =
909 StorageMap<_, Twox64Concat, ParaId, UpgradeRestriction>;
910
911 #[pallet::storage]
915 pub(super) type UpgradeCooldowns<T: Config> =
916 StorageValue<_, Vec<(ParaId, BlockNumberFor<T>)>, ValueQuery>;
917
918 #[pallet::storage]
925 pub(super) type UpcomingUpgrades<T: Config> =
926 StorageValue<_, Vec<(ParaId, BlockNumberFor<T>)>, ValueQuery>;
927
928 #[pallet::storage]
930 pub type ActionsQueue<T: Config> =
931 StorageMap<_, Twox64Concat, SessionIndex, Vec<ParaId>, ValueQuery>;
932
933 #[pallet::storage]
938 pub(super) type UpcomingParasGenesis<T: Config> =
939 StorageMap<_, Twox64Concat, ParaId, ParaGenesisArgs>;
940
941 #[pallet::storage]
943 pub(super) type CodeByHashRefs<T: Config> =
944 StorageMap<_, Identity, ValidationCodeHash, u32, ValueQuery>;
945
946 #[pallet::storage]
951 pub type CodeByHash<T: Config> = StorageMap<_, Identity, ValidationCodeHash, ValidationCode>;
952
953 #[pallet::genesis_config]
954 #[derive(DefaultNoBound)]
955 pub struct GenesisConfig<T: Config> {
956 #[serde(skip)]
957 pub _config: core::marker::PhantomData<T>,
958 pub paras: Vec<(ParaId, ParaGenesisArgs)>,
959 }
960
961 #[pallet::genesis_build]
962 impl<T: Config> BuildGenesisConfig for GenesisConfig<T> {
963 fn build(&self) {
964 let mut parachains = ParachainsCache::new();
965 for (id, genesis_args) in &self.paras {
966 if genesis_args.validation_code.0.is_empty() {
967 panic!("empty validation code is not allowed in genesis");
968 }
969 Pallet::<T>::initialize_para_now(&mut parachains, *id, genesis_args);
970 T::AssignCoretime::assign_coretime(*id)
971 .expect("Assigning coretime works at genesis; qed");
972 }
973 }
975 }
976
977 #[pallet::call]
978 impl<T: Config> Pallet<T> {
979 #[pallet::call_index(0)]
981 #[pallet::weight(<T as Config>::WeightInfo::force_set_current_code(new_code.0.len() as u32))]
982 pub fn force_set_current_code(
983 origin: OriginFor<T>,
984 para: ParaId,
985 new_code: ValidationCode,
986 ) -> DispatchResult {
987 ensure_root(origin)?;
988 Self::do_force_set_current_code_update(para, new_code);
989 Ok(())
990 }
991
992 #[pallet::call_index(1)]
994 #[pallet::weight(<T as Config>::WeightInfo::force_set_current_head(new_head.0.len() as u32))]
995 pub fn force_set_current_head(
996 origin: OriginFor<T>,
997 para: ParaId,
998 new_head: HeadData,
999 ) -> DispatchResult {
1000 ensure_root(origin)?;
1001 Self::set_current_head(para, new_head);
1002 Ok(())
1003 }
1004
1005 #[pallet::call_index(2)]
1007 #[pallet::weight(<T as Config>::WeightInfo::force_schedule_code_upgrade(new_code.0.len() as u32))]
1008 pub fn force_schedule_code_upgrade(
1009 origin: OriginFor<T>,
1010 para: ParaId,
1011 new_code: ValidationCode,
1012 relay_parent_number: BlockNumberFor<T>,
1013 ) -> DispatchResult {
1014 ensure_root(origin)?;
1015 let config = configuration::ActiveConfig::<T>::get();
1016 Self::schedule_code_upgrade(
1017 para,
1018 new_code,
1019 relay_parent_number,
1020 &config,
1021 UpgradeStrategy::ApplyAtExpectedBlock,
1022 );
1023 Self::deposit_event(Event::CodeUpgradeScheduled(para));
1024 Ok(())
1025 }
1026
1027 #[pallet::call_index(3)]
1029 #[pallet::weight(<T as Config>::WeightInfo::force_note_new_head(new_head.0.len() as u32))]
1030 pub fn force_note_new_head(
1031 origin: OriginFor<T>,
1032 para: ParaId,
1033 new_head: HeadData,
1034 ) -> DispatchResult {
1035 ensure_root(origin)?;
1036 let now = frame_system::Pallet::<T>::block_number();
1037 Self::note_new_head(para, new_head, now);
1038 Self::deposit_event(Event::NewHeadNoted(para));
1039 Ok(())
1040 }
1041
1042 #[pallet::call_index(4)]
1046 #[pallet::weight(<T as Config>::WeightInfo::force_queue_action())]
1047 pub fn force_queue_action(origin: OriginFor<T>, para: ParaId) -> DispatchResult {
1048 ensure_root(origin)?;
1049 let next_session = shared::CurrentSessionIndex::<T>::get().saturating_add(One::one());
1050 ActionsQueue::<T>::mutate(next_session, |v| {
1051 if let Err(i) = v.binary_search(¶) {
1052 v.insert(i, para);
1053 }
1054 });
1055 Self::deposit_event(Event::ActionQueued(para, next_session));
1056 Ok(())
1057 }
1058
1059 #[pallet::call_index(5)]
1074 #[pallet::weight(<T as Config>::WeightInfo::add_trusted_validation_code(validation_code.0.len() as u32))]
1075 pub fn add_trusted_validation_code(
1076 origin: OriginFor<T>,
1077 validation_code: ValidationCode,
1078 ) -> DispatchResult {
1079 ensure_root(origin)?;
1080 let code_hash = validation_code.hash();
1081
1082 if let Some(vote) = PvfActiveVoteMap::<T>::get(&code_hash) {
1083 PvfActiveVoteMap::<T>::remove(&code_hash);
1085 PvfActiveVoteList::<T>::mutate(|l| {
1086 if let Ok(i) = l.binary_search(&code_hash) {
1087 l.remove(i);
1088 }
1089 });
1090
1091 let cfg = configuration::ActiveConfig::<T>::get();
1092 Self::enact_pvf_accepted(
1093 frame_system::Pallet::<T>::block_number(),
1094 &code_hash,
1095 &vote.causes,
1096 vote.age,
1097 &cfg,
1098 );
1099 return Ok(())
1100 }
1101
1102 if CodeByHash::<T>::contains_key(&code_hash) {
1103 return Ok(())
1105 }
1106
1107 CodeByHash::<T>::insert(code_hash, &validation_code);
1113
1114 Ok(())
1115 }
1116
1117 #[pallet::call_index(6)]
1123 #[pallet::weight(<T as Config>::WeightInfo::poke_unused_validation_code())]
1124 pub fn poke_unused_validation_code(
1125 origin: OriginFor<T>,
1126 validation_code_hash: ValidationCodeHash,
1127 ) -> DispatchResult {
1128 ensure_root(origin)?;
1129 if CodeByHashRefs::<T>::get(&validation_code_hash) == 0 {
1130 CodeByHash::<T>::remove(&validation_code_hash);
1131 }
1132 Ok(())
1133 }
1134
1135 #[pallet::call_index(7)]
1138 #[pallet::weight(
1139 <T as Config>::WeightInfo::include_pvf_check_statement_finalize_upgrade_accept()
1140 .max(<T as Config>::WeightInfo::include_pvf_check_statement_finalize_upgrade_reject())
1141 .max(<T as Config>::WeightInfo::include_pvf_check_statement_finalize_onboarding_accept()
1142 .max(<T as Config>::WeightInfo::include_pvf_check_statement_finalize_onboarding_reject())
1143 )
1144 )]
1145 pub fn include_pvf_check_statement(
1146 origin: OriginFor<T>,
1147 stmt: PvfCheckStatement,
1148 signature: ValidatorSignature,
1149 ) -> DispatchResultWithPostInfo {
1150 ensure_none(origin)?;
1151
1152 let validators = shared::ActiveValidatorKeys::<T>::get();
1153 let current_session = shared::CurrentSessionIndex::<T>::get();
1154 if stmt.session_index < current_session {
1155 return Err(Error::<T>::PvfCheckStatementStale.into())
1156 } else if stmt.session_index > current_session {
1157 return Err(Error::<T>::PvfCheckStatementFuture.into())
1158 }
1159 let validator_index = stmt.validator_index.0 as usize;
1160 let validator_public = validators
1161 .get(validator_index)
1162 .ok_or(Error::<T>::PvfCheckValidatorIndexOutOfBounds)?;
1163
1164 let signing_payload = stmt.signing_payload();
1165 ensure!(
1166 signature.verify(&signing_payload[..], &validator_public),
1167 Error::<T>::PvfCheckInvalidSignature,
1168 );
1169
1170 let mut active_vote = PvfActiveVoteMap::<T>::get(&stmt.subject)
1171 .ok_or(Error::<T>::PvfCheckSubjectInvalid)?;
1172
1173 ensure!(
1175 !active_vote
1176 .has_vote(validator_index)
1177 .ok_or(Error::<T>::PvfCheckValidatorIndexOutOfBounds)?,
1178 Error::<T>::PvfCheckDoubleVote,
1179 );
1180
1181 if stmt.accept {
1183 active_vote.votes_accept.set(validator_index, true);
1184 } else {
1185 active_vote.votes_reject.set(validator_index, true);
1186 }
1187
1188 if let Some(outcome) = active_vote.quorum(validators.len()) {
1189 PvfActiveVoteMap::<T>::remove(&stmt.subject);
1194 PvfActiveVoteList::<T>::mutate(|l| {
1195 if let Ok(i) = l.binary_search(&stmt.subject) {
1196 l.remove(i);
1197 }
1198 });
1199 match outcome {
1200 PvfCheckOutcome::Accepted => {
1201 let cfg = configuration::ActiveConfig::<T>::get();
1202 Self::enact_pvf_accepted(
1203 frame_system::Pallet::<T>::block_number(),
1204 &stmt.subject,
1205 &active_vote.causes,
1206 active_vote.age,
1207 &cfg,
1208 );
1209 },
1210 PvfCheckOutcome::Rejected => {
1211 Self::enact_pvf_rejected(&stmt.subject, active_vote.causes);
1212 },
1213 }
1214
1215 Ok(().into())
1217 } else {
1218 PvfActiveVoteMap::<T>::insert(&stmt.subject, active_vote);
1223 Ok(Some(<T as Config>::WeightInfo::include_pvf_check_statement()).into())
1224 }
1225 }
1226
1227 #[pallet::call_index(8)]
1229 #[pallet::weight(<T as Config>::WeightInfo::force_set_most_recent_context())]
1230 pub fn force_set_most_recent_context(
1231 origin: OriginFor<T>,
1232 para: ParaId,
1233 context: BlockNumberFor<T>,
1234 ) -> DispatchResult {
1235 ensure_root(origin)?;
1236 MostRecentContext::<T>::insert(¶, context);
1237 Ok(())
1238 }
1239
1240 #[pallet::call_index(9)]
1245 #[pallet::weight(<T as Config>::WeightInfo::remove_upgrade_cooldown())]
1246 pub fn remove_upgrade_cooldown(origin: OriginFor<T>, para: ParaId) -> DispatchResult {
1247 let who = ensure_signed(origin)?;
1248
1249 let removed = UpgradeCooldowns::<T>::mutate(|cooldowns| {
1250 let Some(pos) = cooldowns.iter().position(|(p, _)| p == ¶) else {
1251 return Ok::<_, DispatchError>(false)
1252 };
1253 let (_, cooldown_until) = cooldowns.remove(pos);
1254
1255 let cost = Self::calculate_remove_upgrade_cooldown_cost(cooldown_until);
1256
1257 T::Fungible::burn_from(
1259 &who,
1260 cost,
1261 Preservation::Preserve,
1262 Precision::Exact,
1263 Fortitude::Polite,
1264 )?;
1265
1266 Ok(true)
1267 })?;
1268
1269 if removed {
1270 UpgradeRestrictionSignal::<T>::remove(para);
1271
1272 Self::deposit_event(Event::UpgradeCooldownRemoved { para_id: para });
1273 }
1274
1275 Ok(())
1276 }
1277
1278 #[pallet::call_index(10)]
1290 #[pallet::weight(<T as Config>::WeightInfo::authorize_force_set_current_code_hash())]
1291 pub fn authorize_force_set_current_code_hash(
1292 origin: OriginFor<T>,
1293 para: ParaId,
1294 new_code_hash: ValidationCodeHash,
1295 valid_period: BlockNumberFor<T>,
1296 ) -> DispatchResult {
1297 T::AuthorizeCurrentCodeOrigin::ensure_origin(origin, ¶)?;
1298
1299 let now = frame_system::Pallet::<T>::block_number();
1300 let expire_at = now.saturating_add(valid_period);
1301
1302 AuthorizedCodeHash::<T>::insert(
1304 ¶,
1305 AuthorizedCodeHashAndExpiry::from((new_code_hash, expire_at)),
1306 );
1307 Self::deposit_event(Event::CodeAuthorized {
1308 para_id: para,
1309 code_hash: new_code_hash,
1310 expire_at,
1311 });
1312
1313 Ok(())
1314 }
1315
1316 #[pallet::call_index(11)]
1319 #[pallet::weight(<T as Config>::WeightInfo::apply_authorized_force_set_current_code(new_code.0.len() as u32))]
1320 pub fn apply_authorized_force_set_current_code(
1321 _origin: OriginFor<T>,
1322 para: ParaId,
1323 new_code: ValidationCode,
1324 ) -> DispatchResultWithPostInfo {
1325 let _ = Self::validate_code_is_authorized(&new_code, ¶)?;
1329 AuthorizedCodeHash::<T>::remove(para);
1331
1332 Self::do_force_set_current_code_update(para, new_code);
1334
1335 Ok(PostDispatchInfo {
1337 actual_weight: Some(T::BlockWeights::get().max_block),
1339 pays_fee: Pays::No,
1341 })
1342 }
1343 }
1344
1345 impl<T: Config> Pallet<T> {
1346 pub(crate) fn calculate_remove_upgrade_cooldown_cost(
1347 cooldown_until: BlockNumberFor<T>,
1348 ) -> BalanceOf<T> {
1349 let time_left =
1350 cooldown_until.saturating_sub(frame_system::Pallet::<T>::block_number());
1351
1352 BalanceOf::<T>::from(time_left).saturating_mul(T::CooldownRemovalMultiplier::get())
1353 }
1354 }
1355
1356 #[pallet::view_functions]
1357 impl<T: Config> Pallet<T> {
1358 pub fn remove_upgrade_cooldown_cost(para: ParaId) -> BalanceOf<T> {
1360 UpgradeCooldowns::<T>::get()
1361 .iter()
1362 .find(|(p, _)| p == ¶)
1363 .map(|(_, c)| Self::calculate_remove_upgrade_cooldown_cost(*c))
1364 .unwrap_or_default()
1365 }
1366 }
1367
1368 #[pallet::validate_unsigned]
1369 impl<T: Config> ValidateUnsigned for Pallet<T> {
1370 type Call = Call<T>;
1371
1372 fn validate_unsigned(_source: TransactionSource, call: &Self::Call) -> TransactionValidity {
1373 match call {
1374 Call::include_pvf_check_statement { stmt, signature } => {
1375 let current_session = shared::CurrentSessionIndex::<T>::get();
1376 if stmt.session_index < current_session {
1377 return InvalidTransaction::Stale.into()
1378 } else if stmt.session_index > current_session {
1379 return InvalidTransaction::Future.into()
1380 }
1381
1382 let validator_index = stmt.validator_index.0 as usize;
1383 let validators = shared::ActiveValidatorKeys::<T>::get();
1384 let validator_public = match validators.get(validator_index) {
1385 Some(pk) => pk,
1386 None =>
1387 return InvalidTransaction::Custom(INVALID_TX_BAD_VALIDATOR_IDX).into(),
1388 };
1389
1390 let signing_payload = stmt.signing_payload();
1391 if !signature.verify(&signing_payload[..], &validator_public) {
1392 return InvalidTransaction::BadProof.into();
1393 }
1394
1395 let active_vote = match PvfActiveVoteMap::<T>::get(&stmt.subject) {
1396 Some(v) => v,
1397 None => return InvalidTransaction::Custom(INVALID_TX_BAD_SUBJECT).into(),
1398 };
1399
1400 match active_vote.has_vote(validator_index) {
1401 Some(false) => (),
1402 Some(true) =>
1403 return InvalidTransaction::Custom(INVALID_TX_DOUBLE_VOTE).into(),
1404 None =>
1405 return InvalidTransaction::Custom(INVALID_TX_BAD_VALIDATOR_IDX).into(),
1406 }
1407
1408 ValidTransaction::with_tag_prefix("PvfPreCheckingVote")
1409 .priority(T::UnsignedPriority::get())
1410 .longevity(
1411 TryInto::<u64>::try_into(
1412 T::NextSessionRotation::average_session_length() / 2u32.into(),
1413 )
1414 .unwrap_or(64_u64),
1415 )
1416 .and_provides((stmt.session_index, stmt.validator_index, stmt.subject))
1417 .propagate(true)
1418 .build()
1419 },
1420 Call::apply_authorized_force_set_current_code { para, new_code } =>
1421 match Self::validate_code_is_authorized(new_code, para) {
1422 Ok(authorized_code) => {
1423 let now = frame_system::Pallet::<T>::block_number();
1424 let longevity = authorized_code.expire_at.saturating_sub(now);
1425
1426 ValidTransaction::with_tag_prefix("ApplyAuthorizedForceSetCurrentCode")
1427 .priority(T::UnsignedPriority::get())
1428 .longevity(TryInto::<u64>::try_into(longevity).unwrap_or(64_u64))
1429 .and_provides((para, authorized_code.code_hash))
1430 .propagate(true)
1431 .build()
1432 },
1433 Err(_) =>
1434 return InvalidTransaction::Custom(INVALID_TX_UNAUTHORIZED_CODE).into(),
1435 },
1436 _ => InvalidTransaction::Call.into(),
1437 }
1438 }
1439
1440 fn pre_dispatch(_call: &Self::Call) -> Result<(), TransactionValidityError> {
1441 Ok(())
1449 }
1450 }
1451}
1452
1453const INVALID_TX_BAD_VALIDATOR_IDX: u8 = 1;
1455const INVALID_TX_BAD_SUBJECT: u8 = 2;
1456const INVALID_TX_DOUBLE_VOTE: u8 = 3;
1457const INVALID_TX_UNAUTHORIZED_CODE: u8 = 4;
1458
1459pub const MAX_PARA_HEADS: usize = 1024;
1467
1468impl<T: Config> Pallet<T> {
1469 pub(crate) fn schedule_code_upgrade_external(
1474 id: ParaId,
1475 new_code: ValidationCode,
1476 upgrade_strategy: UpgradeStrategy,
1477 ) -> DispatchResult {
1478 ensure!(Self::can_upgrade_validation_code(id), Error::<T>::CannotUpgradeCode);
1480 let config = configuration::ActiveConfig::<T>::get();
1481 ensure!(new_code.0.len() >= MIN_CODE_SIZE as usize, Error::<T>::InvalidCode);
1483 ensure!(new_code.0.len() <= config.max_code_size as usize, Error::<T>::InvalidCode);
1484
1485 let current_block = frame_system::Pallet::<T>::block_number();
1486 let upgrade_block = current_block.saturating_add(config.validation_upgrade_delay);
1488 Self::schedule_code_upgrade(id, new_code, upgrade_block, &config, upgrade_strategy);
1489 Self::deposit_event(Event::CodeUpgradeScheduled(id));
1490 Ok(())
1491 }
1492
1493 pub(crate) fn set_current_head(para: ParaId, new_head: HeadData) {
1495 Heads::<T>::insert(¶, new_head);
1496 Self::deposit_event(Event::CurrentHeadUpdated(para));
1497 }
1498
1499 pub(crate) fn initializer_initialize(now: BlockNumberFor<T>) -> Weight {
1501 Self::prune_old_code(now) +
1502 Self::process_scheduled_upgrade_changes(now) +
1503 Self::process_future_code_upgrades_at(now) +
1504 Self::prune_expired_authorizations(now)
1505 }
1506
1507 pub(crate) fn initializer_finalize(now: BlockNumberFor<T>) {
1509 Self::process_scheduled_upgrade_cooldowns(now);
1510 }
1511
1512 pub(crate) fn initializer_on_new_session(
1516 notification: &SessionChangeNotification<BlockNumberFor<T>>,
1517 ) -> Vec<ParaId> {
1518 let outgoing_paras = Self::apply_actions_queue(notification.session_index);
1519 Self::groom_ongoing_pvf_votes(¬ification.new_config, notification.validators.len());
1520 outgoing_paras
1521 }
1522
1523 pub(crate) fn current_code(para_id: &ParaId) -> Option<ValidationCode> {
1525 CurrentCodeHash::<T>::get(para_id).and_then(|code_hash| {
1526 let code = CodeByHash::<T>::get(&code_hash);
1527 if code.is_none() {
1528 log::error!(
1529 "Pallet paras storage is inconsistent, code not found for hash {}",
1530 code_hash,
1531 );
1532 debug_assert!(false, "inconsistent paras storages");
1533 }
1534 code
1535 })
1536 }
1537
1538 pub fn sorted_para_heads() -> Vec<(u32, Vec<u8>)> {
1541 let mut heads: Vec<(u32, Vec<u8>)> =
1542 Heads::<T>::iter().map(|(id, head)| (id.into(), head.0)).collect();
1543 heads.sort_by_key(|(id, _)| *id);
1544 heads.truncate(MAX_PARA_HEADS);
1545 heads
1546 }
1547
1548 fn apply_actions_queue(session: SessionIndex) -> Vec<ParaId> {
1557 let actions = ActionsQueue::<T>::take(session);
1558 let mut parachains = ParachainsCache::new();
1559 let now = frame_system::Pallet::<T>::block_number();
1560 let mut outgoing = Vec::new();
1561
1562 for para in actions {
1563 let lifecycle = ParaLifecycles::<T>::get(¶);
1564 match lifecycle {
1565 None | Some(ParaLifecycle::Parathread) | Some(ParaLifecycle::Parachain) => { },
1567 Some(ParaLifecycle::Onboarding) => {
1568 if let Some(genesis_data) = UpcomingParasGenesis::<T>::take(¶) {
1569 Self::initialize_para_now(&mut parachains, para, &genesis_data);
1570 }
1571 },
1572 Some(ParaLifecycle::UpgradingParathread) => {
1574 parachains.add(para);
1575 ParaLifecycles::<T>::insert(¶, ParaLifecycle::Parachain);
1576 },
1577 Some(ParaLifecycle::DowngradingParachain) => {
1579 parachains.remove(para);
1580 ParaLifecycles::<T>::insert(¶, ParaLifecycle::Parathread);
1581 },
1582 Some(ParaLifecycle::OffboardingParachain) |
1584 Some(ParaLifecycle::OffboardingParathread) => {
1585 parachains.remove(para);
1586
1587 Heads::<T>::remove(¶);
1588 MostRecentContext::<T>::remove(¶);
1589 FutureCodeUpgrades::<T>::remove(¶);
1590 UpgradeGoAheadSignal::<T>::remove(¶);
1591 UpgradeRestrictionSignal::<T>::remove(¶);
1592 ParaLifecycles::<T>::remove(¶);
1593 let removed_future_code_hash = FutureCodeHash::<T>::take(¶);
1594 if let Some(removed_future_code_hash) = removed_future_code_hash {
1595 Self::decrease_code_ref(&removed_future_code_hash);
1596 }
1597
1598 let removed_code_hash = CurrentCodeHash::<T>::take(¶);
1599 if let Some(removed_code_hash) = removed_code_hash {
1600 Self::note_past_code(para, now, now, removed_code_hash);
1601 }
1602
1603 outgoing.push(para);
1604 },
1605 }
1606 }
1607
1608 if !outgoing.is_empty() {
1609 UpcomingUpgrades::<T>::mutate(|upcoming_upgrades| {
1616 upcoming_upgrades.retain(|(para, _)| !outgoing.contains(para));
1617 });
1618 UpgradeCooldowns::<T>::mutate(|upgrade_cooldowns| {
1619 upgrade_cooldowns.retain(|(para, _)| !outgoing.contains(para));
1620 });
1621 FutureCodeUpgradesAt::<T>::mutate(|future_upgrades| {
1622 future_upgrades.retain(|(para, _)| !outgoing.contains(para));
1623 });
1624 }
1625
1626 drop(parachains);
1628
1629 outgoing
1630 }
1631
1632 fn note_past_code(
1639 id: ParaId,
1640 at: BlockNumberFor<T>,
1641 now: BlockNumberFor<T>,
1642 old_code_hash: ValidationCodeHash,
1643 ) -> Weight {
1644 PastCodeMeta::<T>::mutate(&id, |past_meta| {
1645 past_meta.note_replacement(at, now);
1646 });
1647
1648 PastCodeHash::<T>::insert(&(id, at), old_code_hash);
1649
1650 PastCodePruning::<T>::mutate(|pruning| {
1653 let insert_idx =
1654 pruning.binary_search_by_key(&now, |&(_, b)| b).unwrap_or_else(|idx| idx);
1655 pruning.insert(insert_idx, (id, now));
1656 });
1657
1658 T::DbWeight::get().reads_writes(2, 3)
1659 }
1660
1661 fn prune_old_code(now: BlockNumberFor<T>) -> Weight {
1664 let config = configuration::ActiveConfig::<T>::get();
1665 let code_retention_period = config.code_retention_period;
1666 if now <= code_retention_period {
1667 let weight = T::DbWeight::get().reads_writes(1, 0);
1668 return weight
1669 }
1670
1671 let pruning_height = now - (code_retention_period + One::one());
1673
1674 let pruning_tasks_done =
1675 PastCodePruning::<T>::mutate(|pruning_tasks: &mut Vec<(_, BlockNumberFor<T>)>| {
1676 let (pruning_tasks_done, pruning_tasks_to_do) = {
1677 let up_to_idx =
1679 pruning_tasks.iter().take_while(|&(_, at)| at <= &pruning_height).count();
1680 (up_to_idx, pruning_tasks.drain(..up_to_idx))
1681 };
1682
1683 for (para_id, _) in pruning_tasks_to_do {
1684 let full_deactivate = PastCodeMeta::<T>::mutate(¶_id, |meta| {
1685 for pruned_repl_at in meta.prune_up_to(pruning_height) {
1686 let removed_code_hash =
1687 PastCodeHash::<T>::take(&(para_id, pruned_repl_at));
1688
1689 if let Some(removed_code_hash) = removed_code_hash {
1690 Self::decrease_code_ref(&removed_code_hash);
1691 } else {
1692 log::warn!(
1693 target: LOG_TARGET,
1694 "Missing code for removed hash {:?}",
1695 removed_code_hash,
1696 );
1697 }
1698 }
1699
1700 meta.is_empty() && Heads::<T>::get(¶_id).is_none()
1701 });
1702
1703 if full_deactivate {
1706 PastCodeMeta::<T>::remove(¶_id);
1707 }
1708 }
1709
1710 pruning_tasks_done as u64
1711 });
1712
1713 T::DbWeight::get().reads_writes(1 + pruning_tasks_done, 2 * pruning_tasks_done)
1716 }
1717
1718 fn prune_expired_authorizations(now: BlockNumberFor<T>) -> Weight {
1721 let mut weight = T::DbWeight::get().reads(1);
1722 let to_remove = AuthorizedCodeHash::<T>::iter().filter_map(
1723 |(para, AuthorizedCodeHashAndExpiry { expire_at, .. })| {
1724 if expire_at <= now {
1725 Some(para)
1726 } else {
1727 None
1728 }
1729 },
1730 );
1731 for para in to_remove {
1732 AuthorizedCodeHash::<T>::remove(¶);
1733 weight.saturating_accrue(T::DbWeight::get().writes(1));
1734 }
1735
1736 weight
1737 }
1738
1739 fn process_future_code_upgrades_at(now: BlockNumberFor<T>) -> Weight {
1744 let mut weight = T::DbWeight::get().reads_writes(1, 1);
1746 FutureCodeUpgradesAt::<T>::mutate(
1747 |upcoming_upgrades: &mut Vec<(ParaId, BlockNumberFor<T>)>| {
1748 let num = upcoming_upgrades.iter().take_while(|&(_, at)| at <= &now).count();
1749 for (id, expected_at) in upcoming_upgrades.drain(..num) {
1750 weight += T::DbWeight::get().reads_writes(1, 1);
1751
1752 let new_code_hash = if let Some(new_code_hash) = FutureCodeHash::<T>::take(&id)
1754 {
1755 new_code_hash
1756 } else {
1757 log::error!(target: LOG_TARGET, "Missing future code hash for {:?}", &id);
1758 continue
1759 };
1760
1761 weight += Self::set_current_code(id, new_code_hash, expected_at);
1762 }
1763 num
1764 },
1765 );
1766
1767 weight
1768 }
1769
1770 fn process_scheduled_upgrade_changes(now: BlockNumberFor<T>) -> Weight {
1776 let mut weight = T::DbWeight::get().reads_writes(1, 1);
1778 let upgrades_signaled = UpcomingUpgrades::<T>::mutate(
1779 |upcoming_upgrades: &mut Vec<(ParaId, BlockNumberFor<T>)>| {
1780 let num = upcoming_upgrades.iter().take_while(|&(_, at)| at <= &now).count();
1781 for (para, _) in upcoming_upgrades.drain(..num) {
1782 UpgradeGoAheadSignal::<T>::insert(¶, UpgradeGoAhead::GoAhead);
1783 }
1784 num
1785 },
1786 );
1787 weight += T::DbWeight::get().writes(upgrades_signaled as u64);
1788
1789 weight += T::DbWeight::get().reads(1);
1791 let cooldowns_expired =
1792 UpgradeCooldowns::<T>::get().iter().take_while(|&(_, at)| at <= &now).count();
1793
1794 weight += T::DbWeight::get().reads_writes(1, 1);
1798 weight += T::DbWeight::get().reads(cooldowns_expired as u64);
1799
1800 weight
1801 }
1802
1803 fn process_scheduled_upgrade_cooldowns(now: BlockNumberFor<T>) {
1807 UpgradeCooldowns::<T>::mutate(
1808 |upgrade_cooldowns: &mut Vec<(ParaId, BlockNumberFor<T>)>| {
1809 upgrade_cooldowns.retain(|(para, at)| {
1811 if at <= &now {
1812 UpgradeRestrictionSignal::<T>::remove(¶);
1813 false
1814 } else {
1815 true
1816 }
1817 });
1818 },
1819 );
1820 }
1821
1822 fn groom_ongoing_pvf_votes(
1825 cfg: &configuration::HostConfiguration<BlockNumberFor<T>>,
1826 new_n_validators: usize,
1827 ) -> Weight {
1828 let mut weight = T::DbWeight::get().reads(1);
1829
1830 let potentially_active_votes = PvfActiveVoteList::<T>::get();
1831
1832 let mut actually_active_votes = Vec::with_capacity(potentially_active_votes.len());
1837
1838 for vote_subject in potentially_active_votes {
1839 let mut vote_state = match PvfActiveVoteMap::<T>::take(&vote_subject) {
1840 Some(v) => v,
1841 None => {
1842 log::warn!(
1846 target: LOG_TARGET,
1847 "The PvfActiveVoteMap is out of sync with PvfActiveVoteList!",
1848 );
1849 debug_assert!(false);
1850 continue
1851 },
1852 };
1853
1854 vote_state.age += 1;
1855 if vote_state.age < cfg.pvf_voting_ttl {
1856 weight += T::DbWeight::get().writes(1);
1857 vote_state.reinitialize_ballots(new_n_validators);
1858 PvfActiveVoteMap::<T>::insert(&vote_subject, vote_state);
1859
1860 actually_active_votes.push(vote_subject);
1862 } else {
1863 weight += Self::enact_pvf_rejected(&vote_subject, vote_state.causes);
1865 }
1866 }
1867
1868 weight += T::DbWeight::get().writes(1);
1869 PvfActiveVoteList::<T>::put(actually_active_votes);
1870
1871 weight
1872 }
1873
1874 fn enact_pvf_accepted(
1875 now: BlockNumberFor<T>,
1876 code_hash: &ValidationCodeHash,
1877 causes: &[PvfCheckCause<BlockNumberFor<T>>],
1878 sessions_observed: SessionIndex,
1879 cfg: &configuration::HostConfiguration<BlockNumberFor<T>>,
1880 ) -> Weight {
1881 let mut weight = Weight::zero();
1882 for cause in causes {
1883 weight += T::DbWeight::get().reads_writes(3, 2);
1884 Self::deposit_event(Event::PvfCheckAccepted(*code_hash, cause.para_id()));
1885
1886 match cause {
1887 PvfCheckCause::Onboarding(id) => {
1888 weight += Self::proceed_with_onboarding(*id, sessions_observed);
1889 },
1890 PvfCheckCause::Upgrade { id, included_at, upgrade_strategy } => {
1891 weight += Self::proceed_with_upgrade(
1892 *id,
1893 code_hash,
1894 now,
1895 *included_at,
1896 cfg,
1897 *upgrade_strategy,
1898 );
1899 },
1900 }
1901 }
1902 weight
1903 }
1904
1905 fn proceed_with_onboarding(id: ParaId, sessions_observed: SessionIndex) -> Weight {
1906 let weight = T::DbWeight::get().reads_writes(2, 1);
1907
1908 let onboard_at: SessionIndex = shared::CurrentSessionIndex::<T>::get() +
1914 cmp::max(shared::SESSION_DELAY.saturating_sub(sessions_observed), 1);
1915
1916 ActionsQueue::<T>::mutate(onboard_at, |v| {
1917 if let Err(i) = v.binary_search(&id) {
1918 v.insert(i, id);
1919 }
1920 });
1921
1922 weight
1923 }
1924
1925 fn proceed_with_upgrade(
1926 id: ParaId,
1927 code_hash: &ValidationCodeHash,
1928 now: BlockNumberFor<T>,
1929 relay_parent_number: BlockNumberFor<T>,
1930 cfg: &configuration::HostConfiguration<BlockNumberFor<T>>,
1931 upgrade_strategy: UpgradeStrategy,
1932 ) -> Weight {
1933 let mut weight = Weight::zero();
1934
1935 let expected_at = cmp::max(
1948 relay_parent_number + cfg.validation_upgrade_delay,
1949 now + cfg.minimum_validation_upgrade_delay,
1950 );
1951
1952 match upgrade_strategy {
1953 UpgradeStrategy::ApplyAtExpectedBlock => {
1954 FutureCodeUpgradesAt::<T>::mutate(|future_upgrades| {
1955 let insert_idx = future_upgrades
1956 .binary_search_by_key(&expected_at, |&(_, b)| b)
1957 .unwrap_or_else(|idx| idx);
1958 future_upgrades.insert(insert_idx, (id, expected_at));
1959 });
1960
1961 weight += T::DbWeight::get().reads_writes(0, 2);
1962 },
1963 UpgradeStrategy::SetGoAheadSignal => {
1964 FutureCodeUpgrades::<T>::insert(&id, expected_at);
1965
1966 UpcomingUpgrades::<T>::mutate(|upcoming_upgrades| {
1967 let insert_idx = upcoming_upgrades
1968 .binary_search_by_key(&expected_at, |&(_, b)| b)
1969 .unwrap_or_else(|idx| idx);
1970 upcoming_upgrades.insert(insert_idx, (id, expected_at));
1971 });
1972
1973 weight += T::DbWeight::get().reads_writes(1, 3);
1974 },
1975 }
1976
1977 let expected_at = expected_at.saturated_into();
1978 let log = ConsensusLog::ParaScheduleUpgradeCode(id, *code_hash, expected_at);
1979 frame_system::Pallet::<T>::deposit_log(log.into());
1980
1981 weight
1982 }
1983
1984 fn enact_pvf_rejected(
1985 code_hash: &ValidationCodeHash,
1986 causes: Vec<PvfCheckCause<BlockNumberFor<T>>>,
1987 ) -> Weight {
1988 let mut weight = Weight::zero();
1989
1990 for cause in causes {
1991 weight += Self::decrease_code_ref(code_hash);
1994
1995 weight += T::DbWeight::get().reads_writes(3, 2);
1996 Self::deposit_event(Event::PvfCheckRejected(*code_hash, cause.para_id()));
1997
1998 match cause {
1999 PvfCheckCause::Onboarding(id) => {
2000 weight += T::DbWeight::get().writes(3);
2006 UpcomingParasGenesis::<T>::remove(&id);
2007 CurrentCodeHash::<T>::remove(&id);
2008 ParaLifecycles::<T>::remove(&id);
2009 },
2010 PvfCheckCause::Upgrade { id, .. } => {
2011 weight += T::DbWeight::get().writes(2);
2012 UpgradeGoAheadSignal::<T>::insert(&id, UpgradeGoAhead::Abort);
2013 FutureCodeHash::<T>::remove(&id);
2014 },
2015 }
2016 }
2017
2018 weight
2019 }
2020
2021 pub fn can_schedule_para_initialize(id: &ParaId) -> bool {
2025 ParaLifecycles::<T>::get(id).is_none()
2026 }
2027
2028 pub(crate) fn schedule_para_initialize(
2039 id: ParaId,
2040 mut genesis_data: ParaGenesisArgs,
2041 ) -> DispatchResult {
2042 ensure!(Self::can_schedule_para_initialize(&id), Error::<T>::CannotOnboard);
2045 ensure!(!genesis_data.validation_code.0.is_empty(), Error::<T>::CannotOnboard);
2046 ParaLifecycles::<T>::insert(&id, ParaLifecycle::Onboarding);
2047
2048 let validation_code =
2082 mem::replace(&mut genesis_data.validation_code, ValidationCode(Vec::new()));
2083 UpcomingParasGenesis::<T>::insert(&id, genesis_data);
2084 let validation_code_hash = validation_code.hash();
2085 CurrentCodeHash::<T>::insert(&id, validation_code_hash);
2086
2087 let cfg = configuration::ActiveConfig::<T>::get();
2088 Self::kick_off_pvf_check(
2089 PvfCheckCause::Onboarding(id),
2090 validation_code_hash,
2091 validation_code,
2092 &cfg,
2093 );
2094
2095 Ok(())
2096 }
2097
2098 pub(crate) fn schedule_para_cleanup(id: ParaId) -> DispatchResult {
2108 if let Some(future_code_hash) = FutureCodeHash::<T>::get(&id) {
2120 let active_prechecking = PvfActiveVoteList::<T>::get();
2121 if active_prechecking.contains(&future_code_hash) {
2122 return Err(Error::<T>::CannotOffboard.into())
2123 }
2124 }
2125
2126 let lifecycle = ParaLifecycles::<T>::get(&id);
2127 match lifecycle {
2128 None => return Ok(()),
2130 Some(ParaLifecycle::Parathread) => {
2131 ParaLifecycles::<T>::insert(&id, ParaLifecycle::OffboardingParathread);
2132 },
2133 Some(ParaLifecycle::Parachain) => {
2134 ParaLifecycles::<T>::insert(&id, ParaLifecycle::OffboardingParachain);
2135 },
2136 _ => return Err(Error::<T>::CannotOffboard.into()),
2137 }
2138
2139 let scheduled_session = Self::scheduled_session();
2140 ActionsQueue::<T>::mutate(scheduled_session, |v| {
2141 if let Err(i) = v.binary_search(&id) {
2142 v.insert(i, id);
2143 }
2144 });
2145
2146 if <T as Config>::QueueFootprinter::message_count(UmpQueueId::Para(id)) != 0 {
2147 return Err(Error::<T>::CannotOffboard.into())
2148 }
2149
2150 Ok(())
2151 }
2152
2153 pub(crate) fn schedule_parathread_upgrade(id: ParaId) -> DispatchResult {
2157 let scheduled_session = Self::scheduled_session();
2158 let lifecycle = ParaLifecycles::<T>::get(&id).ok_or(Error::<T>::NotRegistered)?;
2159
2160 ensure!(lifecycle == ParaLifecycle::Parathread, Error::<T>::CannotUpgrade);
2161
2162 ParaLifecycles::<T>::insert(&id, ParaLifecycle::UpgradingParathread);
2163 ActionsQueue::<T>::mutate(scheduled_session, |v| {
2164 if let Err(i) = v.binary_search(&id) {
2165 v.insert(i, id);
2166 }
2167 });
2168
2169 Ok(())
2170 }
2171
2172 pub(crate) fn schedule_parachain_downgrade(id: ParaId) -> DispatchResult {
2176 let scheduled_session = Self::scheduled_session();
2177 let lifecycle = ParaLifecycles::<T>::get(&id).ok_or(Error::<T>::NotRegistered)?;
2178
2179 ensure!(lifecycle == ParaLifecycle::Parachain, Error::<T>::CannotDowngrade);
2180
2181 ParaLifecycles::<T>::insert(&id, ParaLifecycle::DowngradingParachain);
2182 ActionsQueue::<T>::mutate(scheduled_session, |v| {
2183 if let Err(i) = v.binary_search(&id) {
2184 v.insert(i, id);
2185 }
2186 });
2187
2188 Ok(())
2189 }
2190
2191 pub(crate) fn schedule_code_upgrade(
2210 id: ParaId,
2211 new_code: ValidationCode,
2212 inclusion_block_number: BlockNumberFor<T>,
2213 cfg: &configuration::HostConfiguration<BlockNumberFor<T>>,
2214 upgrade_strategy: UpgradeStrategy,
2215 ) {
2216 let new_code_len = new_code.0.len();
2218 if new_code_len < MIN_CODE_SIZE as usize || new_code_len > cfg.max_code_size as usize {
2219 log::warn!(target: LOG_TARGET, "attempted to schedule an upgrade with invalid new validation code",);
2220 return
2221 }
2222
2223 if FutureCodeHash::<T>::contains_key(&id) {
2225 log::warn!(target: LOG_TARGET, "ended up scheduling an upgrade while one is pending",);
2234 return
2235 }
2236
2237 let code_hash = new_code.hash();
2238
2239 if CurrentCodeHash::<T>::get(&id) == Some(code_hash) {
2244 log::warn!(
2247 target: LOG_TARGET,
2248 "para tried to upgrade to the same code. Abort the upgrade",
2249 );
2250 return
2251 }
2252
2253 FutureCodeHash::<T>::insert(&id, &code_hash);
2255 UpgradeRestrictionSignal::<T>::insert(&id, UpgradeRestriction::Present);
2256
2257 let next_possible_upgrade_at = inclusion_block_number + cfg.validation_upgrade_cooldown;
2258 UpgradeCooldowns::<T>::mutate(|upgrade_cooldowns| {
2259 let insert_idx = upgrade_cooldowns
2260 .binary_search_by_key(&next_possible_upgrade_at, |&(_, b)| b)
2261 .unwrap_or_else(|idx| idx);
2262 upgrade_cooldowns.insert(insert_idx, (id, next_possible_upgrade_at));
2263 });
2264
2265 Self::kick_off_pvf_check(
2266 PvfCheckCause::Upgrade { id, included_at: inclusion_block_number, upgrade_strategy },
2267 code_hash,
2268 new_code,
2269 cfg,
2270 );
2271 }
2272
2273 fn kick_off_pvf_check(
2287 cause: PvfCheckCause<BlockNumberFor<T>>,
2288 code_hash: ValidationCodeHash,
2289 code: ValidationCode,
2290 cfg: &configuration::HostConfiguration<BlockNumberFor<T>>,
2291 ) -> Weight {
2292 let mut weight = Weight::zero();
2293
2294 weight += T::DbWeight::get().reads_writes(3, 2);
2295 Self::deposit_event(Event::PvfCheckStarted(code_hash, cause.para_id()));
2296
2297 weight += T::DbWeight::get().reads(1);
2298 match PvfActiveVoteMap::<T>::get(&code_hash) {
2299 None => {
2300 let known_code = CodeByHash::<T>::contains_key(&code_hash);
2303 weight += T::DbWeight::get().reads(1);
2304
2305 if known_code {
2306 weight += T::DbWeight::get().reads(1);
2309 let now = frame_system::Pallet::<T>::block_number();
2310 weight += Self::enact_pvf_accepted(now, &code_hash, &[cause], 0, cfg);
2311 } else {
2312 weight += T::DbWeight::get().reads_writes(3, 2);
2315 let now = frame_system::Pallet::<T>::block_number();
2316 let n_validators = shared::ActiveValidatorKeys::<T>::get().len();
2317 PvfActiveVoteMap::<T>::insert(
2318 &code_hash,
2319 PvfCheckActiveVoteState::new(now, n_validators, cause),
2320 );
2321 PvfActiveVoteList::<T>::mutate(|l| {
2322 if let Err(idx) = l.binary_search(&code_hash) {
2323 l.insert(idx, code_hash);
2324 }
2325 });
2326 }
2327 },
2328 Some(mut vote_state) => {
2329 weight += T::DbWeight::get().writes(1);
2332 vote_state.causes.push(cause);
2333 PvfActiveVoteMap::<T>::insert(&code_hash, vote_state);
2334 },
2335 }
2336
2337 weight += Self::increase_code_ref(&code_hash, &code);
2349
2350 weight
2351 }
2352
2353 pub(crate) fn note_new_head(
2357 id: ParaId,
2358 new_head: HeadData,
2359 execution_context: BlockNumberFor<T>,
2360 ) {
2361 Heads::<T>::insert(&id, &new_head);
2362 MostRecentContext::<T>::insert(&id, execution_context);
2363
2364 if let Some(expected_at) = FutureCodeUpgrades::<T>::get(&id) {
2365 if expected_at <= execution_context {
2366 FutureCodeUpgrades::<T>::remove(&id);
2367 UpgradeGoAheadSignal::<T>::remove(&id);
2368
2369 let new_code_hash = if let Some(new_code_hash) = FutureCodeHash::<T>::take(&id) {
2371 new_code_hash
2372 } else {
2373 log::error!(target: LOG_TARGET, "Missing future code hash for {:?}", &id);
2374 return
2375 };
2376
2377 Self::set_current_code(id, new_code_hash, expected_at);
2378 }
2379 } else {
2380 UpgradeGoAheadSignal::<T>::remove(&id);
2385 };
2386
2387 T::OnNewHead::on_new_head(id, &new_head);
2388 }
2389
2390 pub(crate) fn set_current_code(
2395 id: ParaId,
2396 new_code_hash: ValidationCodeHash,
2397 at: BlockNumberFor<T>,
2398 ) -> Weight {
2399 let maybe_prior_code_hash = CurrentCodeHash::<T>::get(&id);
2400 CurrentCodeHash::<T>::insert(&id, &new_code_hash);
2401
2402 let log = ConsensusLog::ParaUpgradeCode(id, new_code_hash);
2403 <frame_system::Pallet<T>>::deposit_log(log.into());
2404
2405 let now = <frame_system::Pallet<T>>::block_number();
2407
2408 let weight = if let Some(prior_code_hash) = maybe_prior_code_hash {
2409 Self::note_past_code(id, at, now, prior_code_hash)
2410 } else {
2411 log::error!(target: LOG_TARGET, "Missing prior code hash for para {:?}", &id);
2412 Weight::zero()
2413 };
2414
2415 weight + T::DbWeight::get().writes(1)
2416 }
2417
2418 fn do_force_set_current_code_update(para: ParaId, new_code: ValidationCode) {
2420 let new_code_hash = new_code.hash();
2421 Self::increase_code_ref(&new_code_hash, &new_code);
2422 Self::set_current_code(para, new_code_hash, frame_system::Pallet::<T>::block_number());
2423 Self::deposit_event(Event::CurrentCodeUpdated(para));
2424 }
2425
2426 pub(crate) fn pvfs_require_precheck() -> Vec<ValidationCodeHash> {
2429 PvfActiveVoteList::<T>::get()
2430 }
2431
2432 pub(crate) fn submit_pvf_check_statement(
2439 stmt: PvfCheckStatement,
2440 signature: ValidatorSignature,
2441 ) {
2442 use frame_system::offchain::SubmitTransaction;
2443
2444 let xt = T::create_bare(Call::include_pvf_check_statement { stmt, signature }.into());
2445 if let Err(e) = SubmitTransaction::<T, Call<T>>::submit_transaction(xt) {
2446 log::error!(target: LOG_TARGET, "Error submitting pvf check statement: {:?}", e,);
2447 }
2448 }
2449
2450 pub fn lifecycle(id: ParaId) -> Option<ParaLifecycle> {
2452 ParaLifecycles::<T>::get(&id)
2453 }
2454
2455 pub fn is_valid_para(id: ParaId) -> bool {
2459 if let Some(state) = ParaLifecycles::<T>::get(&id) {
2460 !state.is_onboarding() && !state.is_offboarding()
2461 } else {
2462 false
2463 }
2464 }
2465
2466 pub fn is_offboarding(id: ParaId) -> bool {
2470 ParaLifecycles::<T>::get(&id).map_or(false, |state| state.is_offboarding())
2471 }
2472
2473 pub fn is_parachain(id: ParaId) -> bool {
2478 if let Some(state) = ParaLifecycles::<T>::get(&id) {
2479 state.is_parachain()
2480 } else {
2481 false
2482 }
2483 }
2484
2485 pub fn is_parathread(id: ParaId) -> bool {
2489 if let Some(state) = ParaLifecycles::<T>::get(&id) {
2490 state.is_parathread()
2491 } else {
2492 false
2493 }
2494 }
2495
2496 pub(crate) fn can_upgrade_validation_code(id: ParaId) -> bool {
2499 FutureCodeHash::<T>::get(&id).is_none() && UpgradeRestrictionSignal::<T>::get(&id).is_none()
2500 }
2501
2502 fn scheduled_session() -> SessionIndex {
2504 shared::Pallet::<T>::scheduled_session()
2505 }
2506
2507 fn increase_code_ref(code_hash: &ValidationCodeHash, code: &ValidationCode) -> Weight {
2511 let mut weight = T::DbWeight::get().reads_writes(1, 1);
2512 CodeByHashRefs::<T>::mutate(code_hash, |refs| {
2513 if *refs == 0 {
2514 weight += T::DbWeight::get().writes(1);
2515 CodeByHash::<T>::insert(code_hash, code);
2516 }
2517 *refs += 1;
2518 });
2519 weight
2520 }
2521
2522 fn decrease_code_ref(code_hash: &ValidationCodeHash) -> Weight {
2527 let mut weight = T::DbWeight::get().reads(1);
2528 let refs = CodeByHashRefs::<T>::get(code_hash);
2529 if refs == 0 {
2530 log::error!(target: LOG_TARGET, "Code refs is already zero for {:?}", code_hash);
2531 return weight
2532 }
2533 if refs <= 1 {
2534 weight += T::DbWeight::get().writes(2);
2535 CodeByHash::<T>::remove(code_hash);
2536 CodeByHashRefs::<T>::remove(code_hash);
2537 } else {
2538 weight += T::DbWeight::get().writes(1);
2539 CodeByHashRefs::<T>::insert(code_hash, refs - 1);
2540 }
2541 weight
2542 }
2543
2544 #[cfg(any(feature = "std", feature = "runtime-benchmarks", test))]
2546 pub fn test_on_new_session() {
2547 Self::initializer_on_new_session(&SessionChangeNotification {
2548 session_index: shared::CurrentSessionIndex::<T>::get(),
2549 ..Default::default()
2550 });
2551 }
2552
2553 #[cfg(any(feature = "runtime-benchmarks", test))]
2554 pub fn heads_insert(para_id: &ParaId, head_data: HeadData) {
2555 Heads::<T>::insert(para_id, head_data);
2556 }
2557
2558 pub(crate) fn initialize_para_now(
2560 parachains: &mut ParachainsCache<T>,
2561 id: ParaId,
2562 genesis_data: &ParaGenesisArgs,
2563 ) {
2564 match genesis_data.para_kind {
2565 ParaKind::Parachain => {
2566 parachains.add(id);
2567 ParaLifecycles::<T>::insert(&id, ParaLifecycle::Parachain);
2568 },
2569 ParaKind::Parathread => ParaLifecycles::<T>::insert(&id, ParaLifecycle::Parathread),
2570 }
2571
2572 if !genesis_data.validation_code.0.is_empty() {
2578 let code_hash = genesis_data.validation_code.hash();
2579 Self::increase_code_ref(&code_hash, &genesis_data.validation_code);
2580 CurrentCodeHash::<T>::insert(&id, code_hash);
2581 }
2582
2583 Heads::<T>::insert(&id, &genesis_data.genesis_head);
2584 MostRecentContext::<T>::insert(&id, BlockNumberFor::<T>::from(0u32));
2585 }
2586
2587 #[cfg(test)]
2588 pub(crate) fn active_vote_state(
2589 code_hash: &ValidationCodeHash,
2590 ) -> Option<PvfCheckActiveVoteState<BlockNumberFor<T>>> {
2591 PvfActiveVoteMap::<T>::get(code_hash)
2592 }
2593
2594 pub(crate) fn validate_code_is_authorized(
2599 code: &ValidationCode,
2600 para: &ParaId,
2601 ) -> Result<AuthorizedCodeHashAndExpiry<BlockNumberFor<T>>, Error<T>> {
2602 let authorized = AuthorizedCodeHash::<T>::get(para).ok_or(Error::<T>::NothingAuthorized)?;
2603 let now = frame_system::Pallet::<T>::block_number();
2604 ensure!(authorized.expire_at > now, Error::<T>::InvalidBlockNumber);
2605 ensure!(authorized.code_hash == code.hash(), Error::<T>::Unauthorized);
2606 Ok(authorized)
2607 }
2608}
2609
2610pub(crate) struct ParachainsCache<T: Config> {
2613 parachains: Option<BTreeSet<ParaId>>,
2615 _config: PhantomData<T>,
2616}
2617
2618impl<T: Config> ParachainsCache<T> {
2619 pub fn new() -> Self {
2620 Self { parachains: None, _config: PhantomData }
2621 }
2622
2623 fn ensure_initialized(&mut self) -> &mut BTreeSet<ParaId> {
2624 self.parachains
2625 .get_or_insert_with(|| Parachains::<T>::get().into_iter().collect())
2626 }
2627
2628 pub fn add(&mut self, id: ParaId) {
2630 let parachains = self.ensure_initialized();
2631 parachains.insert(id);
2632 }
2633
2634 pub fn remove(&mut self, id: ParaId) {
2637 let parachains = self.ensure_initialized();
2638 parachains.remove(&id);
2639 }
2640}
2641
2642impl<T: Config> Drop for ParachainsCache<T> {
2643 fn drop(&mut self) {
2644 if let Some(parachains) = self.parachains.take() {
2645 Parachains::<T>::put(parachains.into_iter().collect::<Vec<ParaId>>());
2646 }
2647 }
2648}