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

Make sure to preserve backing votes #6382

Merged
merged 6 commits into from
Dec 7, 2022
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
52 changes: 18 additions & 34 deletions node/core/dispute-coordinator/src/import.rs
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,9 @@

use std::collections::{BTreeMap, HashMap, HashSet};

use polkadot_node_primitives::{CandidateVotes, DisputeStatus, SignedDisputeStatement, Timestamp};
use polkadot_node_primitives::{
disputes::ValidCandidateVotes, CandidateVotes, DisputeStatus, SignedDisputeStatement, Timestamp,
};
use polkadot_node_subsystem_util::rolling_session_window::RollingSessionWindow;
use polkadot_primitives::v2::{
CandidateReceipt, DisputeStatement, IndexedVec, SessionIndex, SessionInfo,
Expand Down Expand Up @@ -101,7 +103,7 @@ impl OwnVoteState {
let mut our_valid_votes = env
.controlled_indices()
.iter()
.filter_map(|i| votes.valid.get_key_value(i))
.filter_map(|i| votes.valid.raw().get_key_value(i))
.peekable();
let mut our_invalid_votes =
env.controlled_indices.iter().filter_map(|i| votes.invalid.get_key_value(i));
Expand Down Expand Up @@ -163,8 +165,11 @@ impl CandidateVoteState<CandidateVotes> {
///
/// in case there have not been any previous votes.
pub fn new_from_receipt(candidate_receipt: CandidateReceipt) -> Self {
let votes =
CandidateVotes { candidate_receipt, valid: BTreeMap::new(), invalid: BTreeMap::new() };
let votes = CandidateVotes {
candidate_receipt,
valid: ValidCandidateVotes::new(),
invalid: BTreeMap::new(),
};
Self { votes, own_vote: OwnVoteState::NoVote, dispute_status: None }
}

Expand All @@ -178,7 +183,7 @@ impl CandidateVoteState<CandidateVotes> {
polkadot_primitives::v2::supermajority_threshold(n_validators);

// We have a dispute, if we have votes on both sides:
let is_disputed = !votes.invalid.is_empty() && !votes.valid.is_empty();
let is_disputed = !votes.invalid.is_empty() && !votes.valid.raw().is_empty();

let dispute_status = if is_disputed {
let mut status = DisputeStatus::active();
Expand All @@ -187,7 +192,7 @@ impl CandidateVoteState<CandidateVotes> {
if is_confirmed {
status = status.confirm();
};
let concluded_for = votes.valid.len() >= supermajority_threshold;
let concluded_for = votes.valid.raw().len() >= supermajority_threshold;
if concluded_for {
status = status.conclude_for(now);
};
Expand Down Expand Up @@ -262,25 +267,20 @@ impl CandidateVoteState<CandidateVotes> {

match statement.statement() {
DisputeStatement::Valid(valid_kind) => {
let fresh = insert_into_statements(
&mut votes.valid,
*valid_kind,
let fresh = votes.valid.insert_vote(
val_index,
*valid_kind,
statement.into_validator_signature(),
);

if fresh {
imported_valid_votes += 1;
}
},
DisputeStatement::Invalid(invalid_kind) => {
let fresh = insert_into_statements(
&mut votes.invalid,
*invalid_kind,
val_index,
statement.into_validator_signature(),
);

let fresh = votes
.invalid
.insert(val_index, (*invalid_kind, statement.into_validator_signature()))
.is_none();
if fresh {
new_invalid_voters.push(val_index);
imported_invalid_votes += 1;
Expand Down Expand Up @@ -481,12 +481,7 @@ impl ImportResult {
},
"Signature check for imported approval votes failed! This is a serious bug. Session: {:?}, candidate hash: {:?}, validator index: {:?}", env.session_index(), votes.candidate_receipt.hash(), index
);
if insert_into_statements(
&mut votes.valid,
ValidDisputeStatementKind::ApprovalChecking,
index,
sig,
) {
if votes.valid.insert_vote(index, ValidDisputeStatementKind::ApprovalChecking, sig) {
imported_valid_votes += 1;
imported_approval_votes += 1;
}
Expand Down Expand Up @@ -535,14 +530,3 @@ fn find_controlled_validator_indices(

controlled
}

// Returns 'true' if no other vote by that validator was already
// present and 'false' otherwise. Same semantics as `HashSet`.
fn insert_into_statements<T>(
m: &mut BTreeMap<ValidatorIndex, (T, ValidatorSignature)>,
tag: T,
val_index: ValidatorIndex,
val_signature: ValidatorSignature,
) -> bool {
m.insert(val_index, (tag, val_signature)).is_none()
}
16 changes: 10 additions & 6 deletions node/core/dispute-coordinator/src/initialized.rs
Original file line number Diff line number Diff line change
Expand Up @@ -26,8 +26,8 @@ use futures::{
use sc_keystore::LocalKeystore;

use polkadot_node_primitives::{
CandidateVotes, DisputeMessage, DisputeMessageCheckError, DisputeStatus,
SignedDisputeStatement, Timestamp,
disputes::ValidCandidateVotes, CandidateVotes, DisputeMessage, DisputeMessageCheckError,
DisputeStatus, SignedDisputeStatement, Timestamp,
};
use polkadot_node_subsystem::{
messages::{
Expand Down Expand Up @@ -1024,7 +1024,7 @@ impl Initialized {
imported_approval_votes = ?import_result.imported_approval_votes(),
imported_valid_votes = ?import_result.imported_valid_votes(),
imported_invalid_votes = ?import_result.imported_invalid_votes(),
total_valid_votes = ?import_result.new_state().votes().valid.len(),
total_valid_votes = ?import_result.new_state().votes().valid.raw().len(),
total_invalid_votes = ?import_result.new_state().votes().invalid.len(),
confirmed = ?import_result.new_state().is_confirmed(),
"Import summary"
Expand Down Expand Up @@ -1099,7 +1099,7 @@ impl Initialized {
.map(CandidateVotes::from)
.unwrap_or_else(|| CandidateVotes {
candidate_receipt: candidate_receipt.clone(),
valid: BTreeMap::new(),
valid: ValidCandidateVotes::new(),
invalid: BTreeMap::new(),
});

Expand Down Expand Up @@ -1272,8 +1272,12 @@ fn make_dispute_message(
.map_err(|()| DisputeMessageCreationError::InvalidStoredStatement)?;
(our_vote, our_index, other_vote, *validator_index)
} else {
let (validator_index, (statement_kind, validator_signature)) =
votes.valid.iter().next().ok_or(DisputeMessageCreationError::NoOppositeVote)?;
let (validator_index, (statement_kind, validator_signature)) = votes
.valid
.raw()
.iter()
.next()
.ok_or(DisputeMessageCreationError::NoOppositeVote)?;
let other_vote = SignedDisputeStatement::new_checked(
DisputeStatement::Valid(*statement_kind),
*our_vote.candidate_hash(),
Expand Down
21 changes: 6 additions & 15 deletions node/core/dispute-coordinator/src/scraping/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ use std::num::NonZeroUsize;
use futures::channel::oneshot;
use lru::LruCache;

use polkadot_node_primitives::MAX_FINALITY_LAG;
use polkadot_node_primitives::{DISPUTE_CANDIDATE_LIFETIME_AFTER_FINALIZATION, MAX_FINALITY_LAG};
use polkadot_node_subsystem::{
messages::ChainApiMessage, overseer, ActivatedLeaf, ActiveLeavesUpdate, ChainApiError,
SubsystemSender,
Expand Down Expand Up @@ -65,7 +65,7 @@ const LRU_OBSERVED_BLOCKS_CAPACITY: NonZeroUsize = match NonZeroUsize::new(20) {
/// With this information it provides a `CandidateComparator` and as a return value of
/// `process_active_leaves_update` any scraped votes.
///
/// Scraped candidates are available `CANDIDATE_LIFETIME_AFTER_FINALIZATION` more blocks
/// Scraped candidates are available `DISPUTE_CANDIDATE_LIFETIME_AFTER_FINALIZATION` more blocks
/// after finalization as a precaution not to prune them prematurely.
pub struct ChainScraper {
/// All candidates we have seen included, which not yet have been finalized.
Expand All @@ -87,16 +87,6 @@ impl ChainScraper {
/// As long as we have `MAX_FINALITY_LAG` this makes sense as a value.
pub(crate) const ANCESTRY_SIZE_LIMIT: u32 = MAX_FINALITY_LAG;

/// How many blocks after finalization a backed/included candidate should be kept.
/// We don't want to remove scraped candidates on finalization because we want to
/// be sure that disputes will conclude on abandoned forks.
/// Removing the candidate on finalization creates a possibility for an attacker to
/// avoid slashing. If a bad fork is abandoned too quickly because in the same another
/// better one gets finalized the entries for the bad fork will be pruned and we
/// will never participate in a dispute for it. We want such disputes to conclude
/// in a timely manner so that the offenders are slashed.
pub(crate) const CANDIDATE_LIFETIME_AFTER_FINALIZATION: BlockNumber = 2;

/// Create a properly initialized `OrderingProvider`.
///
/// Returns: `Self` and any scraped votes.
Expand Down Expand Up @@ -175,12 +165,13 @@ impl ChainScraper {

/// Prune finalized candidates.
///
/// We keep each candidate for `CANDIDATE_LIFETIME_AFTER_FINALIZATION` blocks after finalization.
/// We keep each candidate for `DISPUTE_CANDIDATE_LIFETIME_AFTER_FINALIZATION` blocks after finalization.
/// After that we treat it as low priority.
pub fn process_finalized_block(&mut self, finalized_block_number: &BlockNumber) {
// `CANDIDATE_LIFETIME_AFTER_FINALIZATION - 1` because `finalized_block_number`counts to the
// `DISPUTE_CANDIDATE_LIFETIME_AFTER_FINALIZATION - 1` because `finalized_block_number`counts to the
// candidate lifetime.
match finalized_block_number.checked_sub(Self::CANDIDATE_LIFETIME_AFTER_FINALIZATION - 1) {
match finalized_block_number.checked_sub(DISPUTE_CANDIDATE_LIFETIME_AFTER_FINALIZATION - 1)
{
Some(key_to_prune) => {
self.backed_candidates.remove_up_to_height(&key_to_prune);
self.included_candidates.remove_up_to_height(&key_to_prune);
Expand Down
11 changes: 6 additions & 5 deletions node/core/dispute-coordinator/src/scraping/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ use parity_scale_codec::Encode;
use sp_core::testing::TaskExecutor;

use ::test_helpers::{dummy_collator, dummy_collator_signature, dummy_hash};
use polkadot_node_primitives::DISPUTE_CANDIDATE_LIFETIME_AFTER_FINALIZATION;
use polkadot_node_subsystem::{
jaeger,
messages::{
Expand Down Expand Up @@ -452,9 +453,9 @@ fn scraper_prunes_finalized_candidates() {

let candidate = make_candidate_receipt(get_block_number_hash(TEST_TARGET_BLOCK_NUMBER));

// After `CANDIDATE_LIFETIME_AFTER_FINALIZATION` blocks the candidate should be removed
// After `DISPUTE_CANDIDATE_LIFETIME_AFTER_FINALIZATION` blocks the candidate should be removed
finalized_block_number =
TEST_TARGET_BLOCK_NUMBER + ChainScraper::CANDIDATE_LIFETIME_AFTER_FINALIZATION;
TEST_TARGET_BLOCK_NUMBER + DISPUTE_CANDIDATE_LIFETIME_AFTER_FINALIZATION;
process_finalized_block(&mut scraper, &finalized_block_number);

assert!(!scraper.is_candidate_backed(&candidate.hash()));
Expand Down Expand Up @@ -512,10 +513,10 @@ fn scraper_handles_backed_but_not_included_candidate() {
// The candidate should be removed.
assert!(
finalized_block_number <
TEST_TARGET_BLOCK_NUMBER + ChainScraper::CANDIDATE_LIFETIME_AFTER_FINALIZATION
TEST_TARGET_BLOCK_NUMBER + DISPUTE_CANDIDATE_LIFETIME_AFTER_FINALIZATION
);
finalized_block_number +=
TEST_TARGET_BLOCK_NUMBER + ChainScraper::CANDIDATE_LIFETIME_AFTER_FINALIZATION;
TEST_TARGET_BLOCK_NUMBER + DISPUTE_CANDIDATE_LIFETIME_AFTER_FINALIZATION;
process_finalized_block(&mut scraper, &finalized_block_number);

assert!(!scraper.is_candidate_included(&candidate.hash()));
Expand Down Expand Up @@ -562,7 +563,7 @@ fn scraper_handles_the_same_candidate_incuded_in_two_different_block_heights() {
// Finalize blocks to enforce pruning of scraped events.
// The magic candidate was added twice, so it shouldn't be removed if we finalize two more blocks.
finalized_block_number = test_targets.first().expect("there are two block nums") +
ChainScraper::CANDIDATE_LIFETIME_AFTER_FINALIZATION;
DISPUTE_CANDIDATE_LIFETIME_AFTER_FINALIZATION;
process_finalized_block(&mut scraper, &finalized_block_number);

let magic_candidate = make_candidate_receipt(get_magic_candidate_hash());
Expand Down
31 changes: 17 additions & 14 deletions node/core/dispute-coordinator/src/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -706,7 +706,7 @@ fn too_many_unconfirmed_statements_are_considered_spam() {
.await;

let (_, _, votes) = rx.await.unwrap().get(0).unwrap().clone();
assert_eq!(votes.valid.len(), 1);
assert_eq!(votes.valid.raw().len(), 1);
assert_eq!(votes.invalid.len(), 1);
}

Expand Down Expand Up @@ -839,8 +839,11 @@ fn approval_vote_import_works() {
.await;

let (_, _, votes) = rx.await.unwrap().get(0).unwrap().clone();
assert_eq!(votes.valid.len(), 2);
assert!(votes.valid.get(&ValidatorIndex(4)).is_some(), "Approval vote is missing!");
assert_eq!(votes.valid.raw().len(), 2);
assert!(
votes.valid.raw().get(&ValidatorIndex(4)).is_some(),
"Approval vote is missing!"
);
assert_eq!(votes.invalid.len(), 1);
}

Expand Down Expand Up @@ -964,7 +967,7 @@ fn dispute_gets_confirmed_via_participation() {
.await;

let (_, _, votes) = rx.await.unwrap().get(0).unwrap().clone();
assert_eq!(votes.valid.len(), 2);
assert_eq!(votes.valid.raw().len(), 2);
assert_eq!(votes.invalid.len(), 1);
}

Expand Down Expand Up @@ -999,7 +1002,7 @@ fn dispute_gets_confirmed_via_participation() {
.await;

let (_, _, votes) = rx.await.unwrap().get(0).unwrap().clone();
assert_eq!(votes.valid.len(), 1);
assert_eq!(votes.valid.raw().len(), 1);
assert_eq!(votes.invalid.len(), 1);
}

Expand Down Expand Up @@ -1132,7 +1135,7 @@ fn dispute_gets_confirmed_at_byzantine_threshold() {
.await;

let (_, _, votes) = rx.await.unwrap().get(0).unwrap().clone();
assert_eq!(votes.valid.len(), 2);
assert_eq!(votes.valid.raw().len(), 2);
assert_eq!(votes.invalid.len(), 2);
}

Expand Down Expand Up @@ -1167,7 +1170,7 @@ fn dispute_gets_confirmed_at_byzantine_threshold() {
.await;

let (_, _, votes) = rx.await.unwrap().get(0).unwrap().clone();
assert_eq!(votes.valid.len(), 1);
assert_eq!(votes.valid.raw().len(), 1);
assert_eq!(votes.invalid.len(), 1);
}

Expand Down Expand Up @@ -1246,7 +1249,7 @@ fn backing_statements_import_works_and_no_spam() {
.await;

let (_, _, votes) = rx.await.unwrap().get(0).unwrap().clone();
assert_eq!(votes.valid.len(), 2);
assert_eq!(votes.valid.raw().len(), 2);
assert_eq!(votes.invalid.len(), 0);
}

Expand Down Expand Up @@ -1394,7 +1397,7 @@ fn conflicting_votes_lead_to_dispute_participation() {
.await;

let (_, _, votes) = rx.await.unwrap().get(0).unwrap().clone();
assert_eq!(votes.valid.len(), 2);
assert_eq!(votes.valid.raw().len(), 2);
assert_eq!(votes.invalid.len(), 1);
}

Expand All @@ -1421,7 +1424,7 @@ fn conflicting_votes_lead_to_dispute_participation() {
.await;

let (_, _, votes) = rx.await.unwrap().get(0).unwrap().clone();
assert_eq!(votes.valid.len(), 2);
assert_eq!(votes.valid.raw().len(), 2);
assert_eq!(votes.invalid.len(), 2);
}

Expand Down Expand Up @@ -1505,7 +1508,7 @@ fn positive_votes_dont_trigger_participation() {
.await;

let (_, _, votes) = rx.await.unwrap().get(0).unwrap().clone();
assert_eq!(votes.valid.len(), 1);
assert_eq!(votes.valid.raw().len(), 1);
assert!(votes.invalid.is_empty());
}

Expand Down Expand Up @@ -1541,7 +1544,7 @@ fn positive_votes_dont_trigger_participation() {
.await;

let (_, _, votes) = rx.await.unwrap().get(0).unwrap().clone();
assert_eq!(votes.valid.len(), 2);
assert_eq!(votes.valid.raw().len(), 2);
assert!(votes.invalid.is_empty());
}

Expand Down Expand Up @@ -3075,7 +3078,7 @@ fn refrain_from_participation() {
.await;

let (_, _, votes) = rx.await.unwrap().get(0).unwrap().clone();
assert_eq!(votes.valid.len(), 1);
assert_eq!(votes.valid.raw().len(), 1);
assert_eq!(votes.invalid.len(), 1);
}

Expand Down Expand Up @@ -3183,7 +3186,7 @@ fn participation_for_included_candidates() {
.await;

let (_, _, votes) = rx.await.unwrap().get(0).unwrap().clone();
assert_eq!(votes.valid.len(), 2); // 2 => we have participated
assert_eq!(votes.valid.raw().len(), 2); // 2 => we have participated
assert_eq!(votes.invalid.len(), 1);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -216,7 +216,7 @@ where

// Check if votes are within the limit
for (session_index, candidate_hash, selected_votes) in votes {
let votes_len = selected_votes.valid.len() + selected_votes.invalid.len();
let votes_len = selected_votes.valid.raw().len() + selected_votes.invalid.len();
if votes_len + total_votes_len > MAX_DISPUTE_VOTES_FORWARDED_TO_RUNTIME {
// we are done - no more votes can be added
return result
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -393,7 +393,9 @@ impl TestDisputes {
ValidDisputeStatementKind::Explicit,
0,
local_votes_count,
),
)
.into_iter()
.collect(),
invalid: BTreeMap::new(),
},
);
Expand Down
Loading