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 pallet_prelude::*,
122 traits::{EnsureOriginWithArg, EstimateNextSessionRotation},
123 DefaultNoBound,
124};
125use frame_system::pallet_prelude::*;
126use polkadot_primitives::{
127 ConsensusLog, HeadData, Id as ParaId, PvfCheckStatement, SessionIndex, UpgradeGoAhead,
128 UpgradeRestriction, ValidationCode, ValidationCodeHash, ValidatorSignature, MIN_CODE_SIZE,
129};
130use scale_info::{Type, TypeInfo};
131use sp_core::RuntimeDebug;
132use sp_runtime::{
133 traits::{AppVerify, One, Saturating},
134 DispatchResult, SaturatedConversion,
135};
136
137use serde::{Deserialize, Serialize};
138
139pub use crate::Origin as ParachainOrigin;
140
141#[cfg(feature = "runtime-benchmarks")]
142pub mod benchmarking;
143
144#[cfg(test)]
145pub(crate) mod tests;
146
147pub use pallet::*;
148
149const LOG_TARGET: &str = "runtime::paras";
150
151#[derive(Default, Encode, Decode, TypeInfo)]
153#[cfg_attr(test, derive(Debug, Clone, PartialEq))]
154pub struct ReplacementTimes<N> {
155 expected_at: N,
159 activated_at: N,
163}
164
165#[derive(Default, Encode, Decode, TypeInfo)]
168#[cfg_attr(test, derive(Debug, Clone, PartialEq))]
169pub struct ParaPastCodeMeta<N> {
170 upgrade_times: Vec<ReplacementTimes<N>>,
176 last_pruned: Option<N>,
179}
180
181#[derive(PartialEq, Eq, Clone, Encode, Decode, RuntimeDebug, TypeInfo)]
187pub enum ParaLifecycle {
188 Onboarding,
190 Parathread,
192 Parachain,
194 UpgradingParathread,
196 DowngradingParachain,
198 OffboardingParathread,
200 OffboardingParachain,
202}
203
204impl ParaLifecycle {
205 pub fn is_onboarding(&self) -> bool {
209 matches!(self, ParaLifecycle::Onboarding)
210 }
211
212 pub fn is_stable(&self) -> bool {
215 matches!(self, ParaLifecycle::Parathread | ParaLifecycle::Parachain)
216 }
217
218 pub fn is_parachain(&self) -> bool {
222 matches!(
223 self,
224 ParaLifecycle::Parachain |
225 ParaLifecycle::DowngradingParachain |
226 ParaLifecycle::OffboardingParachain
227 )
228 }
229
230 pub fn is_parathread(&self) -> bool {
234 matches!(
235 self,
236 ParaLifecycle::Parathread |
237 ParaLifecycle::UpgradingParathread |
238 ParaLifecycle::OffboardingParathread
239 )
240 }
241
242 pub fn is_offboarding(&self) -> bool {
244 matches!(self, ParaLifecycle::OffboardingParathread | ParaLifecycle::OffboardingParachain)
245 }
246
247 pub fn is_transitioning(&self) -> bool {
249 !Self::is_stable(self)
250 }
251}
252
253impl<N: Ord + Copy + PartialEq> ParaPastCodeMeta<N> {
254 pub(crate) fn note_replacement(&mut self, expected_at: N, activated_at: N) {
256 self.upgrade_times.push(ReplacementTimes { expected_at, activated_at })
257 }
258
259 fn is_empty(&self) -> bool {
261 self.upgrade_times.is_empty()
262 }
263
264 #[cfg(test)]
267 fn most_recent_change(&self) -> Option<N> {
268 self.upgrade_times.last().map(|x| x.expected_at)
269 }
270
271 fn prune_up_to(&'_ mut self, max: N) -> impl Iterator<Item = N> + '_ {
282 let to_prune = self.upgrade_times.iter().take_while(|t| t.activated_at <= max).count();
283 let drained = if to_prune == 0 {
284 self.upgrade_times.drain(self.upgrade_times.len()..)
286 } else {
287 self.last_pruned = Some(self.upgrade_times[to_prune - 1].activated_at);
289 self.upgrade_times.drain(..to_prune)
290 };
291
292 drained.map(|times| times.expected_at)
293 }
294}
295
296#[derive(
298 PartialEq,
299 Eq,
300 Clone,
301 Encode,
302 Decode,
303 DecodeWithMemTracking,
304 RuntimeDebug,
305 TypeInfo,
306 Serialize,
307 Deserialize,
308)]
309pub struct ParaGenesisArgs {
310 pub genesis_head: HeadData,
312 pub validation_code: ValidationCode,
314 #[serde(rename = "parachain")]
316 pub para_kind: ParaKind,
317}
318
319#[derive(DecodeWithMemTracking, PartialEq, Eq, Clone, RuntimeDebug)]
321pub enum ParaKind {
322 Parathread,
323 Parachain,
324}
325
326impl Serialize for ParaKind {
327 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
328 where
329 S: serde::Serializer,
330 {
331 match self {
332 ParaKind::Parachain => serializer.serialize_bool(true),
333 ParaKind::Parathread => serializer.serialize_bool(false),
334 }
335 }
336}
337
338impl<'de> Deserialize<'de> for ParaKind {
339 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
340 where
341 D: serde::Deserializer<'de>,
342 {
343 match serde::de::Deserialize::deserialize(deserializer) {
344 Ok(true) => Ok(ParaKind::Parachain),
345 Ok(false) => Ok(ParaKind::Parathread),
346 _ => Err(serde::de::Error::custom("invalid ParaKind serde representation")),
347 }
348 }
349}
350
351impl Encode for ParaKind {
354 fn size_hint(&self) -> usize {
355 true.size_hint()
356 }
357
358 fn using_encoded<R, F: FnOnce(&[u8]) -> R>(&self, f: F) -> R {
359 match self {
360 ParaKind::Parachain => true.using_encoded(f),
361 ParaKind::Parathread => false.using_encoded(f),
362 }
363 }
364}
365
366impl Decode for ParaKind {
367 fn decode<I: codec::Input>(input: &mut I) -> Result<Self, codec::Error> {
368 match bool::decode(input) {
369 Ok(true) => Ok(ParaKind::Parachain),
370 Ok(false) => Ok(ParaKind::Parathread),
371 _ => Err("Invalid ParaKind representation".into()),
372 }
373 }
374}
375
376impl TypeInfo for ParaKind {
377 type Identity = bool;
378 fn type_info() -> Type {
379 bool::type_info()
380 }
381}
382
383#[derive(Debug, Encode, Decode, TypeInfo)]
386pub(crate) enum PvfCheckCause<BlockNumber> {
387 Onboarding(ParaId),
389 Upgrade {
391 id: ParaId,
394 included_at: BlockNumber,
403 upgrade_strategy: UpgradeStrategy,
408 },
409}
410
411#[derive(Debug, Copy, Clone, PartialEq, TypeInfo, Decode, Encode)]
419pub enum UpgradeStrategy {
420 SetGoAheadSignal,
425 ApplyAtExpectedBlock,
429}
430
431impl<BlockNumber> PvfCheckCause<BlockNumber> {
432 fn para_id(&self) -> ParaId {
434 match *self {
435 PvfCheckCause::Onboarding(id) => id,
436 PvfCheckCause::Upgrade { id, .. } => id,
437 }
438 }
439}
440
441#[derive(Copy, Clone, Encode, Decode, RuntimeDebug, TypeInfo)]
443enum PvfCheckOutcome {
444 Accepted,
445 Rejected,
446}
447
448#[derive(Encode, Decode, TypeInfo)]
450pub(crate) struct PvfCheckActiveVoteState<BlockNumber> {
451 votes_accept: BitVec<u8, BitOrderLsb0>,
457 votes_reject: BitVec<u8, BitOrderLsb0>,
458
459 age: SessionIndex,
462 created_at: BlockNumber,
464 causes: Vec<PvfCheckCause<BlockNumber>>,
466}
467
468impl<BlockNumber> PvfCheckActiveVoteState<BlockNumber> {
469 fn new(now: BlockNumber, n_validators: usize, cause: PvfCheckCause<BlockNumber>) -> Self {
472 let mut causes = Vec::with_capacity(1);
473 causes.push(cause);
474 Self {
475 created_at: now,
476 votes_accept: bitvec::bitvec![u8, BitOrderLsb0; 0; n_validators],
477 votes_reject: bitvec::bitvec![u8, BitOrderLsb0; 0; n_validators],
478 age: 0,
479 causes,
480 }
481 }
482
483 fn reinitialize_ballots(&mut self, n_validators: usize) {
486 let clear_and_resize = |v: &mut BitVec<_, _>| {
487 v.clear();
488 v.resize(n_validators, false);
489 };
490 clear_and_resize(&mut self.votes_accept);
491 clear_and_resize(&mut self.votes_reject);
492 }
493
494 fn has_vote(&self, validator_index: usize) -> Option<bool> {
497 let accept_vote = self.votes_accept.get(validator_index)?;
498 let reject_vote = self.votes_reject.get(validator_index)?;
499 Some(*accept_vote || *reject_vote)
500 }
501
502 fn quorum(&self, n_validators: usize) -> Option<PvfCheckOutcome> {
504 let accept_threshold = polkadot_primitives::supermajority_threshold(n_validators);
505 let reject_threshold = n_validators - accept_threshold;
507
508 if self.votes_accept.count_ones() >= accept_threshold {
509 Some(PvfCheckOutcome::Accepted)
510 } else if self.votes_reject.count_ones() > reject_threshold {
511 Some(PvfCheckOutcome::Rejected)
512 } else {
513 None
514 }
515 }
516
517 #[cfg(test)]
518 pub(crate) fn causes(&self) -> &[PvfCheckCause<BlockNumber>] {
519 self.causes.as_slice()
520 }
521}
522
523pub trait OnNewHead {
525 fn on_new_head(id: ParaId, head: &HeadData) -> Weight;
528}
529
530#[impl_trait_for_tuples::impl_for_tuples(30)]
531impl OnNewHead for Tuple {
532 fn on_new_head(id: ParaId, head: &HeadData) -> Weight {
533 let mut weight: Weight = Default::default();
534 for_tuples!( #( weight.saturating_accrue(Tuple::on_new_head(id, head)); )* );
535 weight
536 }
537}
538
539pub trait AssignCoretime {
544 fn assign_coretime(id: ParaId) -> DispatchResult;
546}
547
548impl AssignCoretime for () {
549 fn assign_coretime(_: ParaId) -> DispatchResult {
550 Ok(())
551 }
552}
553
554#[derive(Debug, Encode, Decode, DecodeWithMemTracking, TypeInfo)]
556#[cfg_attr(test, derive(PartialEq))]
557pub struct AuthorizedCodeHashAndExpiry<T> {
558 code_hash: ValidationCodeHash,
559 expire_at: T,
560}
561impl<T> From<(ValidationCodeHash, T)> for AuthorizedCodeHashAndExpiry<T> {
562 fn from(value: (ValidationCodeHash, T)) -> Self {
563 AuthorizedCodeHashAndExpiry { code_hash: value.0, expire_at: value.1 }
564 }
565}
566
567pub trait WeightInfo {
568 fn force_set_current_code(c: u32) -> Weight;
569 fn force_set_current_head(s: u32) -> Weight;
570 fn force_set_most_recent_context() -> Weight;
571 fn force_schedule_code_upgrade(c: u32) -> Weight;
572 fn force_note_new_head(s: u32) -> Weight;
573 fn force_queue_action() -> Weight;
574 fn add_trusted_validation_code(c: u32) -> Weight;
575 fn poke_unused_validation_code() -> Weight;
576 fn remove_upgrade_cooldown() -> Weight;
577
578 fn include_pvf_check_statement_finalize_upgrade_accept() -> Weight;
579 fn include_pvf_check_statement_finalize_upgrade_reject() -> Weight;
580 fn include_pvf_check_statement_finalize_onboarding_accept() -> Weight;
581 fn include_pvf_check_statement_finalize_onboarding_reject() -> Weight;
582 fn include_pvf_check_statement() -> Weight;
583 fn authorize_force_set_current_code_hash() -> Weight;
584 fn apply_authorized_force_set_current_code(c: u32) -> Weight;
585}
586
587pub struct TestWeightInfo;
588impl WeightInfo for TestWeightInfo {
589 fn force_set_current_code(_c: u32) -> Weight {
590 Weight::MAX
591 }
592 fn force_set_current_head(_s: u32) -> Weight {
593 Weight::MAX
594 }
595 fn force_set_most_recent_context() -> Weight {
596 Weight::MAX
597 }
598 fn force_schedule_code_upgrade(_c: u32) -> Weight {
599 Weight::MAX
600 }
601 fn force_note_new_head(_s: u32) -> Weight {
602 Weight::MAX
603 }
604 fn force_queue_action() -> Weight {
605 Weight::MAX
606 }
607 fn add_trusted_validation_code(_c: u32) -> Weight {
608 Weight::zero()
610 }
611 fn poke_unused_validation_code() -> Weight {
612 Weight::MAX
613 }
614 fn include_pvf_check_statement_finalize_upgrade_accept() -> Weight {
615 Weight::MAX
616 }
617 fn include_pvf_check_statement_finalize_upgrade_reject() -> Weight {
618 Weight::MAX
619 }
620 fn include_pvf_check_statement_finalize_onboarding_accept() -> Weight {
621 Weight::MAX
622 }
623 fn include_pvf_check_statement_finalize_onboarding_reject() -> Weight {
624 Weight::MAX
625 }
626 fn include_pvf_check_statement() -> Weight {
627 Weight::MAX - Weight::from_parts(1, 1)
629 }
630 fn remove_upgrade_cooldown() -> Weight {
631 Weight::MAX
632 }
633 fn authorize_force_set_current_code_hash() -> Weight {
634 Weight::MAX
635 }
636 fn apply_authorized_force_set_current_code(_c: u32) -> Weight {
637 Weight::MAX
638 }
639}
640
641#[frame_support::pallet]
642pub mod pallet {
643 use super::*;
644 use frame_support::traits::{
645 fungible::{Inspect, Mutate},
646 tokens::{Fortitude, Precision, Preservation},
647 };
648 use sp_runtime::transaction_validity::{
649 InvalidTransaction, TransactionPriority, TransactionSource, TransactionValidity,
650 ValidTransaction,
651 };
652
653 type BalanceOf<T> = <<T as Config>::Fungible as Inspect<AccountIdFor<T>>>::Balance;
654
655 #[pallet::pallet]
656 #[pallet::without_storage_info]
657 pub struct Pallet<T>(_);
658
659 #[pallet::config]
660 pub trait Config:
661 frame_system::Config
662 + configuration::Config
663 + shared::Config
664 + frame_system::offchain::CreateBare<Call<Self>>
665 {
666 #[allow(deprecated)]
667 type RuntimeEvent: From<Event<Self>> + IsType<<Self as frame_system::Config>::RuntimeEvent>;
668
669 #[pallet::constant]
670 type UnsignedPriority: Get<TransactionPriority>;
671
672 type NextSessionRotation: EstimateNextSessionRotation<BlockNumberFor<Self>>;
673
674 type QueueFootprinter: QueueFootprinter<Origin = UmpQueueId>;
679
680 type OnNewHead: OnNewHead;
682
683 type WeightInfo: WeightInfo;
685
686 type AssignCoretime: AssignCoretime;
692
693 type Fungible: Mutate<Self::AccountId, Balance: From<BlockNumberFor<Self>>>;
695
696 type CooldownRemovalMultiplier: Get<BalanceOf<Self>>;
706
707 type AuthorizeCurrentCodeOrigin: EnsureOriginWithArg<Self::RuntimeOrigin, ParaId>;
712 }
713
714 #[pallet::event]
715 #[pallet::generate_deposit(pub(super) fn deposit_event)]
716 pub enum Event<T: Config> {
717 CurrentCodeUpdated(ParaId),
719 CurrentHeadUpdated(ParaId),
721 CodeUpgradeScheduled(ParaId),
723 NewHeadNoted(ParaId),
725 ActionQueued(ParaId, SessionIndex),
727 PvfCheckStarted(ValidationCodeHash, ParaId),
730 PvfCheckAccepted(ValidationCodeHash, ParaId),
733 PvfCheckRejected(ValidationCodeHash, ParaId),
736 UpgradeCooldownRemoved {
738 para_id: ParaId,
740 },
741 CodeAuthorized {
743 para_id: ParaId,
745 code_hash: ValidationCodeHash,
747 expire_at: BlockNumberFor<T>,
749 },
750 }
751
752 #[pallet::error]
753 pub enum Error<T> {
754 NotRegistered,
756 CannotOnboard,
758 CannotOffboard,
760 CannotUpgrade,
762 CannotDowngrade,
764 PvfCheckStatementStale,
766 PvfCheckStatementFuture,
768 PvfCheckValidatorIndexOutOfBounds,
770 PvfCheckInvalidSignature,
772 PvfCheckDoubleVote,
774 PvfCheckSubjectInvalid,
776 CannotUpgradeCode,
778 InvalidCode,
780 NothingAuthorized,
782 Unauthorized,
784 InvalidBlockNumber,
786 }
787
788 #[pallet::storage]
793 pub(super) type PvfActiveVoteMap<T: Config> = StorageMap<
794 _,
795 Twox64Concat,
796 ValidationCodeHash,
797 PvfCheckActiveVoteState<BlockNumberFor<T>>,
798 OptionQuery,
799 >;
800
801 #[pallet::storage]
803 pub(super) type PvfActiveVoteList<T: Config> =
804 StorageValue<_, Vec<ValidationCodeHash>, ValueQuery>;
805
806 #[pallet::storage]
811 pub type Parachains<T: Config> = StorageValue<_, Vec<ParaId>, ValueQuery>;
812
813 #[pallet::storage]
815 pub(super) type ParaLifecycles<T: Config> = StorageMap<_, Twox64Concat, ParaId, ParaLifecycle>;
816
817 #[pallet::storage]
819 pub type Heads<T: Config> = StorageMap<_, Twox64Concat, ParaId, HeadData>;
820
821 #[pallet::storage]
823 pub type MostRecentContext<T: Config> = StorageMap<_, Twox64Concat, ParaId, BlockNumberFor<T>>;
824
825 #[pallet::storage]
829 pub type CurrentCodeHash<T: Config> = StorageMap<_, Twox64Concat, ParaId, ValidationCodeHash>;
830
831 #[pallet::storage]
836 pub(super) type PastCodeHash<T: Config> =
837 StorageMap<_, Twox64Concat, (ParaId, BlockNumberFor<T>), ValidationCodeHash>;
838
839 #[pallet::storage]
843 pub type PastCodeMeta<T: Config> =
844 StorageMap<_, Twox64Concat, ParaId, ParaPastCodeMeta<BlockNumberFor<T>>, ValueQuery>;
845
846 #[pallet::storage]
853 pub(super) type PastCodePruning<T: Config> =
854 StorageValue<_, Vec<(ParaId, BlockNumberFor<T>)>, ValueQuery>;
855
856 #[pallet::storage]
861 pub type FutureCodeUpgrades<T: Config> = StorageMap<_, Twox64Concat, ParaId, BlockNumberFor<T>>;
862
863 #[pallet::storage]
872 pub(super) type FutureCodeUpgradesAt<T: Config> =
873 StorageValue<_, Vec<(ParaId, BlockNumberFor<T>)>, ValueQuery>;
874
875 #[pallet::storage]
879 pub type FutureCodeHash<T: Config> = StorageMap<_, Twox64Concat, ParaId, ValidationCodeHash>;
880
881 #[pallet::storage]
883 pub type AuthorizedCodeHash<T: Config> =
884 StorageMap<_, Twox64Concat, ParaId, AuthorizedCodeHashAndExpiry<BlockNumberFor<T>>>;
885
886 #[pallet::storage]
897 pub(super) type UpgradeGoAheadSignal<T: Config> =
898 StorageMap<_, Twox64Concat, ParaId, UpgradeGoAhead>;
899
900 #[pallet::storage]
910 pub type UpgradeRestrictionSignal<T: Config> =
911 StorageMap<_, Twox64Concat, ParaId, UpgradeRestriction>;
912
913 #[pallet::storage]
917 pub(super) type UpgradeCooldowns<T: Config> =
918 StorageValue<_, Vec<(ParaId, BlockNumberFor<T>)>, ValueQuery>;
919
920 #[pallet::storage]
927 pub(super) type UpcomingUpgrades<T: Config> =
928 StorageValue<_, Vec<(ParaId, BlockNumberFor<T>)>, ValueQuery>;
929
930 #[pallet::storage]
932 pub type ActionsQueue<T: Config> =
933 StorageMap<_, Twox64Concat, SessionIndex, Vec<ParaId>, ValueQuery>;
934
935 #[pallet::storage]
940 pub(super) type UpcomingParasGenesis<T: Config> =
941 StorageMap<_, Twox64Concat, ParaId, ParaGenesisArgs>;
942
943 #[pallet::storage]
945 pub(super) type CodeByHashRefs<T: Config> =
946 StorageMap<_, Identity, ValidationCodeHash, u32, ValueQuery>;
947
948 #[pallet::storage]
953 pub type CodeByHash<T: Config> = StorageMap<_, Identity, ValidationCodeHash, ValidationCode>;
954
955 #[pallet::genesis_config]
956 #[derive(DefaultNoBound)]
957 pub struct GenesisConfig<T: Config> {
958 #[serde(skip)]
959 pub _config: core::marker::PhantomData<T>,
960 pub paras: Vec<(ParaId, ParaGenesisArgs)>,
961 }
962
963 #[pallet::genesis_build]
964 impl<T: Config> BuildGenesisConfig for GenesisConfig<T> {
965 fn build(&self) {
966 let mut parachains = ParachainsCache::new();
967 for (id, genesis_args) in &self.paras {
968 if genesis_args.validation_code.0.is_empty() {
969 panic!("empty validation code is not allowed in genesis");
970 }
971 Pallet::<T>::initialize_para_now(&mut parachains, *id, genesis_args);
972 if genesis_args.para_kind == ParaKind::Parachain {
973 T::AssignCoretime::assign_coretime(*id)
974 .expect("Assigning coretime works at genesis; qed");
975 }
976 }
977 }
979 }
980
981 #[pallet::call]
982 impl<T: Config> Pallet<T> {
983 #[pallet::call_index(0)]
985 #[pallet::weight(<T as Config>::WeightInfo::force_set_current_code(new_code.0.len() as u32))]
986 pub fn force_set_current_code(
987 origin: OriginFor<T>,
988 para: ParaId,
989 new_code: ValidationCode,
990 ) -> DispatchResult {
991 ensure_root(origin)?;
992 Self::do_force_set_current_code_update(para, new_code);
993 Ok(())
994 }
995
996 #[pallet::call_index(1)]
998 #[pallet::weight(<T as Config>::WeightInfo::force_set_current_head(new_head.0.len() as u32))]
999 pub fn force_set_current_head(
1000 origin: OriginFor<T>,
1001 para: ParaId,
1002 new_head: HeadData,
1003 ) -> DispatchResult {
1004 ensure_root(origin)?;
1005 Self::set_current_head(para, new_head);
1006 Ok(())
1007 }
1008
1009 #[pallet::call_index(2)]
1011 #[pallet::weight(<T as Config>::WeightInfo::force_schedule_code_upgrade(new_code.0.len() as u32))]
1012 pub fn force_schedule_code_upgrade(
1013 origin: OriginFor<T>,
1014 para: ParaId,
1015 new_code: ValidationCode,
1016 relay_parent_number: BlockNumberFor<T>,
1017 ) -> DispatchResult {
1018 ensure_root(origin)?;
1019 let config = configuration::ActiveConfig::<T>::get();
1020 Self::schedule_code_upgrade(
1021 para,
1022 new_code,
1023 relay_parent_number,
1024 &config,
1025 UpgradeStrategy::ApplyAtExpectedBlock,
1026 );
1027 Self::deposit_event(Event::CodeUpgradeScheduled(para));
1028 Ok(())
1029 }
1030
1031 #[pallet::call_index(3)]
1033 #[pallet::weight(<T as Config>::WeightInfo::force_note_new_head(new_head.0.len() as u32))]
1034 pub fn force_note_new_head(
1035 origin: OriginFor<T>,
1036 para: ParaId,
1037 new_head: HeadData,
1038 ) -> DispatchResult {
1039 ensure_root(origin)?;
1040 let now = frame_system::Pallet::<T>::block_number();
1041 Self::note_new_head(para, new_head, now);
1042 Self::deposit_event(Event::NewHeadNoted(para));
1043 Ok(())
1044 }
1045
1046 #[pallet::call_index(4)]
1050 #[pallet::weight(<T as Config>::WeightInfo::force_queue_action())]
1051 pub fn force_queue_action(origin: OriginFor<T>, para: ParaId) -> DispatchResult {
1052 ensure_root(origin)?;
1053 let next_session = shared::CurrentSessionIndex::<T>::get().saturating_add(One::one());
1054 ActionsQueue::<T>::mutate(next_session, |v| {
1055 if let Err(i) = v.binary_search(¶) {
1056 v.insert(i, para);
1057 }
1058 });
1059 Self::deposit_event(Event::ActionQueued(para, next_session));
1060 Ok(())
1061 }
1062
1063 #[pallet::call_index(5)]
1078 #[pallet::weight(<T as Config>::WeightInfo::add_trusted_validation_code(validation_code.0.len() as u32))]
1079 pub fn add_trusted_validation_code(
1080 origin: OriginFor<T>,
1081 validation_code: ValidationCode,
1082 ) -> DispatchResult {
1083 ensure_root(origin)?;
1084 let code_hash = validation_code.hash();
1085
1086 if let Some(vote) = PvfActiveVoteMap::<T>::get(&code_hash) {
1087 PvfActiveVoteMap::<T>::remove(&code_hash);
1089 PvfActiveVoteList::<T>::mutate(|l| {
1090 if let Ok(i) = l.binary_search(&code_hash) {
1091 l.remove(i);
1092 }
1093 });
1094
1095 let cfg = configuration::ActiveConfig::<T>::get();
1096 Self::enact_pvf_accepted(
1097 frame_system::Pallet::<T>::block_number(),
1098 &code_hash,
1099 &vote.causes,
1100 vote.age,
1101 &cfg,
1102 );
1103 return Ok(())
1104 }
1105
1106 if CodeByHash::<T>::contains_key(&code_hash) {
1107 return Ok(())
1109 }
1110
1111 CodeByHash::<T>::insert(code_hash, &validation_code);
1117
1118 Ok(())
1119 }
1120
1121 #[pallet::call_index(6)]
1127 #[pallet::weight(<T as Config>::WeightInfo::poke_unused_validation_code())]
1128 pub fn poke_unused_validation_code(
1129 origin: OriginFor<T>,
1130 validation_code_hash: ValidationCodeHash,
1131 ) -> DispatchResult {
1132 ensure_root(origin)?;
1133 if CodeByHashRefs::<T>::get(&validation_code_hash) == 0 {
1134 CodeByHash::<T>::remove(&validation_code_hash);
1135 }
1136 Ok(())
1137 }
1138
1139 #[pallet::call_index(7)]
1142 #[pallet::weight(
1143 <T as Config>::WeightInfo::include_pvf_check_statement_finalize_upgrade_accept()
1144 .max(<T as Config>::WeightInfo::include_pvf_check_statement_finalize_upgrade_reject())
1145 .max(<T as Config>::WeightInfo::include_pvf_check_statement_finalize_onboarding_accept()
1146 .max(<T as Config>::WeightInfo::include_pvf_check_statement_finalize_onboarding_reject())
1147 )
1148 )]
1149 pub fn include_pvf_check_statement(
1150 origin: OriginFor<T>,
1151 stmt: PvfCheckStatement,
1152 signature: ValidatorSignature,
1153 ) -> DispatchResultWithPostInfo {
1154 ensure_none(origin)?;
1155
1156 let validators = shared::ActiveValidatorKeys::<T>::get();
1157 let current_session = shared::CurrentSessionIndex::<T>::get();
1158 if stmt.session_index < current_session {
1159 return Err(Error::<T>::PvfCheckStatementStale.into())
1160 } else if stmt.session_index > current_session {
1161 return Err(Error::<T>::PvfCheckStatementFuture.into())
1162 }
1163 let validator_index = stmt.validator_index.0 as usize;
1164 let validator_public = validators
1165 .get(validator_index)
1166 .ok_or(Error::<T>::PvfCheckValidatorIndexOutOfBounds)?;
1167
1168 let signing_payload = stmt.signing_payload();
1169 ensure!(
1170 signature.verify(&signing_payload[..], &validator_public),
1171 Error::<T>::PvfCheckInvalidSignature,
1172 );
1173
1174 let mut active_vote = PvfActiveVoteMap::<T>::get(&stmt.subject)
1175 .ok_or(Error::<T>::PvfCheckSubjectInvalid)?;
1176
1177 ensure!(
1179 !active_vote
1180 .has_vote(validator_index)
1181 .ok_or(Error::<T>::PvfCheckValidatorIndexOutOfBounds)?,
1182 Error::<T>::PvfCheckDoubleVote,
1183 );
1184
1185 if stmt.accept {
1187 active_vote.votes_accept.set(validator_index, true);
1188 } else {
1189 active_vote.votes_reject.set(validator_index, true);
1190 }
1191
1192 if let Some(outcome) = active_vote.quorum(validators.len()) {
1193 PvfActiveVoteMap::<T>::remove(&stmt.subject);
1198 PvfActiveVoteList::<T>::mutate(|l| {
1199 if let Ok(i) = l.binary_search(&stmt.subject) {
1200 l.remove(i);
1201 }
1202 });
1203 match outcome {
1204 PvfCheckOutcome::Accepted => {
1205 let cfg = configuration::ActiveConfig::<T>::get();
1206 Self::enact_pvf_accepted(
1207 frame_system::Pallet::<T>::block_number(),
1208 &stmt.subject,
1209 &active_vote.causes,
1210 active_vote.age,
1211 &cfg,
1212 );
1213 },
1214 PvfCheckOutcome::Rejected => {
1215 Self::enact_pvf_rejected(&stmt.subject, active_vote.causes);
1216 },
1217 }
1218
1219 Ok(().into())
1221 } else {
1222 PvfActiveVoteMap::<T>::insert(&stmt.subject, active_vote);
1227 Ok(Some(<T as Config>::WeightInfo::include_pvf_check_statement()).into())
1228 }
1229 }
1230
1231 #[pallet::call_index(8)]
1233 #[pallet::weight(<T as Config>::WeightInfo::force_set_most_recent_context())]
1234 pub fn force_set_most_recent_context(
1235 origin: OriginFor<T>,
1236 para: ParaId,
1237 context: BlockNumberFor<T>,
1238 ) -> DispatchResult {
1239 ensure_root(origin)?;
1240 MostRecentContext::<T>::insert(¶, context);
1241 Ok(())
1242 }
1243
1244 #[pallet::call_index(9)]
1249 #[pallet::weight(<T as Config>::WeightInfo::remove_upgrade_cooldown())]
1250 pub fn remove_upgrade_cooldown(origin: OriginFor<T>, para: ParaId) -> DispatchResult {
1251 let who = ensure_signed(origin)?;
1252
1253 let removed = UpgradeCooldowns::<T>::mutate(|cooldowns| {
1254 let Some(pos) = cooldowns.iter().position(|(p, _)| p == ¶) else {
1255 return Ok::<_, DispatchError>(false)
1256 };
1257 let (_, cooldown_until) = cooldowns.remove(pos);
1258
1259 let cost = Self::calculate_remove_upgrade_cooldown_cost(cooldown_until);
1260
1261 T::Fungible::burn_from(
1263 &who,
1264 cost,
1265 Preservation::Preserve,
1266 Precision::Exact,
1267 Fortitude::Polite,
1268 )?;
1269
1270 Ok(true)
1271 })?;
1272
1273 if removed {
1274 UpgradeRestrictionSignal::<T>::remove(para);
1275
1276 Self::deposit_event(Event::UpgradeCooldownRemoved { para_id: para });
1277 }
1278
1279 Ok(())
1280 }
1281
1282 #[pallet::call_index(10)]
1294 #[pallet::weight(<T as Config>::WeightInfo::authorize_force_set_current_code_hash())]
1295 pub fn authorize_force_set_current_code_hash(
1296 origin: OriginFor<T>,
1297 para: ParaId,
1298 new_code_hash: ValidationCodeHash,
1299 valid_period: BlockNumberFor<T>,
1300 ) -> DispatchResult {
1301 T::AuthorizeCurrentCodeOrigin::ensure_origin(origin, ¶)?;
1302
1303 let now = frame_system::Pallet::<T>::block_number();
1304 let expire_at = now.saturating_add(valid_period);
1305
1306 AuthorizedCodeHash::<T>::insert(
1308 ¶,
1309 AuthorizedCodeHashAndExpiry::from((new_code_hash, expire_at)),
1310 );
1311 Self::deposit_event(Event::CodeAuthorized {
1312 para_id: para,
1313 code_hash: new_code_hash,
1314 expire_at,
1315 });
1316
1317 Ok(())
1318 }
1319
1320 #[pallet::call_index(11)]
1323 #[pallet::weight(<T as Config>::WeightInfo::apply_authorized_force_set_current_code(new_code.0.len() as u32))]
1324 pub fn apply_authorized_force_set_current_code(
1325 _origin: OriginFor<T>,
1326 para: ParaId,
1327 new_code: ValidationCode,
1328 ) -> DispatchResultWithPostInfo {
1329 let _ = Self::validate_code_is_authorized(&new_code, ¶)?;
1333 AuthorizedCodeHash::<T>::remove(para);
1335
1336 Self::do_force_set_current_code_update(para, new_code);
1338
1339 Ok(Pays::No.into())
1340 }
1341 }
1342
1343 impl<T: Config> Pallet<T> {
1344 pub(crate) fn calculate_remove_upgrade_cooldown_cost(
1345 cooldown_until: BlockNumberFor<T>,
1346 ) -> BalanceOf<T> {
1347 let time_left =
1348 cooldown_until.saturating_sub(frame_system::Pallet::<T>::block_number());
1349
1350 BalanceOf::<T>::from(time_left).saturating_mul(T::CooldownRemovalMultiplier::get())
1351 }
1352 }
1353
1354 #[pallet::view_functions]
1355 impl<T: Config> Pallet<T> {
1356 pub fn remove_upgrade_cooldown_cost(para: ParaId) -> BalanceOf<T> {
1358 UpgradeCooldowns::<T>::get()
1359 .iter()
1360 .find(|(p, _)| p == ¶)
1361 .map(|(_, c)| Self::calculate_remove_upgrade_cooldown_cost(*c))
1362 .unwrap_or_default()
1363 }
1364 }
1365
1366 #[pallet::validate_unsigned]
1367 impl<T: Config> ValidateUnsigned for Pallet<T> {
1368 type Call = Call<T>;
1369
1370 fn validate_unsigned(_source: TransactionSource, call: &Self::Call) -> TransactionValidity {
1371 match call {
1372 Call::include_pvf_check_statement { stmt, signature } => {
1373 let current_session = shared::CurrentSessionIndex::<T>::get();
1374 if stmt.session_index < current_session {
1375 return InvalidTransaction::Stale.into()
1376 } else if stmt.session_index > current_session {
1377 return InvalidTransaction::Future.into()
1378 }
1379
1380 let validator_index = stmt.validator_index.0 as usize;
1381 let validators = shared::ActiveValidatorKeys::<T>::get();
1382 let validator_public = match validators.get(validator_index) {
1383 Some(pk) => pk,
1384 None =>
1385 return InvalidTransaction::Custom(INVALID_TX_BAD_VALIDATOR_IDX).into(),
1386 };
1387
1388 let signing_payload = stmt.signing_payload();
1389 if !signature.verify(&signing_payload[..], &validator_public) {
1390 return InvalidTransaction::BadProof.into();
1391 }
1392
1393 let active_vote = match PvfActiveVoteMap::<T>::get(&stmt.subject) {
1394 Some(v) => v,
1395 None => return InvalidTransaction::Custom(INVALID_TX_BAD_SUBJECT).into(),
1396 };
1397
1398 match active_vote.has_vote(validator_index) {
1399 Some(false) => (),
1400 Some(true) =>
1401 return InvalidTransaction::Custom(INVALID_TX_DOUBLE_VOTE).into(),
1402 None =>
1403 return InvalidTransaction::Custom(INVALID_TX_BAD_VALIDATOR_IDX).into(),
1404 }
1405
1406 ValidTransaction::with_tag_prefix("PvfPreCheckingVote")
1407 .priority(T::UnsignedPriority::get())
1408 .longevity(
1409 TryInto::<u64>::try_into(
1410 T::NextSessionRotation::average_session_length() / 2u32.into(),
1411 )
1412 .unwrap_or(64_u64),
1413 )
1414 .and_provides((stmt.session_index, stmt.validator_index, stmt.subject))
1415 .propagate(true)
1416 .build()
1417 },
1418 Call::apply_authorized_force_set_current_code { para, new_code } =>
1419 match Self::validate_code_is_authorized(new_code, para) {
1420 Ok(authorized_code) => {
1421 let now = frame_system::Pallet::<T>::block_number();
1422 let longevity = authorized_code.expire_at.saturating_sub(now);
1423
1424 ValidTransaction::with_tag_prefix("ApplyAuthorizedForceSetCurrentCode")
1425 .priority(T::UnsignedPriority::get())
1426 .longevity(TryInto::<u64>::try_into(longevity).unwrap_or(64_u64))
1427 .and_provides((para, authorized_code.code_hash))
1428 .propagate(true)
1429 .build()
1430 },
1431 Err(_) =>
1432 return InvalidTransaction::Custom(INVALID_TX_UNAUTHORIZED_CODE).into(),
1433 },
1434 _ => InvalidTransaction::Call.into(),
1435 }
1436 }
1437
1438 fn pre_dispatch(_call: &Self::Call) -> Result<(), TransactionValidityError> {
1439 Ok(())
1447 }
1448 }
1449}
1450
1451const INVALID_TX_BAD_VALIDATOR_IDX: u8 = 1;
1453const INVALID_TX_BAD_SUBJECT: u8 = 2;
1454const INVALID_TX_DOUBLE_VOTE: u8 = 3;
1455const INVALID_TX_UNAUTHORIZED_CODE: u8 = 4;
1456
1457pub const MAX_PARA_HEADS: usize = 1024;
1465
1466impl<T: Config> Pallet<T> {
1467 pub(crate) fn schedule_code_upgrade_external(
1472 id: ParaId,
1473 new_code: ValidationCode,
1474 upgrade_strategy: UpgradeStrategy,
1475 ) -> DispatchResult {
1476 ensure!(Self::can_upgrade_validation_code(id), Error::<T>::CannotUpgradeCode);
1478 let config = configuration::ActiveConfig::<T>::get();
1479 ensure!(new_code.0.len() >= MIN_CODE_SIZE as usize, Error::<T>::InvalidCode);
1481 ensure!(new_code.0.len() <= config.max_code_size as usize, Error::<T>::InvalidCode);
1482
1483 let current_block = frame_system::Pallet::<T>::block_number();
1484 let upgrade_block = current_block.saturating_add(config.validation_upgrade_delay);
1486 Self::schedule_code_upgrade(id, new_code, upgrade_block, &config, upgrade_strategy);
1487 Self::deposit_event(Event::CodeUpgradeScheduled(id));
1488 Ok(())
1489 }
1490
1491 pub(crate) fn set_current_head(para: ParaId, new_head: HeadData) {
1493 Heads::<T>::insert(¶, new_head);
1494 Self::deposit_event(Event::CurrentHeadUpdated(para));
1495 }
1496
1497 pub(crate) fn initializer_initialize(now: BlockNumberFor<T>) -> Weight {
1499 Self::prune_old_code(now) +
1500 Self::process_scheduled_upgrade_changes(now) +
1501 Self::process_future_code_upgrades_at(now) +
1502 Self::prune_expired_authorizations(now)
1503 }
1504
1505 pub(crate) fn initializer_finalize(now: BlockNumberFor<T>) {
1507 Self::process_scheduled_upgrade_cooldowns(now);
1508 }
1509
1510 pub(crate) fn initializer_on_new_session(
1514 notification: &SessionChangeNotification<BlockNumberFor<T>>,
1515 ) -> Vec<ParaId> {
1516 let outgoing_paras = Self::apply_actions_queue(notification.session_index);
1517 Self::groom_ongoing_pvf_votes(¬ification.new_config, notification.validators.len());
1518 outgoing_paras
1519 }
1520
1521 pub(crate) fn current_code(para_id: &ParaId) -> Option<ValidationCode> {
1523 CurrentCodeHash::<T>::get(para_id).and_then(|code_hash| {
1524 let code = CodeByHash::<T>::get(&code_hash);
1525 if code.is_none() {
1526 log::error!(
1527 "Pallet paras storage is inconsistent, code not found for hash {}",
1528 code_hash,
1529 );
1530 debug_assert!(false, "inconsistent paras storages");
1531 }
1532 code
1533 })
1534 }
1535
1536 pub fn sorted_para_heads() -> Vec<(u32, Vec<u8>)> {
1539 let mut heads: Vec<(u32, Vec<u8>)> =
1540 Heads::<T>::iter().map(|(id, head)| (id.into(), head.0)).collect();
1541 heads.sort_by_key(|(id, _)| *id);
1542 heads.truncate(MAX_PARA_HEADS);
1543 heads
1544 }
1545
1546 fn apply_actions_queue(session: SessionIndex) -> Vec<ParaId> {
1555 let actions = ActionsQueue::<T>::take(session);
1556 let mut parachains = ParachainsCache::new();
1557 let now = frame_system::Pallet::<T>::block_number();
1558 let mut outgoing = Vec::new();
1559
1560 for para in actions {
1561 let lifecycle = ParaLifecycles::<T>::get(¶);
1562 match lifecycle {
1563 None | Some(ParaLifecycle::Parathread) | Some(ParaLifecycle::Parachain) => { },
1565 Some(ParaLifecycle::Onboarding) => {
1566 if let Some(genesis_data) = UpcomingParasGenesis::<T>::take(¶) {
1567 Self::initialize_para_now(&mut parachains, para, &genesis_data);
1568 }
1569 },
1570 Some(ParaLifecycle::UpgradingParathread) => {
1572 parachains.add(para);
1573 ParaLifecycles::<T>::insert(¶, ParaLifecycle::Parachain);
1574 },
1575 Some(ParaLifecycle::DowngradingParachain) => {
1577 parachains.remove(para);
1578 ParaLifecycles::<T>::insert(¶, ParaLifecycle::Parathread);
1579 },
1580 Some(ParaLifecycle::OffboardingParachain) |
1582 Some(ParaLifecycle::OffboardingParathread) => {
1583 parachains.remove(para);
1584
1585 Heads::<T>::remove(¶);
1586 MostRecentContext::<T>::remove(¶);
1587 FutureCodeUpgrades::<T>::remove(¶);
1588 UpgradeGoAheadSignal::<T>::remove(¶);
1589 UpgradeRestrictionSignal::<T>::remove(¶);
1590 ParaLifecycles::<T>::remove(¶);
1591 let removed_future_code_hash = FutureCodeHash::<T>::take(¶);
1592 if let Some(removed_future_code_hash) = removed_future_code_hash {
1593 Self::decrease_code_ref(&removed_future_code_hash);
1594 }
1595
1596 let removed_code_hash = CurrentCodeHash::<T>::take(¶);
1597 if let Some(removed_code_hash) = removed_code_hash {
1598 Self::note_past_code(para, now, now, removed_code_hash);
1599 }
1600
1601 outgoing.push(para);
1602 },
1603 }
1604 }
1605
1606 if !outgoing.is_empty() {
1607 UpcomingUpgrades::<T>::mutate(|upcoming_upgrades| {
1614 upcoming_upgrades.retain(|(para, _)| !outgoing.contains(para));
1615 });
1616 UpgradeCooldowns::<T>::mutate(|upgrade_cooldowns| {
1617 upgrade_cooldowns.retain(|(para, _)| !outgoing.contains(para));
1618 });
1619 FutureCodeUpgradesAt::<T>::mutate(|future_upgrades| {
1620 future_upgrades.retain(|(para, _)| !outgoing.contains(para));
1621 });
1622 }
1623
1624 drop(parachains);
1626
1627 outgoing
1628 }
1629
1630 fn note_past_code(
1637 id: ParaId,
1638 at: BlockNumberFor<T>,
1639 now: BlockNumberFor<T>,
1640 old_code_hash: ValidationCodeHash,
1641 ) -> Weight {
1642 PastCodeMeta::<T>::mutate(&id, |past_meta| {
1643 past_meta.note_replacement(at, now);
1644 });
1645
1646 PastCodeHash::<T>::insert(&(id, at), old_code_hash);
1647
1648 PastCodePruning::<T>::mutate(|pruning| {
1651 let insert_idx =
1652 pruning.binary_search_by_key(&now, |&(_, b)| b).unwrap_or_else(|idx| idx);
1653 pruning.insert(insert_idx, (id, now));
1654 });
1655
1656 T::DbWeight::get().reads_writes(2, 3)
1657 }
1658
1659 fn prune_old_code(now: BlockNumberFor<T>) -> Weight {
1662 let config = configuration::ActiveConfig::<T>::get();
1663 let code_retention_period = config.code_retention_period;
1664 if now <= code_retention_period {
1665 let weight = T::DbWeight::get().reads_writes(1, 0);
1666 return weight
1667 }
1668
1669 let pruning_height = now - (code_retention_period + One::one());
1671
1672 let pruning_tasks_done =
1673 PastCodePruning::<T>::mutate(|pruning_tasks: &mut Vec<(_, BlockNumberFor<T>)>| {
1674 let (pruning_tasks_done, pruning_tasks_to_do) = {
1675 let up_to_idx =
1677 pruning_tasks.iter().take_while(|&(_, at)| at <= &pruning_height).count();
1678 (up_to_idx, pruning_tasks.drain(..up_to_idx))
1679 };
1680
1681 for (para_id, _) in pruning_tasks_to_do {
1682 let full_deactivate = PastCodeMeta::<T>::mutate(¶_id, |meta| {
1683 for pruned_repl_at in meta.prune_up_to(pruning_height) {
1684 let removed_code_hash =
1685 PastCodeHash::<T>::take(&(para_id, pruned_repl_at));
1686
1687 if let Some(removed_code_hash) = removed_code_hash {
1688 Self::decrease_code_ref(&removed_code_hash);
1689 } else {
1690 log::warn!(
1691 target: LOG_TARGET,
1692 "Missing code for removed hash {:?}",
1693 removed_code_hash,
1694 );
1695 }
1696 }
1697
1698 meta.is_empty() && Heads::<T>::get(¶_id).is_none()
1699 });
1700
1701 if full_deactivate {
1704 PastCodeMeta::<T>::remove(¶_id);
1705 }
1706 }
1707
1708 pruning_tasks_done as u64
1709 });
1710
1711 T::DbWeight::get().reads_writes(1 + pruning_tasks_done, 2 * pruning_tasks_done)
1714 }
1715
1716 fn prune_expired_authorizations(now: BlockNumberFor<T>) -> Weight {
1719 let mut weight = T::DbWeight::get().reads(1);
1720 let to_remove = AuthorizedCodeHash::<T>::iter().filter_map(
1721 |(para, AuthorizedCodeHashAndExpiry { expire_at, .. })| {
1722 if expire_at <= now {
1723 Some(para)
1724 } else {
1725 None
1726 }
1727 },
1728 );
1729 for para in to_remove {
1730 AuthorizedCodeHash::<T>::remove(¶);
1731 weight.saturating_accrue(T::DbWeight::get().writes(1));
1732 }
1733
1734 weight
1735 }
1736
1737 fn process_future_code_upgrades_at(now: BlockNumberFor<T>) -> Weight {
1742 let mut weight = T::DbWeight::get().reads_writes(1, 1);
1744 FutureCodeUpgradesAt::<T>::mutate(
1745 |upcoming_upgrades: &mut Vec<(ParaId, BlockNumberFor<T>)>| {
1746 let num = upcoming_upgrades.iter().take_while(|&(_, at)| at <= &now).count();
1747 for (id, expected_at) in upcoming_upgrades.drain(..num) {
1748 weight += T::DbWeight::get().reads_writes(1, 1);
1749
1750 let new_code_hash = if let Some(new_code_hash) = FutureCodeHash::<T>::take(&id)
1752 {
1753 new_code_hash
1754 } else {
1755 log::error!(target: LOG_TARGET, "Missing future code hash for {:?}", &id);
1756 continue
1757 };
1758
1759 weight += Self::set_current_code(id, new_code_hash, expected_at);
1760 }
1761 num
1762 },
1763 );
1764
1765 weight
1766 }
1767
1768 fn process_scheduled_upgrade_changes(now: BlockNumberFor<T>) -> Weight {
1774 let mut weight = T::DbWeight::get().reads_writes(1, 1);
1776 let upgrades_signaled = UpcomingUpgrades::<T>::mutate(
1777 |upcoming_upgrades: &mut Vec<(ParaId, BlockNumberFor<T>)>| {
1778 let num = upcoming_upgrades.iter().take_while(|&(_, at)| at <= &now).count();
1779 for (para, _) in upcoming_upgrades.drain(..num) {
1780 UpgradeGoAheadSignal::<T>::insert(¶, UpgradeGoAhead::GoAhead);
1781 }
1782 num
1783 },
1784 );
1785 weight += T::DbWeight::get().writes(upgrades_signaled as u64);
1786
1787 weight += T::DbWeight::get().reads(1);
1789 let cooldowns_expired =
1790 UpgradeCooldowns::<T>::get().iter().take_while(|&(_, at)| at <= &now).count();
1791
1792 weight += T::DbWeight::get().reads_writes(1, 1);
1796 weight += T::DbWeight::get().reads(cooldowns_expired as u64);
1797
1798 weight
1799 }
1800
1801 fn process_scheduled_upgrade_cooldowns(now: BlockNumberFor<T>) {
1805 UpgradeCooldowns::<T>::mutate(
1806 |upgrade_cooldowns: &mut Vec<(ParaId, BlockNumberFor<T>)>| {
1807 upgrade_cooldowns.retain(|(para, at)| {
1809 if at <= &now {
1810 UpgradeRestrictionSignal::<T>::remove(¶);
1811 false
1812 } else {
1813 true
1814 }
1815 });
1816 },
1817 );
1818 }
1819
1820 fn groom_ongoing_pvf_votes(
1823 cfg: &configuration::HostConfiguration<BlockNumberFor<T>>,
1824 new_n_validators: usize,
1825 ) -> Weight {
1826 let mut weight = T::DbWeight::get().reads(1);
1827
1828 let potentially_active_votes = PvfActiveVoteList::<T>::get();
1829
1830 let mut actually_active_votes = Vec::with_capacity(potentially_active_votes.len());
1835
1836 for vote_subject in potentially_active_votes {
1837 let mut vote_state = match PvfActiveVoteMap::<T>::take(&vote_subject) {
1838 Some(v) => v,
1839 None => {
1840 log::warn!(
1844 target: LOG_TARGET,
1845 "The PvfActiveVoteMap is out of sync with PvfActiveVoteList!",
1846 );
1847 debug_assert!(false);
1848 continue
1849 },
1850 };
1851
1852 vote_state.age += 1;
1853 if vote_state.age < cfg.pvf_voting_ttl {
1854 weight += T::DbWeight::get().writes(1);
1855 vote_state.reinitialize_ballots(new_n_validators);
1856 PvfActiveVoteMap::<T>::insert(&vote_subject, vote_state);
1857
1858 actually_active_votes.push(vote_subject);
1860 } else {
1861 weight += Self::enact_pvf_rejected(&vote_subject, vote_state.causes);
1863 }
1864 }
1865
1866 weight += T::DbWeight::get().writes(1);
1867 PvfActiveVoteList::<T>::put(actually_active_votes);
1868
1869 weight
1870 }
1871
1872 fn enact_pvf_accepted(
1873 now: BlockNumberFor<T>,
1874 code_hash: &ValidationCodeHash,
1875 causes: &[PvfCheckCause<BlockNumberFor<T>>],
1876 sessions_observed: SessionIndex,
1877 cfg: &configuration::HostConfiguration<BlockNumberFor<T>>,
1878 ) -> Weight {
1879 let mut weight = Weight::zero();
1880 for cause in causes {
1881 weight += T::DbWeight::get().reads_writes(3, 2);
1882 Self::deposit_event(Event::PvfCheckAccepted(*code_hash, cause.para_id()));
1883
1884 match cause {
1885 PvfCheckCause::Onboarding(id) => {
1886 weight += Self::proceed_with_onboarding(*id, sessions_observed);
1887 },
1888 PvfCheckCause::Upgrade { id, included_at, upgrade_strategy } => {
1889 weight += Self::proceed_with_upgrade(
1890 *id,
1891 code_hash,
1892 now,
1893 *included_at,
1894 cfg,
1895 *upgrade_strategy,
1896 );
1897 },
1898 }
1899 }
1900 weight
1901 }
1902
1903 fn proceed_with_onboarding(id: ParaId, sessions_observed: SessionIndex) -> Weight {
1904 let weight = T::DbWeight::get().reads_writes(2, 1);
1905
1906 let onboard_at: SessionIndex = shared::CurrentSessionIndex::<T>::get() +
1912 cmp::max(shared::SESSION_DELAY.saturating_sub(sessions_observed), 1);
1913
1914 ActionsQueue::<T>::mutate(onboard_at, |v| {
1915 if let Err(i) = v.binary_search(&id) {
1916 v.insert(i, id);
1917 }
1918 });
1919
1920 weight
1921 }
1922
1923 fn proceed_with_upgrade(
1924 id: ParaId,
1925 code_hash: &ValidationCodeHash,
1926 now: BlockNumberFor<T>,
1927 relay_parent_number: BlockNumberFor<T>,
1928 cfg: &configuration::HostConfiguration<BlockNumberFor<T>>,
1929 upgrade_strategy: UpgradeStrategy,
1930 ) -> Weight {
1931 let mut weight = Weight::zero();
1932
1933 let expected_at = cmp::max(
1946 relay_parent_number + cfg.validation_upgrade_delay,
1947 now + cfg.minimum_validation_upgrade_delay,
1948 );
1949
1950 match upgrade_strategy {
1951 UpgradeStrategy::ApplyAtExpectedBlock => {
1952 FutureCodeUpgradesAt::<T>::mutate(|future_upgrades| {
1953 let insert_idx = future_upgrades
1954 .binary_search_by_key(&expected_at, |&(_, b)| b)
1955 .unwrap_or_else(|idx| idx);
1956 future_upgrades.insert(insert_idx, (id, expected_at));
1957 });
1958
1959 weight += T::DbWeight::get().reads_writes(0, 2);
1960 },
1961 UpgradeStrategy::SetGoAheadSignal => {
1962 FutureCodeUpgrades::<T>::insert(&id, expected_at);
1963
1964 UpcomingUpgrades::<T>::mutate(|upcoming_upgrades| {
1965 let insert_idx = upcoming_upgrades
1966 .binary_search_by_key(&expected_at, |&(_, b)| b)
1967 .unwrap_or_else(|idx| idx);
1968 upcoming_upgrades.insert(insert_idx, (id, expected_at));
1969 });
1970
1971 weight += T::DbWeight::get().reads_writes(1, 3);
1972 },
1973 }
1974
1975 let expected_at = expected_at.saturated_into();
1976 let log = ConsensusLog::ParaScheduleUpgradeCode(id, *code_hash, expected_at);
1977 frame_system::Pallet::<T>::deposit_log(log.into());
1978
1979 weight
1980 }
1981
1982 fn enact_pvf_rejected(
1983 code_hash: &ValidationCodeHash,
1984 causes: Vec<PvfCheckCause<BlockNumberFor<T>>>,
1985 ) -> Weight {
1986 let mut weight = Weight::zero();
1987
1988 for cause in causes {
1989 weight += Self::decrease_code_ref(code_hash);
1992
1993 weight += T::DbWeight::get().reads_writes(3, 2);
1994 Self::deposit_event(Event::PvfCheckRejected(*code_hash, cause.para_id()));
1995
1996 match cause {
1997 PvfCheckCause::Onboarding(id) => {
1998 weight += T::DbWeight::get().writes(3);
2004 UpcomingParasGenesis::<T>::remove(&id);
2005 CurrentCodeHash::<T>::remove(&id);
2006 ParaLifecycles::<T>::remove(&id);
2007 },
2008 PvfCheckCause::Upgrade { id, .. } => {
2009 weight += T::DbWeight::get().writes(2);
2010 UpgradeGoAheadSignal::<T>::insert(&id, UpgradeGoAhead::Abort);
2011 FutureCodeHash::<T>::remove(&id);
2012 },
2013 }
2014 }
2015
2016 weight
2017 }
2018
2019 pub fn can_schedule_para_initialize(id: &ParaId) -> bool {
2023 ParaLifecycles::<T>::get(id).is_none()
2024 }
2025
2026 pub(crate) fn schedule_para_initialize(
2037 id: ParaId,
2038 mut genesis_data: ParaGenesisArgs,
2039 ) -> DispatchResult {
2040 ensure!(Self::can_schedule_para_initialize(&id), Error::<T>::CannotOnboard);
2043 ensure!(!genesis_data.validation_code.0.is_empty(), Error::<T>::CannotOnboard);
2044 ParaLifecycles::<T>::insert(&id, ParaLifecycle::Onboarding);
2045
2046 let validation_code =
2080 mem::replace(&mut genesis_data.validation_code, ValidationCode(Vec::new()));
2081 UpcomingParasGenesis::<T>::insert(&id, genesis_data);
2082 let validation_code_hash = validation_code.hash();
2083 CurrentCodeHash::<T>::insert(&id, validation_code_hash);
2084
2085 let cfg = configuration::ActiveConfig::<T>::get();
2086 Self::kick_off_pvf_check(
2087 PvfCheckCause::Onboarding(id),
2088 validation_code_hash,
2089 validation_code,
2090 &cfg,
2091 );
2092
2093 Ok(())
2094 }
2095
2096 pub(crate) fn schedule_para_cleanup(id: ParaId) -> DispatchResult {
2106 if let Some(future_code_hash) = FutureCodeHash::<T>::get(&id) {
2118 let active_prechecking = PvfActiveVoteList::<T>::get();
2119 if active_prechecking.contains(&future_code_hash) {
2120 return Err(Error::<T>::CannotOffboard.into())
2121 }
2122 }
2123
2124 let lifecycle = ParaLifecycles::<T>::get(&id);
2125 match lifecycle {
2126 None => return Ok(()),
2128 Some(ParaLifecycle::Parathread) => {
2129 ParaLifecycles::<T>::insert(&id, ParaLifecycle::OffboardingParathread);
2130 },
2131 Some(ParaLifecycle::Parachain) => {
2132 ParaLifecycles::<T>::insert(&id, ParaLifecycle::OffboardingParachain);
2133 },
2134 _ => return Err(Error::<T>::CannotOffboard.into()),
2135 }
2136
2137 let scheduled_session = Self::scheduled_session();
2138 ActionsQueue::<T>::mutate(scheduled_session, |v| {
2139 if let Err(i) = v.binary_search(&id) {
2140 v.insert(i, id);
2141 }
2142 });
2143
2144 if <T as Config>::QueueFootprinter::message_count(UmpQueueId::Para(id)) != 0 {
2145 return Err(Error::<T>::CannotOffboard.into())
2146 }
2147
2148 Ok(())
2149 }
2150
2151 pub(crate) fn schedule_parathread_upgrade(id: ParaId) -> DispatchResult {
2155 let scheduled_session = Self::scheduled_session();
2156 let lifecycle = ParaLifecycles::<T>::get(&id).ok_or(Error::<T>::NotRegistered)?;
2157
2158 ensure!(lifecycle == ParaLifecycle::Parathread, Error::<T>::CannotUpgrade);
2159
2160 ParaLifecycles::<T>::insert(&id, ParaLifecycle::UpgradingParathread);
2161 ActionsQueue::<T>::mutate(scheduled_session, |v| {
2162 if let Err(i) = v.binary_search(&id) {
2163 v.insert(i, id);
2164 }
2165 });
2166
2167 Ok(())
2168 }
2169
2170 pub(crate) fn schedule_parachain_downgrade(id: ParaId) -> DispatchResult {
2174 let scheduled_session = Self::scheduled_session();
2175 let lifecycle = ParaLifecycles::<T>::get(&id).ok_or(Error::<T>::NotRegistered)?;
2176
2177 ensure!(lifecycle == ParaLifecycle::Parachain, Error::<T>::CannotDowngrade);
2178
2179 ParaLifecycles::<T>::insert(&id, ParaLifecycle::DowngradingParachain);
2180 ActionsQueue::<T>::mutate(scheduled_session, |v| {
2181 if let Err(i) = v.binary_search(&id) {
2182 v.insert(i, id);
2183 }
2184 });
2185
2186 Ok(())
2187 }
2188
2189 pub(crate) fn schedule_code_upgrade(
2208 id: ParaId,
2209 new_code: ValidationCode,
2210 inclusion_block_number: BlockNumberFor<T>,
2211 cfg: &configuration::HostConfiguration<BlockNumberFor<T>>,
2212 upgrade_strategy: UpgradeStrategy,
2213 ) {
2214 let new_code_len = new_code.0.len();
2216 if new_code_len < MIN_CODE_SIZE as usize || new_code_len > cfg.max_code_size as usize {
2217 log::warn!(target: LOG_TARGET, "attempted to schedule an upgrade with invalid new validation code",);
2218 return
2219 }
2220
2221 if FutureCodeHash::<T>::contains_key(&id) {
2223 log::warn!(target: LOG_TARGET, "ended up scheduling an upgrade while one is pending",);
2232 return
2233 }
2234
2235 let code_hash = new_code.hash();
2236
2237 if CurrentCodeHash::<T>::get(&id) == Some(code_hash) {
2242 log::warn!(
2245 target: LOG_TARGET,
2246 "para tried to upgrade to the same code. Abort the upgrade",
2247 );
2248 return
2249 }
2250
2251 FutureCodeHash::<T>::insert(&id, &code_hash);
2253 UpgradeRestrictionSignal::<T>::insert(&id, UpgradeRestriction::Present);
2254
2255 let next_possible_upgrade_at = inclusion_block_number + cfg.validation_upgrade_cooldown;
2256 UpgradeCooldowns::<T>::mutate(|upgrade_cooldowns| {
2257 let insert_idx = upgrade_cooldowns
2258 .binary_search_by_key(&next_possible_upgrade_at, |&(_, b)| b)
2259 .unwrap_or_else(|idx| idx);
2260 upgrade_cooldowns.insert(insert_idx, (id, next_possible_upgrade_at));
2261 });
2262
2263 Self::kick_off_pvf_check(
2264 PvfCheckCause::Upgrade { id, included_at: inclusion_block_number, upgrade_strategy },
2265 code_hash,
2266 new_code,
2267 cfg,
2268 );
2269 }
2270
2271 fn kick_off_pvf_check(
2285 cause: PvfCheckCause<BlockNumberFor<T>>,
2286 code_hash: ValidationCodeHash,
2287 code: ValidationCode,
2288 cfg: &configuration::HostConfiguration<BlockNumberFor<T>>,
2289 ) -> Weight {
2290 let mut weight = Weight::zero();
2291
2292 weight += T::DbWeight::get().reads_writes(3, 2);
2293 Self::deposit_event(Event::PvfCheckStarted(code_hash, cause.para_id()));
2294
2295 weight += T::DbWeight::get().reads(1);
2296 match PvfActiveVoteMap::<T>::get(&code_hash) {
2297 None => {
2298 let known_code = CodeByHash::<T>::contains_key(&code_hash);
2301 weight += T::DbWeight::get().reads(1);
2302
2303 if known_code {
2304 weight += T::DbWeight::get().reads(1);
2307 let now = frame_system::Pallet::<T>::block_number();
2308 weight += Self::enact_pvf_accepted(now, &code_hash, &[cause], 0, cfg);
2309 } else {
2310 weight += T::DbWeight::get().reads_writes(3, 2);
2313 let now = frame_system::Pallet::<T>::block_number();
2314 let n_validators = shared::ActiveValidatorKeys::<T>::get().len();
2315 PvfActiveVoteMap::<T>::insert(
2316 &code_hash,
2317 PvfCheckActiveVoteState::new(now, n_validators, cause),
2318 );
2319 PvfActiveVoteList::<T>::mutate(|l| {
2320 if let Err(idx) = l.binary_search(&code_hash) {
2321 l.insert(idx, code_hash);
2322 }
2323 });
2324 }
2325 },
2326 Some(mut vote_state) => {
2327 weight += T::DbWeight::get().writes(1);
2330 vote_state.causes.push(cause);
2331 PvfActiveVoteMap::<T>::insert(&code_hash, vote_state);
2332 },
2333 }
2334
2335 weight += Self::increase_code_ref(&code_hash, &code);
2347
2348 weight
2349 }
2350
2351 pub(crate) fn note_new_head(
2355 id: ParaId,
2356 new_head: HeadData,
2357 execution_context: BlockNumberFor<T>,
2358 ) {
2359 Heads::<T>::insert(&id, &new_head);
2360 MostRecentContext::<T>::insert(&id, execution_context);
2361
2362 if let Some(expected_at) = FutureCodeUpgrades::<T>::get(&id) {
2363 if expected_at <= execution_context {
2364 FutureCodeUpgrades::<T>::remove(&id);
2365 UpgradeGoAheadSignal::<T>::remove(&id);
2366
2367 let new_code_hash = if let Some(new_code_hash) = FutureCodeHash::<T>::take(&id) {
2369 new_code_hash
2370 } else {
2371 log::error!(target: LOG_TARGET, "Missing future code hash for {:?}", &id);
2372 return
2373 };
2374
2375 Self::set_current_code(id, new_code_hash, expected_at);
2376 }
2377 } else {
2378 UpgradeGoAheadSignal::<T>::remove(&id);
2383 };
2384
2385 T::OnNewHead::on_new_head(id, &new_head);
2386 }
2387
2388 pub(crate) fn set_current_code(
2393 id: ParaId,
2394 new_code_hash: ValidationCodeHash,
2395 at: BlockNumberFor<T>,
2396 ) -> Weight {
2397 let maybe_prior_code_hash = CurrentCodeHash::<T>::get(&id);
2398 CurrentCodeHash::<T>::insert(&id, &new_code_hash);
2399
2400 let log = ConsensusLog::ParaUpgradeCode(id, new_code_hash);
2401 <frame_system::Pallet<T>>::deposit_log(log.into());
2402
2403 let now = <frame_system::Pallet<T>>::block_number();
2405
2406 let weight = if let Some(prior_code_hash) = maybe_prior_code_hash {
2407 Self::note_past_code(id, at, now, prior_code_hash)
2408 } else {
2409 log::error!(target: LOG_TARGET, "Missing prior code hash for para {:?}", &id);
2410 Weight::zero()
2411 };
2412
2413 weight + T::DbWeight::get().writes(1)
2414 }
2415
2416 fn do_force_set_current_code_update(para: ParaId, new_code: ValidationCode) {
2418 let new_code_hash = new_code.hash();
2419 Self::increase_code_ref(&new_code_hash, &new_code);
2420 Self::set_current_code(para, new_code_hash, frame_system::Pallet::<T>::block_number());
2421 Self::deposit_event(Event::CurrentCodeUpdated(para));
2422 }
2423
2424 pub(crate) fn pvfs_require_precheck() -> Vec<ValidationCodeHash> {
2427 PvfActiveVoteList::<T>::get()
2428 }
2429
2430 pub(crate) fn submit_pvf_check_statement(
2437 stmt: PvfCheckStatement,
2438 signature: ValidatorSignature,
2439 ) {
2440 use frame_system::offchain::SubmitTransaction;
2441
2442 let xt = T::create_bare(Call::include_pvf_check_statement { stmt, signature }.into());
2443 if let Err(e) = SubmitTransaction::<T, Call<T>>::submit_transaction(xt) {
2444 log::error!(target: LOG_TARGET, "Error submitting pvf check statement: {:?}", e,);
2445 }
2446 }
2447
2448 pub fn lifecycle(id: ParaId) -> Option<ParaLifecycle> {
2450 ParaLifecycles::<T>::get(&id)
2451 }
2452
2453 pub fn is_valid_para(id: ParaId) -> bool {
2457 if let Some(state) = ParaLifecycles::<T>::get(&id) {
2458 !state.is_onboarding() && !state.is_offboarding()
2459 } else {
2460 false
2461 }
2462 }
2463
2464 pub fn is_offboarding(id: ParaId) -> bool {
2468 ParaLifecycles::<T>::get(&id).map_or(false, |state| state.is_offboarding())
2469 }
2470
2471 pub fn is_parachain(id: ParaId) -> bool {
2476 if let Some(state) = ParaLifecycles::<T>::get(&id) {
2477 state.is_parachain()
2478 } else {
2479 false
2480 }
2481 }
2482
2483 pub fn is_parathread(id: ParaId) -> bool {
2487 if let Some(state) = ParaLifecycles::<T>::get(&id) {
2488 state.is_parathread()
2489 } else {
2490 false
2491 }
2492 }
2493
2494 pub(crate) fn can_upgrade_validation_code(id: ParaId) -> bool {
2497 FutureCodeHash::<T>::get(&id).is_none() && UpgradeRestrictionSignal::<T>::get(&id).is_none()
2498 }
2499
2500 fn scheduled_session() -> SessionIndex {
2502 shared::Pallet::<T>::scheduled_session()
2503 }
2504
2505 fn increase_code_ref(code_hash: &ValidationCodeHash, code: &ValidationCode) -> Weight {
2509 let mut weight = T::DbWeight::get().reads_writes(1, 1);
2510 CodeByHashRefs::<T>::mutate(code_hash, |refs| {
2511 if *refs == 0 {
2512 weight += T::DbWeight::get().writes(1);
2513 CodeByHash::<T>::insert(code_hash, code);
2514 }
2515 *refs += 1;
2516 });
2517 weight
2518 }
2519
2520 fn decrease_code_ref(code_hash: &ValidationCodeHash) -> Weight {
2525 let mut weight = T::DbWeight::get().reads(1);
2526 let refs = CodeByHashRefs::<T>::get(code_hash);
2527 if refs == 0 {
2528 log::error!(target: LOG_TARGET, "Code refs is already zero for {:?}", code_hash);
2529 return weight
2530 }
2531 if refs <= 1 {
2532 weight += T::DbWeight::get().writes(2);
2533 CodeByHash::<T>::remove(code_hash);
2534 CodeByHashRefs::<T>::remove(code_hash);
2535 } else {
2536 weight += T::DbWeight::get().writes(1);
2537 CodeByHashRefs::<T>::insert(code_hash, refs - 1);
2538 }
2539 weight
2540 }
2541
2542 #[cfg(any(feature = "std", feature = "runtime-benchmarks", test))]
2544 pub fn test_on_new_session() {
2545 Self::initializer_on_new_session(&SessionChangeNotification {
2546 session_index: shared::CurrentSessionIndex::<T>::get(),
2547 ..Default::default()
2548 });
2549 }
2550
2551 #[cfg(any(feature = "runtime-benchmarks", test))]
2552 pub fn heads_insert(para_id: &ParaId, head_data: HeadData) {
2553 Heads::<T>::insert(para_id, head_data);
2554 }
2555
2556 pub(crate) fn initialize_para_now(
2558 parachains: &mut ParachainsCache<T>,
2559 id: ParaId,
2560 genesis_data: &ParaGenesisArgs,
2561 ) {
2562 match genesis_data.para_kind {
2563 ParaKind::Parachain => {
2564 parachains.add(id);
2565 ParaLifecycles::<T>::insert(&id, ParaLifecycle::Parachain);
2566 },
2567 ParaKind::Parathread => ParaLifecycles::<T>::insert(&id, ParaLifecycle::Parathread),
2568 }
2569
2570 if !genesis_data.validation_code.0.is_empty() {
2576 let code_hash = genesis_data.validation_code.hash();
2577 Self::increase_code_ref(&code_hash, &genesis_data.validation_code);
2578 CurrentCodeHash::<T>::insert(&id, code_hash);
2579 }
2580
2581 Heads::<T>::insert(&id, &genesis_data.genesis_head);
2582 MostRecentContext::<T>::insert(&id, BlockNumberFor::<T>::from(0u32));
2583 }
2584
2585 #[cfg(test)]
2586 pub(crate) fn active_vote_state(
2587 code_hash: &ValidationCodeHash,
2588 ) -> Option<PvfCheckActiveVoteState<BlockNumberFor<T>>> {
2589 PvfActiveVoteMap::<T>::get(code_hash)
2590 }
2591
2592 pub(crate) fn validate_code_is_authorized(
2597 code: &ValidationCode,
2598 para: &ParaId,
2599 ) -> Result<AuthorizedCodeHashAndExpiry<BlockNumberFor<T>>, Error<T>> {
2600 let authorized = AuthorizedCodeHash::<T>::get(para).ok_or(Error::<T>::NothingAuthorized)?;
2601 let now = frame_system::Pallet::<T>::block_number();
2602 ensure!(authorized.expire_at > now, Error::<T>::InvalidBlockNumber);
2603 ensure!(authorized.code_hash == code.hash(), Error::<T>::Unauthorized);
2604 Ok(authorized)
2605 }
2606}
2607
2608pub(crate) struct ParachainsCache<T: Config> {
2611 parachains: Option<BTreeSet<ParaId>>,
2613 _config: PhantomData<T>,
2614}
2615
2616impl<T: Config> ParachainsCache<T> {
2617 pub fn new() -> Self {
2618 Self { parachains: None, _config: PhantomData }
2619 }
2620
2621 fn ensure_initialized(&mut self) -> &mut BTreeSet<ParaId> {
2622 self.parachains
2623 .get_or_insert_with(|| Parachains::<T>::get().into_iter().collect())
2624 }
2625
2626 pub fn add(&mut self, id: ParaId) {
2628 let parachains = self.ensure_initialized();
2629 parachains.insert(id);
2630 }
2631
2632 pub fn remove(&mut self, id: ParaId) {
2635 let parachains = self.ensure_initialized();
2636 parachains.remove(&id);
2637 }
2638}
2639
2640impl<T: Config> Drop for ParachainsCache<T> {
2641 fn drop(&mut self) {
2642 if let Some(parachains) = self.parachains.take() {
2643 Parachains::<T>::put(parachains.into_iter().collect::<Vec<ParaId>>());
2644 }
2645 }
2646}