use crate::{
configuration,
inclusion::{QueueFootprinter, UmpQueueId},
initializer::SessionChangeNotification,
shared,
};
use alloc::{collections::btree_set::BTreeSet, vec::Vec};
use bitvec::{order::Lsb0 as BitOrderLsb0, vec::BitVec};
use codec::{Decode, Encode};
use core::{cmp, mem};
use pezframe_support::{
pezpallet_prelude::*,
traits::{EnsureOriginWithArg, EstimateNextSessionRotation},
DefaultNoBound,
};
use pezframe_system::pezpallet_prelude::*;
use pezkuwi_primitives::{
ConsensusLog, HeadData, Id as ParaId, PvfCheckStatement, SessionIndex, UpgradeGoAhead,
UpgradeRestriction, ValidationCode, ValidationCodeHash, ValidatorSignature, MIN_CODE_SIZE,
};
use pezsp_core::RuntimeDebug;
use pezsp_runtime::{
traits::{AppVerify, One, Saturating},
DispatchResult, SaturatedConversion,
};
use scale_info::{Type, TypeInfo};
use serde::{Deserialize, Serialize};
pub use crate::Origin as TeyrchainOrigin;
#[cfg(feature = "runtime-benchmarks")]
pub mod benchmarking;
#[cfg(test)]
pub(crate) mod tests;
pub use pezpallet::*;
const LOG_TARGET: &str = "runtime::paras";
#[derive(Default, Encode, Decode, TypeInfo)]
#[cfg_attr(test, derive(Debug, Clone, PartialEq))]
pub struct ReplacementTimes<N> {
expected_at: N,
activated_at: N,
}
#[derive(Default, Encode, Decode, TypeInfo)]
#[cfg_attr(test, derive(Debug, Clone, PartialEq))]
pub struct ParaPastCodeMeta<N> {
upgrade_times: Vec<ReplacementTimes<N>>,
last_pruned: Option<N>,
}
#[derive(PartialEq, Eq, Clone, Encode, Decode, RuntimeDebug, TypeInfo)]
pub enum ParaLifecycle {
Onboarding,
Parathread,
Teyrchain,
UpgradingParathread,
DowngradingTeyrchain,
OffboardingParathread,
OffboardingTeyrchain,
}
impl ParaLifecycle {
pub fn is_onboarding(&self) -> bool {
matches!(self, ParaLifecycle::Onboarding)
}
pub fn is_stable(&self) -> bool {
matches!(self, ParaLifecycle::Parathread | ParaLifecycle::Teyrchain)
}
pub fn is_teyrchain(&self) -> bool {
matches!(
self,
ParaLifecycle::Teyrchain
| ParaLifecycle::DowngradingTeyrchain
| ParaLifecycle::OffboardingTeyrchain
)
}
pub fn is_parathread(&self) -> bool {
matches!(
self,
ParaLifecycle::Parathread
| ParaLifecycle::UpgradingParathread
| ParaLifecycle::OffboardingParathread
)
}
pub fn is_offboarding(&self) -> bool {
matches!(self, ParaLifecycle::OffboardingParathread | ParaLifecycle::OffboardingTeyrchain)
}
pub fn is_transitioning(&self) -> bool {
!Self::is_stable(self)
}
}
impl<N: Ord + Copy + PartialEq> ParaPastCodeMeta<N> {
pub(crate) fn note_replacement(&mut self, expected_at: N, activated_at: N) {
self.upgrade_times.push(ReplacementTimes { expected_at, activated_at })
}
fn is_empty(&self) -> bool {
self.upgrade_times.is_empty()
}
#[cfg(test)]
fn most_recent_change(&self) -> Option<N> {
self.upgrade_times.last().map(|x| x.expected_at)
}
fn prune_up_to(&'_ mut self, max: N) -> impl Iterator<Item = N> + '_ {
let to_prune = self.upgrade_times.iter().take_while(|t| t.activated_at <= max).count();
let drained = if to_prune == 0 {
self.upgrade_times.drain(self.upgrade_times.len()..)
} else {
self.last_pruned = Some(self.upgrade_times[to_prune - 1].activated_at);
self.upgrade_times.drain(..to_prune)
};
drained.map(|times| times.expected_at)
}
}
#[derive(
PartialEq,
Eq,
Clone,
Encode,
Decode,
DecodeWithMemTracking,
RuntimeDebug,
TypeInfo,
Serialize,
Deserialize,
)]
pub struct ParaGenesisArgs {
pub genesis_head: HeadData,
pub validation_code: ValidationCode,
#[serde(rename = "teyrchain")]
pub para_kind: ParaKind,
}
#[derive(DecodeWithMemTracking, PartialEq, Eq, Clone, RuntimeDebug)]
pub enum ParaKind {
Parathread,
Teyrchain,
}
impl Serialize for ParaKind {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: serde::Serializer,
{
match self {
ParaKind::Teyrchain => serializer.serialize_bool(true),
ParaKind::Parathread => serializer.serialize_bool(false),
}
}
}
impl<'de> Deserialize<'de> for ParaKind {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: serde::Deserializer<'de>,
{
match serde::de::Deserialize::deserialize(deserializer) {
Ok(true) => Ok(ParaKind::Teyrchain),
Ok(false) => Ok(ParaKind::Parathread),
_ => Err(serde::de::Error::custom("invalid ParaKind serde representation")),
}
}
}
impl Encode for ParaKind {
fn size_hint(&self) -> usize {
true.size_hint()
}
fn using_encoded<R, F: FnOnce(&[u8]) -> R>(&self, f: F) -> R {
match self {
ParaKind::Teyrchain => true.using_encoded(f),
ParaKind::Parathread => false.using_encoded(f),
}
}
}
impl Decode for ParaKind {
fn decode<I: codec::Input>(input: &mut I) -> Result<Self, codec::Error> {
match bool::decode(input) {
Ok(true) => Ok(ParaKind::Teyrchain),
Ok(false) => Ok(ParaKind::Parathread),
_ => Err("Invalid ParaKind representation".into()),
}
}
}
impl TypeInfo for ParaKind {
type Identity = bool;
fn type_info() -> Type {
bool::type_info()
}
}
#[derive(Debug, Encode, Decode, TypeInfo)]
pub(crate) enum PvfCheckCause<BlockNumber> {
Onboarding(ParaId),
Upgrade {
id: ParaId,
included_at: BlockNumber,
upgrade_strategy: UpgradeStrategy,
},
}
#[derive(Debug, Copy, Clone, PartialEq, TypeInfo, Decode, Encode)]
pub enum UpgradeStrategy {
SetGoAheadSignal,
ApplyAtExpectedBlock,
}
impl<BlockNumber> PvfCheckCause<BlockNumber> {
fn para_id(&self) -> ParaId {
match *self {
PvfCheckCause::Onboarding(id) => id,
PvfCheckCause::Upgrade { id, .. } => id,
}
}
}
#[derive(Copy, Clone, Encode, Decode, RuntimeDebug, TypeInfo)]
enum PvfCheckOutcome {
Accepted,
Rejected,
}
#[derive(Encode, Decode, TypeInfo)]
pub(crate) struct PvfCheckActiveVoteState<BlockNumber> {
votes_accept: BitVec<u8, BitOrderLsb0>,
votes_reject: BitVec<u8, BitOrderLsb0>,
age: SessionIndex,
created_at: BlockNumber,
causes: Vec<PvfCheckCause<BlockNumber>>,
}
impl<BlockNumber> PvfCheckActiveVoteState<BlockNumber> {
fn new(now: BlockNumber, n_validators: usize, cause: PvfCheckCause<BlockNumber>) -> Self {
let mut causes = Vec::with_capacity(1);
causes.push(cause);
Self {
created_at: now,
votes_accept: bitvec::bitvec![u8, BitOrderLsb0; 0; n_validators],
votes_reject: bitvec::bitvec![u8, BitOrderLsb0; 0; n_validators],
age: 0,
causes,
}
}
fn reinitialize_ballots(&mut self, n_validators: usize) {
let clear_and_resize = |v: &mut BitVec<_, _>| {
v.clear();
v.resize(n_validators, false);
};
clear_and_resize(&mut self.votes_accept);
clear_and_resize(&mut self.votes_reject);
}
fn has_vote(&self, validator_index: usize) -> Option<bool> {
let accept_vote = self.votes_accept.get(validator_index)?;
let reject_vote = self.votes_reject.get(validator_index)?;
Some(*accept_vote || *reject_vote)
}
fn quorum(&self, n_validators: usize) -> Option<PvfCheckOutcome> {
let accept_threshold = pezkuwi_primitives::supermajority_threshold(n_validators);
let reject_threshold = n_validators - accept_threshold;
if self.votes_accept.count_ones() >= accept_threshold {
Some(PvfCheckOutcome::Accepted)
} else if self.votes_reject.count_ones() > reject_threshold {
Some(PvfCheckOutcome::Rejected)
} else {
None
}
}
#[cfg(test)]
pub(crate) fn causes(&self) -> &[PvfCheckCause<BlockNumber>] {
self.causes.as_slice()
}
}
pub trait OnNewHead {
fn on_new_head(id: ParaId, head: &HeadData) -> Weight;
}
#[impl_trait_for_tuples::impl_for_tuples(30)]
impl OnNewHead for Tuple {
fn on_new_head(id: ParaId, head: &HeadData) -> Weight {
let mut weight: Weight = Default::default();
for_tuples!( #( weight.saturating_accrue(Tuple::on_new_head(id, head)); )* );
weight
}
}
pub trait AssignCoretime {
fn assign_coretime(id: ParaId) -> DispatchResult;
}
impl AssignCoretime for () {
fn assign_coretime(_: ParaId) -> DispatchResult {
Ok(())
}
}
#[derive(Debug, Encode, Decode, DecodeWithMemTracking, TypeInfo)]
#[cfg_attr(test, derive(PartialEq))]
pub struct AuthorizedCodeHashAndExpiry<T> {
code_hash: ValidationCodeHash,
expire_at: T,
}
impl<T> From<(ValidationCodeHash, T)> for AuthorizedCodeHashAndExpiry<T> {
fn from(value: (ValidationCodeHash, T)) -> Self {
AuthorizedCodeHashAndExpiry { code_hash: value.0, expire_at: value.1 }
}
}
pub trait WeightInfo {
fn force_set_current_code(c: u32) -> Weight;
fn force_set_current_head(s: u32) -> Weight;
fn force_set_most_recent_context() -> Weight;
fn force_schedule_code_upgrade(c: u32) -> Weight;
fn force_note_new_head(s: u32) -> Weight;
fn force_queue_action() -> Weight;
fn add_trusted_validation_code(c: u32) -> Weight;
fn poke_unused_validation_code() -> Weight;
fn remove_upgrade_cooldown() -> Weight;
fn include_pvf_check_statement_finalize_upgrade_accept() -> Weight;
fn include_pvf_check_statement_finalize_upgrade_reject() -> Weight;
fn include_pvf_check_statement_finalize_onboarding_accept() -> Weight;
fn include_pvf_check_statement_finalize_onboarding_reject() -> Weight;
fn include_pvf_check_statement() -> Weight;
fn authorize_force_set_current_code_hash() -> Weight;
fn apply_authorized_force_set_current_code(c: u32) -> Weight;
}
pub struct TestWeightInfo;
impl WeightInfo for TestWeightInfo {
fn force_set_current_code(_c: u32) -> Weight {
Weight::MAX
}
fn force_set_current_head(_s: u32) -> Weight {
Weight::MAX
}
fn force_set_most_recent_context() -> Weight {
Weight::MAX
}
fn force_schedule_code_upgrade(_c: u32) -> Weight {
Weight::MAX
}
fn force_note_new_head(_s: u32) -> Weight {
Weight::MAX
}
fn force_queue_action() -> Weight {
Weight::MAX
}
fn add_trusted_validation_code(_c: u32) -> Weight {
Weight::zero()
}
fn poke_unused_validation_code() -> Weight {
Weight::MAX
}
fn include_pvf_check_statement_finalize_upgrade_accept() -> Weight {
Weight::MAX
}
fn include_pvf_check_statement_finalize_upgrade_reject() -> Weight {
Weight::MAX
}
fn include_pvf_check_statement_finalize_onboarding_accept() -> Weight {
Weight::MAX
}
fn include_pvf_check_statement_finalize_onboarding_reject() -> Weight {
Weight::MAX
}
fn include_pvf_check_statement() -> Weight {
Weight::MAX - Weight::from_parts(1, 1)
}
fn remove_upgrade_cooldown() -> Weight {
Weight::MAX
}
fn authorize_force_set_current_code_hash() -> Weight {
Weight::MAX
}
fn apply_authorized_force_set_current_code(_c: u32) -> Weight {
Weight::MAX
}
}
#[pezframe_support::pezpallet]
pub mod pezpallet {
use super::*;
use pezframe_support::traits::{
fungible::{Inspect, Mutate},
tokens::{Fortitude, Precision, Preservation},
};
use pezsp_runtime::transaction_validity::{
InvalidTransaction, TransactionPriority, TransactionSource, TransactionValidity,
ValidTransaction,
};
type BalanceOf<T> = <<T as Config>::Fungible as Inspect<AccountIdFor<T>>>::Balance;
#[pezpallet::pezpallet]
#[pezpallet::without_storage_info]
pub struct Pezpallet<T>(_);
#[pezpallet::config]
pub trait Config:
pezframe_system::Config
+ configuration::Config
+ shared::Config
+ pezframe_system::offchain::CreateBare<Call<Self>>
{
#[allow(deprecated)]
type RuntimeEvent: From<Event<Self>>
+ IsType<<Self as pezframe_system::Config>::RuntimeEvent>;
#[pezpallet::constant]
type UnsignedPriority: Get<TransactionPriority>;
type NextSessionRotation: EstimateNextSessionRotation<BlockNumberFor<Self>>;
type QueueFootprinter: QueueFootprinter<Origin = UmpQueueId>;
type OnNewHead: OnNewHead;
type WeightInfo: WeightInfo;
type AssignCoretime: AssignCoretime;
type Fungible: Mutate<Self::AccountId, Balance: From<BlockNumberFor<Self>>>;
type CooldownRemovalMultiplier: Get<BalanceOf<Self>>;
type AuthorizeCurrentCodeOrigin: EnsureOriginWithArg<Self::RuntimeOrigin, ParaId>;
}
#[pezpallet::event]
#[pezpallet::generate_deposit(pub(super) fn deposit_event)]
pub enum Event<T: Config> {
CurrentCodeUpdated(ParaId),
CurrentHeadUpdated(ParaId),
CodeUpgradeScheduled(ParaId),
NewHeadNoted(ParaId),
ActionQueued(ParaId, SessionIndex),
PvfCheckStarted(ValidationCodeHash, ParaId),
PvfCheckAccepted(ValidationCodeHash, ParaId),
PvfCheckRejected(ValidationCodeHash, ParaId),
UpgradeCooldownRemoved {
para_id: ParaId,
},
CodeAuthorized {
para_id: ParaId,
code_hash: ValidationCodeHash,
expire_at: BlockNumberFor<T>,
},
}
#[pezpallet::error]
pub enum Error<T> {
NotRegistered,
CannotOnboard,
CannotOffboard,
CannotUpgrade,
CannotDowngrade,
PvfCheckStatementStale,
PvfCheckStatementFuture,
PvfCheckValidatorIndexOutOfBounds,
PvfCheckInvalidSignature,
PvfCheckDoubleVote,
PvfCheckSubjectInvalid,
CannotUpgradeCode,
InvalidCode,
NothingAuthorized,
Unauthorized,
InvalidBlockNumber,
}
#[pezpallet::storage]
pub(super) type PvfActiveVoteMap<T: Config> = StorageMap<
_,
Twox64Concat,
ValidationCodeHash,
PvfCheckActiveVoteState<BlockNumberFor<T>>,
OptionQuery,
>;
#[pezpallet::storage]
pub(super) type PvfActiveVoteList<T: Config> =
StorageValue<_, Vec<ValidationCodeHash>, ValueQuery>;
#[pezpallet::storage]
pub type Teyrchains<T: Config> = StorageValue<_, Vec<ParaId>, ValueQuery>;
#[pezpallet::storage]
pub(super) type ParaLifecycles<T: Config> = StorageMap<_, Twox64Concat, ParaId, ParaLifecycle>;
#[pezpallet::storage]
pub type Heads<T: Config> = StorageMap<_, Twox64Concat, ParaId, HeadData>;
#[pezpallet::storage]
pub type MostRecentContext<T: Config> = StorageMap<_, Twox64Concat, ParaId, BlockNumberFor<T>>;
#[pezpallet::storage]
pub type CurrentCodeHash<T: Config> = StorageMap<_, Twox64Concat, ParaId, ValidationCodeHash>;
#[pezpallet::storage]
pub(super) type PastCodeHash<T: Config> =
StorageMap<_, Twox64Concat, (ParaId, BlockNumberFor<T>), ValidationCodeHash>;
#[pezpallet::storage]
pub type PastCodeMeta<T: Config> =
StorageMap<_, Twox64Concat, ParaId, ParaPastCodeMeta<BlockNumberFor<T>>, ValueQuery>;
#[pezpallet::storage]
pub(super) type PastCodePruning<T: Config> =
StorageValue<_, Vec<(ParaId, BlockNumberFor<T>)>, ValueQuery>;
#[pezpallet::storage]
pub type FutureCodeUpgrades<T: Config> = StorageMap<_, Twox64Concat, ParaId, BlockNumberFor<T>>;
#[pezpallet::storage]
pub(super) type FutureCodeUpgradesAt<T: Config> =
StorageValue<_, Vec<(ParaId, BlockNumberFor<T>)>, ValueQuery>;
#[pezpallet::storage]
pub type FutureCodeHash<T: Config> = StorageMap<_, Twox64Concat, ParaId, ValidationCodeHash>;
#[pezpallet::storage]
pub type AuthorizedCodeHash<T: Config> =
StorageMap<_, Twox64Concat, ParaId, AuthorizedCodeHashAndExpiry<BlockNumberFor<T>>>;
#[pezpallet::storage]
pub(super) type UpgradeGoAheadSignal<T: Config> =
StorageMap<_, Twox64Concat, ParaId, UpgradeGoAhead>;
#[pezpallet::storage]
pub type UpgradeRestrictionSignal<T: Config> =
StorageMap<_, Twox64Concat, ParaId, UpgradeRestriction>;
#[pezpallet::storage]
pub(super) type UpgradeCooldowns<T: Config> =
StorageValue<_, Vec<(ParaId, BlockNumberFor<T>)>, ValueQuery>;
#[pezpallet::storage]
pub(super) type UpcomingUpgrades<T: Config> =
StorageValue<_, Vec<(ParaId, BlockNumberFor<T>)>, ValueQuery>;
#[pezpallet::storage]
pub type ActionsQueue<T: Config> =
StorageMap<_, Twox64Concat, SessionIndex, Vec<ParaId>, ValueQuery>;
#[pezpallet::storage]
pub(super) type UpcomingParasGenesis<T: Config> =
StorageMap<_, Twox64Concat, ParaId, ParaGenesisArgs>;
#[pezpallet::storage]
pub(super) type CodeByHashRefs<T: Config> =
StorageMap<_, Identity, ValidationCodeHash, u32, ValueQuery>;
#[pezpallet::storage]
pub type CodeByHash<T: Config> = StorageMap<_, Identity, ValidationCodeHash, ValidationCode>;
#[pezpallet::genesis_config]
#[derive(DefaultNoBound)]
pub struct GenesisConfig<T: Config> {
#[serde(skip)]
pub _config: core::marker::PhantomData<T>,
pub paras: Vec<(ParaId, ParaGenesisArgs)>,
}
#[pezpallet::genesis_build]
impl<T: Config> BuildGenesisConfig for GenesisConfig<T> {
fn build(&self) {
let mut teyrchains = TeyrchainsCache::new();
for (id, genesis_args) in &self.paras {
if genesis_args.validation_code.0.is_empty() {
panic!("empty validation code is not allowed in genesis");
}
Pezpallet::<T>::initialize_para_now(&mut teyrchains, *id, genesis_args);
if genesis_args.para_kind == ParaKind::Teyrchain {
T::AssignCoretime::assign_coretime(*id)
.expect("Assigning coretime works at genesis; qed");
}
}
}
}
#[pezpallet::call]
impl<T: Config> Pezpallet<T> {
#[pezpallet::call_index(0)]
#[pezpallet::weight(<T as Config>::WeightInfo::force_set_current_code(new_code.0.len() as u32))]
pub fn force_set_current_code(
origin: OriginFor<T>,
para: ParaId,
new_code: ValidationCode,
) -> DispatchResult {
ensure_root(origin)?;
Self::do_force_set_current_code_update(para, new_code);
Ok(())
}
#[pezpallet::call_index(1)]
#[pezpallet::weight(<T as Config>::WeightInfo::force_set_current_head(new_head.0.len() as u32))]
pub fn force_set_current_head(
origin: OriginFor<T>,
para: ParaId,
new_head: HeadData,
) -> DispatchResult {
ensure_root(origin)?;
Self::set_current_head(para, new_head);
Ok(())
}
#[pezpallet::call_index(2)]
#[pezpallet::weight(<T as Config>::WeightInfo::force_schedule_code_upgrade(new_code.0.len() as u32))]
pub fn force_schedule_code_upgrade(
origin: OriginFor<T>,
para: ParaId,
new_code: ValidationCode,
relay_parent_number: BlockNumberFor<T>,
) -> DispatchResult {
ensure_root(origin)?;
let config = configuration::ActiveConfig::<T>::get();
Self::schedule_code_upgrade(
para,
new_code,
relay_parent_number,
&config,
UpgradeStrategy::ApplyAtExpectedBlock,
);
Self::deposit_event(Event::CodeUpgradeScheduled(para));
Ok(())
}
#[pezpallet::call_index(3)]
#[pezpallet::weight(<T as Config>::WeightInfo::force_note_new_head(new_head.0.len() as u32))]
pub fn force_note_new_head(
origin: OriginFor<T>,
para: ParaId,
new_head: HeadData,
) -> DispatchResult {
ensure_root(origin)?;
let now = pezframe_system::Pezpallet::<T>::block_number();
Self::note_new_head(para, new_head, now);
Self::deposit_event(Event::NewHeadNoted(para));
Ok(())
}
#[pezpallet::call_index(4)]
#[pezpallet::weight(<T as Config>::WeightInfo::force_queue_action())]
pub fn force_queue_action(origin: OriginFor<T>, para: ParaId) -> DispatchResult {
ensure_root(origin)?;
let next_session = shared::CurrentSessionIndex::<T>::get().saturating_add(One::one());
ActionsQueue::<T>::mutate(next_session, |v| {
if let Err(i) = v.binary_search(¶) {
v.insert(i, para);
}
});
Self::deposit_event(Event::ActionQueued(para, next_session));
Ok(())
}
#[pezpallet::call_index(5)]
#[pezpallet::weight(<T as Config>::WeightInfo::add_trusted_validation_code(validation_code.0.len() as u32))]
pub fn add_trusted_validation_code(
origin: OriginFor<T>,
validation_code: ValidationCode,
) -> DispatchResult {
ensure_root(origin)?;
let code_hash = validation_code.hash();
if let Some(vote) = PvfActiveVoteMap::<T>::get(&code_hash) {
PvfActiveVoteMap::<T>::remove(&code_hash);
PvfActiveVoteList::<T>::mutate(|l| {
if let Ok(i) = l.binary_search(&code_hash) {
l.remove(i);
}
});
let cfg = configuration::ActiveConfig::<T>::get();
Self::enact_pvf_accepted(
pezframe_system::Pezpallet::<T>::block_number(),
&code_hash,
&vote.causes,
vote.age,
&cfg,
);
return Ok(());
}
if CodeByHash::<T>::contains_key(&code_hash) {
return Ok(());
}
CodeByHash::<T>::insert(code_hash, &validation_code);
Ok(())
}
#[pezpallet::call_index(6)]
#[pezpallet::weight(<T as Config>::WeightInfo::poke_unused_validation_code())]
pub fn poke_unused_validation_code(
origin: OriginFor<T>,
validation_code_hash: ValidationCodeHash,
) -> DispatchResult {
ensure_root(origin)?;
if CodeByHashRefs::<T>::get(&validation_code_hash) == 0 {
CodeByHash::<T>::remove(&validation_code_hash);
}
Ok(())
}
#[pezpallet::call_index(7)]
#[pezpallet::weight(
<T as Config>::WeightInfo::include_pvf_check_statement_finalize_upgrade_accept()
.max(<T as Config>::WeightInfo::include_pvf_check_statement_finalize_upgrade_reject())
.max(<T as Config>::WeightInfo::include_pvf_check_statement_finalize_onboarding_accept()
.max(<T as Config>::WeightInfo::include_pvf_check_statement_finalize_onboarding_reject())
)
)]
pub fn include_pvf_check_statement(
origin: OriginFor<T>,
stmt: PvfCheckStatement,
signature: ValidatorSignature,
) -> DispatchResultWithPostInfo {
ensure_none(origin)?;
let validators = shared::ActiveValidatorKeys::<T>::get();
let current_session = shared::CurrentSessionIndex::<T>::get();
if stmt.session_index < current_session {
return Err(Error::<T>::PvfCheckStatementStale.into());
} else if stmt.session_index > current_session {
return Err(Error::<T>::PvfCheckStatementFuture.into());
}
let validator_index = stmt.validator_index.0 as usize;
let validator_public = validators
.get(validator_index)
.ok_or(Error::<T>::PvfCheckValidatorIndexOutOfBounds)?;
let signing_payload = stmt.signing_payload();
ensure!(
signature.verify(&signing_payload[..], &validator_public),
Error::<T>::PvfCheckInvalidSignature,
);
let mut active_vote = PvfActiveVoteMap::<T>::get(&stmt.subject)
.ok_or(Error::<T>::PvfCheckSubjectInvalid)?;
ensure!(
!active_vote
.has_vote(validator_index)
.ok_or(Error::<T>::PvfCheckValidatorIndexOutOfBounds)?,
Error::<T>::PvfCheckDoubleVote,
);
if stmt.accept {
active_vote.votes_accept.set(validator_index, true);
} else {
active_vote.votes_reject.set(validator_index, true);
}
if let Some(outcome) = active_vote.quorum(validators.len()) {
PvfActiveVoteMap::<T>::remove(&stmt.subject);
PvfActiveVoteList::<T>::mutate(|l| {
if let Ok(i) = l.binary_search(&stmt.subject) {
l.remove(i);
}
});
match outcome {
PvfCheckOutcome::Accepted => {
let cfg = configuration::ActiveConfig::<T>::get();
Self::enact_pvf_accepted(
pezframe_system::Pezpallet::<T>::block_number(),
&stmt.subject,
&active_vote.causes,
active_vote.age,
&cfg,
);
},
PvfCheckOutcome::Rejected => {
Self::enact_pvf_rejected(&stmt.subject, active_vote.causes);
},
}
Ok(().into())
} else {
PvfActiveVoteMap::<T>::insert(&stmt.subject, active_vote);
Ok(Some(<T as Config>::WeightInfo::include_pvf_check_statement()).into())
}
}
#[pezpallet::call_index(8)]
#[pezpallet::weight(<T as Config>::WeightInfo::force_set_most_recent_context())]
pub fn force_set_most_recent_context(
origin: OriginFor<T>,
para: ParaId,
context: BlockNumberFor<T>,
) -> DispatchResult {
ensure_root(origin)?;
MostRecentContext::<T>::insert(¶, context);
Ok(())
}
#[pezpallet::call_index(9)]
#[pezpallet::weight(<T as Config>::WeightInfo::remove_upgrade_cooldown())]
pub fn remove_upgrade_cooldown(origin: OriginFor<T>, para: ParaId) -> DispatchResult {
let who = ensure_signed(origin)?;
let removed = UpgradeCooldowns::<T>::mutate(|cooldowns| {
let Some(pos) = cooldowns.iter().position(|(p, _)| p == ¶) else {
return Ok::<_, DispatchError>(false);
};
let (_, cooldown_until) = cooldowns.remove(pos);
let cost = Self::calculate_remove_upgrade_cooldown_cost(cooldown_until);
T::Fungible::burn_from(
&who,
cost,
Preservation::Preserve,
Precision::Exact,
Fortitude::Polite,
)?;
Ok(true)
})?;
if removed {
UpgradeRestrictionSignal::<T>::remove(para);
Self::deposit_event(Event::UpgradeCooldownRemoved { para_id: para });
}
Ok(())
}
#[pezpallet::call_index(10)]
#[pezpallet::weight(<T as Config>::WeightInfo::authorize_force_set_current_code_hash())]
pub fn authorize_force_set_current_code_hash(
origin: OriginFor<T>,
para: ParaId,
new_code_hash: ValidationCodeHash,
valid_period: BlockNumberFor<T>,
) -> DispatchResult {
T::AuthorizeCurrentCodeOrigin::ensure_origin(origin, ¶)?;
ensure!(Self::is_valid_para(para), Error::<T>::NotRegistered);
let now = pezframe_system::Pezpallet::<T>::block_number();
let expire_at = now.saturating_add(valid_period);
AuthorizedCodeHash::<T>::insert(
¶,
AuthorizedCodeHashAndExpiry::from((new_code_hash, expire_at)),
);
Self::deposit_event(Event::CodeAuthorized {
para_id: para,
code_hash: new_code_hash,
expire_at,
});
Ok(())
}
#[pezpallet::call_index(11)]
#[pezpallet::weight(<T as Config>::WeightInfo::apply_authorized_force_set_current_code(new_code.0.len() as u32))]
pub fn apply_authorized_force_set_current_code(
_origin: OriginFor<T>,
para: ParaId,
new_code: ValidationCode,
) -> DispatchResultWithPostInfo {
let _ = Self::validate_code_is_authorized(&new_code, ¶)?;
AuthorizedCodeHash::<T>::remove(para);
Self::do_force_set_current_code_update(para, new_code);
Ok(Pays::No.into())
}
}
impl<T: Config> Pezpallet<T> {
pub(crate) fn calculate_remove_upgrade_cooldown_cost(
cooldown_until: BlockNumberFor<T>,
) -> BalanceOf<T> {
let time_left =
cooldown_until.saturating_sub(pezframe_system::Pezpallet::<T>::block_number());
BalanceOf::<T>::from(time_left).saturating_mul(T::CooldownRemovalMultiplier::get())
}
}
#[pezpallet::view_functions]
impl<T: Config> Pezpallet<T> {
pub fn remove_upgrade_cooldown_cost(para: ParaId) -> BalanceOf<T> {
UpgradeCooldowns::<T>::get()
.iter()
.find(|(p, _)| p == ¶)
.map(|(_, c)| Self::calculate_remove_upgrade_cooldown_cost(*c))
.unwrap_or_default()
}
}
#[pezpallet::validate_unsigned]
impl<T: Config> ValidateUnsigned for Pezpallet<T> {
type Call = Call<T>;
fn validate_unsigned(_source: TransactionSource, call: &Self::Call) -> TransactionValidity {
match call {
Call::include_pvf_check_statement { stmt, signature } => {
let current_session = shared::CurrentSessionIndex::<T>::get();
if stmt.session_index < current_session {
return InvalidTransaction::Stale.into();
} else if stmt.session_index > current_session {
return InvalidTransaction::Future.into();
}
let validator_index = stmt.validator_index.0 as usize;
let validators = shared::ActiveValidatorKeys::<T>::get();
let validator_public = match validators.get(validator_index) {
Some(pk) => pk,
None => {
return InvalidTransaction::Custom(INVALID_TX_BAD_VALIDATOR_IDX).into()
},
};
let signing_payload = stmt.signing_payload();
if !signature.verify(&signing_payload[..], &validator_public) {
return InvalidTransaction::BadProof.into();
}
let active_vote = match PvfActiveVoteMap::<T>::get(&stmt.subject) {
Some(v) => v,
None => return InvalidTransaction::Custom(INVALID_TX_BAD_SUBJECT).into(),
};
match active_vote.has_vote(validator_index) {
Some(false) => (),
Some(true) => {
return InvalidTransaction::Custom(INVALID_TX_DOUBLE_VOTE).into()
},
None => {
return InvalidTransaction::Custom(INVALID_TX_BAD_VALIDATOR_IDX).into()
},
}
ValidTransaction::with_tag_prefix("PvfPreCheckingVote")
.priority(T::UnsignedPriority::get())
.longevity(
TryInto::<u64>::try_into(
T::NextSessionRotation::average_session_length() / 2u32.into(),
)
.unwrap_or(64_u64),
)
.and_provides((stmt.session_index, stmt.validator_index, stmt.subject))
.propagate(true)
.build()
},
Call::apply_authorized_force_set_current_code { para, new_code } => {
match Self::validate_code_is_authorized(new_code, para) {
Ok(authorized_code) => {
let now = pezframe_system::Pezpallet::<T>::block_number();
let longevity = authorized_code.expire_at.saturating_sub(now);
ValidTransaction::with_tag_prefix("ApplyAuthorizedForceSetCurrentCode")
.priority(T::UnsignedPriority::get())
.longevity(TryInto::<u64>::try_into(longevity).unwrap_or(64_u64))
.and_provides((para, authorized_code.code_hash))
.propagate(true)
.build()
},
Err(_) => {
return InvalidTransaction::Custom(INVALID_TX_UNAUTHORIZED_CODE).into()
},
}
},
_ => InvalidTransaction::Call.into(),
}
}
fn pre_dispatch(_call: &Self::Call) -> Result<(), TransactionValidityError> {
Ok(())
}
}
}
const INVALID_TX_BAD_VALIDATOR_IDX: u8 = 1;
const INVALID_TX_BAD_SUBJECT: u8 = 2;
const INVALID_TX_DOUBLE_VOTE: u8 = 3;
const INVALID_TX_UNAUTHORIZED_CODE: u8 = 4;
pub const MAX_PARA_HEADS: usize = 1024;
impl<T: Config> Pezpallet<T> {
pub(crate) fn schedule_code_upgrade_external(
id: ParaId,
new_code: ValidationCode,
upgrade_strategy: UpgradeStrategy,
) -> DispatchResult {
ensure!(Self::can_upgrade_validation_code(id), Error::<T>::CannotUpgradeCode);
let config = configuration::ActiveConfig::<T>::get();
ensure!(new_code.0.len() >= MIN_CODE_SIZE as usize, Error::<T>::InvalidCode);
ensure!(new_code.0.len() <= config.max_code_size as usize, Error::<T>::InvalidCode);
let current_block = pezframe_system::Pezpallet::<T>::block_number();
let upgrade_block = current_block.saturating_add(config.validation_upgrade_delay);
Self::schedule_code_upgrade(id, new_code, upgrade_block, &config, upgrade_strategy);
Self::deposit_event(Event::CodeUpgradeScheduled(id));
Ok(())
}
pub(crate) fn set_current_head(para: ParaId, new_head: HeadData) {
Heads::<T>::insert(¶, new_head);
Self::deposit_event(Event::CurrentHeadUpdated(para));
}
pub(crate) fn initializer_initialize(now: BlockNumberFor<T>) -> Weight {
Self::prune_old_code(now)
+ Self::process_scheduled_upgrade_changes(now)
+ Self::process_future_code_upgrades_at(now)
+ Self::prune_expired_authorizations(now)
}
pub(crate) fn initializer_finalize(now: BlockNumberFor<T>) {
Self::process_scheduled_upgrade_cooldowns(now);
}
pub(crate) fn initializer_on_new_session(
notification: &SessionChangeNotification<BlockNumberFor<T>>,
) -> Vec<ParaId> {
let outgoing_paras = Self::apply_actions_queue(notification.session_index);
Self::groom_ongoing_pvf_votes(¬ification.new_config, notification.validators.len());
outgoing_paras
}
pub(crate) fn current_code(para_id: &ParaId) -> Option<ValidationCode> {
CurrentCodeHash::<T>::get(para_id).and_then(|code_hash| {
let code = CodeByHash::<T>::get(&code_hash);
if code.is_none() {
log::error!(
"Pezpallet paras storage is inconsistent, code not found for hash {}",
code_hash,
);
debug_assert!(false, "inconsistent paras storages");
}
code
})
}
pub fn sorted_para_heads() -> Vec<(u32, Vec<u8>)> {
let mut heads: Vec<(u32, Vec<u8>)> =
Heads::<T>::iter().map(|(id, head)| (id.into(), head.0)).collect();
heads.sort_by_key(|(id, _)| *id);
heads.truncate(MAX_PARA_HEADS);
heads
}
fn apply_actions_queue(session: SessionIndex) -> Vec<ParaId> {
let actions = ActionsQueue::<T>::take(session);
let mut teyrchains = TeyrchainsCache::new();
let now = pezframe_system::Pezpallet::<T>::block_number();
let mut outgoing = Vec::new();
for para in actions {
let lifecycle = ParaLifecycles::<T>::get(¶);
match lifecycle {
None | Some(ParaLifecycle::Parathread) | Some(ParaLifecycle::Teyrchain) => {
},
Some(ParaLifecycle::Onboarding) => {
if let Some(genesis_data) = UpcomingParasGenesis::<T>::take(¶) {
Self::initialize_para_now(&mut teyrchains, para, &genesis_data);
}
},
Some(ParaLifecycle::UpgradingParathread) => {
teyrchains.add(para);
ParaLifecycles::<T>::insert(¶, ParaLifecycle::Teyrchain);
},
Some(ParaLifecycle::DowngradingTeyrchain) => {
teyrchains.remove(para);
ParaLifecycles::<T>::insert(¶, ParaLifecycle::Parathread);
},
Some(ParaLifecycle::OffboardingTeyrchain)
| Some(ParaLifecycle::OffboardingParathread) => {
teyrchains.remove(para);
Heads::<T>::remove(¶);
MostRecentContext::<T>::remove(¶);
FutureCodeUpgrades::<T>::remove(¶);
UpgradeGoAheadSignal::<T>::remove(¶);
UpgradeRestrictionSignal::<T>::remove(¶);
ParaLifecycles::<T>::remove(¶);
AuthorizedCodeHash::<T>::remove(¶);
let removed_future_code_hash = FutureCodeHash::<T>::take(¶);
if let Some(removed_future_code_hash) = removed_future_code_hash {
Self::decrease_code_ref(&removed_future_code_hash);
}
let removed_code_hash = CurrentCodeHash::<T>::take(¶);
if let Some(removed_code_hash) = removed_code_hash {
Self::note_past_code(para, now, now, removed_code_hash);
}
outgoing.push(para);
},
}
}
if !outgoing.is_empty() {
UpcomingUpgrades::<T>::mutate(|upcoming_upgrades| {
upcoming_upgrades.retain(|(para, _)| !outgoing.contains(para));
});
UpgradeCooldowns::<T>::mutate(|upgrade_cooldowns| {
upgrade_cooldowns.retain(|(para, _)| !outgoing.contains(para));
});
FutureCodeUpgradesAt::<T>::mutate(|future_upgrades| {
future_upgrades.retain(|(para, _)| !outgoing.contains(para));
});
}
drop(teyrchains);
outgoing
}
fn note_past_code(
id: ParaId,
at: BlockNumberFor<T>,
now: BlockNumberFor<T>,
old_code_hash: ValidationCodeHash,
) -> Weight {
PastCodeMeta::<T>::mutate(&id, |past_meta| {
past_meta.note_replacement(at, now);
});
PastCodeHash::<T>::insert(&(id, at), old_code_hash);
PastCodePruning::<T>::mutate(|pruning| {
let insert_idx =
pruning.binary_search_by_key(&now, |&(_, b)| b).unwrap_or_else(|idx| idx);
pruning.insert(insert_idx, (id, now));
});
T::DbWeight::get().reads_writes(2, 3)
}
fn prune_old_code(now: BlockNumberFor<T>) -> Weight {
let config = configuration::ActiveConfig::<T>::get();
let code_retention_period = config.code_retention_period;
if now <= code_retention_period {
let weight = T::DbWeight::get().reads_writes(1, 0);
return weight;
}
let pruning_height = now - (code_retention_period + One::one());
let pruning_tasks_done =
PastCodePruning::<T>::mutate(|pruning_tasks: &mut Vec<(_, BlockNumberFor<T>)>| {
let (pruning_tasks_done, pruning_tasks_to_do) = {
let up_to_idx =
pruning_tasks.iter().take_while(|&(_, at)| at <= &pruning_height).count();
(up_to_idx, pruning_tasks.drain(..up_to_idx))
};
for (para_id, _) in pruning_tasks_to_do {
let full_deactivate = PastCodeMeta::<T>::mutate(¶_id, |meta| {
for pruned_repl_at in meta.prune_up_to(pruning_height) {
let removed_code_hash =
PastCodeHash::<T>::take(&(para_id, pruned_repl_at));
if let Some(removed_code_hash) = removed_code_hash {
Self::decrease_code_ref(&removed_code_hash);
} else {
log::warn!(
target: LOG_TARGET,
"Missing code for removed hash {:?}",
removed_code_hash,
);
}
}
meta.is_empty() && Heads::<T>::get(¶_id).is_none()
});
if full_deactivate {
PastCodeMeta::<T>::remove(¶_id);
}
}
pruning_tasks_done as u64
});
T::DbWeight::get().reads_writes(1 + pruning_tasks_done, 2 * pruning_tasks_done)
}
fn prune_expired_authorizations(now: BlockNumberFor<T>) -> Weight {
let mut weight = T::DbWeight::get().reads(1);
let to_remove = AuthorizedCodeHash::<T>::iter().filter_map(
|(para, AuthorizedCodeHashAndExpiry { expire_at, .. })| {
if expire_at <= now {
Some(para)
} else {
None
}
},
);
for para in to_remove {
AuthorizedCodeHash::<T>::remove(¶);
weight.saturating_accrue(T::DbWeight::get().writes(1));
}
weight
}
fn process_future_code_upgrades_at(now: BlockNumberFor<T>) -> Weight {
let mut weight = T::DbWeight::get().reads_writes(1, 1);
FutureCodeUpgradesAt::<T>::mutate(
|upcoming_upgrades: &mut Vec<(ParaId, BlockNumberFor<T>)>| {
let num = upcoming_upgrades.iter().take_while(|&(_, at)| at <= &now).count();
for (id, expected_at) in upcoming_upgrades.drain(..num) {
weight += T::DbWeight::get().reads_writes(1, 1);
let new_code_hash = if let Some(new_code_hash) = FutureCodeHash::<T>::take(&id)
{
new_code_hash
} else {
log::error!(target: LOG_TARGET, "Missing future code hash for {:?}", &id);
continue;
};
weight += Self::set_current_code(id, new_code_hash, expected_at);
}
num
},
);
weight
}
fn process_scheduled_upgrade_changes(now: BlockNumberFor<T>) -> Weight {
let mut weight = T::DbWeight::get().reads_writes(1, 1);
let upgrades_signaled = UpcomingUpgrades::<T>::mutate(
|upcoming_upgrades: &mut Vec<(ParaId, BlockNumberFor<T>)>| {
let num = upcoming_upgrades.iter().take_while(|&(_, at)| at <= &now).count();
for (para, _) in upcoming_upgrades.drain(..num) {
UpgradeGoAheadSignal::<T>::insert(¶, UpgradeGoAhead::GoAhead);
}
num
},
);
weight += T::DbWeight::get().writes(upgrades_signaled as u64);
weight += T::DbWeight::get().reads(1);
let cooldowns_expired =
UpgradeCooldowns::<T>::get().iter().take_while(|&(_, at)| at <= &now).count();
weight += T::DbWeight::get().reads_writes(1, 1);
weight += T::DbWeight::get().reads(cooldowns_expired as u64);
weight
}
fn process_scheduled_upgrade_cooldowns(now: BlockNumberFor<T>) {
UpgradeCooldowns::<T>::mutate(
|upgrade_cooldowns: &mut Vec<(ParaId, BlockNumberFor<T>)>| {
upgrade_cooldowns.retain(|(para, at)| {
if at <= &now {
UpgradeRestrictionSignal::<T>::remove(¶);
false
} else {
true
}
});
},
);
}
fn groom_ongoing_pvf_votes(
cfg: &configuration::HostConfiguration<BlockNumberFor<T>>,
new_n_validators: usize,
) -> Weight {
let mut weight = T::DbWeight::get().reads(1);
let potentially_active_votes = PvfActiveVoteList::<T>::get();
let mut actually_active_votes = Vec::with_capacity(potentially_active_votes.len());
for vote_subject in potentially_active_votes {
let mut vote_state = match PvfActiveVoteMap::<T>::take(&vote_subject) {
Some(v) => v,
None => {
log::warn!(
target: LOG_TARGET,
"The PvfActiveVoteMap is out of sync with PvfActiveVoteList!",
);
debug_assert!(false);
continue;
},
};
vote_state.age += 1;
if vote_state.age < cfg.pvf_voting_ttl {
weight += T::DbWeight::get().writes(1);
vote_state.reinitialize_ballots(new_n_validators);
PvfActiveVoteMap::<T>::insert(&vote_subject, vote_state);
actually_active_votes.push(vote_subject);
} else {
weight += Self::enact_pvf_rejected(&vote_subject, vote_state.causes);
}
}
weight += T::DbWeight::get().writes(1);
PvfActiveVoteList::<T>::put(actually_active_votes);
weight
}
fn enact_pvf_accepted(
now: BlockNumberFor<T>,
code_hash: &ValidationCodeHash,
causes: &[PvfCheckCause<BlockNumberFor<T>>],
sessions_observed: SessionIndex,
cfg: &configuration::HostConfiguration<BlockNumberFor<T>>,
) -> Weight {
let mut weight = Weight::zero();
for cause in causes {
weight += T::DbWeight::get().reads_writes(3, 2);
Self::deposit_event(Event::PvfCheckAccepted(*code_hash, cause.para_id()));
match cause {
PvfCheckCause::Onboarding(id) => {
weight += Self::proceed_with_onboarding(*id, sessions_observed);
},
PvfCheckCause::Upgrade { id, included_at, upgrade_strategy } => {
weight += Self::proceed_with_upgrade(
*id,
code_hash,
now,
*included_at,
cfg,
*upgrade_strategy,
);
},
}
}
weight
}
fn proceed_with_onboarding(id: ParaId, sessions_observed: SessionIndex) -> Weight {
let weight = T::DbWeight::get().reads_writes(2, 1);
let onboard_at: SessionIndex = shared::CurrentSessionIndex::<T>::get()
+ cmp::max(shared::SESSION_DELAY.saturating_sub(sessions_observed), 1);
ActionsQueue::<T>::mutate(onboard_at, |v| {
if let Err(i) = v.binary_search(&id) {
v.insert(i, id);
}
});
weight
}
fn proceed_with_upgrade(
id: ParaId,
code_hash: &ValidationCodeHash,
now: BlockNumberFor<T>,
relay_parent_number: BlockNumberFor<T>,
cfg: &configuration::HostConfiguration<BlockNumberFor<T>>,
upgrade_strategy: UpgradeStrategy,
) -> Weight {
let mut weight = Weight::zero();
let expected_at = cmp::max(
relay_parent_number + cfg.validation_upgrade_delay,
now + cfg.minimum_validation_upgrade_delay,
);
match upgrade_strategy {
UpgradeStrategy::ApplyAtExpectedBlock => {
FutureCodeUpgradesAt::<T>::mutate(|future_upgrades| {
let insert_idx = future_upgrades
.binary_search_by_key(&expected_at, |&(_, b)| b)
.unwrap_or_else(|idx| idx);
future_upgrades.insert(insert_idx, (id, expected_at));
});
weight += T::DbWeight::get().reads_writes(0, 2);
},
UpgradeStrategy::SetGoAheadSignal => {
FutureCodeUpgrades::<T>::insert(&id, expected_at);
UpcomingUpgrades::<T>::mutate(|upcoming_upgrades| {
let insert_idx = upcoming_upgrades
.binary_search_by_key(&expected_at, |&(_, b)| b)
.unwrap_or_else(|idx| idx);
upcoming_upgrades.insert(insert_idx, (id, expected_at));
});
weight += T::DbWeight::get().reads_writes(1, 3);
},
}
let expected_at = expected_at.saturated_into();
let log = ConsensusLog::ParaScheduleUpgradeCode(id, *code_hash, expected_at);
pezframe_system::Pezpallet::<T>::deposit_log(log.into());
weight
}
fn enact_pvf_rejected(
code_hash: &ValidationCodeHash,
causes: Vec<PvfCheckCause<BlockNumberFor<T>>>,
) -> Weight {
let mut weight = Weight::zero();
for cause in causes {
weight += Self::decrease_code_ref(code_hash);
weight += T::DbWeight::get().reads_writes(3, 2);
Self::deposit_event(Event::PvfCheckRejected(*code_hash, cause.para_id()));
match cause {
PvfCheckCause::Onboarding(id) => {
weight += T::DbWeight::get().writes(3);
UpcomingParasGenesis::<T>::remove(&id);
CurrentCodeHash::<T>::remove(&id);
ParaLifecycles::<T>::remove(&id);
},
PvfCheckCause::Upgrade { id, .. } => {
weight += T::DbWeight::get().writes(2);
UpgradeGoAheadSignal::<T>::insert(&id, UpgradeGoAhead::Abort);
FutureCodeHash::<T>::remove(&id);
},
}
}
weight
}
pub fn can_schedule_para_initialize(id: &ParaId) -> bool {
ParaLifecycles::<T>::get(id).is_none()
}
pub(crate) fn schedule_para_initialize(
id: ParaId,
mut genesis_data: ParaGenesisArgs,
) -> DispatchResult {
ensure!(Self::can_schedule_para_initialize(&id), Error::<T>::CannotOnboard);
ensure!(!genesis_data.validation_code.0.is_empty(), Error::<T>::CannotOnboard);
ParaLifecycles::<T>::insert(&id, ParaLifecycle::Onboarding);
let validation_code =
mem::replace(&mut genesis_data.validation_code, ValidationCode(Vec::new()));
UpcomingParasGenesis::<T>::insert(&id, genesis_data);
let validation_code_hash = validation_code.hash();
CurrentCodeHash::<T>::insert(&id, validation_code_hash);
let cfg = configuration::ActiveConfig::<T>::get();
Self::kick_off_pvf_check(
PvfCheckCause::Onboarding(id),
validation_code_hash,
validation_code,
&cfg,
);
Ok(())
}
pub(crate) fn schedule_para_cleanup(id: ParaId) -> DispatchResult {
if let Some(future_code_hash) = FutureCodeHash::<T>::get(&id) {
let active_prechecking = PvfActiveVoteList::<T>::get();
if active_prechecking.contains(&future_code_hash) {
return Err(Error::<T>::CannotOffboard.into());
}
}
let lifecycle = ParaLifecycles::<T>::get(&id);
match lifecycle {
None => return Ok(()),
Some(ParaLifecycle::Parathread) => {
ParaLifecycles::<T>::insert(&id, ParaLifecycle::OffboardingParathread);
},
Some(ParaLifecycle::Teyrchain) => {
ParaLifecycles::<T>::insert(&id, ParaLifecycle::OffboardingTeyrchain);
},
_ => return Err(Error::<T>::CannotOffboard.into()),
}
let scheduled_session = Self::scheduled_session();
ActionsQueue::<T>::mutate(scheduled_session, |v| {
if let Err(i) = v.binary_search(&id) {
v.insert(i, id);
}
});
if <T as Config>::QueueFootprinter::message_count(UmpQueueId::Para(id)) != 0 {
return Err(Error::<T>::CannotOffboard.into());
}
Ok(())
}
pub(crate) fn schedule_parathread_upgrade(id: ParaId) -> DispatchResult {
let scheduled_session = Self::scheduled_session();
let lifecycle = ParaLifecycles::<T>::get(&id).ok_or(Error::<T>::NotRegistered)?;
ensure!(lifecycle == ParaLifecycle::Parathread, Error::<T>::CannotUpgrade);
ParaLifecycles::<T>::insert(&id, ParaLifecycle::UpgradingParathread);
ActionsQueue::<T>::mutate(scheduled_session, |v| {
if let Err(i) = v.binary_search(&id) {
v.insert(i, id);
}
});
Ok(())
}
pub(crate) fn schedule_teyrchain_downgrade(id: ParaId) -> DispatchResult {
let scheduled_session = Self::scheduled_session();
let lifecycle = ParaLifecycles::<T>::get(&id).ok_or(Error::<T>::NotRegistered)?;
ensure!(lifecycle == ParaLifecycle::Teyrchain, Error::<T>::CannotDowngrade);
ParaLifecycles::<T>::insert(&id, ParaLifecycle::DowngradingTeyrchain);
ActionsQueue::<T>::mutate(scheduled_session, |v| {
if let Err(i) = v.binary_search(&id) {
v.insert(i, id);
}
});
Ok(())
}
pub(crate) fn schedule_code_upgrade(
id: ParaId,
new_code: ValidationCode,
inclusion_block_number: BlockNumberFor<T>,
cfg: &configuration::HostConfiguration<BlockNumberFor<T>>,
upgrade_strategy: UpgradeStrategy,
) {
let new_code_len = new_code.0.len();
if new_code_len < MIN_CODE_SIZE as usize || new_code_len > cfg.max_code_size as usize {
log::warn!(target: LOG_TARGET, "attempted to schedule an upgrade with invalid new validation code",);
return;
}
if FutureCodeHash::<T>::contains_key(&id) {
log::warn!(target: LOG_TARGET, "ended up scheduling an upgrade while one is pending",);
return;
}
let code_hash = new_code.hash();
if CurrentCodeHash::<T>::get(&id) == Some(code_hash) {
log::warn!(
target: LOG_TARGET,
"para tried to upgrade to the same code. Abort the upgrade",
);
return;
}
FutureCodeHash::<T>::insert(&id, &code_hash);
UpgradeRestrictionSignal::<T>::insert(&id, UpgradeRestriction::Present);
let next_possible_upgrade_at = inclusion_block_number + cfg.validation_upgrade_cooldown;
UpgradeCooldowns::<T>::mutate(|upgrade_cooldowns| {
let insert_idx = upgrade_cooldowns
.binary_search_by_key(&next_possible_upgrade_at, |&(_, b)| b)
.unwrap_or_else(|idx| idx);
upgrade_cooldowns.insert(insert_idx, (id, next_possible_upgrade_at));
});
Self::kick_off_pvf_check(
PvfCheckCause::Upgrade { id, included_at: inclusion_block_number, upgrade_strategy },
code_hash,
new_code,
cfg,
);
}
fn kick_off_pvf_check(
cause: PvfCheckCause<BlockNumberFor<T>>,
code_hash: ValidationCodeHash,
code: ValidationCode,
cfg: &configuration::HostConfiguration<BlockNumberFor<T>>,
) -> Weight {
let mut weight = Weight::zero();
weight += T::DbWeight::get().reads_writes(3, 2);
Self::deposit_event(Event::PvfCheckStarted(code_hash, cause.para_id()));
weight += T::DbWeight::get().reads(1);
match PvfActiveVoteMap::<T>::get(&code_hash) {
None => {
let known_code = CodeByHash::<T>::contains_key(&code_hash);
weight += T::DbWeight::get().reads(1);
if known_code {
weight += T::DbWeight::get().reads(1);
let now = pezframe_system::Pezpallet::<T>::block_number();
weight += Self::enact_pvf_accepted(now, &code_hash, &[cause], 0, cfg);
} else {
weight += T::DbWeight::get().reads_writes(3, 2);
let now = pezframe_system::Pezpallet::<T>::block_number();
let n_validators = shared::ActiveValidatorKeys::<T>::get().len();
PvfActiveVoteMap::<T>::insert(
&code_hash,
PvfCheckActiveVoteState::new(now, n_validators, cause),
);
PvfActiveVoteList::<T>::mutate(|l| {
if let Err(idx) = l.binary_search(&code_hash) {
l.insert(idx, code_hash);
}
});
}
},
Some(mut vote_state) => {
weight += T::DbWeight::get().writes(1);
vote_state.causes.push(cause);
PvfActiveVoteMap::<T>::insert(&code_hash, vote_state);
},
}
weight += Self::increase_code_ref(&code_hash, &code);
weight
}
pub(crate) fn note_new_head(
id: ParaId,
new_head: HeadData,
execution_context: BlockNumberFor<T>,
) {
Heads::<T>::insert(&id, &new_head);
MostRecentContext::<T>::insert(&id, execution_context);
if let Some(expected_at) = FutureCodeUpgrades::<T>::get(&id) {
if expected_at <= execution_context {
FutureCodeUpgrades::<T>::remove(&id);
UpgradeGoAheadSignal::<T>::remove(&id);
let new_code_hash = if let Some(new_code_hash) = FutureCodeHash::<T>::take(&id) {
new_code_hash
} else {
log::error!(target: LOG_TARGET, "Missing future code hash for {:?}", &id);
return;
};
Self::set_current_code(id, new_code_hash, expected_at);
}
} else {
UpgradeGoAheadSignal::<T>::remove(&id);
};
T::OnNewHead::on_new_head(id, &new_head);
}
pub(crate) fn set_current_code(
id: ParaId,
new_code_hash: ValidationCodeHash,
at: BlockNumberFor<T>,
) -> Weight {
let maybe_prior_code_hash = CurrentCodeHash::<T>::get(&id);
CurrentCodeHash::<T>::insert(&id, &new_code_hash);
let log = ConsensusLog::ParaUpgradeCode(id, new_code_hash);
<pezframe_system::Pezpallet<T>>::deposit_log(log.into());
let now = <pezframe_system::Pezpallet<T>>::block_number();
let weight = if let Some(prior_code_hash) = maybe_prior_code_hash {
Self::note_past_code(id, at, now, prior_code_hash)
} else {
log::error!(target: LOG_TARGET, "Missing prior code hash for para {:?}", &id);
Weight::zero()
};
weight + T::DbWeight::get().writes(1)
}
fn do_force_set_current_code_update(para: ParaId, new_code: ValidationCode) {
let new_code_hash = new_code.hash();
Self::increase_code_ref(&new_code_hash, &new_code);
Self::set_current_code(
para,
new_code_hash,
pezframe_system::Pezpallet::<T>::block_number(),
);
Self::deposit_event(Event::CurrentCodeUpdated(para));
}
pub(crate) fn pvfs_require_precheck() -> Vec<ValidationCodeHash> {
PvfActiveVoteList::<T>::get()
}
pub(crate) fn submit_pvf_check_statement(
stmt: PvfCheckStatement,
signature: ValidatorSignature,
) {
use pezframe_system::offchain::SubmitTransaction;
let xt = T::create_bare(Call::include_pvf_check_statement { stmt, signature }.into());
if let Err(e) = SubmitTransaction::<T, Call<T>>::submit_transaction(xt) {
log::error!(target: LOG_TARGET, "Error submitting pvf check statement: {:?}", e,);
}
}
pub fn lifecycle(id: ParaId) -> Option<ParaLifecycle> {
ParaLifecycles::<T>::get(&id)
}
pub fn is_valid_para(id: ParaId) -> bool {
if let Some(state) = ParaLifecycles::<T>::get(&id) {
!state.is_onboarding() && !state.is_offboarding()
} else {
false
}
}
pub fn is_offboarding(id: ParaId) -> bool {
ParaLifecycles::<T>::get(&id).map_or(false, |state| state.is_offboarding())
}
pub fn is_teyrchain(id: ParaId) -> bool {
if let Some(state) = ParaLifecycles::<T>::get(&id) {
state.is_teyrchain()
} else {
false
}
}
pub fn is_parathread(id: ParaId) -> bool {
if let Some(state) = ParaLifecycles::<T>::get(&id) {
state.is_parathread()
} else {
false
}
}
pub(crate) fn can_upgrade_validation_code(id: ParaId) -> bool {
FutureCodeHash::<T>::get(&id).is_none() && UpgradeRestrictionSignal::<T>::get(&id).is_none()
}
fn scheduled_session() -> SessionIndex {
shared::Pezpallet::<T>::scheduled_session()
}
fn increase_code_ref(code_hash: &ValidationCodeHash, code: &ValidationCode) -> Weight {
let mut weight = T::DbWeight::get().reads_writes(1, 1);
CodeByHashRefs::<T>::mutate(code_hash, |refs| {
if *refs == 0 {
weight += T::DbWeight::get().writes(1);
CodeByHash::<T>::insert(code_hash, code);
}
*refs += 1;
});
weight
}
fn decrease_code_ref(code_hash: &ValidationCodeHash) -> Weight {
let mut weight = T::DbWeight::get().reads(1);
let refs = CodeByHashRefs::<T>::get(code_hash);
if refs == 0 {
log::error!(target: LOG_TARGET, "Code refs is already zero for {:?}", code_hash);
return weight;
}
if refs <= 1 {
weight += T::DbWeight::get().writes(2);
CodeByHash::<T>::remove(code_hash);
CodeByHashRefs::<T>::remove(code_hash);
} else {
weight += T::DbWeight::get().writes(1);
CodeByHashRefs::<T>::insert(code_hash, refs - 1);
}
weight
}
#[cfg(any(feature = "std", feature = "runtime-benchmarks", test))]
pub fn test_on_new_session() {
Self::initializer_on_new_session(&SessionChangeNotification {
session_index: shared::CurrentSessionIndex::<T>::get(),
..Default::default()
});
}
#[cfg(any(feature = "runtime-benchmarks", test))]
pub fn heads_insert(para_id: &ParaId, head_data: HeadData) {
Heads::<T>::insert(para_id, head_data);
}
pub(crate) fn initialize_para_now(
teyrchains: &mut TeyrchainsCache<T>,
id: ParaId,
genesis_data: &ParaGenesisArgs,
) {
match genesis_data.para_kind {
ParaKind::Teyrchain => {
teyrchains.add(id);
ParaLifecycles::<T>::insert(&id, ParaLifecycle::Teyrchain);
},
ParaKind::Parathread => ParaLifecycles::<T>::insert(&id, ParaLifecycle::Parathread),
}
if !genesis_data.validation_code.0.is_empty() {
let code_hash = genesis_data.validation_code.hash();
Self::increase_code_ref(&code_hash, &genesis_data.validation_code);
CurrentCodeHash::<T>::insert(&id, code_hash);
}
Heads::<T>::insert(&id, &genesis_data.genesis_head);
MostRecentContext::<T>::insert(&id, BlockNumberFor::<T>::from(0u32));
}
#[cfg(test)]
pub(crate) fn active_vote_state(
code_hash: &ValidationCodeHash,
) -> Option<PvfCheckActiveVoteState<BlockNumberFor<T>>> {
PvfActiveVoteMap::<T>::get(code_hash)
}
pub(crate) fn validate_code_is_authorized(
code: &ValidationCode,
para: &ParaId,
) -> Result<AuthorizedCodeHashAndExpiry<BlockNumberFor<T>>, Error<T>> {
let authorized = AuthorizedCodeHash::<T>::get(para).ok_or(Error::<T>::NothingAuthorized)?;
let now = pezframe_system::Pezpallet::<T>::block_number();
ensure!(authorized.expire_at > now, Error::<T>::InvalidBlockNumber);
ensure!(authorized.code_hash == code.hash(), Error::<T>::Unauthorized);
Ok(authorized)
}
}
pub(crate) struct TeyrchainsCache<T: Config> {
teyrchains: Option<BTreeSet<ParaId>>,
_config: PhantomData<T>,
}
impl<T: Config> TeyrchainsCache<T> {
pub fn new() -> Self {
Self { teyrchains: None, _config: PhantomData }
}
fn ensure_initialized(&mut self) -> &mut BTreeSet<ParaId> {
self.teyrchains
.get_or_insert_with(|| Teyrchains::<T>::get().into_iter().collect())
}
pub fn add(&mut self, id: ParaId) {
let teyrchains = self.ensure_initialized();
teyrchains.insert(id);
}
pub fn remove(&mut self, id: ParaId) {
let teyrchains = self.ensure_initialized();
teyrchains.remove(&id);
}
}
impl<T: Config> Drop for TeyrchainsCache<T> {
fn drop(&mut self) {
if let Some(teyrchains) = self.teyrchains.take() {
Teyrchains::<T>::put(teyrchains.into_iter().collect::<Vec<ParaId>>());
}
}
}