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 ensure!(Self::is_valid_para(para), Error::<T>::NotRegistered);
1304
1305 let now = frame_system::Pallet::<T>::block_number();
1306 let expire_at = now.saturating_add(valid_period);
1307
1308 AuthorizedCodeHash::<T>::insert(
1310 ¶,
1311 AuthorizedCodeHashAndExpiry::from((new_code_hash, expire_at)),
1312 );
1313 Self::deposit_event(Event::CodeAuthorized {
1314 para_id: para,
1315 code_hash: new_code_hash,
1316 expire_at,
1317 });
1318
1319 Ok(())
1320 }
1321
1322 #[pallet::call_index(11)]
1325 #[pallet::weight(<T as Config>::WeightInfo::apply_authorized_force_set_current_code(new_code.0.len() as u32))]
1326 pub fn apply_authorized_force_set_current_code(
1327 _origin: OriginFor<T>,
1328 para: ParaId,
1329 new_code: ValidationCode,
1330 ) -> DispatchResultWithPostInfo {
1331 let _ = Self::validate_code_is_authorized(&new_code, ¶)?;
1335 AuthorizedCodeHash::<T>::remove(para);
1337
1338 Self::do_force_set_current_code_update(para, new_code);
1340
1341 Ok(Pays::No.into())
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 AuthorizedCodeHash::<T>::remove(¶);
1594 let removed_future_code_hash = FutureCodeHash::<T>::take(¶);
1595 if let Some(removed_future_code_hash) = removed_future_code_hash {
1596 Self::decrease_code_ref(&removed_future_code_hash);
1597 }
1598
1599 let removed_code_hash = CurrentCodeHash::<T>::take(¶);
1600 if let Some(removed_code_hash) = removed_code_hash {
1601 Self::note_past_code(para, now, now, removed_code_hash);
1602 }
1603
1604 outgoing.push(para);
1605 },
1606 }
1607 }
1608
1609 if !outgoing.is_empty() {
1610 UpcomingUpgrades::<T>::mutate(|upcoming_upgrades| {
1617 upcoming_upgrades.retain(|(para, _)| !outgoing.contains(para));
1618 });
1619 UpgradeCooldowns::<T>::mutate(|upgrade_cooldowns| {
1620 upgrade_cooldowns.retain(|(para, _)| !outgoing.contains(para));
1621 });
1622 FutureCodeUpgradesAt::<T>::mutate(|future_upgrades| {
1623 future_upgrades.retain(|(para, _)| !outgoing.contains(para));
1624 });
1625 }
1626
1627 drop(parachains);
1629
1630 outgoing
1631 }
1632
1633 fn note_past_code(
1640 id: ParaId,
1641 at: BlockNumberFor<T>,
1642 now: BlockNumberFor<T>,
1643 old_code_hash: ValidationCodeHash,
1644 ) -> Weight {
1645 PastCodeMeta::<T>::mutate(&id, |past_meta| {
1646 past_meta.note_replacement(at, now);
1647 });
1648
1649 PastCodeHash::<T>::insert(&(id, at), old_code_hash);
1650
1651 PastCodePruning::<T>::mutate(|pruning| {
1654 let insert_idx =
1655 pruning.binary_search_by_key(&now, |&(_, b)| b).unwrap_or_else(|idx| idx);
1656 pruning.insert(insert_idx, (id, now));
1657 });
1658
1659 T::DbWeight::get().reads_writes(2, 3)
1660 }
1661
1662 fn prune_old_code(now: BlockNumberFor<T>) -> Weight {
1665 let config = configuration::ActiveConfig::<T>::get();
1666 let code_retention_period = config.code_retention_period;
1667 if now <= code_retention_period {
1668 let weight = T::DbWeight::get().reads_writes(1, 0);
1669 return weight
1670 }
1671
1672 let pruning_height = now - (code_retention_period + One::one());
1674
1675 let pruning_tasks_done =
1676 PastCodePruning::<T>::mutate(|pruning_tasks: &mut Vec<(_, BlockNumberFor<T>)>| {
1677 let (pruning_tasks_done, pruning_tasks_to_do) = {
1678 let up_to_idx =
1680 pruning_tasks.iter().take_while(|&(_, at)| at <= &pruning_height).count();
1681 (up_to_idx, pruning_tasks.drain(..up_to_idx))
1682 };
1683
1684 for (para_id, _) in pruning_tasks_to_do {
1685 let full_deactivate = PastCodeMeta::<T>::mutate(¶_id, |meta| {
1686 for pruned_repl_at in meta.prune_up_to(pruning_height) {
1687 let removed_code_hash =
1688 PastCodeHash::<T>::take(&(para_id, pruned_repl_at));
1689
1690 if let Some(removed_code_hash) = removed_code_hash {
1691 Self::decrease_code_ref(&removed_code_hash);
1692 } else {
1693 log::warn!(
1694 target: LOG_TARGET,
1695 "Missing code for removed hash {:?}",
1696 removed_code_hash,
1697 );
1698 }
1699 }
1700
1701 meta.is_empty() && Heads::<T>::get(¶_id).is_none()
1702 });
1703
1704 if full_deactivate {
1707 PastCodeMeta::<T>::remove(¶_id);
1708 }
1709 }
1710
1711 pruning_tasks_done as u64
1712 });
1713
1714 T::DbWeight::get().reads_writes(1 + pruning_tasks_done, 2 * pruning_tasks_done)
1717 }
1718
1719 fn prune_expired_authorizations(now: BlockNumberFor<T>) -> Weight {
1722 let mut weight = T::DbWeight::get().reads(1);
1723 let to_remove = AuthorizedCodeHash::<T>::iter().filter_map(
1724 |(para, AuthorizedCodeHashAndExpiry { expire_at, .. })| {
1725 if expire_at <= now {
1726 Some(para)
1727 } else {
1728 None
1729 }
1730 },
1731 );
1732 for para in to_remove {
1733 AuthorizedCodeHash::<T>::remove(¶);
1734 weight.saturating_accrue(T::DbWeight::get().writes(1));
1735 }
1736
1737 weight
1738 }
1739
1740 fn process_future_code_upgrades_at(now: BlockNumberFor<T>) -> Weight {
1745 let mut weight = T::DbWeight::get().reads_writes(1, 1);
1747 FutureCodeUpgradesAt::<T>::mutate(
1748 |upcoming_upgrades: &mut Vec<(ParaId, BlockNumberFor<T>)>| {
1749 let num = upcoming_upgrades.iter().take_while(|&(_, at)| at <= &now).count();
1750 for (id, expected_at) in upcoming_upgrades.drain(..num) {
1751 weight += T::DbWeight::get().reads_writes(1, 1);
1752
1753 let new_code_hash = if let Some(new_code_hash) = FutureCodeHash::<T>::take(&id)
1755 {
1756 new_code_hash
1757 } else {
1758 log::error!(target: LOG_TARGET, "Missing future code hash for {:?}", &id);
1759 continue
1760 };
1761
1762 weight += Self::set_current_code(id, new_code_hash, expected_at);
1763 }
1764 num
1765 },
1766 );
1767
1768 weight
1769 }
1770
1771 fn process_scheduled_upgrade_changes(now: BlockNumberFor<T>) -> Weight {
1777 let mut weight = T::DbWeight::get().reads_writes(1, 1);
1779 let upgrades_signaled = UpcomingUpgrades::<T>::mutate(
1780 |upcoming_upgrades: &mut Vec<(ParaId, BlockNumberFor<T>)>| {
1781 let num = upcoming_upgrades.iter().take_while(|&(_, at)| at <= &now).count();
1782 for (para, _) in upcoming_upgrades.drain(..num) {
1783 UpgradeGoAheadSignal::<T>::insert(¶, UpgradeGoAhead::GoAhead);
1784 }
1785 num
1786 },
1787 );
1788 weight += T::DbWeight::get().writes(upgrades_signaled as u64);
1789
1790 weight += T::DbWeight::get().reads(1);
1792 let cooldowns_expired =
1793 UpgradeCooldowns::<T>::get().iter().take_while(|&(_, at)| at <= &now).count();
1794
1795 weight += T::DbWeight::get().reads_writes(1, 1);
1799 weight += T::DbWeight::get().reads(cooldowns_expired as u64);
1800
1801 weight
1802 }
1803
1804 fn process_scheduled_upgrade_cooldowns(now: BlockNumberFor<T>) {
1808 UpgradeCooldowns::<T>::mutate(
1809 |upgrade_cooldowns: &mut Vec<(ParaId, BlockNumberFor<T>)>| {
1810 upgrade_cooldowns.retain(|(para, at)| {
1812 if at <= &now {
1813 UpgradeRestrictionSignal::<T>::remove(¶);
1814 false
1815 } else {
1816 true
1817 }
1818 });
1819 },
1820 );
1821 }
1822
1823 fn groom_ongoing_pvf_votes(
1826 cfg: &configuration::HostConfiguration<BlockNumberFor<T>>,
1827 new_n_validators: usize,
1828 ) -> Weight {
1829 let mut weight = T::DbWeight::get().reads(1);
1830
1831 let potentially_active_votes = PvfActiveVoteList::<T>::get();
1832
1833 let mut actually_active_votes = Vec::with_capacity(potentially_active_votes.len());
1838
1839 for vote_subject in potentially_active_votes {
1840 let mut vote_state = match PvfActiveVoteMap::<T>::take(&vote_subject) {
1841 Some(v) => v,
1842 None => {
1843 log::warn!(
1847 target: LOG_TARGET,
1848 "The PvfActiveVoteMap is out of sync with PvfActiveVoteList!",
1849 );
1850 debug_assert!(false);
1851 continue
1852 },
1853 };
1854
1855 vote_state.age += 1;
1856 if vote_state.age < cfg.pvf_voting_ttl {
1857 weight += T::DbWeight::get().writes(1);
1858 vote_state.reinitialize_ballots(new_n_validators);
1859 PvfActiveVoteMap::<T>::insert(&vote_subject, vote_state);
1860
1861 actually_active_votes.push(vote_subject);
1863 } else {
1864 weight += Self::enact_pvf_rejected(&vote_subject, vote_state.causes);
1866 }
1867 }
1868
1869 weight += T::DbWeight::get().writes(1);
1870 PvfActiveVoteList::<T>::put(actually_active_votes);
1871
1872 weight
1873 }
1874
1875 fn enact_pvf_accepted(
1876 now: BlockNumberFor<T>,
1877 code_hash: &ValidationCodeHash,
1878 causes: &[PvfCheckCause<BlockNumberFor<T>>],
1879 sessions_observed: SessionIndex,
1880 cfg: &configuration::HostConfiguration<BlockNumberFor<T>>,
1881 ) -> Weight {
1882 let mut weight = Weight::zero();
1883 for cause in causes {
1884 weight += T::DbWeight::get().reads_writes(3, 2);
1885 Self::deposit_event(Event::PvfCheckAccepted(*code_hash, cause.para_id()));
1886
1887 match cause {
1888 PvfCheckCause::Onboarding(id) => {
1889 weight += Self::proceed_with_onboarding(*id, sessions_observed);
1890 },
1891 PvfCheckCause::Upgrade { id, included_at, upgrade_strategy } => {
1892 weight += Self::proceed_with_upgrade(
1893 *id,
1894 code_hash,
1895 now,
1896 *included_at,
1897 cfg,
1898 *upgrade_strategy,
1899 );
1900 },
1901 }
1902 }
1903 weight
1904 }
1905
1906 fn proceed_with_onboarding(id: ParaId, sessions_observed: SessionIndex) -> Weight {
1907 let weight = T::DbWeight::get().reads_writes(2, 1);
1908
1909 let onboard_at: SessionIndex = shared::CurrentSessionIndex::<T>::get() +
1915 cmp::max(shared::SESSION_DELAY.saturating_sub(sessions_observed), 1);
1916
1917 ActionsQueue::<T>::mutate(onboard_at, |v| {
1918 if let Err(i) = v.binary_search(&id) {
1919 v.insert(i, id);
1920 }
1921 });
1922
1923 weight
1924 }
1925
1926 fn proceed_with_upgrade(
1927 id: ParaId,
1928 code_hash: &ValidationCodeHash,
1929 now: BlockNumberFor<T>,
1930 relay_parent_number: BlockNumberFor<T>,
1931 cfg: &configuration::HostConfiguration<BlockNumberFor<T>>,
1932 upgrade_strategy: UpgradeStrategy,
1933 ) -> Weight {
1934 let mut weight = Weight::zero();
1935
1936 let expected_at = cmp::max(
1949 relay_parent_number + cfg.validation_upgrade_delay,
1950 now + cfg.minimum_validation_upgrade_delay,
1951 );
1952
1953 match upgrade_strategy {
1954 UpgradeStrategy::ApplyAtExpectedBlock => {
1955 FutureCodeUpgradesAt::<T>::mutate(|future_upgrades| {
1956 let insert_idx = future_upgrades
1957 .binary_search_by_key(&expected_at, |&(_, b)| b)
1958 .unwrap_or_else(|idx| idx);
1959 future_upgrades.insert(insert_idx, (id, expected_at));
1960 });
1961
1962 weight += T::DbWeight::get().reads_writes(0, 2);
1963 },
1964 UpgradeStrategy::SetGoAheadSignal => {
1965 FutureCodeUpgrades::<T>::insert(&id, expected_at);
1966
1967 UpcomingUpgrades::<T>::mutate(|upcoming_upgrades| {
1968 let insert_idx = upcoming_upgrades
1969 .binary_search_by_key(&expected_at, |&(_, b)| b)
1970 .unwrap_or_else(|idx| idx);
1971 upcoming_upgrades.insert(insert_idx, (id, expected_at));
1972 });
1973
1974 weight += T::DbWeight::get().reads_writes(1, 3);
1975 },
1976 }
1977
1978 let expected_at = expected_at.saturated_into();
1979 let log = ConsensusLog::ParaScheduleUpgradeCode(id, *code_hash, expected_at);
1980 frame_system::Pallet::<T>::deposit_log(log.into());
1981
1982 weight
1983 }
1984
1985 fn enact_pvf_rejected(
1986 code_hash: &ValidationCodeHash,
1987 causes: Vec<PvfCheckCause<BlockNumberFor<T>>>,
1988 ) -> Weight {
1989 let mut weight = Weight::zero();
1990
1991 for cause in causes {
1992 weight += Self::decrease_code_ref(code_hash);
1995
1996 weight += T::DbWeight::get().reads_writes(3, 2);
1997 Self::deposit_event(Event::PvfCheckRejected(*code_hash, cause.para_id()));
1998
1999 match cause {
2000 PvfCheckCause::Onboarding(id) => {
2001 weight += T::DbWeight::get().writes(3);
2007 UpcomingParasGenesis::<T>::remove(&id);
2008 CurrentCodeHash::<T>::remove(&id);
2009 ParaLifecycles::<T>::remove(&id);
2010 },
2011 PvfCheckCause::Upgrade { id, .. } => {
2012 weight += T::DbWeight::get().writes(2);
2013 UpgradeGoAheadSignal::<T>::insert(&id, UpgradeGoAhead::Abort);
2014 FutureCodeHash::<T>::remove(&id);
2015 },
2016 }
2017 }
2018
2019 weight
2020 }
2021
2022 pub fn can_schedule_para_initialize(id: &ParaId) -> bool {
2026 ParaLifecycles::<T>::get(id).is_none()
2027 }
2028
2029 pub(crate) fn schedule_para_initialize(
2040 id: ParaId,
2041 mut genesis_data: ParaGenesisArgs,
2042 ) -> DispatchResult {
2043 ensure!(Self::can_schedule_para_initialize(&id), Error::<T>::CannotOnboard);
2046 ensure!(!genesis_data.validation_code.0.is_empty(), Error::<T>::CannotOnboard);
2047 ParaLifecycles::<T>::insert(&id, ParaLifecycle::Onboarding);
2048
2049 let validation_code =
2083 mem::replace(&mut genesis_data.validation_code, ValidationCode(Vec::new()));
2084 UpcomingParasGenesis::<T>::insert(&id, genesis_data);
2085 let validation_code_hash = validation_code.hash();
2086 CurrentCodeHash::<T>::insert(&id, validation_code_hash);
2087
2088 let cfg = configuration::ActiveConfig::<T>::get();
2089 Self::kick_off_pvf_check(
2090 PvfCheckCause::Onboarding(id),
2091 validation_code_hash,
2092 validation_code,
2093 &cfg,
2094 );
2095
2096 Ok(())
2097 }
2098
2099 pub(crate) fn schedule_para_cleanup(id: ParaId) -> DispatchResult {
2109 if let Some(future_code_hash) = FutureCodeHash::<T>::get(&id) {
2121 let active_prechecking = PvfActiveVoteList::<T>::get();
2122 if active_prechecking.contains(&future_code_hash) {
2123 return Err(Error::<T>::CannotOffboard.into())
2124 }
2125 }
2126
2127 let lifecycle = ParaLifecycles::<T>::get(&id);
2128 match lifecycle {
2129 None => return Ok(()),
2131 Some(ParaLifecycle::Parathread) => {
2132 ParaLifecycles::<T>::insert(&id, ParaLifecycle::OffboardingParathread);
2133 },
2134 Some(ParaLifecycle::Parachain) => {
2135 ParaLifecycles::<T>::insert(&id, ParaLifecycle::OffboardingParachain);
2136 },
2137 _ => return Err(Error::<T>::CannotOffboard.into()),
2138 }
2139
2140 let scheduled_session = Self::scheduled_session();
2141 ActionsQueue::<T>::mutate(scheduled_session, |v| {
2142 if let Err(i) = v.binary_search(&id) {
2143 v.insert(i, id);
2144 }
2145 });
2146
2147 if <T as Config>::QueueFootprinter::message_count(UmpQueueId::Para(id)) != 0 {
2148 return Err(Error::<T>::CannotOffboard.into())
2149 }
2150
2151 Ok(())
2152 }
2153
2154 pub(crate) fn schedule_parathread_upgrade(id: ParaId) -> DispatchResult {
2158 let scheduled_session = Self::scheduled_session();
2159 let lifecycle = ParaLifecycles::<T>::get(&id).ok_or(Error::<T>::NotRegistered)?;
2160
2161 ensure!(lifecycle == ParaLifecycle::Parathread, Error::<T>::CannotUpgrade);
2162
2163 ParaLifecycles::<T>::insert(&id, ParaLifecycle::UpgradingParathread);
2164 ActionsQueue::<T>::mutate(scheduled_session, |v| {
2165 if let Err(i) = v.binary_search(&id) {
2166 v.insert(i, id);
2167 }
2168 });
2169
2170 Ok(())
2171 }
2172
2173 pub(crate) fn schedule_parachain_downgrade(id: ParaId) -> DispatchResult {
2177 let scheduled_session = Self::scheduled_session();
2178 let lifecycle = ParaLifecycles::<T>::get(&id).ok_or(Error::<T>::NotRegistered)?;
2179
2180 ensure!(lifecycle == ParaLifecycle::Parachain, Error::<T>::CannotDowngrade);
2181
2182 ParaLifecycles::<T>::insert(&id, ParaLifecycle::DowngradingParachain);
2183 ActionsQueue::<T>::mutate(scheduled_session, |v| {
2184 if let Err(i) = v.binary_search(&id) {
2185 v.insert(i, id);
2186 }
2187 });
2188
2189 Ok(())
2190 }
2191
2192 pub(crate) fn schedule_code_upgrade(
2211 id: ParaId,
2212 new_code: ValidationCode,
2213 inclusion_block_number: BlockNumberFor<T>,
2214 cfg: &configuration::HostConfiguration<BlockNumberFor<T>>,
2215 upgrade_strategy: UpgradeStrategy,
2216 ) {
2217 let new_code_len = new_code.0.len();
2219 if new_code_len < MIN_CODE_SIZE as usize || new_code_len > cfg.max_code_size as usize {
2220 log::warn!(target: LOG_TARGET, "attempted to schedule an upgrade with invalid new validation code",);
2221 return
2222 }
2223
2224 if FutureCodeHash::<T>::contains_key(&id) {
2226 log::warn!(target: LOG_TARGET, "ended up scheduling an upgrade while one is pending",);
2235 return
2236 }
2237
2238 let code_hash = new_code.hash();
2239
2240 if CurrentCodeHash::<T>::get(&id) == Some(code_hash) {
2245 log::warn!(
2248 target: LOG_TARGET,
2249 "para tried to upgrade to the same code. Abort the upgrade",
2250 );
2251 return
2252 }
2253
2254 FutureCodeHash::<T>::insert(&id, &code_hash);
2256 UpgradeRestrictionSignal::<T>::insert(&id, UpgradeRestriction::Present);
2257
2258 let next_possible_upgrade_at = inclusion_block_number + cfg.validation_upgrade_cooldown;
2259 UpgradeCooldowns::<T>::mutate(|upgrade_cooldowns| {
2260 let insert_idx = upgrade_cooldowns
2261 .binary_search_by_key(&next_possible_upgrade_at, |&(_, b)| b)
2262 .unwrap_or_else(|idx| idx);
2263 upgrade_cooldowns.insert(insert_idx, (id, next_possible_upgrade_at));
2264 });
2265
2266 Self::kick_off_pvf_check(
2267 PvfCheckCause::Upgrade { id, included_at: inclusion_block_number, upgrade_strategy },
2268 code_hash,
2269 new_code,
2270 cfg,
2271 );
2272 }
2273
2274 fn kick_off_pvf_check(
2288 cause: PvfCheckCause<BlockNumberFor<T>>,
2289 code_hash: ValidationCodeHash,
2290 code: ValidationCode,
2291 cfg: &configuration::HostConfiguration<BlockNumberFor<T>>,
2292 ) -> Weight {
2293 let mut weight = Weight::zero();
2294
2295 weight += T::DbWeight::get().reads_writes(3, 2);
2296 Self::deposit_event(Event::PvfCheckStarted(code_hash, cause.para_id()));
2297
2298 weight += T::DbWeight::get().reads(1);
2299 match PvfActiveVoteMap::<T>::get(&code_hash) {
2300 None => {
2301 let known_code = CodeByHash::<T>::contains_key(&code_hash);
2304 weight += T::DbWeight::get().reads(1);
2305
2306 if known_code {
2307 weight += T::DbWeight::get().reads(1);
2310 let now = frame_system::Pallet::<T>::block_number();
2311 weight += Self::enact_pvf_accepted(now, &code_hash, &[cause], 0, cfg);
2312 } else {
2313 weight += T::DbWeight::get().reads_writes(3, 2);
2316 let now = frame_system::Pallet::<T>::block_number();
2317 let n_validators = shared::ActiveValidatorKeys::<T>::get().len();
2318 PvfActiveVoteMap::<T>::insert(
2319 &code_hash,
2320 PvfCheckActiveVoteState::new(now, n_validators, cause),
2321 );
2322 PvfActiveVoteList::<T>::mutate(|l| {
2323 if let Err(idx) = l.binary_search(&code_hash) {
2324 l.insert(idx, code_hash);
2325 }
2326 });
2327 }
2328 },
2329 Some(mut vote_state) => {
2330 weight += T::DbWeight::get().writes(1);
2333 vote_state.causes.push(cause);
2334 PvfActiveVoteMap::<T>::insert(&code_hash, vote_state);
2335 },
2336 }
2337
2338 weight += Self::increase_code_ref(&code_hash, &code);
2350
2351 weight
2352 }
2353
2354 pub(crate) fn note_new_head(
2358 id: ParaId,
2359 new_head: HeadData,
2360 execution_context: BlockNumberFor<T>,
2361 ) {
2362 Heads::<T>::insert(&id, &new_head);
2363 MostRecentContext::<T>::insert(&id, execution_context);
2364
2365 if let Some(expected_at) = FutureCodeUpgrades::<T>::get(&id) {
2366 if expected_at <= execution_context {
2367 FutureCodeUpgrades::<T>::remove(&id);
2368 UpgradeGoAheadSignal::<T>::remove(&id);
2369
2370 let new_code_hash = if let Some(new_code_hash) = FutureCodeHash::<T>::take(&id) {
2372 new_code_hash
2373 } else {
2374 log::error!(target: LOG_TARGET, "Missing future code hash for {:?}", &id);
2375 return
2376 };
2377
2378 Self::set_current_code(id, new_code_hash, expected_at);
2379 }
2380 } else {
2381 UpgradeGoAheadSignal::<T>::remove(&id);
2386 };
2387
2388 T::OnNewHead::on_new_head(id, &new_head);
2389 }
2390
2391 pub(crate) fn set_current_code(
2396 id: ParaId,
2397 new_code_hash: ValidationCodeHash,
2398 at: BlockNumberFor<T>,
2399 ) -> Weight {
2400 let maybe_prior_code_hash = CurrentCodeHash::<T>::get(&id);
2401 CurrentCodeHash::<T>::insert(&id, &new_code_hash);
2402
2403 let log = ConsensusLog::ParaUpgradeCode(id, new_code_hash);
2404 <frame_system::Pallet<T>>::deposit_log(log.into());
2405
2406 let now = <frame_system::Pallet<T>>::block_number();
2408
2409 let weight = if let Some(prior_code_hash) = maybe_prior_code_hash {
2410 Self::note_past_code(id, at, now, prior_code_hash)
2411 } else {
2412 log::error!(target: LOG_TARGET, "Missing prior code hash for para {:?}", &id);
2413 Weight::zero()
2414 };
2415
2416 weight + T::DbWeight::get().writes(1)
2417 }
2418
2419 fn do_force_set_current_code_update(para: ParaId, new_code: ValidationCode) {
2421 let new_code_hash = new_code.hash();
2422 Self::increase_code_ref(&new_code_hash, &new_code);
2423 Self::set_current_code(para, new_code_hash, frame_system::Pallet::<T>::block_number());
2424 Self::deposit_event(Event::CurrentCodeUpdated(para));
2425 }
2426
2427 pub(crate) fn pvfs_require_precheck() -> Vec<ValidationCodeHash> {
2430 PvfActiveVoteList::<T>::get()
2431 }
2432
2433 pub(crate) fn submit_pvf_check_statement(
2440 stmt: PvfCheckStatement,
2441 signature: ValidatorSignature,
2442 ) {
2443 use frame_system::offchain::SubmitTransaction;
2444
2445 let xt = T::create_bare(Call::include_pvf_check_statement { stmt, signature }.into());
2446 if let Err(e) = SubmitTransaction::<T, Call<T>>::submit_transaction(xt) {
2447 log::error!(target: LOG_TARGET, "Error submitting pvf check statement: {:?}", e,);
2448 }
2449 }
2450
2451 pub fn lifecycle(id: ParaId) -> Option<ParaLifecycle> {
2453 ParaLifecycles::<T>::get(&id)
2454 }
2455
2456 pub fn is_valid_para(id: ParaId) -> bool {
2460 if let Some(state) = ParaLifecycles::<T>::get(&id) {
2461 !state.is_onboarding() && !state.is_offboarding()
2462 } else {
2463 false
2464 }
2465 }
2466
2467 pub fn is_offboarding(id: ParaId) -> bool {
2471 ParaLifecycles::<T>::get(&id).map_or(false, |state| state.is_offboarding())
2472 }
2473
2474 pub fn is_parachain(id: ParaId) -> bool {
2479 if let Some(state) = ParaLifecycles::<T>::get(&id) {
2480 state.is_parachain()
2481 } else {
2482 false
2483 }
2484 }
2485
2486 pub fn is_parathread(id: ParaId) -> bool {
2490 if let Some(state) = ParaLifecycles::<T>::get(&id) {
2491 state.is_parathread()
2492 } else {
2493 false
2494 }
2495 }
2496
2497 pub(crate) fn can_upgrade_validation_code(id: ParaId) -> bool {
2500 FutureCodeHash::<T>::get(&id).is_none() && UpgradeRestrictionSignal::<T>::get(&id).is_none()
2501 }
2502
2503 fn scheduled_session() -> SessionIndex {
2505 shared::Pallet::<T>::scheduled_session()
2506 }
2507
2508 fn increase_code_ref(code_hash: &ValidationCodeHash, code: &ValidationCode) -> Weight {
2512 let mut weight = T::DbWeight::get().reads_writes(1, 1);
2513 CodeByHashRefs::<T>::mutate(code_hash, |refs| {
2514 if *refs == 0 {
2515 weight += T::DbWeight::get().writes(1);
2516 CodeByHash::<T>::insert(code_hash, code);
2517 }
2518 *refs += 1;
2519 });
2520 weight
2521 }
2522
2523 fn decrease_code_ref(code_hash: &ValidationCodeHash) -> Weight {
2528 let mut weight = T::DbWeight::get().reads(1);
2529 let refs = CodeByHashRefs::<T>::get(code_hash);
2530 if refs == 0 {
2531 log::error!(target: LOG_TARGET, "Code refs is already zero for {:?}", code_hash);
2532 return weight
2533 }
2534 if refs <= 1 {
2535 weight += T::DbWeight::get().writes(2);
2536 CodeByHash::<T>::remove(code_hash);
2537 CodeByHashRefs::<T>::remove(code_hash);
2538 } else {
2539 weight += T::DbWeight::get().writes(1);
2540 CodeByHashRefs::<T>::insert(code_hash, refs - 1);
2541 }
2542 weight
2543 }
2544
2545 #[cfg(any(feature = "std", feature = "runtime-benchmarks", test))]
2547 pub fn test_on_new_session() {
2548 Self::initializer_on_new_session(&SessionChangeNotification {
2549 session_index: shared::CurrentSessionIndex::<T>::get(),
2550 ..Default::default()
2551 });
2552 }
2553
2554 #[cfg(any(feature = "runtime-benchmarks", test))]
2555 pub fn heads_insert(para_id: &ParaId, head_data: HeadData) {
2556 Heads::<T>::insert(para_id, head_data);
2557 }
2558
2559 pub(crate) fn initialize_para_now(
2561 parachains: &mut ParachainsCache<T>,
2562 id: ParaId,
2563 genesis_data: &ParaGenesisArgs,
2564 ) {
2565 match genesis_data.para_kind {
2566 ParaKind::Parachain => {
2567 parachains.add(id);
2568 ParaLifecycles::<T>::insert(&id, ParaLifecycle::Parachain);
2569 },
2570 ParaKind::Parathread => ParaLifecycles::<T>::insert(&id, ParaLifecycle::Parathread),
2571 }
2572
2573 if !genesis_data.validation_code.0.is_empty() {
2579 let code_hash = genesis_data.validation_code.hash();
2580 Self::increase_code_ref(&code_hash, &genesis_data.validation_code);
2581 CurrentCodeHash::<T>::insert(&id, code_hash);
2582 }
2583
2584 Heads::<T>::insert(&id, &genesis_data.genesis_head);
2585 MostRecentContext::<T>::insert(&id, BlockNumberFor::<T>::from(0u32));
2586 }
2587
2588 #[cfg(test)]
2589 pub(crate) fn active_vote_state(
2590 code_hash: &ValidationCodeHash,
2591 ) -> Option<PvfCheckActiveVoteState<BlockNumberFor<T>>> {
2592 PvfActiveVoteMap::<T>::get(code_hash)
2593 }
2594
2595 pub(crate) fn validate_code_is_authorized(
2600 code: &ValidationCode,
2601 para: &ParaId,
2602 ) -> Result<AuthorizedCodeHashAndExpiry<BlockNumberFor<T>>, Error<T>> {
2603 let authorized = AuthorizedCodeHash::<T>::get(para).ok_or(Error::<T>::NothingAuthorized)?;
2604 let now = frame_system::Pallet::<T>::block_number();
2605 ensure!(authorized.expire_at > now, Error::<T>::InvalidBlockNumber);
2606 ensure!(authorized.code_hash == code.hash(), Error::<T>::Unauthorized);
2607 Ok(authorized)
2608 }
2609}
2610
2611pub(crate) struct ParachainsCache<T: Config> {
2614 parachains: Option<BTreeSet<ParaId>>,
2616 _config: PhantomData<T>,
2617}
2618
2619impl<T: Config> ParachainsCache<T> {
2620 pub fn new() -> Self {
2621 Self { parachains: None, _config: PhantomData }
2622 }
2623
2624 fn ensure_initialized(&mut self) -> &mut BTreeSet<ParaId> {
2625 self.parachains
2626 .get_or_insert_with(|| Parachains::<T>::get().into_iter().collect())
2627 }
2628
2629 pub fn add(&mut self, id: ParaId) {
2631 let parachains = self.ensure_initialized();
2632 parachains.insert(id);
2633 }
2634
2635 pub fn remove(&mut self, id: ParaId) {
2638 let parachains = self.ensure_initialized();
2639 parachains.remove(&id);
2640 }
2641}
2642
2643impl<T: Config> Drop for ParachainsCache<T> {
2644 fn drop(&mut self) {
2645 if let Some(parachains) = self.parachains.take() {
2646 Parachains::<T>::put(parachains.into_iter().collect::<Vec<ParaId>>());
2647 }
2648 }
2649}