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 T::AssignCoretime::assign_coretime(*id)
973 .expect("Assigning coretime works at genesis; qed");
974 }
975 }
977 }
978
979 #[pallet::call]
980 impl<T: Config> Pallet<T> {
981 #[pallet::call_index(0)]
983 #[pallet::weight(<T as Config>::WeightInfo::force_set_current_code(new_code.0.len() as u32))]
984 pub fn force_set_current_code(
985 origin: OriginFor<T>,
986 para: ParaId,
987 new_code: ValidationCode,
988 ) -> DispatchResult {
989 ensure_root(origin)?;
990 Self::do_force_set_current_code_update(para, new_code);
991 Ok(())
992 }
993
994 #[pallet::call_index(1)]
996 #[pallet::weight(<T as Config>::WeightInfo::force_set_current_head(new_head.0.len() as u32))]
997 pub fn force_set_current_head(
998 origin: OriginFor<T>,
999 para: ParaId,
1000 new_head: HeadData,
1001 ) -> DispatchResult {
1002 ensure_root(origin)?;
1003 Self::set_current_head(para, new_head);
1004 Ok(())
1005 }
1006
1007 #[pallet::call_index(2)]
1009 #[pallet::weight(<T as Config>::WeightInfo::force_schedule_code_upgrade(new_code.0.len() as u32))]
1010 pub fn force_schedule_code_upgrade(
1011 origin: OriginFor<T>,
1012 para: ParaId,
1013 new_code: ValidationCode,
1014 relay_parent_number: BlockNumberFor<T>,
1015 ) -> DispatchResult {
1016 ensure_root(origin)?;
1017 let config = configuration::ActiveConfig::<T>::get();
1018 Self::schedule_code_upgrade(
1019 para,
1020 new_code,
1021 relay_parent_number,
1022 &config,
1023 UpgradeStrategy::ApplyAtExpectedBlock,
1024 );
1025 Self::deposit_event(Event::CodeUpgradeScheduled(para));
1026 Ok(())
1027 }
1028
1029 #[pallet::call_index(3)]
1031 #[pallet::weight(<T as Config>::WeightInfo::force_note_new_head(new_head.0.len() as u32))]
1032 pub fn force_note_new_head(
1033 origin: OriginFor<T>,
1034 para: ParaId,
1035 new_head: HeadData,
1036 ) -> DispatchResult {
1037 ensure_root(origin)?;
1038 let now = frame_system::Pallet::<T>::block_number();
1039 Self::note_new_head(para, new_head, now);
1040 Self::deposit_event(Event::NewHeadNoted(para));
1041 Ok(())
1042 }
1043
1044 #[pallet::call_index(4)]
1048 #[pallet::weight(<T as Config>::WeightInfo::force_queue_action())]
1049 pub fn force_queue_action(origin: OriginFor<T>, para: ParaId) -> DispatchResult {
1050 ensure_root(origin)?;
1051 let next_session = shared::CurrentSessionIndex::<T>::get().saturating_add(One::one());
1052 ActionsQueue::<T>::mutate(next_session, |v| {
1053 if let Err(i) = v.binary_search(¶) {
1054 v.insert(i, para);
1055 }
1056 });
1057 Self::deposit_event(Event::ActionQueued(para, next_session));
1058 Ok(())
1059 }
1060
1061 #[pallet::call_index(5)]
1076 #[pallet::weight(<T as Config>::WeightInfo::add_trusted_validation_code(validation_code.0.len() as u32))]
1077 pub fn add_trusted_validation_code(
1078 origin: OriginFor<T>,
1079 validation_code: ValidationCode,
1080 ) -> DispatchResult {
1081 ensure_root(origin)?;
1082 let code_hash = validation_code.hash();
1083
1084 if let Some(vote) = PvfActiveVoteMap::<T>::get(&code_hash) {
1085 PvfActiveVoteMap::<T>::remove(&code_hash);
1087 PvfActiveVoteList::<T>::mutate(|l| {
1088 if let Ok(i) = l.binary_search(&code_hash) {
1089 l.remove(i);
1090 }
1091 });
1092
1093 let cfg = configuration::ActiveConfig::<T>::get();
1094 Self::enact_pvf_accepted(
1095 frame_system::Pallet::<T>::block_number(),
1096 &code_hash,
1097 &vote.causes,
1098 vote.age,
1099 &cfg,
1100 );
1101 return Ok(())
1102 }
1103
1104 if CodeByHash::<T>::contains_key(&code_hash) {
1105 return Ok(())
1107 }
1108
1109 CodeByHash::<T>::insert(code_hash, &validation_code);
1115
1116 Ok(())
1117 }
1118
1119 #[pallet::call_index(6)]
1125 #[pallet::weight(<T as Config>::WeightInfo::poke_unused_validation_code())]
1126 pub fn poke_unused_validation_code(
1127 origin: OriginFor<T>,
1128 validation_code_hash: ValidationCodeHash,
1129 ) -> DispatchResult {
1130 ensure_root(origin)?;
1131 if CodeByHashRefs::<T>::get(&validation_code_hash) == 0 {
1132 CodeByHash::<T>::remove(&validation_code_hash);
1133 }
1134 Ok(())
1135 }
1136
1137 #[pallet::call_index(7)]
1140 #[pallet::weight(
1141 <T as Config>::WeightInfo::include_pvf_check_statement_finalize_upgrade_accept()
1142 .max(<T as Config>::WeightInfo::include_pvf_check_statement_finalize_upgrade_reject())
1143 .max(<T as Config>::WeightInfo::include_pvf_check_statement_finalize_onboarding_accept()
1144 .max(<T as Config>::WeightInfo::include_pvf_check_statement_finalize_onboarding_reject())
1145 )
1146 )]
1147 pub fn include_pvf_check_statement(
1148 origin: OriginFor<T>,
1149 stmt: PvfCheckStatement,
1150 signature: ValidatorSignature,
1151 ) -> DispatchResultWithPostInfo {
1152 ensure_none(origin)?;
1153
1154 let validators = shared::ActiveValidatorKeys::<T>::get();
1155 let current_session = shared::CurrentSessionIndex::<T>::get();
1156 if stmt.session_index < current_session {
1157 return Err(Error::<T>::PvfCheckStatementStale.into())
1158 } else if stmt.session_index > current_session {
1159 return Err(Error::<T>::PvfCheckStatementFuture.into())
1160 }
1161 let validator_index = stmt.validator_index.0 as usize;
1162 let validator_public = validators
1163 .get(validator_index)
1164 .ok_or(Error::<T>::PvfCheckValidatorIndexOutOfBounds)?;
1165
1166 let signing_payload = stmt.signing_payload();
1167 ensure!(
1168 signature.verify(&signing_payload[..], &validator_public),
1169 Error::<T>::PvfCheckInvalidSignature,
1170 );
1171
1172 let mut active_vote = PvfActiveVoteMap::<T>::get(&stmt.subject)
1173 .ok_or(Error::<T>::PvfCheckSubjectInvalid)?;
1174
1175 ensure!(
1177 !active_vote
1178 .has_vote(validator_index)
1179 .ok_or(Error::<T>::PvfCheckValidatorIndexOutOfBounds)?,
1180 Error::<T>::PvfCheckDoubleVote,
1181 );
1182
1183 if stmt.accept {
1185 active_vote.votes_accept.set(validator_index, true);
1186 } else {
1187 active_vote.votes_reject.set(validator_index, true);
1188 }
1189
1190 if let Some(outcome) = active_vote.quorum(validators.len()) {
1191 PvfActiveVoteMap::<T>::remove(&stmt.subject);
1196 PvfActiveVoteList::<T>::mutate(|l| {
1197 if let Ok(i) = l.binary_search(&stmt.subject) {
1198 l.remove(i);
1199 }
1200 });
1201 match outcome {
1202 PvfCheckOutcome::Accepted => {
1203 let cfg = configuration::ActiveConfig::<T>::get();
1204 Self::enact_pvf_accepted(
1205 frame_system::Pallet::<T>::block_number(),
1206 &stmt.subject,
1207 &active_vote.causes,
1208 active_vote.age,
1209 &cfg,
1210 );
1211 },
1212 PvfCheckOutcome::Rejected => {
1213 Self::enact_pvf_rejected(&stmt.subject, active_vote.causes);
1214 },
1215 }
1216
1217 Ok(().into())
1219 } else {
1220 PvfActiveVoteMap::<T>::insert(&stmt.subject, active_vote);
1225 Ok(Some(<T as Config>::WeightInfo::include_pvf_check_statement()).into())
1226 }
1227 }
1228
1229 #[pallet::call_index(8)]
1231 #[pallet::weight(<T as Config>::WeightInfo::force_set_most_recent_context())]
1232 pub fn force_set_most_recent_context(
1233 origin: OriginFor<T>,
1234 para: ParaId,
1235 context: BlockNumberFor<T>,
1236 ) -> DispatchResult {
1237 ensure_root(origin)?;
1238 MostRecentContext::<T>::insert(¶, context);
1239 Ok(())
1240 }
1241
1242 #[pallet::call_index(9)]
1247 #[pallet::weight(<T as Config>::WeightInfo::remove_upgrade_cooldown())]
1248 pub fn remove_upgrade_cooldown(origin: OriginFor<T>, para: ParaId) -> DispatchResult {
1249 let who = ensure_signed(origin)?;
1250
1251 let removed = UpgradeCooldowns::<T>::mutate(|cooldowns| {
1252 let Some(pos) = cooldowns.iter().position(|(p, _)| p == ¶) else {
1253 return Ok::<_, DispatchError>(false)
1254 };
1255 let (_, cooldown_until) = cooldowns.remove(pos);
1256
1257 let cost = Self::calculate_remove_upgrade_cooldown_cost(cooldown_until);
1258
1259 T::Fungible::burn_from(
1261 &who,
1262 cost,
1263 Preservation::Preserve,
1264 Precision::Exact,
1265 Fortitude::Polite,
1266 )?;
1267
1268 Ok(true)
1269 })?;
1270
1271 if removed {
1272 UpgradeRestrictionSignal::<T>::remove(para);
1273
1274 Self::deposit_event(Event::UpgradeCooldownRemoved { para_id: para });
1275 }
1276
1277 Ok(())
1278 }
1279
1280 #[pallet::call_index(10)]
1292 #[pallet::weight(<T as Config>::WeightInfo::authorize_force_set_current_code_hash())]
1293 pub fn authorize_force_set_current_code_hash(
1294 origin: OriginFor<T>,
1295 para: ParaId,
1296 new_code_hash: ValidationCodeHash,
1297 valid_period: BlockNumberFor<T>,
1298 ) -> DispatchResult {
1299 T::AuthorizeCurrentCodeOrigin::ensure_origin(origin, ¶)?;
1300 ensure!(Self::is_valid_para(para), Error::<T>::NotRegistered);
1302
1303 let now = frame_system::Pallet::<T>::block_number();
1304 let expire_at = now.saturating_add(valid_period);
1305
1306 AuthorizedCodeHash::<T>::insert(
1308 ¶,
1309 AuthorizedCodeHashAndExpiry::from((new_code_hash, expire_at)),
1310 );
1311 Self::deposit_event(Event::CodeAuthorized {
1312 para_id: para,
1313 code_hash: new_code_hash,
1314 expire_at,
1315 });
1316
1317 Ok(())
1318 }
1319
1320 #[pallet::call_index(11)]
1323 #[pallet::weight(<T as Config>::WeightInfo::apply_authorized_force_set_current_code(new_code.0.len() as u32))]
1324 pub fn apply_authorized_force_set_current_code(
1325 _origin: OriginFor<T>,
1326 para: ParaId,
1327 new_code: ValidationCode,
1328 ) -> DispatchResultWithPostInfo {
1329 let _ = Self::validate_code_is_authorized(&new_code, ¶)?;
1333 AuthorizedCodeHash::<T>::remove(para);
1335
1336 Self::do_force_set_current_code_update(para, new_code);
1338
1339 Ok(Pays::No.into())
1340 }
1341 }
1342
1343 impl<T: Config> Pallet<T> {
1344 pub(crate) fn calculate_remove_upgrade_cooldown_cost(
1345 cooldown_until: BlockNumberFor<T>,
1346 ) -> BalanceOf<T> {
1347 let time_left =
1348 cooldown_until.saturating_sub(frame_system::Pallet::<T>::block_number());
1349
1350 BalanceOf::<T>::from(time_left).saturating_mul(T::CooldownRemovalMultiplier::get())
1351 }
1352 }
1353
1354 #[pallet::view_functions]
1355 impl<T: Config> Pallet<T> {
1356 pub fn remove_upgrade_cooldown_cost(para: ParaId) -> BalanceOf<T> {
1358 UpgradeCooldowns::<T>::get()
1359 .iter()
1360 .find(|(p, _)| p == ¶)
1361 .map(|(_, c)| Self::calculate_remove_upgrade_cooldown_cost(*c))
1362 .unwrap_or_default()
1363 }
1364 }
1365
1366 #[pallet::validate_unsigned]
1367 impl<T: Config> ValidateUnsigned for Pallet<T> {
1368 type Call = Call<T>;
1369
1370 fn validate_unsigned(_source: TransactionSource, call: &Self::Call) -> TransactionValidity {
1371 match call {
1372 Call::include_pvf_check_statement { stmt, signature } => {
1373 let current_session = shared::CurrentSessionIndex::<T>::get();
1374 if stmt.session_index < current_session {
1375 return InvalidTransaction::Stale.into()
1376 } else if stmt.session_index > current_session {
1377 return InvalidTransaction::Future.into()
1378 }
1379
1380 let validator_index = stmt.validator_index.0 as usize;
1381 let validators = shared::ActiveValidatorKeys::<T>::get();
1382 let validator_public = match validators.get(validator_index) {
1383 Some(pk) => pk,
1384 None =>
1385 return InvalidTransaction::Custom(INVALID_TX_BAD_VALIDATOR_IDX).into(),
1386 };
1387
1388 let signing_payload = stmt.signing_payload();
1389 if !signature.verify(&signing_payload[..], &validator_public) {
1390 return InvalidTransaction::BadProof.into();
1391 }
1392
1393 let active_vote = match PvfActiveVoteMap::<T>::get(&stmt.subject) {
1394 Some(v) => v,
1395 None => return InvalidTransaction::Custom(INVALID_TX_BAD_SUBJECT).into(),
1396 };
1397
1398 match active_vote.has_vote(validator_index) {
1399 Some(false) => (),
1400 Some(true) =>
1401 return InvalidTransaction::Custom(INVALID_TX_DOUBLE_VOTE).into(),
1402 None =>
1403 return InvalidTransaction::Custom(INVALID_TX_BAD_VALIDATOR_IDX).into(),
1404 }
1405
1406 ValidTransaction::with_tag_prefix("PvfPreCheckingVote")
1407 .priority(T::UnsignedPriority::get())
1408 .longevity(
1409 TryInto::<u64>::try_into(
1410 T::NextSessionRotation::average_session_length() / 2u32.into(),
1411 )
1412 .unwrap_or(64_u64),
1413 )
1414 .and_provides((stmt.session_index, stmt.validator_index, stmt.subject))
1415 .propagate(true)
1416 .build()
1417 },
1418 Call::apply_authorized_force_set_current_code { para, new_code } =>
1419 match Self::validate_code_is_authorized(new_code, para) {
1420 Ok(authorized_code) => {
1421 let now = frame_system::Pallet::<T>::block_number();
1422 let longevity = authorized_code.expire_at.saturating_sub(now);
1423
1424 ValidTransaction::with_tag_prefix("ApplyAuthorizedForceSetCurrentCode")
1425 .priority(T::UnsignedPriority::get())
1426 .longevity(TryInto::<u64>::try_into(longevity).unwrap_or(64_u64))
1427 .and_provides((para, authorized_code.code_hash))
1428 .propagate(true)
1429 .build()
1430 },
1431 Err(_) =>
1432 return InvalidTransaction::Custom(INVALID_TX_UNAUTHORIZED_CODE).into(),
1433 },
1434 _ => InvalidTransaction::Call.into(),
1435 }
1436 }
1437
1438 fn pre_dispatch(_call: &Self::Call) -> Result<(), TransactionValidityError> {
1439 Ok(())
1447 }
1448 }
1449}
1450
1451const INVALID_TX_BAD_VALIDATOR_IDX: u8 = 1;
1453const INVALID_TX_BAD_SUBJECT: u8 = 2;
1454const INVALID_TX_DOUBLE_VOTE: u8 = 3;
1455const INVALID_TX_UNAUTHORIZED_CODE: u8 = 4;
1456
1457pub const MAX_PARA_HEADS: usize = 1024;
1465
1466impl<T: Config> Pallet<T> {
1467 pub(crate) fn schedule_code_upgrade_external(
1472 id: ParaId,
1473 new_code: ValidationCode,
1474 upgrade_strategy: UpgradeStrategy,
1475 ) -> DispatchResult {
1476 ensure!(Self::can_upgrade_validation_code(id), Error::<T>::CannotUpgradeCode);
1478 let config = configuration::ActiveConfig::<T>::get();
1479 ensure!(new_code.0.len() >= MIN_CODE_SIZE as usize, Error::<T>::InvalidCode);
1481 ensure!(new_code.0.len() <= config.max_code_size as usize, Error::<T>::InvalidCode);
1482
1483 let current_block = frame_system::Pallet::<T>::block_number();
1484 let upgrade_block = current_block.saturating_add(config.validation_upgrade_delay);
1486 Self::schedule_code_upgrade(id, new_code, upgrade_block, &config, upgrade_strategy);
1487 Self::deposit_event(Event::CodeUpgradeScheduled(id));
1488 Ok(())
1489 }
1490
1491 pub(crate) fn set_current_head(para: ParaId, new_head: HeadData) {
1493 Heads::<T>::insert(¶, new_head);
1494 Self::deposit_event(Event::CurrentHeadUpdated(para));
1495 }
1496
1497 pub(crate) fn initializer_initialize(now: BlockNumberFor<T>) -> Weight {
1499 Self::prune_old_code(now) +
1500 Self::process_scheduled_upgrade_changes(now) +
1501 Self::process_future_code_upgrades_at(now) +
1502 Self::prune_expired_authorizations(now)
1503 }
1504
1505 pub(crate) fn initializer_finalize(now: BlockNumberFor<T>) {
1507 Self::process_scheduled_upgrade_cooldowns(now);
1508 }
1509
1510 pub(crate) fn initializer_on_new_session(
1514 notification: &SessionChangeNotification<BlockNumberFor<T>>,
1515 ) -> Vec<ParaId> {
1516 let outgoing_paras = Self::apply_actions_queue(notification.session_index);
1517 Self::groom_ongoing_pvf_votes(¬ification.new_config, notification.validators.len());
1518 outgoing_paras
1519 }
1520
1521 pub(crate) fn current_code(para_id: &ParaId) -> Option<ValidationCode> {
1523 CurrentCodeHash::<T>::get(para_id).and_then(|code_hash| {
1524 let code = CodeByHash::<T>::get(&code_hash);
1525 if code.is_none() {
1526 log::error!(
1527 "Pallet paras storage is inconsistent, code not found for hash {}",
1528 code_hash,
1529 );
1530 debug_assert!(false, "inconsistent paras storages");
1531 }
1532 code
1533 })
1534 }
1535
1536 pub fn sorted_para_heads() -> Vec<(u32, Vec<u8>)> {
1539 let mut heads: Vec<(u32, Vec<u8>)> =
1540 Heads::<T>::iter().map(|(id, head)| (id.into(), head.0)).collect();
1541 heads.sort_by_key(|(id, _)| *id);
1542 heads.truncate(MAX_PARA_HEADS);
1543 heads
1544 }
1545
1546 fn apply_actions_queue(session: SessionIndex) -> Vec<ParaId> {
1555 let actions = ActionsQueue::<T>::take(session);
1556 let mut parachains = ParachainsCache::new();
1557 let now = frame_system::Pallet::<T>::block_number();
1558 let mut outgoing = Vec::new();
1559
1560 for para in actions {
1561 let lifecycle = ParaLifecycles::<T>::get(¶);
1562 match lifecycle {
1563 None | Some(ParaLifecycle::Parathread) | Some(ParaLifecycle::Parachain) => { },
1565 Some(ParaLifecycle::Onboarding) => {
1566 if let Some(genesis_data) = UpcomingParasGenesis::<T>::take(¶) {
1567 Self::initialize_para_now(&mut parachains, para, &genesis_data);
1568 }
1569 },
1570 Some(ParaLifecycle::UpgradingParathread) => {
1572 parachains.add(para);
1573 ParaLifecycles::<T>::insert(¶, ParaLifecycle::Parachain);
1574 },
1575 Some(ParaLifecycle::DowngradingParachain) => {
1577 parachains.remove(para);
1578 ParaLifecycles::<T>::insert(¶, ParaLifecycle::Parathread);
1579 },
1580 Some(ParaLifecycle::OffboardingParachain) |
1582 Some(ParaLifecycle::OffboardingParathread) => {
1583 parachains.remove(para);
1584
1585 Heads::<T>::remove(¶);
1586 MostRecentContext::<T>::remove(¶);
1587 FutureCodeUpgrades::<T>::remove(¶);
1588 UpgradeGoAheadSignal::<T>::remove(¶);
1589 UpgradeRestrictionSignal::<T>::remove(¶);
1590 ParaLifecycles::<T>::remove(¶);
1591 AuthorizedCodeHash::<T>::remove(¶);
1592 let removed_future_code_hash = FutureCodeHash::<T>::take(¶);
1593 if let Some(removed_future_code_hash) = removed_future_code_hash {
1594 Self::decrease_code_ref(&removed_future_code_hash);
1595 }
1596
1597 let removed_code_hash = CurrentCodeHash::<T>::take(¶);
1598 if let Some(removed_code_hash) = removed_code_hash {
1599 Self::note_past_code(para, now, now, removed_code_hash);
1600 }
1601
1602 outgoing.push(para);
1603 },
1604 }
1605 }
1606
1607 if !outgoing.is_empty() {
1608 UpcomingUpgrades::<T>::mutate(|upcoming_upgrades| {
1615 upcoming_upgrades.retain(|(para, _)| !outgoing.contains(para));
1616 });
1617 UpgradeCooldowns::<T>::mutate(|upgrade_cooldowns| {
1618 upgrade_cooldowns.retain(|(para, _)| !outgoing.contains(para));
1619 });
1620 FutureCodeUpgradesAt::<T>::mutate(|future_upgrades| {
1621 future_upgrades.retain(|(para, _)| !outgoing.contains(para));
1622 });
1623 }
1624
1625 drop(parachains);
1627
1628 outgoing
1629 }
1630
1631 fn note_past_code(
1638 id: ParaId,
1639 at: BlockNumberFor<T>,
1640 now: BlockNumberFor<T>,
1641 old_code_hash: ValidationCodeHash,
1642 ) -> Weight {
1643 PastCodeMeta::<T>::mutate(&id, |past_meta| {
1644 past_meta.note_replacement(at, now);
1645 });
1646
1647 PastCodeHash::<T>::insert(&(id, at), old_code_hash);
1648
1649 PastCodePruning::<T>::mutate(|pruning| {
1652 let insert_idx =
1653 pruning.binary_search_by_key(&now, |&(_, b)| b).unwrap_or_else(|idx| idx);
1654 pruning.insert(insert_idx, (id, now));
1655 });
1656
1657 T::DbWeight::get().reads_writes(2, 3)
1658 }
1659
1660 fn prune_old_code(now: BlockNumberFor<T>) -> Weight {
1663 let config = configuration::ActiveConfig::<T>::get();
1664 let code_retention_period = config.code_retention_period;
1665 if now <= code_retention_period {
1666 let weight = T::DbWeight::get().reads_writes(1, 0);
1667 return weight
1668 }
1669
1670 let pruning_height = now - (code_retention_period + One::one());
1672
1673 let pruning_tasks_done =
1674 PastCodePruning::<T>::mutate(|pruning_tasks: &mut Vec<(_, BlockNumberFor<T>)>| {
1675 let (pruning_tasks_done, pruning_tasks_to_do) = {
1676 let up_to_idx =
1678 pruning_tasks.iter().take_while(|&(_, at)| at <= &pruning_height).count();
1679 (up_to_idx, pruning_tasks.drain(..up_to_idx))
1680 };
1681
1682 for (para_id, _) in pruning_tasks_to_do {
1683 let full_deactivate = PastCodeMeta::<T>::mutate(¶_id, |meta| {
1684 for pruned_repl_at in meta.prune_up_to(pruning_height) {
1685 let removed_code_hash =
1686 PastCodeHash::<T>::take(&(para_id, pruned_repl_at));
1687
1688 if let Some(removed_code_hash) = removed_code_hash {
1689 Self::decrease_code_ref(&removed_code_hash);
1690 } else {
1691 log::warn!(
1692 target: LOG_TARGET,
1693 "Missing code for removed hash {:?}",
1694 removed_code_hash,
1695 );
1696 }
1697 }
1698
1699 meta.is_empty() && Heads::<T>::get(¶_id).is_none()
1700 });
1701
1702 if full_deactivate {
1705 PastCodeMeta::<T>::remove(¶_id);
1706 }
1707 }
1708
1709 pruning_tasks_done as u64
1710 });
1711
1712 T::DbWeight::get().reads_writes(1 + pruning_tasks_done, 2 * pruning_tasks_done)
1715 }
1716
1717 fn prune_expired_authorizations(now: BlockNumberFor<T>) -> Weight {
1720 let mut weight = T::DbWeight::get().reads(1);
1721 let to_remove = AuthorizedCodeHash::<T>::iter().filter_map(
1722 |(para, AuthorizedCodeHashAndExpiry { expire_at, .. })| {
1723 if expire_at <= now {
1724 Some(para)
1725 } else {
1726 None
1727 }
1728 },
1729 );
1730 for para in to_remove {
1731 AuthorizedCodeHash::<T>::remove(¶);
1732 weight.saturating_accrue(T::DbWeight::get().writes(1));
1733 }
1734
1735 weight
1736 }
1737
1738 fn process_future_code_upgrades_at(now: BlockNumberFor<T>) -> Weight {
1743 let mut weight = T::DbWeight::get().reads_writes(1, 1);
1745 FutureCodeUpgradesAt::<T>::mutate(
1746 |upcoming_upgrades: &mut Vec<(ParaId, BlockNumberFor<T>)>| {
1747 let num = upcoming_upgrades.iter().take_while(|&(_, at)| at <= &now).count();
1748 for (id, expected_at) in upcoming_upgrades.drain(..num) {
1749 weight += T::DbWeight::get().reads_writes(1, 1);
1750
1751 let new_code_hash = if let Some(new_code_hash) = FutureCodeHash::<T>::take(&id)
1753 {
1754 new_code_hash
1755 } else {
1756 log::error!(target: LOG_TARGET, "Missing future code hash for {:?}", &id);
1757 continue
1758 };
1759
1760 weight += Self::set_current_code(id, new_code_hash, expected_at);
1761 }
1762 num
1763 },
1764 );
1765
1766 weight
1767 }
1768
1769 fn process_scheduled_upgrade_changes(now: BlockNumberFor<T>) -> Weight {
1775 let mut weight = T::DbWeight::get().reads_writes(1, 1);
1777 let upgrades_signaled = UpcomingUpgrades::<T>::mutate(
1778 |upcoming_upgrades: &mut Vec<(ParaId, BlockNumberFor<T>)>| {
1779 let num = upcoming_upgrades.iter().take_while(|&(_, at)| at <= &now).count();
1780 for (para, _) in upcoming_upgrades.drain(..num) {
1781 UpgradeGoAheadSignal::<T>::insert(¶, UpgradeGoAhead::GoAhead);
1782 }
1783 num
1784 },
1785 );
1786 weight += T::DbWeight::get().writes(upgrades_signaled as u64);
1787
1788 weight += T::DbWeight::get().reads(1);
1790 let cooldowns_expired =
1791 UpgradeCooldowns::<T>::get().iter().take_while(|&(_, at)| at <= &now).count();
1792
1793 weight += T::DbWeight::get().reads_writes(1, 1);
1797 weight += T::DbWeight::get().reads(cooldowns_expired as u64);
1798
1799 weight
1800 }
1801
1802 fn process_scheduled_upgrade_cooldowns(now: BlockNumberFor<T>) {
1806 UpgradeCooldowns::<T>::mutate(
1807 |upgrade_cooldowns: &mut Vec<(ParaId, BlockNumberFor<T>)>| {
1808 upgrade_cooldowns.retain(|(para, at)| {
1810 if at <= &now {
1811 UpgradeRestrictionSignal::<T>::remove(¶);
1812 false
1813 } else {
1814 true
1815 }
1816 });
1817 },
1818 );
1819 }
1820
1821 fn groom_ongoing_pvf_votes(
1824 cfg: &configuration::HostConfiguration<BlockNumberFor<T>>,
1825 new_n_validators: usize,
1826 ) -> Weight {
1827 let mut weight = T::DbWeight::get().reads(1);
1828
1829 let potentially_active_votes = PvfActiveVoteList::<T>::get();
1830
1831 let mut actually_active_votes = Vec::with_capacity(potentially_active_votes.len());
1836
1837 for vote_subject in potentially_active_votes {
1838 let mut vote_state = match PvfActiveVoteMap::<T>::take(&vote_subject) {
1839 Some(v) => v,
1840 None => {
1841 log::warn!(
1845 target: LOG_TARGET,
1846 "The PvfActiveVoteMap is out of sync with PvfActiveVoteList!",
1847 );
1848 debug_assert!(false);
1849 continue
1850 },
1851 };
1852
1853 vote_state.age += 1;
1854 if vote_state.age < cfg.pvf_voting_ttl {
1855 weight += T::DbWeight::get().writes(1);
1856 vote_state.reinitialize_ballots(new_n_validators);
1857 PvfActiveVoteMap::<T>::insert(&vote_subject, vote_state);
1858
1859 actually_active_votes.push(vote_subject);
1861 } else {
1862 weight += Self::enact_pvf_rejected(&vote_subject, vote_state.causes);
1864 }
1865 }
1866
1867 weight += T::DbWeight::get().writes(1);
1868 PvfActiveVoteList::<T>::put(actually_active_votes);
1869
1870 weight
1871 }
1872
1873 fn enact_pvf_accepted(
1874 now: BlockNumberFor<T>,
1875 code_hash: &ValidationCodeHash,
1876 causes: &[PvfCheckCause<BlockNumberFor<T>>],
1877 sessions_observed: SessionIndex,
1878 cfg: &configuration::HostConfiguration<BlockNumberFor<T>>,
1879 ) -> Weight {
1880 let mut weight = Weight::zero();
1881 for cause in causes {
1882 weight += T::DbWeight::get().reads_writes(3, 2);
1883 Self::deposit_event(Event::PvfCheckAccepted(*code_hash, cause.para_id()));
1884
1885 match cause {
1886 PvfCheckCause::Onboarding(id) => {
1887 weight += Self::proceed_with_onboarding(*id, sessions_observed);
1888 },
1889 PvfCheckCause::Upgrade { id, included_at, upgrade_strategy } => {
1890 weight += Self::proceed_with_upgrade(
1891 *id,
1892 code_hash,
1893 now,
1894 *included_at,
1895 cfg,
1896 *upgrade_strategy,
1897 );
1898 },
1899 }
1900 }
1901 weight
1902 }
1903
1904 fn proceed_with_onboarding(id: ParaId, sessions_observed: SessionIndex) -> Weight {
1905 let weight = T::DbWeight::get().reads_writes(2, 1);
1906
1907 let onboard_at: SessionIndex = shared::CurrentSessionIndex::<T>::get() +
1913 cmp::max(shared::SESSION_DELAY.saturating_sub(sessions_observed), 1);
1914
1915 ActionsQueue::<T>::mutate(onboard_at, |v| {
1916 if let Err(i) = v.binary_search(&id) {
1917 v.insert(i, id);
1918 }
1919 });
1920
1921 weight
1922 }
1923
1924 fn proceed_with_upgrade(
1925 id: ParaId,
1926 code_hash: &ValidationCodeHash,
1927 now: BlockNumberFor<T>,
1928 relay_parent_number: BlockNumberFor<T>,
1929 cfg: &configuration::HostConfiguration<BlockNumberFor<T>>,
1930 upgrade_strategy: UpgradeStrategy,
1931 ) -> Weight {
1932 let mut weight = Weight::zero();
1933
1934 let expected_at = cmp::max(
1947 relay_parent_number + cfg.validation_upgrade_delay,
1948 now + cfg.minimum_validation_upgrade_delay,
1949 );
1950
1951 match upgrade_strategy {
1952 UpgradeStrategy::ApplyAtExpectedBlock => {
1953 FutureCodeUpgradesAt::<T>::mutate(|future_upgrades| {
1954 let insert_idx = future_upgrades
1955 .binary_search_by_key(&expected_at, |&(_, b)| b)
1956 .unwrap_or_else(|idx| idx);
1957 future_upgrades.insert(insert_idx, (id, expected_at));
1958 });
1959
1960 weight += T::DbWeight::get().reads_writes(0, 2);
1961 },
1962 UpgradeStrategy::SetGoAheadSignal => {
1963 FutureCodeUpgrades::<T>::insert(&id, expected_at);
1964
1965 UpcomingUpgrades::<T>::mutate(|upcoming_upgrades| {
1966 let insert_idx = upcoming_upgrades
1967 .binary_search_by_key(&expected_at, |&(_, b)| b)
1968 .unwrap_or_else(|idx| idx);
1969 upcoming_upgrades.insert(insert_idx, (id, expected_at));
1970 });
1971
1972 weight += T::DbWeight::get().reads_writes(1, 3);
1973 },
1974 }
1975
1976 let expected_at = expected_at.saturated_into();
1977 let log = ConsensusLog::ParaScheduleUpgradeCode(id, *code_hash, expected_at);
1978 frame_system::Pallet::<T>::deposit_log(log.into());
1979
1980 weight
1981 }
1982
1983 fn enact_pvf_rejected(
1984 code_hash: &ValidationCodeHash,
1985 causes: Vec<PvfCheckCause<BlockNumberFor<T>>>,
1986 ) -> Weight {
1987 let mut weight = Weight::zero();
1988
1989 for cause in causes {
1990 weight += Self::decrease_code_ref(code_hash);
1993
1994 weight += T::DbWeight::get().reads_writes(3, 2);
1995 Self::deposit_event(Event::PvfCheckRejected(*code_hash, cause.para_id()));
1996
1997 match cause {
1998 PvfCheckCause::Onboarding(id) => {
1999 weight += T::DbWeight::get().writes(3);
2005 UpcomingParasGenesis::<T>::remove(&id);
2006 CurrentCodeHash::<T>::remove(&id);
2007 ParaLifecycles::<T>::remove(&id);
2008 },
2009 PvfCheckCause::Upgrade { id, .. } => {
2010 weight += T::DbWeight::get().writes(2);
2011 UpgradeGoAheadSignal::<T>::insert(&id, UpgradeGoAhead::Abort);
2012 FutureCodeHash::<T>::remove(&id);
2013 },
2014 }
2015 }
2016
2017 weight
2018 }
2019
2020 pub fn can_schedule_para_initialize(id: &ParaId) -> bool {
2024 ParaLifecycles::<T>::get(id).is_none()
2025 }
2026
2027 pub(crate) fn schedule_para_initialize(
2038 id: ParaId,
2039 mut genesis_data: ParaGenesisArgs,
2040 ) -> DispatchResult {
2041 ensure!(Self::can_schedule_para_initialize(&id), Error::<T>::CannotOnboard);
2044 ensure!(!genesis_data.validation_code.0.is_empty(), Error::<T>::CannotOnboard);
2045 ParaLifecycles::<T>::insert(&id, ParaLifecycle::Onboarding);
2046
2047 let validation_code =
2081 mem::replace(&mut genesis_data.validation_code, ValidationCode(Vec::new()));
2082 UpcomingParasGenesis::<T>::insert(&id, genesis_data);
2083 let validation_code_hash = validation_code.hash();
2084 CurrentCodeHash::<T>::insert(&id, validation_code_hash);
2085
2086 let cfg = configuration::ActiveConfig::<T>::get();
2087 Self::kick_off_pvf_check(
2088 PvfCheckCause::Onboarding(id),
2089 validation_code_hash,
2090 validation_code,
2091 &cfg,
2092 );
2093
2094 Ok(())
2095 }
2096
2097 pub(crate) fn schedule_para_cleanup(id: ParaId) -> DispatchResult {
2107 if let Some(future_code_hash) = FutureCodeHash::<T>::get(&id) {
2119 let active_prechecking = PvfActiveVoteList::<T>::get();
2120 if active_prechecking.contains(&future_code_hash) {
2121 return Err(Error::<T>::CannotOffboard.into())
2122 }
2123 }
2124
2125 let lifecycle = ParaLifecycles::<T>::get(&id);
2126 match lifecycle {
2127 None => return Ok(()),
2129 Some(ParaLifecycle::Parathread) => {
2130 ParaLifecycles::<T>::insert(&id, ParaLifecycle::OffboardingParathread);
2131 },
2132 Some(ParaLifecycle::Parachain) => {
2133 ParaLifecycles::<T>::insert(&id, ParaLifecycle::OffboardingParachain);
2134 },
2135 _ => return Err(Error::<T>::CannotOffboard.into()),
2136 }
2137
2138 let scheduled_session = Self::scheduled_session();
2139 ActionsQueue::<T>::mutate(scheduled_session, |v| {
2140 if let Err(i) = v.binary_search(&id) {
2141 v.insert(i, id);
2142 }
2143 });
2144
2145 if <T as Config>::QueueFootprinter::message_count(UmpQueueId::Para(id)) != 0 {
2146 return Err(Error::<T>::CannotOffboard.into())
2147 }
2148
2149 Ok(())
2150 }
2151
2152 pub(crate) fn schedule_parathread_upgrade(id: ParaId) -> DispatchResult {
2156 let scheduled_session = Self::scheduled_session();
2157 let lifecycle = ParaLifecycles::<T>::get(&id).ok_or(Error::<T>::NotRegistered)?;
2158
2159 ensure!(lifecycle == ParaLifecycle::Parathread, Error::<T>::CannotUpgrade);
2160
2161 ParaLifecycles::<T>::insert(&id, ParaLifecycle::UpgradingParathread);
2162 ActionsQueue::<T>::mutate(scheduled_session, |v| {
2163 if let Err(i) = v.binary_search(&id) {
2164 v.insert(i, id);
2165 }
2166 });
2167
2168 Ok(())
2169 }
2170
2171 pub(crate) fn schedule_parachain_downgrade(id: ParaId) -> DispatchResult {
2175 let scheduled_session = Self::scheduled_session();
2176 let lifecycle = ParaLifecycles::<T>::get(&id).ok_or(Error::<T>::NotRegistered)?;
2177
2178 ensure!(lifecycle == ParaLifecycle::Parachain, Error::<T>::CannotDowngrade);
2179
2180 ParaLifecycles::<T>::insert(&id, ParaLifecycle::DowngradingParachain);
2181 ActionsQueue::<T>::mutate(scheduled_session, |v| {
2182 if let Err(i) = v.binary_search(&id) {
2183 v.insert(i, id);
2184 }
2185 });
2186
2187 Ok(())
2188 }
2189
2190 pub(crate) fn schedule_code_upgrade(
2209 id: ParaId,
2210 new_code: ValidationCode,
2211 inclusion_block_number: BlockNumberFor<T>,
2212 cfg: &configuration::HostConfiguration<BlockNumberFor<T>>,
2213 upgrade_strategy: UpgradeStrategy,
2214 ) {
2215 let new_code_len = new_code.0.len();
2217 if new_code_len < MIN_CODE_SIZE as usize || new_code_len > cfg.max_code_size as usize {
2218 log::warn!(target: LOG_TARGET, "attempted to schedule an upgrade with invalid new validation code",);
2219 return
2220 }
2221
2222 if FutureCodeHash::<T>::contains_key(&id) {
2224 log::warn!(target: LOG_TARGET, "ended up scheduling an upgrade while one is pending",);
2233 return
2234 }
2235
2236 let code_hash = new_code.hash();
2237
2238 if CurrentCodeHash::<T>::get(&id) == Some(code_hash) {
2243 log::warn!(
2246 target: LOG_TARGET,
2247 "para tried to upgrade to the same code. Abort the upgrade",
2248 );
2249 return
2250 }
2251
2252 FutureCodeHash::<T>::insert(&id, &code_hash);
2254 UpgradeRestrictionSignal::<T>::insert(&id, UpgradeRestriction::Present);
2255
2256 let next_possible_upgrade_at = inclusion_block_number + cfg.validation_upgrade_cooldown;
2257 UpgradeCooldowns::<T>::mutate(|upgrade_cooldowns| {
2258 let insert_idx = upgrade_cooldowns
2259 .binary_search_by_key(&next_possible_upgrade_at, |&(_, b)| b)
2260 .unwrap_or_else(|idx| idx);
2261 upgrade_cooldowns.insert(insert_idx, (id, next_possible_upgrade_at));
2262 });
2263
2264 Self::kick_off_pvf_check(
2265 PvfCheckCause::Upgrade { id, included_at: inclusion_block_number, upgrade_strategy },
2266 code_hash,
2267 new_code,
2268 cfg,
2269 );
2270 }
2271
2272 fn kick_off_pvf_check(
2286 cause: PvfCheckCause<BlockNumberFor<T>>,
2287 code_hash: ValidationCodeHash,
2288 code: ValidationCode,
2289 cfg: &configuration::HostConfiguration<BlockNumberFor<T>>,
2290 ) -> Weight {
2291 let mut weight = Weight::zero();
2292
2293 weight += T::DbWeight::get().reads_writes(3, 2);
2294 Self::deposit_event(Event::PvfCheckStarted(code_hash, cause.para_id()));
2295
2296 weight += T::DbWeight::get().reads(1);
2297 match PvfActiveVoteMap::<T>::get(&code_hash) {
2298 None => {
2299 let known_code = CodeByHash::<T>::contains_key(&code_hash);
2302 weight += T::DbWeight::get().reads(1);
2303
2304 if known_code {
2305 weight += T::DbWeight::get().reads(1);
2308 let now = frame_system::Pallet::<T>::block_number();
2309 weight += Self::enact_pvf_accepted(now, &code_hash, &[cause], 0, cfg);
2310 } else {
2311 weight += T::DbWeight::get().reads_writes(3, 2);
2314 let now = frame_system::Pallet::<T>::block_number();
2315 let n_validators = shared::ActiveValidatorKeys::<T>::get().len();
2316 PvfActiveVoteMap::<T>::insert(
2317 &code_hash,
2318 PvfCheckActiveVoteState::new(now, n_validators, cause),
2319 );
2320 PvfActiveVoteList::<T>::mutate(|l| {
2321 if let Err(idx) = l.binary_search(&code_hash) {
2322 l.insert(idx, code_hash);
2323 }
2324 });
2325 }
2326 },
2327 Some(mut vote_state) => {
2328 weight += T::DbWeight::get().writes(1);
2331 vote_state.causes.push(cause);
2332 PvfActiveVoteMap::<T>::insert(&code_hash, vote_state);
2333 },
2334 }
2335
2336 weight += Self::increase_code_ref(&code_hash, &code);
2348
2349 weight
2350 }
2351
2352 pub(crate) fn note_new_head(
2356 id: ParaId,
2357 new_head: HeadData,
2358 execution_context: BlockNumberFor<T>,
2359 ) {
2360 Heads::<T>::insert(&id, &new_head);
2361 MostRecentContext::<T>::insert(&id, execution_context);
2362
2363 if let Some(expected_at) = FutureCodeUpgrades::<T>::get(&id) {
2364 if expected_at <= execution_context {
2365 FutureCodeUpgrades::<T>::remove(&id);
2366 UpgradeGoAheadSignal::<T>::remove(&id);
2367
2368 let new_code_hash = if let Some(new_code_hash) = FutureCodeHash::<T>::take(&id) {
2370 new_code_hash
2371 } else {
2372 log::error!(target: LOG_TARGET, "Missing future code hash for {:?}", &id);
2373 return
2374 };
2375
2376 Self::set_current_code(id, new_code_hash, expected_at);
2377 }
2378 } else {
2379 UpgradeGoAheadSignal::<T>::remove(&id);
2384 };
2385
2386 T::OnNewHead::on_new_head(id, &new_head);
2387 }
2388
2389 pub(crate) fn set_current_code(
2394 id: ParaId,
2395 new_code_hash: ValidationCodeHash,
2396 at: BlockNumberFor<T>,
2397 ) -> Weight {
2398 let maybe_prior_code_hash = CurrentCodeHash::<T>::get(&id);
2399 CurrentCodeHash::<T>::insert(&id, &new_code_hash);
2400
2401 let log = ConsensusLog::ParaUpgradeCode(id, new_code_hash);
2402 <frame_system::Pallet<T>>::deposit_log(log.into());
2403
2404 let now = <frame_system::Pallet<T>>::block_number();
2406
2407 let weight = if let Some(prior_code_hash) = maybe_prior_code_hash {
2408 Self::note_past_code(id, at, now, prior_code_hash)
2409 } else {
2410 log::error!(target: LOG_TARGET, "Missing prior code hash for para {:?}", &id);
2411 Weight::zero()
2412 };
2413
2414 weight + T::DbWeight::get().writes(1)
2415 }
2416
2417 fn do_force_set_current_code_update(para: ParaId, new_code: ValidationCode) {
2419 let new_code_hash = new_code.hash();
2420 Self::increase_code_ref(&new_code_hash, &new_code);
2421 Self::set_current_code(para, new_code_hash, frame_system::Pallet::<T>::block_number());
2422 Self::deposit_event(Event::CurrentCodeUpdated(para));
2423 }
2424
2425 pub(crate) fn pvfs_require_precheck() -> Vec<ValidationCodeHash> {
2428 PvfActiveVoteList::<T>::get()
2429 }
2430
2431 pub(crate) fn submit_pvf_check_statement(
2438 stmt: PvfCheckStatement,
2439 signature: ValidatorSignature,
2440 ) {
2441 use frame_system::offchain::SubmitTransaction;
2442
2443 let xt = T::create_bare(Call::include_pvf_check_statement { stmt, signature }.into());
2444 if let Err(e) = SubmitTransaction::<T, Call<T>>::submit_transaction(xt) {
2445 log::error!(target: LOG_TARGET, "Error submitting pvf check statement: {:?}", e,);
2446 }
2447 }
2448
2449 pub fn lifecycle(id: ParaId) -> Option<ParaLifecycle> {
2451 ParaLifecycles::<T>::get(&id)
2452 }
2453
2454 pub fn is_valid_para(id: ParaId) -> bool {
2458 if let Some(state) = ParaLifecycles::<T>::get(&id) {
2459 !state.is_onboarding() && !state.is_offboarding()
2460 } else {
2461 false
2462 }
2463 }
2464
2465 pub fn is_offboarding(id: ParaId) -> bool {
2469 ParaLifecycles::<T>::get(&id).map_or(false, |state| state.is_offboarding())
2470 }
2471
2472 pub fn is_parachain(id: ParaId) -> bool {
2477 if let Some(state) = ParaLifecycles::<T>::get(&id) {
2478 state.is_parachain()
2479 } else {
2480 false
2481 }
2482 }
2483
2484 pub fn is_parathread(id: ParaId) -> bool {
2488 if let Some(state) = ParaLifecycles::<T>::get(&id) {
2489 state.is_parathread()
2490 } else {
2491 false
2492 }
2493 }
2494
2495 pub(crate) fn can_upgrade_validation_code(id: ParaId) -> bool {
2498 FutureCodeHash::<T>::get(&id).is_none() && UpgradeRestrictionSignal::<T>::get(&id).is_none()
2499 }
2500
2501 fn scheduled_session() -> SessionIndex {
2503 shared::Pallet::<T>::scheduled_session()
2504 }
2505
2506 fn increase_code_ref(code_hash: &ValidationCodeHash, code: &ValidationCode) -> Weight {
2510 let mut weight = T::DbWeight::get().reads_writes(1, 1);
2511 CodeByHashRefs::<T>::mutate(code_hash, |refs| {
2512 if *refs == 0 {
2513 weight += T::DbWeight::get().writes(1);
2514 CodeByHash::<T>::insert(code_hash, code);
2515 }
2516 *refs += 1;
2517 });
2518 weight
2519 }
2520
2521 fn decrease_code_ref(code_hash: &ValidationCodeHash) -> Weight {
2526 let mut weight = T::DbWeight::get().reads(1);
2527 let refs = CodeByHashRefs::<T>::get(code_hash);
2528 if refs == 0 {
2529 log::error!(target: LOG_TARGET, "Code refs is already zero for {:?}", code_hash);
2530 return weight
2531 }
2532 if refs <= 1 {
2533 weight += T::DbWeight::get().writes(2);
2534 CodeByHash::<T>::remove(code_hash);
2535 CodeByHashRefs::<T>::remove(code_hash);
2536 } else {
2537 weight += T::DbWeight::get().writes(1);
2538 CodeByHashRefs::<T>::insert(code_hash, refs - 1);
2539 }
2540 weight
2541 }
2542
2543 #[cfg(any(feature = "std", feature = "runtime-benchmarks", test))]
2545 pub fn test_on_new_session() {
2546 Self::initializer_on_new_session(&SessionChangeNotification {
2547 session_index: shared::CurrentSessionIndex::<T>::get(),
2548 ..Default::default()
2549 });
2550 }
2551
2552 #[cfg(any(feature = "runtime-benchmarks", test))]
2553 pub fn heads_insert(para_id: &ParaId, head_data: HeadData) {
2554 Heads::<T>::insert(para_id, head_data);
2555 }
2556
2557 pub(crate) fn initialize_para_now(
2559 parachains: &mut ParachainsCache<T>,
2560 id: ParaId,
2561 genesis_data: &ParaGenesisArgs,
2562 ) {
2563 match genesis_data.para_kind {
2564 ParaKind::Parachain => {
2565 parachains.add(id);
2566 ParaLifecycles::<T>::insert(&id, ParaLifecycle::Parachain);
2567 },
2568 ParaKind::Parathread => ParaLifecycles::<T>::insert(&id, ParaLifecycle::Parathread),
2569 }
2570
2571 if !genesis_data.validation_code.0.is_empty() {
2577 let code_hash = genesis_data.validation_code.hash();
2578 Self::increase_code_ref(&code_hash, &genesis_data.validation_code);
2579 CurrentCodeHash::<T>::insert(&id, code_hash);
2580 }
2581
2582 Heads::<T>::insert(&id, &genesis_data.genesis_head);
2583 MostRecentContext::<T>::insert(&id, BlockNumberFor::<T>::from(0u32));
2584 }
2585
2586 #[cfg(test)]
2587 pub(crate) fn active_vote_state(
2588 code_hash: &ValidationCodeHash,
2589 ) -> Option<PvfCheckActiveVoteState<BlockNumberFor<T>>> {
2590 PvfActiveVoteMap::<T>::get(code_hash)
2591 }
2592
2593 pub(crate) fn validate_code_is_authorized(
2598 code: &ValidationCode,
2599 para: &ParaId,
2600 ) -> Result<AuthorizedCodeHashAndExpiry<BlockNumberFor<T>>, Error<T>> {
2601 let authorized = AuthorizedCodeHash::<T>::get(para).ok_or(Error::<T>::NothingAuthorized)?;
2602 let now = frame_system::Pallet::<T>::block_number();
2603 ensure!(authorized.expire_at > now, Error::<T>::InvalidBlockNumber);
2604 ensure!(authorized.code_hash == code.hash(), Error::<T>::Unauthorized);
2605 Ok(authorized)
2606 }
2607}
2608
2609pub(crate) struct ParachainsCache<T: Config> {
2612 parachains: Option<BTreeSet<ParaId>>,
2614 _config: PhantomData<T>,
2615}
2616
2617impl<T: Config> ParachainsCache<T> {
2618 pub fn new() -> Self {
2619 Self { parachains: None, _config: PhantomData }
2620 }
2621
2622 fn ensure_initialized(&mut self) -> &mut BTreeSet<ParaId> {
2623 self.parachains
2624 .get_or_insert_with(|| Parachains::<T>::get().into_iter().collect())
2625 }
2626
2627 pub fn add(&mut self, id: ParaId) {
2629 let parachains = self.ensure_initialized();
2630 parachains.insert(id);
2631 }
2632
2633 pub fn remove(&mut self, id: ParaId) {
2636 let parachains = self.ensure_initialized();
2637 parachains.remove(&id);
2638 }
2639}
2640
2641impl<T: Config> Drop for ParachainsCache<T> {
2642 fn drop(&mut self) {
2643 if let Some(parachains) = self.parachains.take() {
2644 Parachains::<T>::put(parachains.into_iter().collect::<Vec<ParaId>>());
2645 }
2646 }
2647}