diff --git a/runtime/parachains/src/paras.rs b/runtime/parachains/src/paras.rs index 2bcef84740c8..6b629cc97d96 100644 --- a/runtime/parachains/src/paras.rs +++ b/runtime/parachains/src/paras.rs @@ -14,14 +14,97 @@ // You should have received a copy of the GNU General Public License // along with Polkadot. If not, see . -//! The paras pallet is responsible for storing data on parachains and parathreads. +//! The paras pallet acts as the main registry of paras. //! -//! It tracks which paras are parachains, what their current head data is in -//! this fork of the relay chain, what their validation code is, and what their past and upcoming -//! validation code is. +//! # 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│ +//! └─────┘ +//! ``` //! -//! A para is not considered live until it is registered and activated in this pallet. Activation can -//! only occur at session boundaries. use crate::{configuration, initializer::SessionChangeNotification, shared}; use bitvec::{order::Lsb0 as BitOrderLsb0, vec::BitVec}; @@ -289,10 +372,11 @@ impl PvfCheckActiveVoteState { /// Returns `None` if the quorum is not reached, or the direction of the decision. fn quorum(&self, n_validators: usize) -> Option { let q_threshold = primitives::v1::supermajority_threshold(n_validators); - if self.votes_accept.count_ones() >= q_threshold { - Some(PvfCheckOutcome::Accepted) - } else if self.votes_reject.count_ones() >= q_threshold { + // 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 } @@ -706,6 +790,7 @@ pub mod pallet { let mut active_vote = PvfActiveVoteMap::::get(&stmt.subject) .ok_or(Error::::PvfCheckSubjectInvalid)?; + // Ensure that the validator submitting this statement hasn't voted already. ensure!( !active_vote .has_vote(validator_index) @@ -1463,7 +1548,9 @@ impl Pallet { // // Any candidate that attempts to do that should be rejected by // `can_upgrade_validation_code`. - UpgradeGoAheadSignal::::insert(&id, UpgradeGoAhead::Abort); + // + // NOTE: we cannot set `UpgradeGoAheadSignal` signal here since this will be reset by + // the following call `note_new_head` log::warn!( target: "runtime::paras", "ended up scheduling an upgrade while one is pending", @@ -1477,11 +1564,12 @@ impl Pallet { // process right away. weight += T::DbWeight::get().reads(1); if CurrentCodeHash::::get(&id) == Some(code_hash) { - log::info!( + // NOTE: we cannot set `UpgradeGoAheadSignal` signal here since this will be reset by + // the following call `note_new_head` + log::warn!( target: "runtime::paras", "para tried to upgrade to the same code. Abort the upgrade", ); - UpgradeGoAheadSignal::::insert(&id, UpgradeGoAhead::Abort); return weight }