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
1474pub const MIN_PARA_HEAD_LEN: usize = 64;
1478
1479impl<T: Config> Pallet<T> {
1480 pub(crate) fn schedule_code_upgrade_external(
1485 id: ParaId,
1486 new_code: ValidationCode,
1487 upgrade_strategy: UpgradeStrategy,
1488 ) -> DispatchResult {
1489 ensure!(Self::can_upgrade_validation_code(id), Error::<T>::CannotUpgradeCode);
1491 let config = configuration::ActiveConfig::<T>::get();
1492 ensure!(new_code.0.len() >= MIN_CODE_SIZE as usize, Error::<T>::InvalidCode);
1494 ensure!(new_code.0.len() <= config.max_code_size as usize, Error::<T>::InvalidCode);
1495
1496 let current_block = frame_system::Pallet::<T>::block_number();
1497 let upgrade_block = current_block.saturating_add(config.validation_upgrade_delay);
1499 Self::schedule_code_upgrade(id, new_code, upgrade_block, &config, upgrade_strategy);
1500 Self::deposit_event(Event::CodeUpgradeScheduled(id));
1501 Ok(())
1502 }
1503
1504 pub(crate) fn set_current_head(para: ParaId, new_head: HeadData) {
1506 Heads::<T>::insert(¶, new_head);
1507 Self::deposit_event(Event::CurrentHeadUpdated(para));
1508 }
1509
1510 pub(crate) fn initializer_initialize(now: BlockNumberFor<T>) -> Weight {
1512 Self::prune_old_code(now) +
1513 Self::process_scheduled_upgrade_changes(now) +
1514 Self::process_future_code_upgrades_at(now) +
1515 Self::prune_expired_authorizations(now)
1516 }
1517
1518 pub(crate) fn initializer_finalize(now: BlockNumberFor<T>) {
1520 Self::process_scheduled_upgrade_cooldowns(now);
1521 }
1522
1523 pub(crate) fn initializer_on_new_session(
1527 notification: &SessionChangeNotification<BlockNumberFor<T>>,
1528 ) -> Vec<ParaId> {
1529 let outgoing_paras = Self::apply_actions_queue(notification.session_index);
1530 Self::groom_ongoing_pvf_votes(¬ification.new_config, notification.validators.len());
1531 outgoing_paras
1532 }
1533
1534 pub(crate) fn current_code(para_id: &ParaId) -> Option<ValidationCode> {
1536 CurrentCodeHash::<T>::get(para_id).and_then(|code_hash| {
1537 let code = CodeByHash::<T>::get(&code_hash);
1538 if code.is_none() {
1539 log::error!(
1540 "Pallet paras storage is inconsistent, code not found for hash {}",
1541 code_hash,
1542 );
1543 debug_assert!(false, "inconsistent paras storages");
1544 }
1545 code
1546 })
1547 }
1548
1549 pub fn sorted_para_heads() -> Vec<(u32, Vec<u8>)> {
1552 let mut heads: Vec<(u32, Vec<u8>)> = Heads::<T>::iter()
1553 .map(|(id, head)| (id.into(), head.0))
1554 .filter(|(_id, head_data)| head_data.len() >= MIN_PARA_HEAD_LEN)
1556 .collect();
1557 heads.sort_by_key(|(id, _)| *id);
1558 heads.truncate(MAX_PARA_HEADS);
1559 heads
1560 }
1561
1562 fn apply_actions_queue(session: SessionIndex) -> Vec<ParaId> {
1571 let actions = ActionsQueue::<T>::take(session);
1572 let mut parachains = ParachainsCache::new();
1573 let now = frame_system::Pallet::<T>::block_number();
1574 let mut outgoing = Vec::new();
1575
1576 for para in actions {
1577 let lifecycle = ParaLifecycles::<T>::get(¶);
1578 match lifecycle {
1579 None | Some(ParaLifecycle::Parathread) | Some(ParaLifecycle::Parachain) => { },
1581 Some(ParaLifecycle::Onboarding) => {
1582 if let Some(genesis_data) = UpcomingParasGenesis::<T>::take(¶) {
1583 Self::initialize_para_now(&mut parachains, para, &genesis_data);
1584 }
1585 },
1586 Some(ParaLifecycle::UpgradingParathread) => {
1588 parachains.add(para);
1589 ParaLifecycles::<T>::insert(¶, ParaLifecycle::Parachain);
1590 },
1591 Some(ParaLifecycle::DowngradingParachain) => {
1593 parachains.remove(para);
1594 ParaLifecycles::<T>::insert(¶, ParaLifecycle::Parathread);
1595 },
1596 Some(ParaLifecycle::OffboardingParachain) |
1598 Some(ParaLifecycle::OffboardingParathread) => {
1599 parachains.remove(para);
1600
1601 Heads::<T>::remove(¶);
1602 MostRecentContext::<T>::remove(¶);
1603 FutureCodeUpgrades::<T>::remove(¶);
1604 UpgradeGoAheadSignal::<T>::remove(¶);
1605 UpgradeRestrictionSignal::<T>::remove(¶);
1606 ParaLifecycles::<T>::remove(¶);
1607 AuthorizedCodeHash::<T>::remove(¶);
1608 let removed_future_code_hash = FutureCodeHash::<T>::take(¶);
1609 if let Some(removed_future_code_hash) = removed_future_code_hash {
1610 Self::decrease_code_ref(&removed_future_code_hash);
1611 }
1612
1613 let removed_code_hash = CurrentCodeHash::<T>::take(¶);
1614 if let Some(removed_code_hash) = removed_code_hash {
1615 Self::note_past_code(para, now, now, removed_code_hash);
1616 }
1617
1618 outgoing.push(para);
1619 },
1620 }
1621 }
1622
1623 if !outgoing.is_empty() {
1624 UpcomingUpgrades::<T>::mutate(|upcoming_upgrades| {
1631 upcoming_upgrades.retain(|(para, _)| !outgoing.contains(para));
1632 });
1633 UpgradeCooldowns::<T>::mutate(|upgrade_cooldowns| {
1634 upgrade_cooldowns.retain(|(para, _)| !outgoing.contains(para));
1635 });
1636 FutureCodeUpgradesAt::<T>::mutate(|future_upgrades| {
1637 future_upgrades.retain(|(para, _)| !outgoing.contains(para));
1638 });
1639 }
1640
1641 drop(parachains);
1643
1644 outgoing
1645 }
1646
1647 fn note_past_code(
1654 id: ParaId,
1655 at: BlockNumberFor<T>,
1656 now: BlockNumberFor<T>,
1657 old_code_hash: ValidationCodeHash,
1658 ) -> Weight {
1659 PastCodeMeta::<T>::mutate(&id, |past_meta| {
1660 past_meta.note_replacement(at, now);
1661 });
1662
1663 PastCodeHash::<T>::insert(&(id, at), old_code_hash);
1664
1665 PastCodePruning::<T>::mutate(|pruning| {
1668 let insert_idx =
1669 pruning.binary_search_by_key(&now, |&(_, b)| b).unwrap_or_else(|idx| idx);
1670 pruning.insert(insert_idx, (id, now));
1671 });
1672
1673 T::DbWeight::get().reads_writes(2, 3)
1674 }
1675
1676 fn prune_old_code(now: BlockNumberFor<T>) -> Weight {
1679 let config = configuration::ActiveConfig::<T>::get();
1680 let code_retention_period = config.code_retention_period;
1681 if now <= code_retention_period {
1682 let weight = T::DbWeight::get().reads_writes(1, 0);
1683 return weight;
1684 }
1685
1686 let pruning_height = now - (code_retention_period + One::one());
1688
1689 let pruning_tasks_done =
1690 PastCodePruning::<T>::mutate(|pruning_tasks: &mut Vec<(_, BlockNumberFor<T>)>| {
1691 let (pruning_tasks_done, pruning_tasks_to_do) = {
1692 let up_to_idx =
1694 pruning_tasks.iter().take_while(|&(_, at)| at <= &pruning_height).count();
1695 (up_to_idx, pruning_tasks.drain(..up_to_idx))
1696 };
1697
1698 for (para_id, _) in pruning_tasks_to_do {
1699 let full_deactivate = PastCodeMeta::<T>::mutate(¶_id, |meta| {
1700 for pruned_repl_at in meta.prune_up_to(pruning_height) {
1701 let removed_code_hash =
1702 PastCodeHash::<T>::take(&(para_id, pruned_repl_at));
1703
1704 if let Some(removed_code_hash) = removed_code_hash {
1705 Self::decrease_code_ref(&removed_code_hash);
1706 } else {
1707 log::warn!(
1708 target: LOG_TARGET,
1709 "Missing code for removed hash {:?}",
1710 removed_code_hash,
1711 );
1712 }
1713 }
1714
1715 meta.is_empty() && Heads::<T>::get(¶_id).is_none()
1716 });
1717
1718 if full_deactivate {
1721 PastCodeMeta::<T>::remove(¶_id);
1722 }
1723 }
1724
1725 pruning_tasks_done as u64
1726 });
1727
1728 T::DbWeight::get().reads_writes(1 + pruning_tasks_done, 2 * pruning_tasks_done)
1731 }
1732
1733 fn prune_expired_authorizations(now: BlockNumberFor<T>) -> Weight {
1736 let mut weight = T::DbWeight::get().reads(1);
1737 let to_remove = AuthorizedCodeHash::<T>::iter().filter_map(
1738 |(para, AuthorizedCodeHashAndExpiry { expire_at, .. })| {
1739 if expire_at <= now {
1740 Some(para)
1741 } else {
1742 None
1743 }
1744 },
1745 );
1746 for para in to_remove {
1747 AuthorizedCodeHash::<T>::remove(¶);
1748 weight.saturating_accrue(T::DbWeight::get().writes(1));
1749 }
1750
1751 weight
1752 }
1753
1754 fn process_future_code_upgrades_at(now: BlockNumberFor<T>) -> Weight {
1759 let mut weight = T::DbWeight::get().reads_writes(1, 1);
1761 FutureCodeUpgradesAt::<T>::mutate(
1762 |upcoming_upgrades: &mut Vec<(ParaId, BlockNumberFor<T>)>| {
1763 let num = upcoming_upgrades.iter().take_while(|&(_, at)| at <= &now).count();
1764 for (id, expected_at) in upcoming_upgrades.drain(..num) {
1765 weight += T::DbWeight::get().reads_writes(1, 1);
1766
1767 let new_code_hash = if let Some(new_code_hash) = FutureCodeHash::<T>::take(&id)
1769 {
1770 new_code_hash
1771 } else {
1772 log::error!(target: LOG_TARGET, "Missing future code hash for {:?}", &id);
1773 continue;
1774 };
1775
1776 weight += Self::set_current_code(id, new_code_hash, expected_at);
1777 }
1778 num
1779 },
1780 );
1781
1782 weight
1783 }
1784
1785 fn process_scheduled_upgrade_changes(now: BlockNumberFor<T>) -> Weight {
1791 let mut weight = T::DbWeight::get().reads_writes(1, 1);
1793 let upgrades_signaled = UpcomingUpgrades::<T>::mutate(
1794 |upcoming_upgrades: &mut Vec<(ParaId, BlockNumberFor<T>)>| {
1795 let num = upcoming_upgrades.iter().take_while(|&(_, at)| at <= &now).count();
1796 for (para, _) in upcoming_upgrades.drain(..num) {
1797 UpgradeGoAheadSignal::<T>::insert(¶, UpgradeGoAhead::GoAhead);
1798 }
1799 num
1800 },
1801 );
1802 weight += T::DbWeight::get().writes(upgrades_signaled as u64);
1803
1804 weight += T::DbWeight::get().reads(1);
1806 let cooldowns_expired =
1807 UpgradeCooldowns::<T>::get().iter().take_while(|&(_, at)| at <= &now).count();
1808
1809 weight += T::DbWeight::get().reads_writes(1, 1);
1813 weight += T::DbWeight::get().reads(cooldowns_expired as u64);
1814
1815 weight
1816 }
1817
1818 fn process_scheduled_upgrade_cooldowns(now: BlockNumberFor<T>) {
1822 UpgradeCooldowns::<T>::mutate(
1823 |upgrade_cooldowns: &mut Vec<(ParaId, BlockNumberFor<T>)>| {
1824 upgrade_cooldowns.retain(|(para, at)| {
1826 if at <= &now {
1827 UpgradeRestrictionSignal::<T>::remove(¶);
1828 false
1829 } else {
1830 true
1831 }
1832 });
1833 },
1834 );
1835 }
1836
1837 fn groom_ongoing_pvf_votes(
1840 cfg: &configuration::HostConfiguration<BlockNumberFor<T>>,
1841 new_n_validators: usize,
1842 ) -> Weight {
1843 let mut weight = T::DbWeight::get().reads(1);
1844
1845 let potentially_active_votes = PvfActiveVoteList::<T>::get();
1846
1847 let mut actually_active_votes = Vec::with_capacity(potentially_active_votes.len());
1852
1853 for vote_subject in potentially_active_votes {
1854 let mut vote_state = match PvfActiveVoteMap::<T>::take(&vote_subject) {
1855 Some(v) => v,
1856 None => {
1857 log::warn!(
1861 target: LOG_TARGET,
1862 "The PvfActiveVoteMap is out of sync with PvfActiveVoteList!",
1863 );
1864 debug_assert!(false);
1865 continue;
1866 },
1867 };
1868
1869 vote_state.age += 1;
1870 if vote_state.age < cfg.pvf_voting_ttl {
1871 weight += T::DbWeight::get().writes(1);
1872 vote_state.reinitialize_ballots(new_n_validators);
1873 PvfActiveVoteMap::<T>::insert(&vote_subject, vote_state);
1874
1875 actually_active_votes.push(vote_subject);
1877 } else {
1878 weight += Self::enact_pvf_rejected(&vote_subject, vote_state.causes);
1880 }
1881 }
1882
1883 weight += T::DbWeight::get().writes(1);
1884 PvfActiveVoteList::<T>::put(actually_active_votes);
1885
1886 weight
1887 }
1888
1889 fn enact_pvf_accepted(
1890 now: BlockNumberFor<T>,
1891 code_hash: &ValidationCodeHash,
1892 causes: &[PvfCheckCause<BlockNumberFor<T>>],
1893 sessions_observed: SessionIndex,
1894 cfg: &configuration::HostConfiguration<BlockNumberFor<T>>,
1895 ) -> Weight {
1896 let mut weight = Weight::zero();
1897 for cause in causes {
1898 weight += T::DbWeight::get().reads_writes(3, 2);
1899 Self::deposit_event(Event::PvfCheckAccepted(*code_hash, cause.para_id()));
1900
1901 match cause {
1902 PvfCheckCause::Onboarding(id) => {
1903 weight += Self::proceed_with_onboarding(*id, sessions_observed);
1904 },
1905 PvfCheckCause::Upgrade { id, included_at, upgrade_strategy } => {
1906 weight += Self::proceed_with_upgrade(
1907 *id,
1908 code_hash,
1909 now,
1910 *included_at,
1911 cfg,
1912 *upgrade_strategy,
1913 );
1914 },
1915 }
1916 }
1917 weight
1918 }
1919
1920 fn proceed_with_onboarding(id: ParaId, sessions_observed: SessionIndex) -> Weight {
1921 let weight = T::DbWeight::get().reads_writes(2, 1);
1922
1923 let onboard_at: SessionIndex = shared::CurrentSessionIndex::<T>::get() +
1929 cmp::max(shared::SESSION_DELAY.saturating_sub(sessions_observed), 1);
1930
1931 ActionsQueue::<T>::mutate(onboard_at, |v| {
1932 if let Err(i) = v.binary_search(&id) {
1933 v.insert(i, id);
1934 }
1935 });
1936
1937 weight
1938 }
1939
1940 fn proceed_with_upgrade(
1941 id: ParaId,
1942 code_hash: &ValidationCodeHash,
1943 now: BlockNumberFor<T>,
1944 relay_parent_number: BlockNumberFor<T>,
1945 cfg: &configuration::HostConfiguration<BlockNumberFor<T>>,
1946 upgrade_strategy: UpgradeStrategy,
1947 ) -> Weight {
1948 let mut weight = Weight::zero();
1949
1950 let expected_at = cmp::max(
1963 relay_parent_number + cfg.validation_upgrade_delay,
1964 now + cfg.minimum_validation_upgrade_delay,
1965 );
1966
1967 match upgrade_strategy {
1968 UpgradeStrategy::ApplyAtExpectedBlock => {
1969 FutureCodeUpgradesAt::<T>::mutate(|future_upgrades| {
1970 let insert_idx = future_upgrades
1971 .binary_search_by_key(&expected_at, |&(_, b)| b)
1972 .unwrap_or_else(|idx| idx);
1973 future_upgrades.insert(insert_idx, (id, expected_at));
1974 });
1975
1976 weight += T::DbWeight::get().reads_writes(0, 2);
1977 },
1978 UpgradeStrategy::SetGoAheadSignal => {
1979 FutureCodeUpgrades::<T>::insert(&id, expected_at);
1980
1981 UpcomingUpgrades::<T>::mutate(|upcoming_upgrades| {
1982 let insert_idx = upcoming_upgrades
1983 .binary_search_by_key(&expected_at, |&(_, b)| b)
1984 .unwrap_or_else(|idx| idx);
1985 upcoming_upgrades.insert(insert_idx, (id, expected_at));
1986 });
1987
1988 weight += T::DbWeight::get().reads_writes(1, 3);
1989 },
1990 }
1991
1992 let expected_at = expected_at.saturated_into();
1993 let log = ConsensusLog::ParaScheduleUpgradeCode(id, *code_hash, expected_at);
1994 frame_system::Pallet::<T>::deposit_log(log.into());
1995
1996 weight
1997 }
1998
1999 fn enact_pvf_rejected(
2000 code_hash: &ValidationCodeHash,
2001 causes: Vec<PvfCheckCause<BlockNumberFor<T>>>,
2002 ) -> Weight {
2003 let mut weight = Weight::zero();
2004
2005 for cause in causes {
2006 weight += Self::decrease_code_ref(code_hash);
2009
2010 weight += T::DbWeight::get().reads_writes(3, 2);
2011 Self::deposit_event(Event::PvfCheckRejected(*code_hash, cause.para_id()));
2012
2013 match cause {
2014 PvfCheckCause::Onboarding(id) => {
2015 weight += T::DbWeight::get().writes(3);
2021 UpcomingParasGenesis::<T>::remove(&id);
2022 CurrentCodeHash::<T>::remove(&id);
2023 ParaLifecycles::<T>::remove(&id);
2024 },
2025 PvfCheckCause::Upgrade { id, .. } => {
2026 weight += T::DbWeight::get().writes(2);
2027 UpgradeGoAheadSignal::<T>::insert(&id, UpgradeGoAhead::Abort);
2028 FutureCodeHash::<T>::remove(&id);
2029 },
2030 }
2031 }
2032
2033 weight
2034 }
2035
2036 pub fn can_schedule_para_initialize(id: &ParaId) -> bool {
2040 ParaLifecycles::<T>::get(id).is_none()
2041 }
2042
2043 pub(crate) fn schedule_para_initialize(
2054 id: ParaId,
2055 mut genesis_data: ParaGenesisArgs,
2056 ) -> DispatchResult {
2057 ensure!(Self::can_schedule_para_initialize(&id), Error::<T>::CannotOnboard);
2060 ensure!(!genesis_data.validation_code.0.is_empty(), Error::<T>::CannotOnboard);
2061 ParaLifecycles::<T>::insert(&id, ParaLifecycle::Onboarding);
2062
2063 let validation_code =
2097 mem::replace(&mut genesis_data.validation_code, ValidationCode(Vec::new()));
2098 UpcomingParasGenesis::<T>::insert(&id, genesis_data);
2099 let validation_code_hash = validation_code.hash();
2100 CurrentCodeHash::<T>::insert(&id, validation_code_hash);
2101
2102 let cfg = configuration::ActiveConfig::<T>::get();
2103 Self::kick_off_pvf_check(
2104 PvfCheckCause::Onboarding(id),
2105 validation_code_hash,
2106 validation_code,
2107 &cfg,
2108 );
2109
2110 Ok(())
2111 }
2112
2113 pub(crate) fn schedule_para_cleanup(id: ParaId) -> DispatchResult {
2123 if let Some(future_code_hash) = FutureCodeHash::<T>::get(&id) {
2135 let active_prechecking = PvfActiveVoteList::<T>::get();
2136 if active_prechecking.contains(&future_code_hash) {
2137 return Err(Error::<T>::CannotOffboard.into());
2138 }
2139 }
2140
2141 let lifecycle = ParaLifecycles::<T>::get(&id);
2142 match lifecycle {
2143 None => return Ok(()),
2145 Some(ParaLifecycle::Parathread) => {
2146 ParaLifecycles::<T>::insert(&id, ParaLifecycle::OffboardingParathread);
2147 },
2148 Some(ParaLifecycle::Parachain) => {
2149 ParaLifecycles::<T>::insert(&id, ParaLifecycle::OffboardingParachain);
2150 },
2151 _ => return Err(Error::<T>::CannotOffboard.into()),
2152 }
2153
2154 let scheduled_session = Self::scheduled_session();
2155 ActionsQueue::<T>::mutate(scheduled_session, |v| {
2156 if let Err(i) = v.binary_search(&id) {
2157 v.insert(i, id);
2158 }
2159 });
2160
2161 if <T as Config>::QueueFootprinter::message_count(UmpQueueId::Para(id)) != 0 {
2162 return Err(Error::<T>::CannotOffboard.into());
2163 }
2164
2165 Ok(())
2166 }
2167
2168 pub(crate) fn schedule_parathread_upgrade(id: ParaId) -> DispatchResult {
2172 let scheduled_session = Self::scheduled_session();
2173 let lifecycle = ParaLifecycles::<T>::get(&id).ok_or(Error::<T>::NotRegistered)?;
2174
2175 ensure!(lifecycle == ParaLifecycle::Parathread, Error::<T>::CannotUpgrade);
2176
2177 ParaLifecycles::<T>::insert(&id, ParaLifecycle::UpgradingParathread);
2178 ActionsQueue::<T>::mutate(scheduled_session, |v| {
2179 if let Err(i) = v.binary_search(&id) {
2180 v.insert(i, id);
2181 }
2182 });
2183
2184 Ok(())
2185 }
2186
2187 pub(crate) fn schedule_parachain_downgrade(id: ParaId) -> DispatchResult {
2191 let scheduled_session = Self::scheduled_session();
2192 let lifecycle = ParaLifecycles::<T>::get(&id).ok_or(Error::<T>::NotRegistered)?;
2193
2194 ensure!(lifecycle == ParaLifecycle::Parachain, Error::<T>::CannotDowngrade);
2195
2196 ParaLifecycles::<T>::insert(&id, ParaLifecycle::DowngradingParachain);
2197 ActionsQueue::<T>::mutate(scheduled_session, |v| {
2198 if let Err(i) = v.binary_search(&id) {
2199 v.insert(i, id);
2200 }
2201 });
2202
2203 Ok(())
2204 }
2205
2206 pub(crate) fn schedule_code_upgrade(
2225 id: ParaId,
2226 new_code: ValidationCode,
2227 inclusion_block_number: BlockNumberFor<T>,
2228 cfg: &configuration::HostConfiguration<BlockNumberFor<T>>,
2229 upgrade_strategy: UpgradeStrategy,
2230 ) {
2231 let new_code_len = new_code.0.len();
2233 if new_code_len < MIN_CODE_SIZE as usize || new_code_len > cfg.max_code_size as usize {
2234 log::warn!(target: LOG_TARGET, "attempted to schedule an upgrade with invalid new validation code",);
2235 return;
2236 }
2237
2238 if FutureCodeHash::<T>::contains_key(&id) {
2240 log::warn!(target: LOG_TARGET, "ended up scheduling an upgrade while one is pending",);
2249 return;
2250 }
2251
2252 let code_hash = new_code.hash();
2253
2254 if CurrentCodeHash::<T>::get(&id) == Some(code_hash) {
2259 log::warn!(
2262 target: LOG_TARGET,
2263 "para tried to upgrade to the same code. Abort the upgrade",
2264 );
2265 return;
2266 }
2267
2268 FutureCodeHash::<T>::insert(&id, &code_hash);
2270 UpgradeRestrictionSignal::<T>::insert(&id, UpgradeRestriction::Present);
2271
2272 let next_possible_upgrade_at = inclusion_block_number + cfg.validation_upgrade_cooldown;
2273 UpgradeCooldowns::<T>::mutate(|upgrade_cooldowns| {
2274 let insert_idx = upgrade_cooldowns
2275 .binary_search_by_key(&next_possible_upgrade_at, |&(_, b)| b)
2276 .unwrap_or_else(|idx| idx);
2277 upgrade_cooldowns.insert(insert_idx, (id, next_possible_upgrade_at));
2278 });
2279
2280 Self::kick_off_pvf_check(
2281 PvfCheckCause::Upgrade { id, included_at: inclusion_block_number, upgrade_strategy },
2282 code_hash,
2283 new_code,
2284 cfg,
2285 );
2286 }
2287
2288 fn kick_off_pvf_check(
2302 cause: PvfCheckCause<BlockNumberFor<T>>,
2303 code_hash: ValidationCodeHash,
2304 code: ValidationCode,
2305 cfg: &configuration::HostConfiguration<BlockNumberFor<T>>,
2306 ) -> Weight {
2307 let mut weight = Weight::zero();
2308
2309 weight += T::DbWeight::get().reads_writes(3, 2);
2310 Self::deposit_event(Event::PvfCheckStarted(code_hash, cause.para_id()));
2311
2312 weight += T::DbWeight::get().reads(1);
2313 match PvfActiveVoteMap::<T>::get(&code_hash) {
2314 None => {
2315 let known_code = CodeByHash::<T>::contains_key(&code_hash);
2318 weight += T::DbWeight::get().reads(1);
2319
2320 if known_code {
2321 weight += T::DbWeight::get().reads(1);
2324 let now = frame_system::Pallet::<T>::block_number();
2325 weight += Self::enact_pvf_accepted(now, &code_hash, &[cause], 0, cfg);
2326 } else {
2327 weight += T::DbWeight::get().reads_writes(3, 2);
2330 let now = frame_system::Pallet::<T>::block_number();
2331 let n_validators = shared::ActiveValidatorKeys::<T>::get().len();
2332 PvfActiveVoteMap::<T>::insert(
2333 &code_hash,
2334 PvfCheckActiveVoteState::new(now, n_validators, cause),
2335 );
2336 PvfActiveVoteList::<T>::mutate(|l| {
2337 if let Err(idx) = l.binary_search(&code_hash) {
2338 l.insert(idx, code_hash);
2339 }
2340 });
2341 }
2342 },
2343 Some(mut vote_state) => {
2344 weight += T::DbWeight::get().writes(1);
2347 vote_state.causes.push(cause);
2348 PvfActiveVoteMap::<T>::insert(&code_hash, vote_state);
2349 },
2350 }
2351
2352 weight += Self::increase_code_ref(&code_hash, &code);
2364
2365 weight
2366 }
2367
2368 pub(crate) fn note_new_head(
2372 id: ParaId,
2373 new_head: HeadData,
2374 execution_context: BlockNumberFor<T>,
2375 ) {
2376 Heads::<T>::insert(&id, &new_head);
2377 MostRecentContext::<T>::insert(&id, execution_context);
2378
2379 if let Some(expected_at) = FutureCodeUpgrades::<T>::get(&id) {
2380 if expected_at <= execution_context {
2381 FutureCodeUpgrades::<T>::remove(&id);
2382 UpgradeGoAheadSignal::<T>::remove(&id);
2383
2384 let new_code_hash = if let Some(new_code_hash) = FutureCodeHash::<T>::take(&id) {
2386 new_code_hash
2387 } else {
2388 log::error!(target: LOG_TARGET, "Missing future code hash for {:?}", &id);
2389 return;
2390 };
2391
2392 Self::set_current_code(id, new_code_hash, expected_at);
2393 }
2394 } else {
2395 UpgradeGoAheadSignal::<T>::remove(&id);
2400 };
2401
2402 T::OnNewHead::on_new_head(id, &new_head);
2403 }
2404
2405 pub(crate) fn set_current_code(
2410 id: ParaId,
2411 new_code_hash: ValidationCodeHash,
2412 at: BlockNumberFor<T>,
2413 ) -> Weight {
2414 let maybe_prior_code_hash = CurrentCodeHash::<T>::get(&id);
2415 CurrentCodeHash::<T>::insert(&id, &new_code_hash);
2416
2417 let log = ConsensusLog::ParaUpgradeCode(id, new_code_hash);
2418 <frame_system::Pallet<T>>::deposit_log(log.into());
2419
2420 let now = <frame_system::Pallet<T>>::block_number();
2422
2423 let weight = if let Some(prior_code_hash) = maybe_prior_code_hash {
2424 Self::note_past_code(id, at, now, prior_code_hash)
2425 } else {
2426 log::error!(target: LOG_TARGET, "Missing prior code hash for para {:?}", &id);
2427 Weight::zero()
2428 };
2429
2430 weight + T::DbWeight::get().writes(1)
2431 }
2432
2433 fn do_force_set_current_code_update(para: ParaId, new_code: ValidationCode) {
2435 let new_code_hash = new_code.hash();
2436 Self::increase_code_ref(&new_code_hash, &new_code);
2437 Self::set_current_code(para, new_code_hash, frame_system::Pallet::<T>::block_number());
2438 Self::deposit_event(Event::CurrentCodeUpdated(para));
2439 }
2440
2441 pub(crate) fn pvfs_require_precheck() -> Vec<ValidationCodeHash> {
2444 PvfActiveVoteList::<T>::get()
2445 }
2446
2447 pub(crate) fn submit_pvf_check_statement(
2454 stmt: PvfCheckStatement,
2455 signature: ValidatorSignature,
2456 ) {
2457 use frame_system::offchain::SubmitTransaction;
2458
2459 let xt = T::create_bare(Call::include_pvf_check_statement { stmt, signature }.into());
2460 if let Err(e) = SubmitTransaction::<T, Call<T>>::submit_transaction(xt) {
2461 log::error!(target: LOG_TARGET, "Error submitting pvf check statement: {:?}", e,);
2462 }
2463 }
2464
2465 pub fn lifecycle(id: ParaId) -> Option<ParaLifecycle> {
2467 ParaLifecycles::<T>::get(&id)
2468 }
2469
2470 pub fn is_valid_para(id: ParaId) -> bool {
2474 if let Some(state) = ParaLifecycles::<T>::get(&id) {
2475 !state.is_onboarding() && !state.is_offboarding()
2476 } else {
2477 false
2478 }
2479 }
2480
2481 pub fn is_offboarding(id: ParaId) -> bool {
2485 ParaLifecycles::<T>::get(&id).map_or(false, |state| state.is_offboarding())
2486 }
2487
2488 pub fn is_parachain(id: ParaId) -> bool {
2493 if let Some(state) = ParaLifecycles::<T>::get(&id) {
2494 state.is_parachain()
2495 } else {
2496 false
2497 }
2498 }
2499
2500 pub fn is_parathread(id: ParaId) -> bool {
2504 if let Some(state) = ParaLifecycles::<T>::get(&id) {
2505 state.is_parathread()
2506 } else {
2507 false
2508 }
2509 }
2510
2511 pub(crate) fn can_upgrade_validation_code(id: ParaId) -> bool {
2514 FutureCodeHash::<T>::get(&id).is_none() && UpgradeRestrictionSignal::<T>::get(&id).is_none()
2515 }
2516
2517 fn scheduled_session() -> SessionIndex {
2519 shared::Pallet::<T>::scheduled_session()
2520 }
2521
2522 fn increase_code_ref(code_hash: &ValidationCodeHash, code: &ValidationCode) -> Weight {
2526 let mut weight = T::DbWeight::get().reads_writes(1, 1);
2527 CodeByHashRefs::<T>::mutate(code_hash, |refs| {
2528 if *refs == 0 {
2529 weight += T::DbWeight::get().writes(1);
2530 CodeByHash::<T>::insert(code_hash, code);
2531 }
2532 *refs += 1;
2533 });
2534 weight
2535 }
2536
2537 fn decrease_code_ref(code_hash: &ValidationCodeHash) -> Weight {
2542 let mut weight = T::DbWeight::get().reads(1);
2543 let refs = CodeByHashRefs::<T>::get(code_hash);
2544 if refs == 0 {
2545 log::error!(target: LOG_TARGET, "Code refs is already zero for {:?}", code_hash);
2546 return weight;
2547 }
2548 if refs <= 1 {
2549 weight += T::DbWeight::get().writes(2);
2550 CodeByHash::<T>::remove(code_hash);
2551 CodeByHashRefs::<T>::remove(code_hash);
2552 } else {
2553 weight += T::DbWeight::get().writes(1);
2554 CodeByHashRefs::<T>::insert(code_hash, refs - 1);
2555 }
2556 weight
2557 }
2558
2559 #[cfg(any(feature = "std", feature = "runtime-benchmarks", test))]
2561 pub fn test_on_new_session() {
2562 Self::initializer_on_new_session(&SessionChangeNotification {
2563 session_index: shared::CurrentSessionIndex::<T>::get(),
2564 ..Default::default()
2565 });
2566 }
2567
2568 #[cfg(any(feature = "runtime-benchmarks", test))]
2569 pub fn heads_insert(para_id: &ParaId, head_data: HeadData) {
2570 Heads::<T>::insert(para_id, head_data);
2571 }
2572
2573 pub(crate) fn initialize_para_now(
2575 parachains: &mut ParachainsCache<T>,
2576 id: ParaId,
2577 genesis_data: &ParaGenesisArgs,
2578 ) {
2579 match genesis_data.para_kind {
2580 ParaKind::Parachain => {
2581 parachains.add(id);
2582 ParaLifecycles::<T>::insert(&id, ParaLifecycle::Parachain);
2583 },
2584 ParaKind::Parathread => ParaLifecycles::<T>::insert(&id, ParaLifecycle::Parathread),
2585 }
2586
2587 if !genesis_data.validation_code.0.is_empty() {
2593 let code_hash = genesis_data.validation_code.hash();
2594 Self::increase_code_ref(&code_hash, &genesis_data.validation_code);
2595 CurrentCodeHash::<T>::insert(&id, code_hash);
2596 }
2597
2598 Heads::<T>::insert(&id, &genesis_data.genesis_head);
2599 MostRecentContext::<T>::insert(&id, BlockNumberFor::<T>::from(0u32));
2600 }
2601
2602 #[cfg(test)]
2603 pub(crate) fn active_vote_state(
2604 code_hash: &ValidationCodeHash,
2605 ) -> Option<PvfCheckActiveVoteState<BlockNumberFor<T>>> {
2606 PvfActiveVoteMap::<T>::get(code_hash)
2607 }
2608
2609 pub(crate) fn validate_code_is_authorized(
2614 code: &ValidationCode,
2615 para: &ParaId,
2616 ) -> Result<AuthorizedCodeHashAndExpiry<BlockNumberFor<T>>, Error<T>> {
2617 let authorized = AuthorizedCodeHash::<T>::get(para).ok_or(Error::<T>::NothingAuthorized)?;
2618 let now = frame_system::Pallet::<T>::block_number();
2619 ensure!(authorized.expire_at > now, Error::<T>::InvalidBlockNumber);
2620 ensure!(authorized.code_hash == code.hash(), Error::<T>::Unauthorized);
2621 Ok(authorized)
2622 }
2623}
2624
2625pub(crate) struct ParachainsCache<T: Config> {
2628 parachains: Option<BTreeSet<ParaId>>,
2630 _config: PhantomData<T>,
2631}
2632
2633impl<T: Config> ParachainsCache<T> {
2634 pub fn new() -> Self {
2635 Self { parachains: None, _config: PhantomData }
2636 }
2637
2638 fn ensure_initialized(&mut self) -> &mut BTreeSet<ParaId> {
2639 self.parachains
2640 .get_or_insert_with(|| Parachains::<T>::get().into_iter().collect())
2641 }
2642
2643 pub fn add(&mut self, id: ParaId) {
2645 let parachains = self.ensure_initialized();
2646 parachains.insert(id);
2647 }
2648
2649 pub fn remove(&mut self, id: ParaId) {
2652 let parachains = self.ensure_initialized();
2653 parachains.remove(&id);
2654 }
2655}
2656
2657impl<T: Config> Drop for ParachainsCache<T> {
2658 fn drop(&mut self) {
2659 if let Some(parachains) = self.parachains.take() {
2660 Parachains::<T>::put(parachains.into_iter().collect::<Vec<ParaId>>());
2661 }
2662 }
2663}