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

Some late short-term fixes for dispute slashing #6249

Merged
merged 49 commits into from
Feb 1, 2023
Merged
Show file tree
Hide file tree
Changes from 9 commits
Commits
Show all changes
49 commits
Select commit Hold shift + click to select a range
80020b3
disputes/slashing: slash only backers for ForInvalid
ordian Oct 24, 2022
ed428b1
add an assertion in mock impl
ordian Nov 7, 2022
8228728
fix tests
ordian Nov 7, 2022
656ab84
do not slash backers on onconcluded disputes
ordian Nov 7, 2022
e3d7ba6
slash an intersection of backers and losers
ordian Nov 9, 2022
78c1cda
Merge branch 'master' into ao-5535-followup
ordian Nov 9, 2022
59b9c0d
zombienet/disputes: check for offence only for invalid disputes
ordian Nov 10, 2022
9ee1e3b
add backing votes to disputes bench builder
ordian Nov 11, 2022
0606aab
Merge branch 'master' into ao-5535-followup
ordian Nov 11, 2022
cabbee2
Update runtime/parachains/src/builder.rs
ordian Nov 11, 2022
d7bd141
Brad implementers guide revisions 2 (#6239)
BradleyOlson64 Nov 11, 2022
adcd81e
Update disputes prioritisation in `dispute-coordinator` (#6130)
tdimitrov Nov 11, 2022
254fbc4
approval-voting: remove redundant validation check (#6266)
ordian Nov 12, 2022
b900d2a
remove fill_block (#6200)
Szegoo Nov 12, 2022
5b60a5b
fix a compilation warning (#6279)
ordian Nov 13, 2022
8d3ff43
Only report concluded if there is an actual dispute. (#6270)
eskimor Nov 14, 2022
71b5aac
[ci] fix buildah image (#6281)
alvicsam Nov 14, 2022
ed143f2
Revert special casing of Kusama for grandpa rounds. (#6217)
eskimor Nov 15, 2022
17b7567
Fixes "for loop over an `Option`" warnings (#6291)
mrcnski Nov 15, 2022
5d607f6
companion for #12599 (#6290)
niklasad1 Nov 15, 2022
a5bff0c
remove the runtime check and test
ordian Nov 15, 2022
b2b1a0d
Merge branch 'master' into ao-5535-followup
ordian Nov 15, 2022
89b03f6
Merge branch 'master' into ao-5535-followup
ordian Nov 15, 2022
c96630c
Merge branch 'master' into ao-5535-followup
ordian Nov 24, 2022
8fba9e0
append keys on past-session slashing
ordian Nov 24, 2022
9e81fa9
Merge branch 'master' into ao-5535-followup
ordian Nov 25, 2022
77e777c
runtime/disputes: allow importing backing votes after explicit for
ordian Nov 25, 2022
cf90b17
explicit MaliciousBacker error and a test
ordian Nov 26, 2022
7c4c3f5
update an outdated comment
ordian Nov 26, 2022
b47dc15
Revert "update an outdated comment"
ordian Nov 30, 2022
4e36bed
Revert "remove the runtime check and test"
ordian Nov 30, 2022
d9f337f
Merge branch 'master' into ao-5535-followup
ordian Nov 30, 2022
01386f2
incremental punishment post conclusion + test
ordian Nov 30, 2022
a08af12
punish backers post FOR vote
ordian Dec 1, 2022
b83279b
remove unnecessary lifetime annotation
ordian Dec 6, 2022
37b1d59
add a comment to zombinet test
ordian Dec 6, 2022
5f1758f
Merge branch 'master' into ao-5535-followup
ordian Dec 6, 2022
a489310
typo
ordian Dec 12, 2022
0fb61ff
Merge branch 'master' into ao-5535-followup
ordian Jan 2, 2023
6358cb1
Merge branch 'ao-5535-followup' of github.com:paritytech/polkadot int…
ordian Jan 2, 2023
b2f82ca
Merge branch 'master' into ao-5535-followup
ordian Jan 5, 2023
a1ee63a
Merge 'master' and resolve conflicts
ordian Jan 7, 2023
142d852
fmt
ordian Jan 7, 2023
8632334
post merge test fixes
ordian Jan 7, 2023
4d63258
another day, another master merge
ordian Jan 10, 2023
9d81c26
fix test after changes in master
ordian Jan 10, 2023
9d3fb1c
Merge branch 'master' into ao-5535-followup
ordian Jan 16, 2023
b8de8d6
address review nits
ordian Jan 16, 2023
036b09e
Merge branch 'master' into ao-5535-followup
ordian Feb 1, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 6 additions & 2 deletions runtime/parachains/src/builder.rs
Original file line number Diff line number Diff line change
Expand Up @@ -595,6 +595,7 @@ impl<T: paras_inherent::Config> BenchBuilder<T> {

let (para_id, core_idx, group_idx) = self.create_indexes(seed);
let candidate_hash = CandidateHash(H256::from(byte32_slice_from(seed)));
let relay_parent = H256::from(byte32_slice_from(seed));

Self::add_availability(
para_id,
Expand All @@ -614,9 +615,12 @@ impl<T: paras_inherent::Config> BenchBuilder<T> {
// so we make sure that we have a super majority with valid statements.
let dispute_statement = if validator_index % 4 == 0 {
DisputeStatement::Invalid(InvalidDisputeStatementKind::Explicit)
} else if validator_index < 3 {
// Set the last two votes as backing for the dispute set to be accepted
ordian marked this conversation as resolved.
Show resolved Hide resolved
DisputeStatement::Valid(
ValidDisputeStatementKind::BackingValid(relay_parent)
)
} else {
// Note that in the future we could use some availability votes as an
// implicit valid kind.
DisputeStatement::Valid(ValidDisputeStatementKind::Explicit)
};
let data = dispute_statement.payload_data(candidate_hash.clone(), session);
Expand Down
47 changes: 45 additions & 2 deletions runtime/parachains/src/disputes.rs
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,7 @@ pub trait SlashingHandler<BlockNumber> {
session: SessionIndex,
candidate_hash: CandidateHash,
losers: impl IntoIterator<Item = ValidatorIndex>,
backers: impl IntoIterator<Item = ValidatorIndex>,
);

/// Punish a series of validators who were against a valid parablock. This
Expand All @@ -89,6 +90,7 @@ pub trait SlashingHandler<BlockNumber> {
session: SessionIndex,
candidate_hash: CandidateHash,
losers: impl IntoIterator<Item = ValidatorIndex>,
backers: impl IntoIterator<Item = ValidatorIndex>,
);

/// Called by the initializer to initialize the slashing pallet.
Expand All @@ -106,13 +108,15 @@ impl<BlockNumber> SlashingHandler<BlockNumber> for () {
_: SessionIndex,
_: CandidateHash,
_: impl IntoIterator<Item = ValidatorIndex>,
_: impl IntoIterator<Item = ValidatorIndex>,
) {
}

fn punish_against_valid(
_: SessionIndex,
_: CandidateHash,
_: impl IntoIterator<Item = ValidatorIndex>,
_: impl IntoIterator<Item = ValidatorIndex>,
) {
}

Expand Down Expand Up @@ -459,6 +463,18 @@ pub mod pallet {
DisputeState<T::BlockNumber>,
>;

/// Backing votes stored for each dispute.
/// This storage is used for slashing.
#[pallet::storage]
pub(super) type BackersOnDisputes<T: Config> = StorageDoubleMap<
_,
Twox64Concat,
tdimitrov marked this conversation as resolved.
Show resolved Hide resolved
SessionIndex,
Blake2_128Concat,
CandidateHash,
Vec<ValidatorIndex>,
>;

/// All included blocks on the chain, as well as the block number in this chain that
/// should be reverted back to if the candidate is disputed and determined to be invalid.
#[pallet::storage]
Expand Down Expand Up @@ -521,6 +537,8 @@ pub mod pallet {
PotentialSpam,
BradleyOlson64 marked this conversation as resolved.
Show resolved Hide resolved
/// A dispute where there are only votes on one side.
SingleSidedDispute,
/// No backing votes were provides along dispute statements.
MissingBackingVotes,
}

#[pallet::call]
Expand Down Expand Up @@ -888,6 +906,8 @@ impl<T: Config> Pallet<T> {
// This should be small, as disputes are rare, so `None` is fine.
#[allow(deprecated)]
<Disputes<T>>::remove_prefix(to_prune, None);
#[allow(deprecated)]
<BackersOnDisputes<T>>::remove_prefix(to_prune, None);

// This is larger, and will be extracted to the `shared` pallet for more proper pruning.
// TODO: https://github.com/paritytech/polkadot/issues/3469
Expand Down Expand Up @@ -984,7 +1004,8 @@ impl<T: Config> Pallet<T> {
let mut summary = {
let mut importer = DisputeStateImporter::new(dispute_state, now);
for (i, (statement, validator_index, signature)) in set.statements.iter().enumerate() {
// assure the validator index and is present in the session info
// ensure the validator index is present in the session info
// and the signature is valid
let validator_public = match session_info.validators.get(*validator_index) {
None => {
filter.remove_index(i);
Expand Down Expand Up @@ -1181,13 +1202,24 @@ impl<T: Config> Pallet<T> {
}
};

let mut backers =
<BackersOnDisputes<T>>::get(&set.session, &set.candidate_hash).unwrap_or_default();

// Import all votes. They were pre-checked.
let summary = {
let mut importer = DisputeStateImporter::new(dispute_state, now);
for (statement, validator_index, _signature) in &set.statements {
let valid = statement.indicates_validity();

importer.import(*validator_index, valid).map_err(Error::<T>::from)?;

match statement {
DisputeStatement::Valid(ValidDisputeStatementKind::BackingSeconded(_)) |
DisputeStatement::Valid(ValidDisputeStatementKind::BackingValid(_)) => {
backers.push(validator_index.clone());
},
_ => {},
}
}

importer.finish()
Expand All @@ -1200,6 +1232,11 @@ impl<T: Config> Pallet<T> {
Error::<T>::SingleSidedDispute,
);

// Reject statements with no accompanying backing votes.
ensure!(!backers.is_empty(), Error::<T>::MissingBackingVotes,);
ordian marked this conversation as resolved.
Show resolved Hide resolved

<BackersOnDisputes<T>>::insert(&set.session, &set.candidate_hash, backers.clone());

let DisputeStatementSet { ref session, ref candidate_hash, .. } = set;
let session = *session;
let candidate_hash = *candidate_hash;
Expand Down Expand Up @@ -1246,10 +1283,16 @@ impl<T: Config> Pallet<T> {
session,
candidate_hash,
summary.slash_against,
backers.clone(),
);

// an invalid candidate, according to 2/3. Punish those on the 'for' side.
T::SlashingHandler::punish_for_invalid(session, candidate_hash, summary.slash_for);
T::SlashingHandler::punish_for_invalid(
session,
candidate_hash,
summary.slash_for,
backers,
);
}

<Disputes<T>>::insert(&session, &candidate_hash, &summary.state);
Expand Down
70 changes: 43 additions & 27 deletions runtime/parachains/src/disputes/slashing.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,32 +16,31 @@

//! Dispute slashing pallet.
//!
//! Once a dispute is concluded, we want to slash validators
//! who were on the wrong side of the dispute. The slashing amount
//! depends on whether the candidate was valid (small) or invalid (big).
//! In addition to that, we might want to kick out the validators from the
//! active set.
//! Once a dispute is concluded, we want to slash validators who were on the
//! wrong side of the dispute. The slashing amount depends on whether the
//! candidate was valid (none at the moment) or invalid (big). In addition to
//! that, we might want to kick out the validators from the active set.
//! Currently, we limit slashing to the backing group for invalid disputes.
//!
//! The `offences` pallet from Substrate provides us with a way to do both.
//! Currently, the interface expects us to provide staking information
//! including nominator exposure in order to submit an offence.
//! Currently, the interface expects us to provide staking information including
//! nominator exposure in order to submit an offence.
//!
//! Normally, we'd able to fetch this information from the runtime as soon as
//! the dispute is concluded. This is also what `im-online` pallet does.
//! However, since a dispute can conclude several sessions after the candidate
//! was backed (see `dispute_period` in `HostConfiguration`), we can't rely on
//! this information be available in the context of the current block. The
ordian marked this conversation as resolved.
Show resolved Hide resolved
//! `babe` and `grandpa` equivocation handlers also have to deal
//! with this problem.
//! `babe` and `grandpa` equivocation handlers also have to deal with this
//! problem.
//!
//! Our implementation looks like a hybrid of `im-online` and `grandpa`
//! equivocation handlers. Meaning, we submit an `offence` for the concluded
//! disputes about the current session candidate directly from the runtime.
//! If, however, the dispute is about a past session, we record unapplied
//! slashes on chain, without `FullIdentification` of the offenders.
//! Later on, a block producer can submit an unsigned transaction with
//! `KeyOwnershipProof` of an offender and submit it to the runtime
//! to produce an offence.
//! disputes about the current session candidate directly from the runtime. If,
//! however, the dispute is about a past session, we record unapplied slashes on
//! chain, without `FullIdentification` of the offenders. Later on, a block
//! producer can submit an unsigned transaction with `KeyOwnershipProof` of an
//! offender and submit it to the runtime to produce an offence.

use crate::{disputes, initializer::ValidatorSetCount, session_info::IdentificationTuple};
use frame_support::{
Expand All @@ -64,7 +63,10 @@ use sp_runtime::{
use sp_session::{GetSessionNumber, GetValidatorCount};
use sp_staking::offence::{DisableStrategy, Kind, Offence, OffenceError, ReportOffence};
use sp_std::{
collections::btree_map::{BTreeMap, Entry},
collections::{
btree_map::{BTreeMap, Entry},
btree_set::BTreeSet,
},
prelude::*,
};

Expand All @@ -73,7 +75,7 @@ const LOG_TARGET: &str = "runtime::parachains::slashing";
// These are constants, but we want to make them configurable
// via `HostConfiguration` in the future.
const SLASH_FOR_INVALID: Perbill = Perbill::from_percent(100);
const SLASH_AGAINST_VALID: Perbill = Perbill::from_perthousand(1);
const SLASH_AGAINST_VALID: Perbill = Perbill::zero();
const DEFENSIVE_PROOF: &'static str = "disputes module should bail on old session";

#[cfg(feature = "runtime-benchmarks")]
Expand Down Expand Up @@ -228,18 +230,30 @@ where
candidate_hash: CandidateHash,
kind: SlashingOffenceKind,
losers: impl IntoIterator<Item = ValidatorIndex>,
backers: impl IntoIterator<Item = ValidatorIndex>,
) {
let losers: Vec<ValidatorIndex> = losers.into_iter().collect();
// sanity check for the current implementation
if kind == SlashingOffenceKind::AgainstValid {
debug_assert!(false, "should only slash ForInvalid disputes");
return
}
let losers: BTreeSet<_> = losers.into_iter().collect();
if losers.is_empty() {
// Nothing to do
return
}
let backers: BTreeSet<_> = backers.into_iter().collect();
let to_punish: Vec<ValidatorIndex> = losers.intersection(&backers).cloned().collect();
if to_punish.is_empty() {
return
}

let session_info = crate::session_info::Pallet::<T>::session_info(session_index);
let session_info = match session_info.defensive_proof(DEFENSIVE_PROOF) {
Some(info) => info,
None => return,
};
let maybe = Self::maybe_identify_validators(session_index, losers.iter().cloned());

let maybe = Self::maybe_identify_validators(session_index, to_punish.iter().cloned());
if let Some(offenders) = maybe {
let validator_set_count = session_info.discovery_keys.len() as ValidatorSetCount;
let offence = SlashingOffence::new(
Expand All @@ -255,7 +269,7 @@ where
return
}

let keys = losers
let keys = to_punish
.into_iter()
.filter_map(|i| session_info.validators.get(i).cloned().map(|id| (i, id)))
.collect();
Expand All @@ -272,18 +286,20 @@ where
session_index: SessionIndex,
candidate_hash: CandidateHash,
losers: impl IntoIterator<Item = ValidatorIndex>,
backers: impl IntoIterator<Item = ValidatorIndex>,
) {
let kind = SlashingOffenceKind::ForInvalid;
Self::do_punish(session_index, candidate_hash, kind, losers);
Self::do_punish(session_index, candidate_hash, kind, losers, backers);
}

fn punish_against_valid(
session_index: SessionIndex,
candidate_hash: CandidateHash,
losers: impl IntoIterator<Item = ValidatorIndex>,
_session_index: SessionIndex,
_candidate_hash: CandidateHash,
_losers: impl IntoIterator<Item = ValidatorIndex>,
_backers: impl IntoIterator<Item = ValidatorIndex>,
) {
let kind = SlashingOffenceKind::AgainstValid;
Self::do_punish(session_index, candidate_hash, kind, losers);
// do nothing for now
// NOTE: changing that requires modifying `do_punish` implementation
}

fn initializer_initialize(now: T::BlockNumber) -> Weight {
Expand Down
7 changes: 4 additions & 3 deletions runtime/parachains/src/disputes/slashing/benchmarking.rs
Original file line number Diff line number Diff line change
Expand Up @@ -109,8 +109,9 @@ where

let validator_index = ValidatorIndex(0);
let losers = [validator_index].into_iter();
let backers = losers.clone();

T::SlashingHandler::punish_against_valid(session_index, CANDIDATE_HASH, losers);
T::SlashingHandler::punish_for_invalid(session_index, CANDIDATE_HASH, losers, backers);

let unapplied = <UnappliedSlashes<T>>::get(session_index, CANDIDATE_HASH);
assert_eq!(unapplied.unwrap().keys.len(), 1);
Expand All @@ -123,7 +124,7 @@ fn dispute_proof(
validator_id: ValidatorId,
validator_index: ValidatorIndex,
) -> DisputeProof {
let kind = SlashingOffenceKind::AgainstValid;
let kind = SlashingOffenceKind::ForInvalid;
let time_slot = DisputesTimeSlot::new(session_index, CANDIDATE_HASH);

DisputeProof { time_slot, kind, validator_index, validator_id }
Expand All @@ -134,7 +135,7 @@ benchmarks! {
where T: Config<KeyOwnerProof = MembershipProof>,
}

// in this setup we have a single `AgainstValid` dispute
// in this setup we have a single `ForInvalid` dispute
// submitted for a past session
report_dispute_lost {
let n in 4..<<T as super::Config>::BenchmarkingConfig as BenchmarkingConfiguration>::MAX_VALIDATORS;
Expand Down
Loading