Skip to content
This repository was archived by the owner on Nov 15, 2023. It is now read-only.
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
21 changes: 15 additions & 6 deletions node/core/dispute-coordinator/src/real/initialized.rs
Original file line number Diff line number Diff line change
Expand Up @@ -244,8 +244,10 @@ impl Initialized {
if !overlay_db.is_empty() {
let ops = overlay_db.into_write_ops();
backend.write(ops)?;
confirm_write()?;
}
// even if the changeset was empty,
// otherwise the caller will error.
confirm_write()?;
}
}

Expand Down Expand Up @@ -882,21 +884,28 @@ impl Initialized {

// Whether or not we know already that this is a good dispute:
//
// Note we can only know for sure whether we reached the `byzantine_threshold` after updating candidate votes above, therefore the spam checking is afterwards:
// Note we can only know for sure whether we reached the `byzantine_threshold` after
// updating candidate votes above, therefore the spam checking is afterwards:
let is_confirmed = is_included ||
was_confirmed ||
is_local || votes.voted_indices().len() >
byzantine_threshold(n_validators);

// Potential spam:
if !is_confirmed {
let mut free_spam_slots = statements.is_empty();
let mut free_spam_slots_available = true;
// Only allow import if all validators voting invalid, have not exceeded
// their spam slots:
for (statement, index) in statements.iter() {
free_spam_slots |= statement.statement().is_backing() ||
// Disputes can only be triggered via an invalidity stating vote, thus we only
// need to increase spam slots on invalid votes. (If we did not, we would also
// increase spam slots for backing validators for example - as validators have to
// provide some opposing vote for dispute-distribution).
free_spam_slots_available &= statement.statement().indicates_validity() ||
self.spam_slots.add_unconfirmed(session, candidate_hash, *index);
}
// No reporting validator had a free spam slot:
if !free_spam_slots {
// Only validity stating votes or validator had free spam slot?
if !free_spam_slots_available {
tracing::debug!(
target: LOG_TARGET,
?candidate_hash,
Expand Down
34 changes: 23 additions & 11 deletions node/core/dispute-coordinator/src/real/spam_slots.rs
Original file line number Diff line number Diff line change
Expand Up @@ -23,19 +23,24 @@ use crate::real::LOG_TARGET;
/// Type used for counting potential spam votes.
type SpamCount = u32;

/// How many unconfirmed disputes a validator is allowed to be a participant in (per session).
/// How many unconfirmed disputes a validator is allowed to import (per session).
///
/// Unconfirmed means: Node has not seen the candidate be included on any chain, it has not cast a
/// vote itself on that dispute, the dispute has not yet reached more than a third of
/// validator's votes and the including relay chain block has not yet been finalized.
///
/// Exact number of `MAX_SPAM_VOTES` is not that important here. It is important that the number is
/// low enough to not cause resource exhaustion, if multiple validators spend their limits. Also
/// if things are working properly, this number cannot really be too low either, as all relevant
/// disputes _should_ have been seen as included my enough validators. (Otherwise the candidate
/// would not have been available in the first place and could not have been included.) So this is
/// really just a fallback mechanism if things go terribly wrong.
/// low enough to not cause resource exhaustion (disk & memory) on the importing validator, even if
/// multiple validators fully make use of their assigned spam slots.
///
/// Also if things are working properly, this number cannot really be too low either, as all
/// relevant disputes _should_ have been seen as included by enough validators. (Otherwise the
/// candidate would not have been available in the first place and could not have been included.)
/// So this is really just a fallback mechanism if things go terribly wrong.
#[cfg(not(test))]
const MAX_SPAM_VOTES: SpamCount = 50;
#[cfg(test)]
const MAX_SPAM_VOTES: SpamCount = 1;

/// Spam slots for raised disputes concerning unknown candidates.
pub struct SpamSlots {
Expand Down Expand Up @@ -76,7 +81,12 @@ impl SpamSlots {
Self { slots, unconfirmed: unconfirmed_disputes }
}

/// Add an unconfirmed dispute if free slots are available.
/// Increase a "voting invalid" validator's spam slot.
///
/// This function should get called for any validator's invalidity vote for any not yet
/// confirmed dispute.
///
/// Returns: `true` if validator still had vacant spam slots, `false` otherwise.
pub fn add_unconfirmed(
&mut self,
session: SessionIndex,
Expand All @@ -90,14 +100,16 @@ impl SpamSlots {
let validators = self.unconfirmed.entry((session, candidate)).or_default();

if validators.insert(validator) {
// We only increment spam slots once per candidate, as each validator has to provide an
// opposing vote for sending out its own vote. Therefore, receiving multiple votes for
// a single candidate is expected and should not get punished here.
*c += 1;
true
} else {
false
}

true
}

/// Clear out spam slots for a given candiate in a session.
/// Clear out spam slots for a given candidate in a session.
///
/// This effectively reduces the spam slot count for all validators participating in a dispute
/// for that candidate. You should call this function once a dispute became obsolete or got
Expand Down
Loading