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_runtime::{
132 traits::{AppVerify, One, Saturating},
133 DispatchResult, SaturatedConversion,
134};
135use Debug;
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, Debug, 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 Debug,
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, Debug)]
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, Debug, 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 #[allow(deprecated)]
1369 #[pallet::validate_unsigned]
1370 impl<T: Config> ValidateUnsigned for Pallet<T> {
1371 type Call = Call<T>;
1372
1373 fn validate_unsigned(_source: TransactionSource, call: &Self::Call) -> TransactionValidity {
1374 match call {
1375 Call::include_pvf_check_statement { stmt, signature } => {
1376 let current_session = shared::CurrentSessionIndex::<T>::get();
1377 if stmt.session_index < current_session {
1378 return InvalidTransaction::Stale.into();
1379 } else if stmt.session_index > current_session {
1380 return InvalidTransaction::Future.into();
1381 }
1382
1383 let validator_index = stmt.validator_index.0 as usize;
1384 let validators = shared::ActiveValidatorKeys::<T>::get();
1385 let validator_public = match validators.get(validator_index) {
1386 Some(pk) => pk,
1387 None => {
1388 return InvalidTransaction::Custom(INVALID_TX_BAD_VALIDATOR_IDX).into()
1389 },
1390 };
1391
1392 let signing_payload = stmt.signing_payload();
1393 if !signature.verify(&signing_payload[..], &validator_public) {
1394 return InvalidTransaction::BadProof.into();
1395 }
1396
1397 let active_vote = match PvfActiveVoteMap::<T>::get(&stmt.subject) {
1398 Some(v) => v,
1399 None => return InvalidTransaction::Custom(INVALID_TX_BAD_SUBJECT).into(),
1400 };
1401
1402 match active_vote.has_vote(validator_index) {
1403 Some(false) => (),
1404 Some(true) => {
1405 return InvalidTransaction::Custom(INVALID_TX_DOUBLE_VOTE).into()
1406 },
1407 None => {
1408 return InvalidTransaction::Custom(INVALID_TX_BAD_VALIDATOR_IDX).into()
1409 },
1410 }
1411
1412 ValidTransaction::with_tag_prefix("PvfPreCheckingVote")
1413 .priority(T::UnsignedPriority::get())
1414 .longevity(
1415 TryInto::<u64>::try_into(
1416 T::NextSessionRotation::average_session_length() / 2u32.into(),
1417 )
1418 .unwrap_or(64_u64),
1419 )
1420 .and_provides((stmt.session_index, stmt.validator_index, stmt.subject))
1421 .propagate(true)
1422 .build()
1423 },
1424 Call::apply_authorized_force_set_current_code { para, new_code } => {
1425 match Self::validate_code_is_authorized(new_code, para) {
1426 Ok(authorized_code) => {
1427 let now = frame_system::Pallet::<T>::block_number();
1428 let longevity = authorized_code.expire_at.saturating_sub(now);
1429
1430 ValidTransaction::with_tag_prefix("ApplyAuthorizedForceSetCurrentCode")
1431 .priority(T::UnsignedPriority::get())
1432 .longevity(TryInto::<u64>::try_into(longevity).unwrap_or(64_u64))
1433 .and_provides((para, authorized_code.code_hash))
1434 .propagate(true)
1435 .build()
1436 },
1437 Err(_) => {
1438 return InvalidTransaction::Custom(INVALID_TX_UNAUTHORIZED_CODE).into()
1439 },
1440 }
1441 },
1442 _ => InvalidTransaction::Call.into(),
1443 }
1444 }
1445
1446 fn pre_dispatch(_call: &Self::Call) -> Result<(), TransactionValidityError> {
1447 Ok(())
1455 }
1456 }
1457}
1458
1459const INVALID_TX_BAD_VALIDATOR_IDX: u8 = 1;
1461const INVALID_TX_BAD_SUBJECT: u8 = 2;
1462const INVALID_TX_DOUBLE_VOTE: u8 = 3;
1463const INVALID_TX_UNAUTHORIZED_CODE: u8 = 4;
1464
1465pub const MAX_PARA_HEADS: usize = 1024;
1473
1474impl<T: Config> Pallet<T> {
1475 pub(crate) fn schedule_code_upgrade_external(
1480 id: ParaId,
1481 new_code: ValidationCode,
1482 upgrade_strategy: UpgradeStrategy,
1483 ) -> DispatchResult {
1484 ensure!(Self::can_upgrade_validation_code(id), Error::<T>::CannotUpgradeCode);
1486 let config = configuration::ActiveConfig::<T>::get();
1487 ensure!(new_code.0.len() >= MIN_CODE_SIZE as usize, Error::<T>::InvalidCode);
1489 ensure!(new_code.0.len() <= config.max_code_size as usize, Error::<T>::InvalidCode);
1490
1491 let current_block = frame_system::Pallet::<T>::block_number();
1492 let upgrade_block = current_block.saturating_add(config.validation_upgrade_delay);
1494 Self::schedule_code_upgrade(id, new_code, upgrade_block, &config, upgrade_strategy);
1495 Self::deposit_event(Event::CodeUpgradeScheduled(id));
1496 Ok(())
1497 }
1498
1499 pub(crate) fn set_current_head(para: ParaId, new_head: HeadData) {
1501 Heads::<T>::insert(¶, new_head);
1502 Self::deposit_event(Event::CurrentHeadUpdated(para));
1503 }
1504
1505 pub(crate) fn initializer_initialize(now: BlockNumberFor<T>) -> Weight {
1507 Self::prune_old_code(now) +
1508 Self::process_scheduled_upgrade_changes(now) +
1509 Self::process_future_code_upgrades_at(now) +
1510 Self::prune_expired_authorizations(now)
1511 }
1512
1513 pub(crate) fn initializer_finalize(now: BlockNumberFor<T>) {
1515 Self::process_scheduled_upgrade_cooldowns(now);
1516 }
1517
1518 pub(crate) fn initializer_on_new_session(
1522 notification: &SessionChangeNotification<BlockNumberFor<T>>,
1523 ) -> Vec<ParaId> {
1524 let outgoing_paras = Self::apply_actions_queue(notification.session_index);
1525 Self::groom_ongoing_pvf_votes(¬ification.new_config, notification.validators.len());
1526 outgoing_paras
1527 }
1528
1529 pub(crate) fn current_code(para_id: &ParaId) -> Option<ValidationCode> {
1531 CurrentCodeHash::<T>::get(para_id).and_then(|code_hash| {
1532 let code = CodeByHash::<T>::get(&code_hash);
1533 if code.is_none() {
1534 log::error!(
1535 "Pallet paras storage is inconsistent, code not found for hash {}",
1536 code_hash,
1537 );
1538 debug_assert!(false, "inconsistent paras storages");
1539 }
1540 code
1541 })
1542 }
1543
1544 pub fn sorted_para_heads() -> Vec<(u32, Vec<u8>)> {
1547 let mut heads: Vec<(u32, Vec<u8>)> =
1548 Heads::<T>::iter().map(|(id, head)| (id.into(), head.0)).collect();
1549 heads.sort_by_key(|(id, _)| *id);
1550 heads.truncate(MAX_PARA_HEADS);
1551 heads
1552 }
1553
1554 fn apply_actions_queue(session: SessionIndex) -> Vec<ParaId> {
1563 let actions = ActionsQueue::<T>::take(session);
1564 let mut parachains = ParachainsCache::new();
1565 let now = frame_system::Pallet::<T>::block_number();
1566 let mut outgoing = Vec::new();
1567
1568 for para in actions {
1569 let lifecycle = ParaLifecycles::<T>::get(¶);
1570 match lifecycle {
1571 None | Some(ParaLifecycle::Parathread) | Some(ParaLifecycle::Parachain) => { },
1573 Some(ParaLifecycle::Onboarding) => {
1574 if let Some(genesis_data) = UpcomingParasGenesis::<T>::take(¶) {
1575 Self::initialize_para_now(&mut parachains, para, &genesis_data);
1576 }
1577 },
1578 Some(ParaLifecycle::UpgradingParathread) => {
1580 parachains.add(para);
1581 ParaLifecycles::<T>::insert(¶, ParaLifecycle::Parachain);
1582 },
1583 Some(ParaLifecycle::DowngradingParachain) => {
1585 parachains.remove(para);
1586 ParaLifecycles::<T>::insert(¶, ParaLifecycle::Parathread);
1587 },
1588 Some(ParaLifecycle::OffboardingParachain) |
1590 Some(ParaLifecycle::OffboardingParathread) => {
1591 parachains.remove(para);
1592
1593 Heads::<T>::remove(¶);
1594 MostRecentContext::<T>::remove(¶);
1595 FutureCodeUpgrades::<T>::remove(¶);
1596 UpgradeGoAheadSignal::<T>::remove(¶);
1597 UpgradeRestrictionSignal::<T>::remove(¶);
1598 ParaLifecycles::<T>::remove(¶);
1599 AuthorizedCodeHash::<T>::remove(¶);
1600 let removed_future_code_hash = FutureCodeHash::<T>::take(¶);
1601 if let Some(removed_future_code_hash) = removed_future_code_hash {
1602 Self::decrease_code_ref(&removed_future_code_hash);
1603 }
1604
1605 let removed_code_hash = CurrentCodeHash::<T>::take(¶);
1606 if let Some(removed_code_hash) = removed_code_hash {
1607 Self::note_past_code(para, now, now, removed_code_hash);
1608 }
1609
1610 outgoing.push(para);
1611 },
1612 }
1613 }
1614
1615 if !outgoing.is_empty() {
1616 UpcomingUpgrades::<T>::mutate(|upcoming_upgrades| {
1623 upcoming_upgrades.retain(|(para, _)| !outgoing.contains(para));
1624 });
1625 UpgradeCooldowns::<T>::mutate(|upgrade_cooldowns| {
1626 upgrade_cooldowns.retain(|(para, _)| !outgoing.contains(para));
1627 });
1628 FutureCodeUpgradesAt::<T>::mutate(|future_upgrades| {
1629 future_upgrades.retain(|(para, _)| !outgoing.contains(para));
1630 });
1631 }
1632
1633 drop(parachains);
1635
1636 outgoing
1637 }
1638
1639 fn note_past_code(
1646 id: ParaId,
1647 at: BlockNumberFor<T>,
1648 now: BlockNumberFor<T>,
1649 old_code_hash: ValidationCodeHash,
1650 ) -> Weight {
1651 PastCodeMeta::<T>::mutate(&id, |past_meta| {
1652 past_meta.note_replacement(at, now);
1653 });
1654
1655 PastCodeHash::<T>::insert(&(id, at), old_code_hash);
1656
1657 PastCodePruning::<T>::mutate(|pruning| {
1660 let insert_idx =
1661 pruning.binary_search_by_key(&now, |&(_, b)| b).unwrap_or_else(|idx| idx);
1662 pruning.insert(insert_idx, (id, now));
1663 });
1664
1665 T::DbWeight::get().reads_writes(2, 3)
1666 }
1667
1668 fn prune_old_code(now: BlockNumberFor<T>) -> Weight {
1671 let config = configuration::ActiveConfig::<T>::get();
1672 let code_retention_period = config.code_retention_period;
1673 if now <= code_retention_period {
1674 let weight = T::DbWeight::get().reads_writes(1, 0);
1675 return weight;
1676 }
1677
1678 let pruning_height = now - (code_retention_period + One::one());
1680
1681 let pruning_tasks_done =
1682 PastCodePruning::<T>::mutate(|pruning_tasks: &mut Vec<(_, BlockNumberFor<T>)>| {
1683 let (pruning_tasks_done, pruning_tasks_to_do) = {
1684 let up_to_idx =
1686 pruning_tasks.iter().take_while(|&(_, at)| at <= &pruning_height).count();
1687 (up_to_idx, pruning_tasks.drain(..up_to_idx))
1688 };
1689
1690 for (para_id, _) in pruning_tasks_to_do {
1691 let full_deactivate = PastCodeMeta::<T>::mutate(¶_id, |meta| {
1692 for pruned_repl_at in meta.prune_up_to(pruning_height) {
1693 let removed_code_hash =
1694 PastCodeHash::<T>::take(&(para_id, pruned_repl_at));
1695
1696 if let Some(removed_code_hash) = removed_code_hash {
1697 Self::decrease_code_ref(&removed_code_hash);
1698 } else {
1699 log::warn!(
1700 target: LOG_TARGET,
1701 "Missing code for removed hash {:?}",
1702 removed_code_hash,
1703 );
1704 }
1705 }
1706
1707 meta.is_empty() && Heads::<T>::get(¶_id).is_none()
1708 });
1709
1710 if full_deactivate {
1713 PastCodeMeta::<T>::remove(¶_id);
1714 }
1715 }
1716
1717 pruning_tasks_done as u64
1718 });
1719
1720 T::DbWeight::get().reads_writes(1 + pruning_tasks_done, 2 * pruning_tasks_done)
1723 }
1724
1725 fn prune_expired_authorizations(now: BlockNumberFor<T>) -> Weight {
1728 let mut weight = T::DbWeight::get().reads(1);
1729 let to_remove = AuthorizedCodeHash::<T>::iter().filter_map(
1730 |(para, AuthorizedCodeHashAndExpiry { expire_at, .. })| {
1731 if expire_at <= now {
1732 Some(para)
1733 } else {
1734 None
1735 }
1736 },
1737 );
1738 for para in to_remove {
1739 AuthorizedCodeHash::<T>::remove(¶);
1740 weight.saturating_accrue(T::DbWeight::get().writes(1));
1741 }
1742
1743 weight
1744 }
1745
1746 fn process_future_code_upgrades_at(now: BlockNumberFor<T>) -> Weight {
1751 let mut weight = T::DbWeight::get().reads_writes(1, 1);
1753 FutureCodeUpgradesAt::<T>::mutate(
1754 |upcoming_upgrades: &mut Vec<(ParaId, BlockNumberFor<T>)>| {
1755 let num = upcoming_upgrades.iter().take_while(|&(_, at)| at <= &now).count();
1756 for (id, expected_at) in upcoming_upgrades.drain(..num) {
1757 weight += T::DbWeight::get().reads_writes(1, 1);
1758
1759 let new_code_hash = if let Some(new_code_hash) = FutureCodeHash::<T>::take(&id)
1761 {
1762 new_code_hash
1763 } else {
1764 log::error!(target: LOG_TARGET, "Missing future code hash for {:?}", &id);
1765 continue;
1766 };
1767
1768 weight += Self::set_current_code(id, new_code_hash, expected_at);
1769 }
1770 num
1771 },
1772 );
1773
1774 weight
1775 }
1776
1777 fn process_scheduled_upgrade_changes(now: BlockNumberFor<T>) -> Weight {
1783 let mut weight = T::DbWeight::get().reads_writes(1, 1);
1785 let upgrades_signaled = UpcomingUpgrades::<T>::mutate(
1786 |upcoming_upgrades: &mut Vec<(ParaId, BlockNumberFor<T>)>| {
1787 let num = upcoming_upgrades.iter().take_while(|&(_, at)| at <= &now).count();
1788 for (para, _) in upcoming_upgrades.drain(..num) {
1789 UpgradeGoAheadSignal::<T>::insert(¶, UpgradeGoAhead::GoAhead);
1790 }
1791 num
1792 },
1793 );
1794 weight += T::DbWeight::get().writes(upgrades_signaled as u64);
1795
1796 weight += T::DbWeight::get().reads(1);
1798 let cooldowns_expired =
1799 UpgradeCooldowns::<T>::get().iter().take_while(|&(_, at)| at <= &now).count();
1800
1801 weight += T::DbWeight::get().reads_writes(1, 1);
1805 weight += T::DbWeight::get().reads(cooldowns_expired as u64);
1806
1807 weight
1808 }
1809
1810 fn process_scheduled_upgrade_cooldowns(now: BlockNumberFor<T>) {
1814 UpgradeCooldowns::<T>::mutate(
1815 |upgrade_cooldowns: &mut Vec<(ParaId, BlockNumberFor<T>)>| {
1816 upgrade_cooldowns.retain(|(para, at)| {
1818 if at <= &now {
1819 UpgradeRestrictionSignal::<T>::remove(¶);
1820 false
1821 } else {
1822 true
1823 }
1824 });
1825 },
1826 );
1827 }
1828
1829 fn groom_ongoing_pvf_votes(
1832 cfg: &configuration::HostConfiguration<BlockNumberFor<T>>,
1833 new_n_validators: usize,
1834 ) -> Weight {
1835 let mut weight = T::DbWeight::get().reads(1);
1836
1837 let potentially_active_votes = PvfActiveVoteList::<T>::get();
1838
1839 let mut actually_active_votes = Vec::with_capacity(potentially_active_votes.len());
1844
1845 for vote_subject in potentially_active_votes {
1846 let mut vote_state = match PvfActiveVoteMap::<T>::take(&vote_subject) {
1847 Some(v) => v,
1848 None => {
1849 log::warn!(
1853 target: LOG_TARGET,
1854 "The PvfActiveVoteMap is out of sync with PvfActiveVoteList!",
1855 );
1856 debug_assert!(false);
1857 continue;
1858 },
1859 };
1860
1861 vote_state.age += 1;
1862 if vote_state.age < cfg.pvf_voting_ttl {
1863 weight += T::DbWeight::get().writes(1);
1864 vote_state.reinitialize_ballots(new_n_validators);
1865 PvfActiveVoteMap::<T>::insert(&vote_subject, vote_state);
1866
1867 actually_active_votes.push(vote_subject);
1869 } else {
1870 weight += Self::enact_pvf_rejected(&vote_subject, vote_state.causes);
1872 }
1873 }
1874
1875 weight += T::DbWeight::get().writes(1);
1876 PvfActiveVoteList::<T>::put(actually_active_votes);
1877
1878 weight
1879 }
1880
1881 fn enact_pvf_accepted(
1882 now: BlockNumberFor<T>,
1883 code_hash: &ValidationCodeHash,
1884 causes: &[PvfCheckCause<BlockNumberFor<T>>],
1885 sessions_observed: SessionIndex,
1886 cfg: &configuration::HostConfiguration<BlockNumberFor<T>>,
1887 ) -> Weight {
1888 let mut weight = Weight::zero();
1889 for cause in causes {
1890 weight += T::DbWeight::get().reads_writes(3, 2);
1891 Self::deposit_event(Event::PvfCheckAccepted(*code_hash, cause.para_id()));
1892
1893 match cause {
1894 PvfCheckCause::Onboarding(id) => {
1895 weight += Self::proceed_with_onboarding(*id, sessions_observed);
1896 },
1897 PvfCheckCause::Upgrade { id, included_at, upgrade_strategy } => {
1898 weight += Self::proceed_with_upgrade(
1899 *id,
1900 code_hash,
1901 now,
1902 *included_at,
1903 cfg,
1904 *upgrade_strategy,
1905 );
1906 },
1907 }
1908 }
1909 weight
1910 }
1911
1912 fn proceed_with_onboarding(id: ParaId, sessions_observed: SessionIndex) -> Weight {
1913 let weight = T::DbWeight::get().reads_writes(2, 1);
1914
1915 let onboard_at: SessionIndex = shared::CurrentSessionIndex::<T>::get() +
1921 cmp::max(shared::SESSION_DELAY.saturating_sub(sessions_observed), 1);
1922
1923 ActionsQueue::<T>::mutate(onboard_at, |v| {
1924 if let Err(i) = v.binary_search(&id) {
1925 v.insert(i, id);
1926 }
1927 });
1928
1929 weight
1930 }
1931
1932 fn proceed_with_upgrade(
1933 id: ParaId,
1934 code_hash: &ValidationCodeHash,
1935 now: BlockNumberFor<T>,
1936 relay_parent_number: BlockNumberFor<T>,
1937 cfg: &configuration::HostConfiguration<BlockNumberFor<T>>,
1938 upgrade_strategy: UpgradeStrategy,
1939 ) -> Weight {
1940 let mut weight = Weight::zero();
1941
1942 let expected_at = cmp::max(
1955 relay_parent_number + cfg.validation_upgrade_delay,
1956 now + cfg.minimum_validation_upgrade_delay,
1957 );
1958
1959 match upgrade_strategy {
1960 UpgradeStrategy::ApplyAtExpectedBlock => {
1961 FutureCodeUpgradesAt::<T>::mutate(|future_upgrades| {
1962 let insert_idx = future_upgrades
1963 .binary_search_by_key(&expected_at, |&(_, b)| b)
1964 .unwrap_or_else(|idx| idx);
1965 future_upgrades.insert(insert_idx, (id, expected_at));
1966 });
1967
1968 weight += T::DbWeight::get().reads_writes(0, 2);
1969 },
1970 UpgradeStrategy::SetGoAheadSignal => {
1971 FutureCodeUpgrades::<T>::insert(&id, expected_at);
1972
1973 UpcomingUpgrades::<T>::mutate(|upcoming_upgrades| {
1974 let insert_idx = upcoming_upgrades
1975 .binary_search_by_key(&expected_at, |&(_, b)| b)
1976 .unwrap_or_else(|idx| idx);
1977 upcoming_upgrades.insert(insert_idx, (id, expected_at));
1978 });
1979
1980 weight += T::DbWeight::get().reads_writes(1, 3);
1981 },
1982 }
1983
1984 let expected_at = expected_at.saturated_into();
1985 let log = ConsensusLog::ParaScheduleUpgradeCode(id, *code_hash, expected_at);
1986 frame_system::Pallet::<T>::deposit_log(log.into());
1987
1988 weight
1989 }
1990
1991 fn enact_pvf_rejected(
1992 code_hash: &ValidationCodeHash,
1993 causes: Vec<PvfCheckCause<BlockNumberFor<T>>>,
1994 ) -> Weight {
1995 let mut weight = Weight::zero();
1996
1997 for cause in causes {
1998 weight += Self::decrease_code_ref(code_hash);
2001
2002 weight += T::DbWeight::get().reads_writes(3, 2);
2003 Self::deposit_event(Event::PvfCheckRejected(*code_hash, cause.para_id()));
2004
2005 match cause {
2006 PvfCheckCause::Onboarding(id) => {
2007 weight += T::DbWeight::get().writes(3);
2013 UpcomingParasGenesis::<T>::remove(&id);
2014 CurrentCodeHash::<T>::remove(&id);
2015 ParaLifecycles::<T>::remove(&id);
2016 },
2017 PvfCheckCause::Upgrade { id, .. } => {
2018 weight += T::DbWeight::get().writes(2);
2019 UpgradeGoAheadSignal::<T>::insert(&id, UpgradeGoAhead::Abort);
2020 FutureCodeHash::<T>::remove(&id);
2021 },
2022 }
2023 }
2024
2025 weight
2026 }
2027
2028 pub fn can_schedule_para_initialize(id: &ParaId) -> bool {
2032 ParaLifecycles::<T>::get(id).is_none()
2033 }
2034
2035 pub(crate) fn schedule_para_initialize(
2046 id: ParaId,
2047 mut genesis_data: ParaGenesisArgs,
2048 ) -> DispatchResult {
2049 ensure!(Self::can_schedule_para_initialize(&id), Error::<T>::CannotOnboard);
2052 ensure!(!genesis_data.validation_code.0.is_empty(), Error::<T>::CannotOnboard);
2053 ParaLifecycles::<T>::insert(&id, ParaLifecycle::Onboarding);
2054
2055 let validation_code =
2089 mem::replace(&mut genesis_data.validation_code, ValidationCode(Vec::new()));
2090 UpcomingParasGenesis::<T>::insert(&id, genesis_data);
2091 let validation_code_hash = validation_code.hash();
2092 CurrentCodeHash::<T>::insert(&id, validation_code_hash);
2093
2094 let cfg = configuration::ActiveConfig::<T>::get();
2095 Self::kick_off_pvf_check(
2096 PvfCheckCause::Onboarding(id),
2097 validation_code_hash,
2098 validation_code,
2099 &cfg,
2100 );
2101
2102 Ok(())
2103 }
2104
2105 pub(crate) fn schedule_para_cleanup(id: ParaId) -> DispatchResult {
2115 if let Some(future_code_hash) = FutureCodeHash::<T>::get(&id) {
2127 let active_prechecking = PvfActiveVoteList::<T>::get();
2128 if active_prechecking.contains(&future_code_hash) {
2129 return Err(Error::<T>::CannotOffboard.into());
2130 }
2131 }
2132
2133 let lifecycle = ParaLifecycles::<T>::get(&id);
2134 match lifecycle {
2135 None => return Ok(()),
2137 Some(ParaLifecycle::Parathread) => {
2138 ParaLifecycles::<T>::insert(&id, ParaLifecycle::OffboardingParathread);
2139 },
2140 Some(ParaLifecycle::Parachain) => {
2141 ParaLifecycles::<T>::insert(&id, ParaLifecycle::OffboardingParachain);
2142 },
2143 _ => return Err(Error::<T>::CannotOffboard.into()),
2144 }
2145
2146 let scheduled_session = Self::scheduled_session();
2147 ActionsQueue::<T>::mutate(scheduled_session, |v| {
2148 if let Err(i) = v.binary_search(&id) {
2149 v.insert(i, id);
2150 }
2151 });
2152
2153 if <T as Config>::QueueFootprinter::message_count(UmpQueueId::Para(id)) != 0 {
2154 return Err(Error::<T>::CannotOffboard.into());
2155 }
2156
2157 Ok(())
2158 }
2159
2160 pub(crate) fn schedule_parathread_upgrade(id: ParaId) -> DispatchResult {
2164 let scheduled_session = Self::scheduled_session();
2165 let lifecycle = ParaLifecycles::<T>::get(&id).ok_or(Error::<T>::NotRegistered)?;
2166
2167 ensure!(lifecycle == ParaLifecycle::Parathread, Error::<T>::CannotUpgrade);
2168
2169 ParaLifecycles::<T>::insert(&id, ParaLifecycle::UpgradingParathread);
2170 ActionsQueue::<T>::mutate(scheduled_session, |v| {
2171 if let Err(i) = v.binary_search(&id) {
2172 v.insert(i, id);
2173 }
2174 });
2175
2176 Ok(())
2177 }
2178
2179 pub(crate) fn schedule_parachain_downgrade(id: ParaId) -> DispatchResult {
2183 let scheduled_session = Self::scheduled_session();
2184 let lifecycle = ParaLifecycles::<T>::get(&id).ok_or(Error::<T>::NotRegistered)?;
2185
2186 ensure!(lifecycle == ParaLifecycle::Parachain, Error::<T>::CannotDowngrade);
2187
2188 ParaLifecycles::<T>::insert(&id, ParaLifecycle::DowngradingParachain);
2189 ActionsQueue::<T>::mutate(scheduled_session, |v| {
2190 if let Err(i) = v.binary_search(&id) {
2191 v.insert(i, id);
2192 }
2193 });
2194
2195 Ok(())
2196 }
2197
2198 pub(crate) fn schedule_code_upgrade(
2217 id: ParaId,
2218 new_code: ValidationCode,
2219 inclusion_block_number: BlockNumberFor<T>,
2220 cfg: &configuration::HostConfiguration<BlockNumberFor<T>>,
2221 upgrade_strategy: UpgradeStrategy,
2222 ) {
2223 let new_code_len = new_code.0.len();
2225 if new_code_len < MIN_CODE_SIZE as usize || new_code_len > cfg.max_code_size as usize {
2226 log::warn!(target: LOG_TARGET, "attempted to schedule an upgrade with invalid new validation code",);
2227 return;
2228 }
2229
2230 if FutureCodeHash::<T>::contains_key(&id) {
2232 log::warn!(target: LOG_TARGET, "ended up scheduling an upgrade while one is pending",);
2241 return;
2242 }
2243
2244 let code_hash = new_code.hash();
2245
2246 if CurrentCodeHash::<T>::get(&id) == Some(code_hash) {
2251 log::warn!(
2254 target: LOG_TARGET,
2255 "para tried to upgrade to the same code. Abort the upgrade",
2256 );
2257 return;
2258 }
2259
2260 FutureCodeHash::<T>::insert(&id, &code_hash);
2262 UpgradeRestrictionSignal::<T>::insert(&id, UpgradeRestriction::Present);
2263
2264 let next_possible_upgrade_at = inclusion_block_number + cfg.validation_upgrade_cooldown;
2265 UpgradeCooldowns::<T>::mutate(|upgrade_cooldowns| {
2266 let insert_idx = upgrade_cooldowns
2267 .binary_search_by_key(&next_possible_upgrade_at, |&(_, b)| b)
2268 .unwrap_or_else(|idx| idx);
2269 upgrade_cooldowns.insert(insert_idx, (id, next_possible_upgrade_at));
2270 });
2271
2272 Self::kick_off_pvf_check(
2273 PvfCheckCause::Upgrade { id, included_at: inclusion_block_number, upgrade_strategy },
2274 code_hash,
2275 new_code,
2276 cfg,
2277 );
2278 }
2279
2280 fn kick_off_pvf_check(
2294 cause: PvfCheckCause<BlockNumberFor<T>>,
2295 code_hash: ValidationCodeHash,
2296 code: ValidationCode,
2297 cfg: &configuration::HostConfiguration<BlockNumberFor<T>>,
2298 ) -> Weight {
2299 let mut weight = Weight::zero();
2300
2301 weight += T::DbWeight::get().reads_writes(3, 2);
2302 Self::deposit_event(Event::PvfCheckStarted(code_hash, cause.para_id()));
2303
2304 weight += T::DbWeight::get().reads(1);
2305 match PvfActiveVoteMap::<T>::get(&code_hash) {
2306 None => {
2307 let known_code = CodeByHash::<T>::contains_key(&code_hash);
2310 weight += T::DbWeight::get().reads(1);
2311
2312 if known_code {
2313 weight += T::DbWeight::get().reads(1);
2316 let now = frame_system::Pallet::<T>::block_number();
2317 weight += Self::enact_pvf_accepted(now, &code_hash, &[cause], 0, cfg);
2318 } else {
2319 weight += T::DbWeight::get().reads_writes(3, 2);
2322 let now = frame_system::Pallet::<T>::block_number();
2323 let n_validators = shared::ActiveValidatorKeys::<T>::get().len();
2324 PvfActiveVoteMap::<T>::insert(
2325 &code_hash,
2326 PvfCheckActiveVoteState::new(now, n_validators, cause),
2327 );
2328 PvfActiveVoteList::<T>::mutate(|l| {
2329 if let Err(idx) = l.binary_search(&code_hash) {
2330 l.insert(idx, code_hash);
2331 }
2332 });
2333 }
2334 },
2335 Some(mut vote_state) => {
2336 weight += T::DbWeight::get().writes(1);
2339 vote_state.causes.push(cause);
2340 PvfActiveVoteMap::<T>::insert(&code_hash, vote_state);
2341 },
2342 }
2343
2344 weight += Self::increase_code_ref(&code_hash, &code);
2356
2357 weight
2358 }
2359
2360 pub(crate) fn note_new_head(
2364 id: ParaId,
2365 new_head: HeadData,
2366 execution_context: BlockNumberFor<T>,
2367 ) {
2368 Heads::<T>::insert(&id, &new_head);
2369 MostRecentContext::<T>::insert(&id, execution_context);
2370
2371 if let Some(expected_at) = FutureCodeUpgrades::<T>::get(&id) {
2372 if expected_at <= execution_context {
2373 FutureCodeUpgrades::<T>::remove(&id);
2374 UpgradeGoAheadSignal::<T>::remove(&id);
2375
2376 let new_code_hash = if let Some(new_code_hash) = FutureCodeHash::<T>::take(&id) {
2378 new_code_hash
2379 } else {
2380 log::error!(target: LOG_TARGET, "Missing future code hash for {:?}", &id);
2381 return;
2382 };
2383
2384 Self::set_current_code(id, new_code_hash, expected_at);
2385 }
2386 } else {
2387 UpgradeGoAheadSignal::<T>::remove(&id);
2392 };
2393
2394 T::OnNewHead::on_new_head(id, &new_head);
2395 }
2396
2397 pub(crate) fn set_current_code(
2402 id: ParaId,
2403 new_code_hash: ValidationCodeHash,
2404 at: BlockNumberFor<T>,
2405 ) -> Weight {
2406 let maybe_prior_code_hash = CurrentCodeHash::<T>::get(&id);
2407 CurrentCodeHash::<T>::insert(&id, &new_code_hash);
2408
2409 let log = ConsensusLog::ParaUpgradeCode(id, new_code_hash);
2410 <frame_system::Pallet<T>>::deposit_log(log.into());
2411
2412 let now = <frame_system::Pallet<T>>::block_number();
2414
2415 let weight = if let Some(prior_code_hash) = maybe_prior_code_hash {
2416 Self::note_past_code(id, at, now, prior_code_hash)
2417 } else {
2418 log::error!(target: LOG_TARGET, "Missing prior code hash for para {:?}", &id);
2419 Weight::zero()
2420 };
2421
2422 weight + T::DbWeight::get().writes(1)
2423 }
2424
2425 fn do_force_set_current_code_update(para: ParaId, new_code: ValidationCode) {
2427 let new_code_hash = new_code.hash();
2428 Self::increase_code_ref(&new_code_hash, &new_code);
2429 Self::set_current_code(para, new_code_hash, frame_system::Pallet::<T>::block_number());
2430 Self::deposit_event(Event::CurrentCodeUpdated(para));
2431 }
2432
2433 pub(crate) fn pvfs_require_precheck() -> Vec<ValidationCodeHash> {
2436 PvfActiveVoteList::<T>::get()
2437 }
2438
2439 pub(crate) fn submit_pvf_check_statement(
2446 stmt: PvfCheckStatement,
2447 signature: ValidatorSignature,
2448 ) {
2449 use frame_system::offchain::SubmitTransaction;
2450
2451 let xt = T::create_bare(Call::include_pvf_check_statement { stmt, signature }.into());
2452 if let Err(e) = SubmitTransaction::<T, Call<T>>::submit_transaction(xt) {
2453 log::error!(target: LOG_TARGET, "Error submitting pvf check statement: {:?}", e,);
2454 }
2455 }
2456
2457 pub fn lifecycle(id: ParaId) -> Option<ParaLifecycle> {
2459 ParaLifecycles::<T>::get(&id)
2460 }
2461
2462 pub fn is_valid_para(id: ParaId) -> bool {
2466 if let Some(state) = ParaLifecycles::<T>::get(&id) {
2467 !state.is_onboarding() && !state.is_offboarding()
2468 } else {
2469 false
2470 }
2471 }
2472
2473 pub fn is_offboarding(id: ParaId) -> bool {
2477 ParaLifecycles::<T>::get(&id).map_or(false, |state| state.is_offboarding())
2478 }
2479
2480 pub fn is_parachain(id: ParaId) -> bool {
2485 if let Some(state) = ParaLifecycles::<T>::get(&id) {
2486 state.is_parachain()
2487 } else {
2488 false
2489 }
2490 }
2491
2492 pub fn is_parathread(id: ParaId) -> bool {
2496 if let Some(state) = ParaLifecycles::<T>::get(&id) {
2497 state.is_parathread()
2498 } else {
2499 false
2500 }
2501 }
2502
2503 pub(crate) fn can_upgrade_validation_code(id: ParaId) -> bool {
2506 FutureCodeHash::<T>::get(&id).is_none() && UpgradeRestrictionSignal::<T>::get(&id).is_none()
2507 }
2508
2509 fn scheduled_session() -> SessionIndex {
2511 shared::Pallet::<T>::scheduled_session()
2512 }
2513
2514 fn increase_code_ref(code_hash: &ValidationCodeHash, code: &ValidationCode) -> Weight {
2518 let mut weight = T::DbWeight::get().reads_writes(1, 1);
2519 CodeByHashRefs::<T>::mutate(code_hash, |refs| {
2520 if *refs == 0 {
2521 weight += T::DbWeight::get().writes(1);
2522 CodeByHash::<T>::insert(code_hash, code);
2523 }
2524 *refs += 1;
2525 });
2526 weight
2527 }
2528
2529 fn decrease_code_ref(code_hash: &ValidationCodeHash) -> Weight {
2534 let mut weight = T::DbWeight::get().reads(1);
2535 let refs = CodeByHashRefs::<T>::get(code_hash);
2536 if refs == 0 {
2537 log::error!(target: LOG_TARGET, "Code refs is already zero for {:?}", code_hash);
2538 return weight;
2539 }
2540 if refs <= 1 {
2541 weight += T::DbWeight::get().writes(2);
2542 CodeByHash::<T>::remove(code_hash);
2543 CodeByHashRefs::<T>::remove(code_hash);
2544 } else {
2545 weight += T::DbWeight::get().writes(1);
2546 CodeByHashRefs::<T>::insert(code_hash, refs - 1);
2547 }
2548 weight
2549 }
2550
2551 #[cfg(any(feature = "std", feature = "runtime-benchmarks", test))]
2553 pub fn test_on_new_session() {
2554 Self::initializer_on_new_session(&SessionChangeNotification {
2555 session_index: shared::CurrentSessionIndex::<T>::get(),
2556 ..Default::default()
2557 });
2558 }
2559
2560 #[cfg(any(feature = "runtime-benchmarks", test))]
2561 pub fn heads_insert(para_id: &ParaId, head_data: HeadData) {
2562 Heads::<T>::insert(para_id, head_data);
2563 }
2564
2565 pub(crate) fn initialize_para_now(
2567 parachains: &mut ParachainsCache<T>,
2568 id: ParaId,
2569 genesis_data: &ParaGenesisArgs,
2570 ) {
2571 match genesis_data.para_kind {
2572 ParaKind::Parachain => {
2573 parachains.add(id);
2574 ParaLifecycles::<T>::insert(&id, ParaLifecycle::Parachain);
2575 },
2576 ParaKind::Parathread => ParaLifecycles::<T>::insert(&id, ParaLifecycle::Parathread),
2577 }
2578
2579 if !genesis_data.validation_code.0.is_empty() {
2585 let code_hash = genesis_data.validation_code.hash();
2586 Self::increase_code_ref(&code_hash, &genesis_data.validation_code);
2587 CurrentCodeHash::<T>::insert(&id, code_hash);
2588 }
2589
2590 Heads::<T>::insert(&id, &genesis_data.genesis_head);
2591 MostRecentContext::<T>::insert(&id, BlockNumberFor::<T>::from(0u32));
2592 }
2593
2594 #[cfg(test)]
2595 pub(crate) fn active_vote_state(
2596 code_hash: &ValidationCodeHash,
2597 ) -> Option<PvfCheckActiveVoteState<BlockNumberFor<T>>> {
2598 PvfActiveVoteMap::<T>::get(code_hash)
2599 }
2600
2601 pub(crate) fn validate_code_is_authorized(
2606 code: &ValidationCode,
2607 para: &ParaId,
2608 ) -> Result<AuthorizedCodeHashAndExpiry<BlockNumberFor<T>>, Error<T>> {
2609 let authorized = AuthorizedCodeHash::<T>::get(para).ok_or(Error::<T>::NothingAuthorized)?;
2610 let now = frame_system::Pallet::<T>::block_number();
2611 ensure!(authorized.expire_at > now, Error::<T>::InvalidBlockNumber);
2612 ensure!(authorized.code_hash == code.hash(), Error::<T>::Unauthorized);
2613 Ok(authorized)
2614 }
2615}
2616
2617pub(crate) struct ParachainsCache<T: Config> {
2620 parachains: Option<BTreeSet<ParaId>>,
2622 _config: PhantomData<T>,
2623}
2624
2625impl<T: Config> ParachainsCache<T> {
2626 pub fn new() -> Self {
2627 Self { parachains: None, _config: PhantomData }
2628 }
2629
2630 fn ensure_initialized(&mut self) -> &mut BTreeSet<ParaId> {
2631 self.parachains
2632 .get_or_insert_with(|| Parachains::<T>::get().into_iter().collect())
2633 }
2634
2635 pub fn add(&mut self, id: ParaId) {
2637 let parachains = self.ensure_initialized();
2638 parachains.insert(id);
2639 }
2640
2641 pub fn remove(&mut self, id: ParaId) {
2644 let parachains = self.ensure_initialized();
2645 parachains.remove(&id);
2646 }
2647}
2648
2649impl<T: Config> Drop for ParachainsCache<T> {
2650 fn drop(&mut self) {
2651 if let Some(parachains) = self.parachains.take() {
2652 Parachains::<T>::put(parachains.into_iter().collect::<Vec<ParaId>>());
2653 }
2654 }
2655}