Newer
Older
// Copyright 2020 Parity Technologies (UK) Ltd.
// This file is part of Polkadot.
// Polkadot is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// Polkadot is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
// You should have received a copy of the GNU General Public License
// along with Polkadot. If not, see <http://www.gnu.org/licenses/>.
//! The paras pallet acts as the main registry of paras.
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
//! # Tracking State of Paras
//!
//! The most important responsibility of this module is to track which parachains and parathreads
//! are active and what their current state is. The current state of a para consists of the current
//! head data and the current validation code (AKA Parachain Validation Function (PVF)).
//!
//! A para is not considered live until it is registered and activated in this pallet.
//!
//! The set of parachains and parathreads cannot change except at session boundaries. This is
//! primarily to ensure that the number and meaning of bits required for the availability bitfields
//! does not change except at session boundaries.
//!
//! # Validation Code Upgrades
//!
//! When a para signals the validation code upgrade it will be processed by this module. This can
//! be in turn split into more fine grained items:
//!
//! - Part of the acceptance criteria checks if the para can indeed signal an upgrade,
//!
//! - When the candidate is enacted, this module schedules code upgrade, storing the prospective
//! validation code.
//!
//! - Actually assign the prospective validation code to be the current one after all conditions are
//! fulfilled.
//!
//! The conditions that must be met before the para can use the new validation code are:
//!
//! 1. The validation code should have been "soaked" in the storage for a given number of blocks. That
//! is, the validation code should have been stored in on-chain storage for some time, so that in
//! case of a revert with a non-extreme height difference, that validation code can still be
//! found on-chain.
//!
//! 2. The validation code was vetted by the validators and declared as non-malicious in a processes
//! known as PVF pre-checking.
//!
//! # Validation Code Management
//!
//! Potentially, one validation code can be used by several different paras. For example, during
//! initial stages of deployment several paras can use the same "shell" validation code, or
//! there can be shards of the same para that use the same validation code.
//!
//! In case a validation code ceases to have any users it must be pruned from the on-chain storage.
//!
//! # Para Lifecycle Management
//!
//! A para can be in one of the two stable states: it is either a parachain or a parathread.
//!
//! However, in order to get into one of those two states, it must first be onboarded. Onboarding
//! can be only enacted at session boundaries. Onboarding must take at least one full session.
//! Moreover, a brand new validation code should go through the PVF pre-checking process.
//!
//! Once the para is in one of the two stable states, it can switch to the other stable state or to
//! initiate offboarding process. The result of offboarding is removal of all data related to that
//! para.
//!
//! # PVF Pre-checking
//!
//! As was mentioned above, a brand new validation code should go through a process of approval.
//! As part of this process, validators from the active set will take the validation code and
//! check if it is malicious. Once they did that and have their judgement, either accept or reject,
//! they issue a statement in a form of an unsigned extrinsic. This extrinsic is processed by this
//! pallet. Once supermajority is gained for accept, then the process that initiated the check
//! is resumed (as mentioned before this can be either upgrading of validation code or onboarding).
//! If supermajority is gained for reject, then the process is canceled.
//!
//! Below is a state diagram that depicts states of a single PVF pre-checking vote.
//!
//! ```text
//! ┌──────────┐
//! supermajority │ │
//! ┌────────for───────────▶│ accepted │
//! vote────┐ │ │ │
//! │ │ │ └──────────┘
//! │ │ │
//! │ ┌───────┐
//! │ │ │
//! └─▶│ init │────supermajority ┌──────────┐
//! │ │ against │ │
//! └───────┘ └──────────▶│ rejected │
//! ▲ │ │ │
//! │ │ session └──────────┘
//! │ └──change
//! │ │
//! │ ▼
//! ┌─────┐
//! start──────▶│reset│
//! └─────┘
//! ```
use crate::{configuration, initializer::SessionChangeNotification, shared};
use bitvec::{order::Lsb0 as BitOrderLsb0, vec::BitVec};
use frame_support::{pallet_prelude::*, traits::EstimateNextSessionRotation};
use frame_system::pallet_prelude::*;
use parity_scale_codec::{Decode, Encode};
use primitives::v2::{
ConsensusLog, HeadData, Id as ParaId, PvfCheckStatement, SessionIndex, UpgradeGoAhead,
UpgradeRestriction, ValidationCode, ValidationCodeHash, ValidatorSignature,
use scale_info::TypeInfo;
use sp_runtime::{
traits::{AppVerify, One},
DispatchResult, SaturatedConversion,
};
#[cfg(feature = "runtime-benchmarks")]
pub(crate) mod benchmarking;
const LOG_TARGET: &str = "runtime::paras";
// the two key times necessary to track for every code replacement.
#[derive(Default, Encode, Decode, TypeInfo)]
#[cfg_attr(test, derive(Debug, Clone, PartialEq))]
pub struct ReplacementTimes<N> {
/// The relay-chain block number that the code upgrade was expected to be activated.
/// This is when the code change occurs from the para's perspective - after the
/// first parablock included with a relay-parent with number >= this value.
expected_at: N,
/// The relay-chain block number at which the parablock activating the code upgrade was
/// actually included. This means considered included and available, so this is the time at which
/// that parablock enters the acceptance period in this fork of the relay-chain.
activated_at: N,
}
/// Metadata used to track previous parachain validation code that we keep in
/// the state.
#[derive(Default, Encode, Decode, TypeInfo)]
#[cfg_attr(test, derive(Debug, Clone, PartialEq))]
pub struct ParaPastCodeMeta<N> {
/// Block numbers where the code was expected to be replaced and where the code
/// was actually replaced, respectively. The first is used to do accurate lookups
/// of historic code in historic contexts, whereas the second is used to do
/// pruning on an accurate timeframe. These can be used as indices
/// into the `PastCodeHash` map along with the `ParaId` to fetch the code itself.
upgrade_times: Vec<ReplacementTimes<N>>,
/// Tracks the highest pruned code-replacement, if any. This is the `activated_at` value,
/// not the `expected_at` value.
last_pruned: Option<N>,
}
/// The possible states of a para, to take into account delayed lifecycle changes.
///
/// If the para is in a "transition state", it is expected that the parachain is
/// queued in the `ActionsQueue` to transition it into a stable state. Its lifecycle
/// state will be used to determine the state transition to apply to the para.
#[derive(PartialEq, Eq, Clone, Encode, Decode, RuntimeDebug, TypeInfo)]
pub enum ParaLifecycle {
/// Para is new and is onboarding as a Parathread or Parachain.
Onboarding,
/// Para is a Parathread.
Parathread,
/// Para is a Parachain.
Parachain,
/// Para is a Parathread which is upgrading to a Parachain.
UpgradingParathread,
/// Para is a Parachain which is downgrading to a Parathread.
DowngradingParachain,
/// Parathread is queued to be offboarded.
OffboardingParathread,
/// Parachain is queued to be offboarded.
OffboardingParachain,
}
impl ParaLifecycle {
/// Returns true if parachain is currently onboarding. To learn if the
/// parachain is onboarding as a parachain or parathread, look at the
/// `UpcomingGenesis` storage item.
pub fn is_onboarding(&self) -> bool {
matches!(self, ParaLifecycle::Onboarding)
}
/// Returns true if para is in a stable state, i.e. it is currently
/// a parachain or parathread, and not in any transition state.
pub fn is_stable(&self) -> bool {
matches!(self, ParaLifecycle::Parathread | ParaLifecycle::Parachain)
}
/// Returns true if para is currently treated as a parachain.
/// This also includes transitioning states, so you may want to combine
/// this check with `is_stable` if you specifically want `Paralifecycle::Parachain`.
pub fn is_parachain(&self) -> bool {
ParaLifecycle::Parachain |
ParaLifecycle::DowngradingParachain |
ParaLifecycle::OffboardingParachain
/// Returns true if para is currently treated as a parathread.
/// This also includes transitioning states, so you may want to combine
/// this check with `is_stable` if you specifically want `Paralifecycle::Parathread`.
pub fn is_parathread(&self) -> bool {
ParaLifecycle::Parathread |
ParaLifecycle::UpgradingParathread |
ParaLifecycle::OffboardingParathread
/// Returns true if para is currently offboarding.
pub fn is_offboarding(&self) -> bool {
matches!(self, ParaLifecycle::OffboardingParathread | ParaLifecycle::OffboardingParachain)
/// Returns true if para is in any transitionary state.
pub fn is_transitioning(&self) -> bool {
!Self::is_stable(self)
}
}
impl<N: Ord + Copy + PartialEq> ParaPastCodeMeta<N> {
// note a replacement has occurred at a given block number.
pub(crate) fn note_replacement(&mut self, expected_at: N, activated_at: N) {
self.upgrade_times.push(ReplacementTimes { expected_at, activated_at })
}
/// Returns `true` if the upgrade logs list is empty.
fn is_empty(&self) -> bool {
self.upgrade_times.is_empty()
}
// The block at which the most recently tracked code change occurred, from the perspective
// of the para.
fn most_recent_change(&self) -> Option<N> {
self.upgrade_times.last().map(|x| x.expected_at.clone())
}
// prunes all code upgrade logs occurring at or before `max`.
// note that code replaced at `x` is the code used to validate all blocks before
// `x`. Thus, `max` should be outside of the slashing window when this is invoked.
//
// Since we don't want to prune anything inside the acceptance period, and the parablock only
// enters the acceptance period after being included, we prune based on the activation height of
// the code change, not the expected height of the code change.
//
// returns an iterator of block numbers at which code was replaced, where the replaced
// code should be now pruned, in ascending order.
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 {
// no-op prune.
self.upgrade_times.drain(self.upgrade_times.len()..)
} else {
// if we are actually pruning something, update the `last_pruned` member.
self.last_pruned = Some(self.upgrade_times[to_prune - 1].activated_at);
self.upgrade_times.drain(..to_prune)
};
drained.map(|times| times.expected_at)
}
}
/// Arguments for initializing a para.
#[derive(PartialEq, Eq, Clone, Encode, Decode, RuntimeDebug, TypeInfo)]
#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]
pub struct ParaGenesisArgs {
/// The initial head data to use.
/// The initial validation code to use.
/// True if parachain, false if parathread.
/// This enum describes a reason why a particular PVF pre-checking vote was initiated. When the
/// PVF vote in question is concluded, this enum indicates what changes should be performed.
#[derive(Encode, Decode, TypeInfo)]
enum PvfCheckCause<BlockNumber> {
/// PVF vote was initiated by the initial onboarding process of the given para.
Onboarding(ParaId),
/// PVF vote was initiated by signalling of an upgrade by the given para.
Upgrade {
/// The ID of the parachain that initiated or is waiting for the conclusion of pre-checking.
id: ParaId,
/// The relay-chain block number that was used as the relay-parent for the parablock that
/// initiated the upgrade.
relay_parent_number: BlockNumber,
},
}
impl<BlockNumber> PvfCheckCause<BlockNumber> {
/// Returns the ID of the para that initiated or subscribed to the pre-checking vote.
fn para_id(&self) -> ParaId {
match *self {
PvfCheckCause::Onboarding(id) => id,
PvfCheckCause::Upgrade { id, .. } => id,
}
}
}
/// Specifies what was the outcome of a PVF pre-checking vote.
#[derive(Copy, Clone, Encode, Decode, RuntimeDebug, TypeInfo)]
enum PvfCheckOutcome {
Accepted,
Rejected,
}
/// This struct describes the current state of an in-progress PVF pre-checking vote.
#[derive(Encode, Decode, TypeInfo)]
struct PvfCheckActiveVoteState<BlockNumber> {
// The two following vectors have their length equal to the number of validators in the active
// set. They start with all zeroes. A 1 is set at an index when the validator at the that index
// makes a vote. Once a 1 is set for either of the vectors, that validator cannot vote anymore.
// Since the active validator set changes each session, the bit vectors are reinitialized as
// well: zeroed and resized so that each validator gets its own bit.
votes_accept: BitVec<u8, BitOrderLsb0>,
votes_reject: BitVec<u8, BitOrderLsb0>,
/// The number of session changes this PVF vote has observed. Therefore, this number is
/// increased at each session boundary. When created, it is initialized with 0.
age: SessionIndex,
/// The block number at which this PVF vote was created.
created_at: BlockNumber,
/// A list of causes for this PVF pre-checking. Has at least one.
causes: Vec<PvfCheckCause<BlockNumber>>,
}
impl<BlockNumber> PvfCheckActiveVoteState<BlockNumber> {
/// Returns a new instance of vote state, started at the specified block `now`, with the
/// number of validators in the current session `n_validators` and the originating `cause`.
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],
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
age: 0,
causes,
}
}
/// Resets all votes and resizes the votes vectors corresponding to the number of validators
/// in the new session.
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);
}
/// Returns `Some(true)` if the validator at the given index has already cast their vote within
/// the ongoing session. Returns `None` in case the index is out of bounds.
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)
}
/// Returns `None` if the quorum is not reached, or the direction of the decision.
fn quorum(&self, n_validators: usize) -> Option<PvfCheckOutcome> {
let q_threshold = primitives::v2::supermajority_threshold(n_validators);
// NOTE: counting the reject votes is deliberately placed first. This is to err on the safe.
if self.votes_reject.count_ones() >= q_threshold {
Some(PvfCheckOutcome::Rejected)
} else if self.votes_accept.count_ones() >= q_threshold {
Some(PvfCheckOutcome::Accepted)
} else {
None
}
}
}
pub trait WeightInfo {
fn force_set_current_code(c: u32) -> Weight;
fn force_set_current_head(s: u32) -> 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 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;
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_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::MAX
}
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 {
// This special value is to distinguish from the finalizing variants above in tests.
Weight::MAX - Weight::from_ref_time(1)
}
#[frame_support::pallet]
pub mod pallet {
use super::*;
use sp_runtime::transaction_validity::{
InvalidTransaction, TransactionPriority, TransactionSource, TransactionValidity,
ValidTransaction,
};
#[pallet::pallet]
#[pallet::generate_store(pub(super) trait Store)]
#[pallet::without_storage_info]
pub struct Pallet<T>(_);
#[pallet::config]
pub trait Config:
frame_system::Config
+ configuration::Config
+ shared::Config
+ frame_system::offchain::SendTransactionTypes<Call<Self>>
{
type RuntimeEvent: From<Event> + IsType<<Self as frame_system::Config>::RuntimeEvent>;
#[pallet::constant]
type UnsignedPriority: Get<TransactionPriority>;
type NextSessionRotation: EstimateNextSessionRotation<Self::BlockNumber>;
/// Weight information for extrinsics in this pallet.
type WeightInfo: WeightInfo;
#[pallet::event]
#[pallet::generate_deposit(pub(super) fn deposit_event)]
pub enum Event {
/// Current code has been updated for a Para. `para_id`
/// Current head has been updated for a Para. `para_id`
/// A code upgrade has been scheduled for a Para. `para_id`
/// A new head has been noted for a Para. `para_id`
/// A para has been queued to execute pending actions. `para_id`
/// The given para either initiated or subscribed to a PVF check for the given validation
/// code. `code_hash` `para_id`
PvfCheckStarted(ValidationCodeHash, ParaId),
/// The given validation code was accepted by the PVF pre-checking vote.
/// `code_hash` `para_id`
PvfCheckAccepted(ValidationCodeHash, ParaId),
/// The given validation code was rejected by the PVF pre-checking vote.
/// `code_hash` `para_id`
PvfCheckRejected(ValidationCodeHash, ParaId),
/// Para is not registered in our system.
NotRegistered,
/// Para cannot be onboarded because it is already tracked by our system.
CannotOnboard,
/// Para cannot be offboarded at this time.
CannotOffboard,
/// Para cannot be upgraded to a parachain.
CannotUpgrade,
/// Para cannot be downgraded to a parathread.
CannotDowngrade,
/// The statement for PVF pre-checking is stale.
PvfCheckStatementStale,
/// The statement for PVF pre-checking is for a future session.
PvfCheckStatementFuture,
/// Claimed validator index is out of bounds.
PvfCheckValidatorIndexOutOfBounds,
/// The signature for the PVF pre-checking is invalid.
PvfCheckInvalidSignature,
/// The given validator already has cast a vote.
PvfCheckDoubleVote,
/// The given PVF does not exist at the moment of process a vote.
PvfCheckSubjectInvalid,
/// The PVF pre-checking statement cannot be included since the PVF pre-checking mechanism
/// is disabled.
PvfCheckDisabled,
}
/// All currently active PVF pre-checking votes.
///
/// Invariant:
/// - There are no PVF pre-checking votes that exists in list but not in the set and vice versa.
#[pallet::storage]
pub(super) type PvfActiveVoteMap<T: Config> = StorageMap<
_,
Twox64Concat,
ValidationCodeHash,
PvfCheckActiveVoteState<T::BlockNumber>,
OptionQuery,
>;
/// The list of all currently active PVF votes. Auxiliary to `PvfActiveVoteMap`.
#[pallet::storage]
pub(super) type PvfActiveVoteList<T: Config> =
StorageValue<_, Vec<ValidationCodeHash>, ValueQuery>;
/// All parachains. Ordered ascending by `ParaId`. Parathreads are not included.
///
/// Consider using the [`ParachainsCache`] type of modifying.
#[pallet::storage]
#[pallet::getter(fn parachains)]
pub(crate) type Parachains<T: Config> = StorageValue<_, Vec<ParaId>, ValueQuery>;
/// The current lifecycle of a all known Para IDs.
#[pallet::storage]
pub(super) type ParaLifecycles<T: Config> = StorageMap<_, Twox64Concat, ParaId, ParaLifecycle>;
/// The head-data of every registered para.
#[pallet::storage]
#[pallet::getter(fn para_head)]
pub(super) type Heads<T: Config> = StorageMap<_, Twox64Concat, ParaId, HeadData>;
/// The validation code hash of every live para.
///
/// Corresponding code can be retrieved with [`CodeByHash`].
#[pallet::storage]
#[pallet::getter(fn current_code_hash)]
pub(super) type CurrentCodeHash<T: Config> =
StorageMap<_, Twox64Concat, ParaId, ValidationCodeHash>;
/// Actual past code hash, indicated by the para id as well as the block number at which it
/// became outdated.
///
/// Corresponding code can be retrieved with [`CodeByHash`].
#[pallet::storage]
pub(super) type PastCodeHash<T: Config> =
StorageMap<_, Twox64Concat, (ParaId, T::BlockNumber), ValidationCodeHash>;
/// Past code of parachains. The parachains themselves may not be registered anymore,
/// but we also keep their code on-chain for the same amount of time as outdated code
/// to keep it available for secondary checkers.
#[pallet::storage]
#[pallet::getter(fn past_code_meta)]
pub(super) type PastCodeMeta<T: Config> =
StorageMap<_, Twox64Concat, ParaId, ParaPastCodeMeta<T::BlockNumber>, ValueQuery>;
/// Which paras have past code that needs pruning and the relay-chain block at which the code was replaced.
/// Note that this is the actual height of the included block, not the expected height at which the
/// code upgrade would be applied, although they may be equal.
/// This is to ensure the entire acceptance period is covered, not an offset acceptance period starting
/// from the time at which the parachain perceives a code upgrade as having occurred.
/// Multiple entries for a single para are permitted. Ordered ascending by block number.
#[pallet::storage]
pub(super) type PastCodePruning<T: Config> =
StorageValue<_, Vec<(ParaId, T::BlockNumber)>, ValueQuery>;
/// The block number at which the planned code change is expected for a para.
/// The change will be applied after the first parablock for this ID included which executes
/// in the context of a relay chain block with a number >= `expected_at`.
#[pallet::storage]
#[pallet::getter(fn future_code_upgrade_at)]
pub(super) type FutureCodeUpgrades<T: Config> =
StorageMap<_, Twox64Concat, ParaId, T::BlockNumber>;
/// The actual future code hash of a para.
///
/// Corresponding code can be retrieved with [`CodeByHash`].
#[pallet::storage]
pub(super) type FutureCodeHash<T: Config> =
StorageMap<_, Twox64Concat, ParaId, ValidationCodeHash>;
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
/// This is used by the relay-chain to communicate to a parachain a go-ahead with in the upgrade procedure.
///
/// This value is absent when there are no upgrades scheduled or during the time the relay chain
/// performs the checks. It is set at the first relay-chain block when the corresponding parachain
/// can switch its upgrade function. As soon as the parachain's block is included, the value
/// gets reset to `None`.
///
/// NOTE that this field is used by parachains via merkle storage proofs, therefore changing
/// the format will require migration of parachains.
#[pallet::storage]
pub(super) type UpgradeGoAheadSignal<T: Config> =
StorageMap<_, Twox64Concat, ParaId, UpgradeGoAhead>;
/// This is used by the relay-chain to communicate that there are restrictions for performing
/// an upgrade for this parachain.
///
/// This may be a because the parachain waits for the upgrade cooldown to expire. Another
/// potential use case is when we want to perform some maintenance (such as storage migration)
/// we could restrict upgrades to make the process simpler.
///
/// NOTE that this field is used by parachains via merkle storage proofs, therefore changing
/// the format will require migration of parachains.
#[pallet::storage]
pub(super) type UpgradeRestrictionSignal<T: Config> =
StorageMap<_, Twox64Concat, ParaId, UpgradeRestriction>;
/// The list of parachains that are awaiting for their upgrade restriction to cooldown.
///
/// Ordered ascending by block number.
#[pallet::storage]
pub(super) type UpgradeCooldowns<T: Config> =
StorageValue<_, Vec<(ParaId, T::BlockNumber)>, ValueQuery>;
/// The list of upcoming code upgrades. Each item is a pair of which para performs a code
/// upgrade and at which relay-chain block it is expected at.
///
/// Ordered ascending by block number.
#[pallet::storage]
pub(super) type UpcomingUpgrades<T: Config> =
StorageValue<_, Vec<(ParaId, T::BlockNumber)>, ValueQuery>;
/// The actions to perform during the start of a specific session index.
#[pallet::storage]
#[pallet::getter(fn actions_queue)]
pub(super) type ActionsQueue<T: Config> =
StorageMap<_, Twox64Concat, SessionIndex, Vec<ParaId>, ValueQuery>;
/// Upcoming paras instantiation arguments.
///
/// NOTE that after PVF pre-checking is enabled the para genesis arg will have it's code set
/// to empty. Instead, the code will be saved into the storage right away via `CodeByHash`.
pub(super) type UpcomingParasGenesis<T: Config> =
StorageMap<_, Twox64Concat, ParaId, ParaGenesisArgs>;
/// The number of reference on the validation code in [`CodeByHash`] storage.
#[pallet::storage]
pub(super) type CodeByHashRefs<T: Config> =
StorageMap<_, Identity, ValidationCodeHash, u32, ValueQuery>;
/// Validation code stored by its hash.
///
/// This storage is consistent with [`FutureCodeHash`], [`CurrentCodeHash`] and
/// [`PastCodeHash`].
#[pallet::storage]
#[pallet::getter(fn code_by_hash)]
pub(super) type CodeByHash<T: Config> =
StorageMap<_, Identity, ValidationCodeHash, ValidationCode>;
#[pallet::genesis_config]
pub struct GenesisConfig {
pub paras: Vec<(ParaId, ParaGenesisArgs)>,
}
#[cfg(feature = "std")]
impl Default for GenesisConfig {
fn default() -> Self {
#[pallet::genesis_build]
impl<T: Config> GenesisBuild<T> for GenesisConfig {
fn build(&self) {
let mut parachains = ParachainsCache::new();
if genesis_args.validation_code.0.is_empty() {
panic!("empty validation code is not allowed in genesis");
Pallet::<T>::initialize_para_now(&mut parachains, *id, genesis_args);
// parachains are flushed on drop
#[pallet::call]
impl<T: Config> Pallet<T> {
/// Set the storage for the parachain validation code immediately.
#[pallet::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 {
let maybe_prior_code_hash = <Self as Store>::CurrentCodeHash::get(¶);
let new_code_hash = new_code.hash();
Self::increase_code_ref(&new_code_hash, &new_code);
<Self as Store>::CurrentCodeHash::insert(¶, new_code_hash);
let now = frame_system::Pallet::<T>::block_number();
if let Some(prior_code_hash) = maybe_prior_code_hash {
Self::note_past_code(para, now, now, prior_code_hash);
} else {
log::error!(
"Pallet paras storage is inconsistent, prior code not found {:?}",
¶
);
}
Self::deposit_event(Event::CurrentCodeUpdated(para));
}
/// Set the storage for the current parachain head data immediately.
#[pallet::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 as Store>::Heads::insert(¶, new_head);
Self::deposit_event(Event::CurrentHeadUpdated(para));
/// Schedule an upgrade as if it was scheduled in the given relay parent block.
#[pallet::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: T::BlockNumber,
let config = configuration::Pallet::<T>::config();
Self::schedule_code_upgrade(para, new_code, relay_parent_number, &config);
Self::deposit_event(Event::CodeUpgradeScheduled(para));
}
/// Note a new block head for para within the context of the current block.
#[pallet::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 = frame_system::Pallet::<T>::block_number();
Self::note_new_head(para, new_head, now);
Self::deposit_event(Event::NewHeadNoted(para));
}
/// Put a parachain directly into the next session's action queue.
/// We can't queue it any sooner than this without going into the
/// initializer...
#[pallet::weight(<T as Config>::WeightInfo::force_queue_action())]
pub fn force_queue_action(origin: OriginFor<T>, para: ParaId) -> DispatchResult {
let next_session = shared::Pallet::<T>::session_index().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));
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
/// Adds the validation code to the storage.
///
/// The code will not be added if it is already present. Additionally, if PVF pre-checking
/// is running for that code, it will be instantly accepted.
///
/// Otherwise, the code will be added into the storage. Note that the code will be added
/// into storage with reference count 0. This is to account the fact that there are no users
/// for this code yet. The caller will have to make sure that this code eventually gets
/// used by some parachain or removed from the storage to avoid storage leaks. For the latter
/// prefer to use the `poke_unused_validation_code` dispatchable to raw storage manipulation.
///
/// This function is mainly meant to be used for upgrading parachains that do not follow
/// the go-ahead signal while the PVF pre-checking feature is enabled.
#[pallet::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) = <Self as Store>::PvfActiveVoteMap::get(&code_hash) {
// Remove the existing vote.
PvfActiveVoteMap::<T>::remove(&code_hash);
PvfActiveVoteList::<T>::mutate(|l| {
if let Ok(i) = l.binary_search(&code_hash) {
l.remove(i);
}
});
let cfg = configuration::Pallet::<T>::config();
Self::enact_pvf_accepted(
<frame_system::Pallet<T>>::block_number(),
&code_hash,
&vote.causes,
vote.age,
&cfg,
);
return Ok(())
}
if <Self as Store>::CodeByHash::contains_key(&code_hash) {
// There is no vote, but the code exists. Nothing to do here.
return Ok(())
}
// At this point the code is unknown and there is no PVF pre-checking vote for it, so we
// can just add the code into the storage.
//
// NOTE That we do not use `increase_code_ref` here, because the code is not yet used
// by any parachain.
<Self as Store>::CodeByHash::insert(code_hash, &validation_code);
Ok(())
}
/// Remove the validation code from the storage iff the reference count is 0.
///
/// This is better than removing the storage directly, because it will not remove the code
/// that was suddenly got used by some parachain while this dispatchable was pending
/// dispatching.
#[pallet::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 <Self as Store>::CodeByHashRefs::get(&validation_code_hash) == 0 {
<Self as Store>::CodeByHash::remove(&validation_code_hash);
}
Ok(())
}
/// Includes a statement for a PVF pre-checking vote. Potentially, finalizes the vote and
/// enacts the results if that was the last vote before achieving the supermajority.
#[pallet::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)?;
// Make sure that PVF pre-checking is enabled.
ensure!(
configuration::Pallet::<T>::config().pvf_checking_enabled,
Error::<T>::PvfCheckDisabled,
);
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
let validators = shared::Pallet::<T>::active_validator_keys();
let current_session = shared::Pallet::<T>::session_index();
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 that the validator submitting this statement hasn't voted already.
ensure!(
!active_vote
.has_vote(validator_index)
.ok_or(Error::<T>::PvfCheckValidatorIndexOutOfBounds)?,
Error::<T>::PvfCheckDoubleVote,
);
// Finally, cast the vote and persist.
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()) {
// The supermajority quorum has been achieved.
//
// Remove the PVF vote from the active map and finalize the PVF checking according
// to the outcome.
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::Pallet::<T>::config();
Self::enact_pvf_accepted(
<frame_system::Pallet<T>>::block_number(),
&stmt.subject,
&active_vote.causes,
active_vote.age,
&cfg,
);
},
PvfCheckOutcome::Rejected => {
Self::enact_pvf_rejected(&stmt.subject, active_vote.causes);
},
}
// No weight refund since this statement was the last one and lead to finalization.
Ok(().into())
} else {
// No quorum has been achieved.
//
// - So just store the updated state back into the storage.
// - Only charge weight for simple vote inclusion.
PvfActiveVoteMap::<T>::insert(&stmt.subject, active_vote);
Ok(Some(<T as Config>::WeightInfo::include_pvf_check_statement()).into())
}
}
}
#[pallet::validate_unsigned]
impl<T: Config> ValidateUnsigned for Pallet<T> {
type Call = Call<T>;
fn validate_unsigned(_source: TransactionSource, call: &Self::Call) -> TransactionValidity {
let (stmt, signature) = match call {
Call::include_pvf_check_statement { stmt, signature } => (stmt, signature),
_ => return InvalidTransaction::Call.into(),
};
if !configuration::Pallet::<T>::config().pvf_checking_enabled {
return InvalidTransaction::Custom(INVALID_TX_PVF_CHECK_DISABLED).into()
}
let current_session = shared::Pallet::<T>::session_index();
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::Pallet::<T>::active_validator_keys();
let validator_public = match validators.get(validator_index) {
Some(pk) => pk,