Skip to content
This repository has been archived by the owner on Nov 15, 2023. It is now read-only.

Commit

Permalink
paras: immediately reject any PVF that cannot reach a supermajority
Browse files Browse the repository at this point in the history
  • Loading branch information
mrcnski committed Jan 2, 2023
1 parent 30c3215 commit d14c979
Show file tree
Hide file tree
Showing 2 changed files with 32 additions and 17 deletions.
32 changes: 17 additions & 15 deletions runtime/parachains/src/paras/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -73,13 +73,14 @@
//!
//! # 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.
//! 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
//! getting a supermajority becomes impossible (>1/3 of validators have already voted against), then
//! we reject.
//!
//! Below is a state diagram that depicts states of a single PVF pre-checking vote.
//!
Expand All @@ -92,8 +93,8 @@
//! │ │ │
//! │ ┌───────┐
//! │ │ │
//! └─▶│ init │────supermajority ┌──────────┐
//! │ │ against │ │
//! └─▶│ init │──── >1/3 against ┌──────────┐
//! │ │ │ │
//! └───────┘ └──────────▶│ rejected │
//! ▲ │ │ │
//! │ │ session └──────────┘
Expand Down Expand Up @@ -452,12 +453,13 @@ impl<BlockNumber> PvfCheckActiveVoteState<BlockNumber> {

/// 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 {
let accept_threshold = primitives::v2::supermajority_threshold(n_validators);
let reject_threshold = primitives::v2::byzantine_threshold(n_validators);

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
}
Expand Down Expand Up @@ -1011,7 +1013,7 @@ pub mod pallet {
}

if let Some(outcome) = active_vote.quorum(validators.len()) {
// The supermajority quorum has been achieved.
// The quorum has been achieved.
//
// Remove the PVF vote from the active map and finalize the PVF checking according
// to the outcome.
Expand Down
17 changes: 15 additions & 2 deletions runtime/parachains/src/paras/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1252,8 +1252,21 @@ fn pvf_check_upgrade_reject() {
Paras::schedule_code_upgrade(a, new_code.clone(), RELAY_PARENT, &Configuration::config());
check_code_is_stored(&new_code);

// Supermajority of validators vote against `new_code`. PVF should be rejected.
IntoIterator::into_iter([0, 1, 2, 3])
// 1/3 of validators vote against `new_code`. PVF should not be rejected yet.
IntoIterator::into_iter([0])
.map(|i| PvfCheckStatement {
accept: false,
subject: new_code.hash(),
session_index: EXPECTED_SESSION,
validator_index: i.into(),
})
.for_each(sign_and_include_pvf_check_statement);

// Verify that the new code is not yet discarded.
check_code_is_stored(&new_code);

// >1/3 of validators vote against `new_code`. PVF should be rejected.
IntoIterator::into_iter([1])
.map(|i| PvfCheckStatement {
accept: false,
subject: new_code.hash(),
Expand Down

0 comments on commit d14c979

Please sign in to comment.