Better spam slots handling (#4845)

* Only bypass spam slots on actualy approval/backing vote import.

* Fix spam slot treatment.

* More tests.

* Make sure backing statements import works.

* Aaaaaand the actual proper fix.

* Better docs + spelling fixes.

* Fix.

* Typo

Co-authored-by: Robert Habermeier <rphmeier@gmail.com>

* Fix doc.

* Update node/core/dispute-coordinator/src/real/initialized.rs

* minor rewording, line wrap

* fix test case

* Fix obsolete comment.

Co-authored-by: Robert Habermeier <rphmeier@gmail.com>
Co-authored-by: Bernhard Schuster <bernhard@ahoi.io>
This commit is contained in:
Robert Klotzner
2022-02-17 15:18:52 +01:00
committed by GitHub
parent ec815011e2
commit 93b7263019
3 changed files with 678 additions and 106 deletions
@@ -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()?;
}
}
@@ -882,7 +884,8 @@ 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() >
@@ -890,13 +893,19 @@ impl Initialized {
// 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,
@@ -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 {
@@ -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,
@@ -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
@@ -32,7 +32,7 @@ use futures::{
use kvdb::KeyValueDB;
use parity_scale_codec::Encode;
use polkadot_node_primitives::SignedDisputeStatement;
use polkadot_node_primitives::{SignedDisputeStatement, SignedFullStatement, Statement};
use polkadot_node_subsystem::{
messages::{
ChainApiMessage, DisputeCoordinatorMessage, DisputeDistributionMessage,
@@ -43,7 +43,7 @@ use polkadot_node_subsystem::{
};
use polkadot_node_subsystem_util::TimeoutExt;
use sc_keystore::LocalKeystore;
use sp_core::testing::TaskExecutor;
use sp_core::{sr25519::Pair, testing::TaskExecutor, Pair as PairT};
use sp_keyring::Sr25519Keyring;
use sp_keystore::{SyncCryptoStore, SyncCryptoStorePtr};
@@ -57,8 +57,8 @@ use polkadot_node_subsystem_test_helpers::{make_subsystem_context, TestSubsystem
use polkadot_primitives::{
v1::{
BlakeTwo256, BlockNumber, CandidateCommitments, CandidateHash, CandidateReceipt, Hash,
HashT, Header, MultiDisputeStatementSet, ScrapedOnChainVotes, SessionIndex, ValidatorId,
ValidatorIndex,
HashT, Header, MultiDisputeStatementSet, ScrapedOnChainVotes, SessionIndex, SigningContext,
ValidatorId, ValidatorIndex,
},
v2::SessionInfo,
};
@@ -78,12 +78,12 @@ use super::db::v1::DbBackend;
const TEST_TIMEOUT: Duration = Duration::from_secs(2);
// sets up a keystore with the given keyring accounts.
fn make_keystore(accounts: &[Sr25519Keyring]) -> LocalKeystore {
fn make_keystore(seeds: impl Iterator<Item = String>) -> LocalKeystore {
let store = LocalKeystore::in_memory();
for s in accounts.iter().copied().map(|k| k.to_seed()) {
for s in seeds {
store
.sr25519_generate_new(polkadot_primitives::v1::PARACHAIN_KEY_TYPE_ID, Some(s.as_str()))
.sr25519_generate_new(polkadot_primitives::v1::PARACHAIN_KEY_TYPE_ID, Some(&s))
.unwrap();
}
@@ -120,7 +120,7 @@ impl MockClock {
}
struct TestState {
validators: Vec<Sr25519Keyring>,
validators: Vec<Pair>,
validator_public: Vec<ValidatorId>,
validator_groups: Vec<Vec<ValidatorIndex>>,
master_keystore: Arc<sc_keystore::LocalKeystore>,
@@ -133,17 +133,28 @@ struct TestState {
impl Default for TestState {
fn default() -> TestState {
let p1 = Pair::from_string("//Polka", None).unwrap();
let p2 = Pair::from_string("//Dot", None).unwrap();
let p3 = Pair::from_string("//Kusama", None).unwrap();
let validators = vec![
Sr25519Keyring::Alice,
Sr25519Keyring::Bob,
Sr25519Keyring::Charlie,
Sr25519Keyring::Dave,
Sr25519Keyring::Eve,
Sr25519Keyring::One,
Sr25519Keyring::Ferdie,
(Sr25519Keyring::Alice.pair(), Sr25519Keyring::Alice.to_seed()),
(Sr25519Keyring::Bob.pair(), Sr25519Keyring::Bob.to_seed()),
(Sr25519Keyring::Charlie.pair(), Sr25519Keyring::Charlie.to_seed()),
(Sr25519Keyring::Dave.pair(), Sr25519Keyring::Dave.to_seed()),
(Sr25519Keyring::Eve.pair(), Sr25519Keyring::Eve.to_seed()),
(Sr25519Keyring::One.pair(), Sr25519Keyring::One.to_seed()),
(Sr25519Keyring::Ferdie.pair(), Sr25519Keyring::Ferdie.to_seed()),
// Two more keys needed so disputes are not confirmed already with only 3 statements.
(p1, "//Polka".into()),
(p2, "//Dot".into()),
(p3, "//Kusama".into()),
];
let validator_public = validators.iter().map(|k| ValidatorId::from(k.public())).collect();
let validator_public = validators
.clone()
.into_iter()
.map(|k| ValidatorId::from(k.0.public()))
.collect();
let validator_groups = vec![
vec![ValidatorIndex(0), ValidatorIndex(1)],
@@ -151,14 +162,15 @@ impl Default for TestState {
vec![ValidatorIndex(4), ValidatorIndex(5), ValidatorIndex(6)],
];
let master_keystore = make_keystore(&validators).into();
let subsystem_keystore = make_keystore(&[Sr25519Keyring::Alice]).into();
let master_keystore = make_keystore(validators.iter().map(|v| v.1.clone())).into();
let subsystem_keystore =
make_keystore(vec![Sr25519Keyring::Alice.to_seed()].into_iter()).into();
let db = Arc::new(kvdb_memorydb::create(1));
let config = Config { col_data: 0 };
TestState {
validators,
validators: validators.into_iter().map(|(pair, _)| pair).collect(),
validator_public,
validator_groups,
master_keystore,
@@ -322,7 +334,7 @@ impl TestState {
}
}
async fn issue_statement_with_index(
async fn issue_explicit_statement_with_index(
&self,
index: usize,
candidate_hash: CandidateHash,
@@ -339,6 +351,32 @@ impl TestState {
.unwrap()
}
async fn issue_backing_statement_with_index(
&self,
index: usize,
candidate_hash: CandidateHash,
session: SessionIndex,
) -> SignedDisputeStatement {
let keystore = self.master_keystore.clone() as SyncCryptoStorePtr;
let validator_id = self.validators[index].public().into();
let context =
SigningContext { session_index: session, parent_hash: Hash::repeat_byte(0xac) };
let statement = SignedFullStatement::sign(
&keystore,
Statement::Valid(candidate_hash),
&context,
ValidatorIndex(index as _),
&validator_id,
)
.await
.unwrap()
.unwrap()
.into_unchecked();
SignedDisputeStatement::from_backing_statement(&statement, context, validator_id).unwrap()
}
fn resume<F>(self, test: F) -> Self
where
F: FnOnce(TestState, VirtualOverseer) -> BoxFuture<'static, TestState>,
@@ -393,6 +431,477 @@ fn make_invalid_candidate_receipt() -> CandidateReceipt {
dummy_candidate_receipt_bad_sig(Default::default(), Some(Default::default()))
}
#[test]
fn too_many_unconfirmed_statements_are_considered_spam() {
test_harness(|mut test_state, mut virtual_overseer| {
Box::pin(async move {
let session = 1;
test_state.handle_resume_sync(&mut virtual_overseer, session).await;
let candidate_receipt1 = make_valid_candidate_receipt();
let candidate_hash1 = candidate_receipt1.hash();
let candidate_receipt2 = make_invalid_candidate_receipt();
let candidate_hash2 = candidate_receipt2.hash();
test_state.activate_leaf_at_session(&mut virtual_overseer, session, 1).await;
let valid_vote1 =
test_state.issue_backing_statement_with_index(3, candidate_hash1, session).await;
let invalid_vote1 = test_state
.issue_explicit_statement_with_index(1, candidate_hash1, session, false)
.await;
let valid_vote2 =
test_state.issue_backing_statement_with_index(3, candidate_hash1, session).await;
let invalid_vote2 = test_state
.issue_explicit_statement_with_index(1, candidate_hash1, session, false)
.await;
let (pending_confirmation, _confirmation_rx) = oneshot::channel();
virtual_overseer
.send(FromOverseer::Communication {
msg: DisputeCoordinatorMessage::ImportStatements {
candidate_hash: candidate_hash1,
candidate_receipt: candidate_receipt1.clone(),
session,
statements: vec![
(valid_vote1, ValidatorIndex(3)),
(invalid_vote1, ValidatorIndex(1)),
],
pending_confirmation,
},
})
.await;
// Participation has to fail, otherwise the dispute will be confirmed.
participation_missing_availability(&mut virtual_overseer).await;
{
let (tx, rx) = oneshot::channel();
virtual_overseer
.send(FromOverseer::Communication {
msg: DisputeCoordinatorMessage::ActiveDisputes(tx),
})
.await;
assert_eq!(rx.await.unwrap(), vec![(session, candidate_hash1)]);
let (tx, rx) = oneshot::channel();
virtual_overseer
.send(FromOverseer::Communication {
msg: DisputeCoordinatorMessage::QueryCandidateVotes(
vec![(session, candidate_hash1)],
tx,
),
})
.await;
let (_, _, votes) = rx.await.unwrap().get(0).unwrap().clone();
assert_eq!(votes.valid.len(), 1);
assert_eq!(votes.invalid.len(), 1);
}
let (pending_confirmation, confirmation_rx) = oneshot::channel();
virtual_overseer
.send(FromOverseer::Communication {
msg: DisputeCoordinatorMessage::ImportStatements {
candidate_hash: candidate_hash2,
candidate_receipt: candidate_receipt2.clone(),
session,
statements: vec![
(valid_vote2, ValidatorIndex(3)),
(invalid_vote2, ValidatorIndex(1)),
],
pending_confirmation,
},
})
.await;
{
let (tx, rx) = oneshot::channel();
virtual_overseer
.send(FromOverseer::Communication {
msg: DisputeCoordinatorMessage::QueryCandidateVotes(
vec![(session, candidate_hash2)],
tx,
),
})
.await;
assert_matches!(rx.await.unwrap().get(0), None);
}
// Result should be invalid, because it should be considered spam.
assert_matches!(confirmation_rx.await, Ok(ImportStatementsResult::InvalidImport));
virtual_overseer.send(FromOverseer::Signal(OverseerSignal::Conclude)).await;
// No more messages expected:
assert!(virtual_overseer.try_recv().await.is_none());
test_state
})
});
}
#[test]
fn dispute_gets_confirmed_via_participation() {
test_harness(|mut test_state, mut virtual_overseer| {
Box::pin(async move {
let session = 1;
test_state.handle_resume_sync(&mut virtual_overseer, session).await;
let candidate_receipt1 = make_valid_candidate_receipt();
let candidate_hash1 = candidate_receipt1.hash();
let candidate_receipt2 = make_invalid_candidate_receipt();
let candidate_hash2 = candidate_receipt2.hash();
test_state.activate_leaf_at_session(&mut virtual_overseer, session, 1).await;
let valid_vote1 = test_state
.issue_explicit_statement_with_index(3, candidate_hash1, session, true)
.await;
let invalid_vote1 = test_state
.issue_explicit_statement_with_index(1, candidate_hash1, session, false)
.await;
let valid_vote2 = test_state
.issue_explicit_statement_with_index(3, candidate_hash1, session, true)
.await;
let invalid_vote2 = test_state
.issue_explicit_statement_with_index(1, candidate_hash1, session, false)
.await;
let (pending_confirmation, _confirmation_rx) = oneshot::channel();
virtual_overseer
.send(FromOverseer::Communication {
msg: DisputeCoordinatorMessage::ImportStatements {
candidate_hash: candidate_hash1,
candidate_receipt: candidate_receipt1.clone(),
session,
statements: vec![
(valid_vote1, ValidatorIndex(3)),
(invalid_vote1, ValidatorIndex(1)),
],
pending_confirmation,
},
})
.await;
participation_with_distribution(&mut virtual_overseer, &candidate_hash1).await;
{
let (tx, rx) = oneshot::channel();
virtual_overseer
.send(FromOverseer::Communication {
msg: DisputeCoordinatorMessage::ActiveDisputes(tx),
})
.await;
assert_eq!(rx.await.unwrap(), vec![(session, candidate_hash1)]);
let (tx, rx) = oneshot::channel();
virtual_overseer
.send(FromOverseer::Communication {
msg: DisputeCoordinatorMessage::QueryCandidateVotes(
vec![(session, candidate_hash1)],
tx,
),
})
.await;
let (_, _, votes) = rx.await.unwrap().get(0).unwrap().clone();
assert_eq!(votes.valid.len(), 2);
assert_eq!(votes.invalid.len(), 1);
}
let (pending_confirmation, confirmation_rx) = oneshot::channel();
virtual_overseer
.send(FromOverseer::Communication {
msg: DisputeCoordinatorMessage::ImportStatements {
candidate_hash: candidate_hash2,
candidate_receipt: candidate_receipt2.clone(),
session,
statements: vec![
(valid_vote2, ValidatorIndex(3)),
(invalid_vote2, ValidatorIndex(1)),
],
pending_confirmation,
},
})
.await;
participation_missing_availability(&mut virtual_overseer).await;
{
let (tx, rx) = oneshot::channel();
virtual_overseer
.send(FromOverseer::Communication {
msg: DisputeCoordinatorMessage::QueryCandidateVotes(
vec![(session, candidate_hash2)],
tx,
),
})
.await;
let (_, _, votes) = rx.await.unwrap().get(0).unwrap().clone();
assert_eq!(votes.valid.len(), 1);
assert_eq!(votes.invalid.len(), 1);
}
// Result should be valid, because our node participated, so spam slots are cleared:
assert_matches!(confirmation_rx.await, Ok(ImportStatementsResult::ValidImport));
virtual_overseer.send(FromOverseer::Signal(OverseerSignal::Conclude)).await;
// No more messages expected:
assert!(virtual_overseer.try_recv().await.is_none());
test_state
})
});
}
#[test]
fn dispute_gets_confirmed_at_byzantine_threshold() {
test_harness(|mut test_state, mut virtual_overseer| {
Box::pin(async move {
let session = 1;
test_state.handle_resume_sync(&mut virtual_overseer, session).await;
let candidate_receipt1 = make_valid_candidate_receipt();
let candidate_hash1 = candidate_receipt1.hash();
let candidate_receipt2 = make_invalid_candidate_receipt();
let candidate_hash2 = candidate_receipt2.hash();
test_state.activate_leaf_at_session(&mut virtual_overseer, session, 1).await;
let valid_vote1 = test_state
.issue_explicit_statement_with_index(3, candidate_hash1, session, true)
.await;
let invalid_vote1 = test_state
.issue_explicit_statement_with_index(1, candidate_hash1, session, false)
.await;
let valid_vote1a = test_state
.issue_explicit_statement_with_index(4, candidate_hash1, session, true)
.await;
let invalid_vote1a = test_state
.issue_explicit_statement_with_index(5, candidate_hash1, session, false)
.await;
let valid_vote2 = test_state
.issue_explicit_statement_with_index(3, candidate_hash1, session, true)
.await;
let invalid_vote2 = test_state
.issue_explicit_statement_with_index(1, candidate_hash1, session, false)
.await;
let (pending_confirmation, _confirmation_rx) = oneshot::channel();
virtual_overseer
.send(FromOverseer::Communication {
msg: DisputeCoordinatorMessage::ImportStatements {
candidate_hash: candidate_hash1,
candidate_receipt: candidate_receipt1.clone(),
session,
statements: vec![
(valid_vote1, ValidatorIndex(3)),
(invalid_vote1, ValidatorIndex(1)),
(valid_vote1a, ValidatorIndex(4)),
(invalid_vote1a, ValidatorIndex(5)),
],
pending_confirmation,
},
})
.await;
participation_missing_availability(&mut virtual_overseer).await;
{
let (tx, rx) = oneshot::channel();
virtual_overseer
.send(FromOverseer::Communication {
msg: DisputeCoordinatorMessage::ActiveDisputes(tx),
})
.await;
assert_eq!(rx.await.unwrap(), vec![(session, candidate_hash1)]);
let (tx, rx) = oneshot::channel();
virtual_overseer
.send(FromOverseer::Communication {
msg: DisputeCoordinatorMessage::QueryCandidateVotes(
vec![(session, candidate_hash1)],
tx,
),
})
.await;
let (_, _, votes) = rx.await.unwrap().get(0).unwrap().clone();
assert_eq!(votes.valid.len(), 2);
assert_eq!(votes.invalid.len(), 2);
}
let (pending_confirmation, confirmation_rx) = oneshot::channel();
virtual_overseer
.send(FromOverseer::Communication {
msg: DisputeCoordinatorMessage::ImportStatements {
candidate_hash: candidate_hash2,
candidate_receipt: candidate_receipt2.clone(),
session,
statements: vec![
(valid_vote2, ValidatorIndex(3)),
(invalid_vote2, ValidatorIndex(1)),
],
pending_confirmation,
},
})
.await;
participation_missing_availability(&mut virtual_overseer).await;
{
let (tx, rx) = oneshot::channel();
virtual_overseer
.send(FromOverseer::Communication {
msg: DisputeCoordinatorMessage::QueryCandidateVotes(
vec![(session, candidate_hash2)],
tx,
),
})
.await;
let (_, _, votes) = rx.await.unwrap().get(0).unwrap().clone();
assert_eq!(votes.valid.len(), 1);
assert_eq!(votes.invalid.len(), 1);
}
// Result should be valid, because byzantine threshold has been reached in first
// import, so spam slots are cleared:
assert_matches!(confirmation_rx.await, Ok(ImportStatementsResult::ValidImport));
virtual_overseer.send(FromOverseer::Signal(OverseerSignal::Conclude)).await;
// No more messages expected:
assert!(virtual_overseer.try_recv().await.is_none());
test_state
})
});
}
#[test]
fn backing_statements_import_works_and_no_spam() {
test_harness(|mut test_state, mut virtual_overseer| {
Box::pin(async move {
let session = 1;
test_state.handle_resume_sync(&mut virtual_overseer, session).await;
let candidate_receipt = make_valid_candidate_receipt();
let candidate_hash = candidate_receipt.hash();
test_state.activate_leaf_at_session(&mut virtual_overseer, session, 1).await;
let valid_vote1 =
test_state.issue_backing_statement_with_index(3, candidate_hash, session).await;
let valid_vote2 =
test_state.issue_backing_statement_with_index(4, candidate_hash, session).await;
let (pending_confirmation, confirmation_rx) = oneshot::channel();
virtual_overseer
.send(FromOverseer::Communication {
msg: DisputeCoordinatorMessage::ImportStatements {
candidate_hash,
candidate_receipt: candidate_receipt.clone(),
session,
statements: vec![
(valid_vote1, ValidatorIndex(3)),
(valid_vote2, ValidatorIndex(4)),
],
pending_confirmation,
},
})
.await;
assert_matches!(confirmation_rx.await, Ok(ImportStatementsResult::ValidImport));
{
// Just backing votes - we should not have any active disputes now.
let (tx, rx) = oneshot::channel();
virtual_overseer
.send(FromOverseer::Communication {
msg: DisputeCoordinatorMessage::ActiveDisputes(tx),
})
.await;
assert!(rx.await.unwrap().is_empty());
let (tx, rx) = oneshot::channel();
virtual_overseer
.send(FromOverseer::Communication {
msg: DisputeCoordinatorMessage::QueryCandidateVotes(
vec![(session, candidate_hash)],
tx,
),
})
.await;
let (_, _, votes) = rx.await.unwrap().get(0).unwrap().clone();
assert_eq!(votes.valid.len(), 2);
assert_eq!(votes.invalid.len(), 0);
}
let candidate_receipt = make_invalid_candidate_receipt();
let candidate_hash = candidate_receipt.hash();
let valid_vote1 =
test_state.issue_backing_statement_with_index(3, candidate_hash, session).await;
let valid_vote2 =
test_state.issue_backing_statement_with_index(4, candidate_hash, session).await;
let (pending_confirmation, confirmation_rx) = oneshot::channel();
// Backing vote import should not have accounted to spam slots, so this should succeed
// as well:
virtual_overseer
.send(FromOverseer::Communication {
msg: DisputeCoordinatorMessage::ImportStatements {
candidate_hash,
candidate_receipt: candidate_receipt.clone(),
session,
statements: vec![
(valid_vote1, ValidatorIndex(3)),
(valid_vote2, ValidatorIndex(4)),
],
pending_confirmation,
},
})
.await;
// Result should be valid, because our node participated, so spam slots are cleared:
assert_matches!(confirmation_rx.await, Ok(ImportStatementsResult::ValidImport));
virtual_overseer.send(FromOverseer::Signal(OverseerSignal::Conclude)).await;
// No more messages expected:
assert!(virtual_overseer.try_recv().await.is_none());
test_state
})
});
}
#[test]
fn conflicting_votes_lead_to_dispute_participation() {
test_harness(|mut test_state, mut virtual_overseer| {
@@ -406,14 +915,17 @@ fn conflicting_votes_lead_to_dispute_participation() {
test_state.activate_leaf_at_session(&mut virtual_overseer, session, 1).await;
let valid_vote =
test_state.issue_statement_with_index(3, candidate_hash, session, true).await;
let valid_vote = test_state
.issue_explicit_statement_with_index(3, candidate_hash, session, true)
.await;
let invalid_vote =
test_state.issue_statement_with_index(1, candidate_hash, session, false).await;
let invalid_vote = test_state
.issue_explicit_statement_with_index(1, candidate_hash, session, false)
.await;
let invalid_vote_2 =
test_state.issue_statement_with_index(2, candidate_hash, session, false).await;
let invalid_vote_2 = test_state
.issue_explicit_statement_with_index(2, candidate_hash, session, false)
.await;
let (pending_confirmation, _confirmation_rx) = oneshot::channel();
virtual_overseer
@@ -510,11 +1022,13 @@ fn positive_votes_dont_trigger_participation() {
test_state.activate_leaf_at_session(&mut virtual_overseer, session, 1).await;
let valid_vote =
test_state.issue_statement_with_index(2, candidate_hash, session, true).await;
let valid_vote = test_state
.issue_explicit_statement_with_index(2, candidate_hash, session, true)
.await;
let valid_vote_2 =
test_state.issue_statement_with_index(1, candidate_hash, session, true).await;
let valid_vote_2 = test_state
.issue_explicit_statement_with_index(1, candidate_hash, session, true)
.await;
let (pending_confirmation, _confirmation_rx) = oneshot::channel();
virtual_overseer
@@ -615,11 +1129,13 @@ fn wrong_validator_index_is_ignored() {
test_state.activate_leaf_at_session(&mut virtual_overseer, session, 1).await;
let valid_vote =
test_state.issue_statement_with_index(2, candidate_hash, session, true).await;
let valid_vote = test_state
.issue_explicit_statement_with_index(2, candidate_hash, session, true)
.await;
let invalid_vote =
test_state.issue_statement_with_index(1, candidate_hash, session, false).await;
let invalid_vote = test_state
.issue_explicit_statement_with_index(1, candidate_hash, session, false)
.await;
let (pending_confirmation, _confirmation_rx) = oneshot::channel();
virtual_overseer
@@ -685,11 +1201,13 @@ fn finality_votes_ignore_disputed_candidates() {
test_state.activate_leaf_at_session(&mut virtual_overseer, session, 1).await;
let valid_vote =
test_state.issue_statement_with_index(2, candidate_hash, session, true).await;
let valid_vote = test_state
.issue_explicit_statement_with_index(2, candidate_hash, session, true)
.await;
let invalid_vote =
test_state.issue_statement_with_index(1, candidate_hash, session, false).await;
let invalid_vote = test_state
.issue_explicit_statement_with_index(1, candidate_hash, session, false)
.await;
let (pending_confirmation, _confirmation_rx) = oneshot::channel();
virtual_overseer
@@ -781,11 +1299,13 @@ fn supermajority_valid_dispute_may_be_finalized() {
let supermajority_threshold =
polkadot_primitives::v1::supermajority_threshold(test_state.validators.len());
let valid_vote =
test_state.issue_statement_with_index(2, candidate_hash, session, true).await;
let valid_vote = test_state
.issue_explicit_statement_with_index(2, candidate_hash, session, true)
.await;
let invalid_vote =
test_state.issue_statement_with_index(1, candidate_hash, session, false).await;
let invalid_vote = test_state
.issue_explicit_statement_with_index(1, candidate_hash, session, false)
.await;
let (pending_confirmation, _confirmation_rx) = oneshot::channel();
virtual_overseer
@@ -807,8 +1327,9 @@ fn supermajority_valid_dispute_may_be_finalized() {
let mut statements = Vec::new();
for i in (0..supermajority_threshold - 1).map(|i| i + 3) {
let vote =
test_state.issue_statement_with_index(i, candidate_hash, session, true).await;
let vote = test_state
.issue_explicit_statement_with_index(i, candidate_hash, session, true)
.await;
statements.push((vote, ValidatorIndex(i as _)));
}
@@ -898,11 +1419,13 @@ fn concluded_supermajority_for_non_active_after_time() {
let supermajority_threshold =
polkadot_primitives::v1::supermajority_threshold(test_state.validators.len());
let valid_vote =
test_state.issue_statement_with_index(2, candidate_hash, session, true).await;
let valid_vote = test_state
.issue_explicit_statement_with_index(2, candidate_hash, session, true)
.await;
let invalid_vote =
test_state.issue_statement_with_index(1, candidate_hash, session, false).await;
let invalid_vote = test_state
.issue_explicit_statement_with_index(1, candidate_hash, session, false)
.await;
let (pending_confirmation, _confirmation_rx) = oneshot::channel();
virtual_overseer
@@ -925,8 +1448,9 @@ fn concluded_supermajority_for_non_active_after_time() {
let mut statements = Vec::new();
// -2: 1 for already imported vote and one for local vote (which is valid).
for i in (0..supermajority_threshold - 2).map(|i| i + 3) {
let vote =
test_state.issue_statement_with_index(i, candidate_hash, session, true).await;
let vote = test_state
.issue_explicit_statement_with_index(i, candidate_hash, session, true)
.await;
statements.push((vote, ValidatorIndex(i as _)));
}
@@ -993,11 +1517,13 @@ fn concluded_supermajority_against_non_active_after_time() {
let supermajority_threshold =
polkadot_primitives::v1::supermajority_threshold(test_state.validators.len());
let valid_vote =
test_state.issue_statement_with_index(2, candidate_hash, session, true).await;
let valid_vote = test_state
.issue_explicit_statement_with_index(2, candidate_hash, session, true)
.await;
let invalid_vote =
test_state.issue_statement_with_index(1, candidate_hash, session, false).await;
let invalid_vote = test_state
.issue_explicit_statement_with_index(1, candidate_hash, session, false)
.await;
let (pending_confirmation, confirmation_rx) = oneshot::channel();
virtual_overseer
@@ -1023,8 +1549,9 @@ fn concluded_supermajority_against_non_active_after_time() {
let mut statements = Vec::new();
// minus 2, because of local vote and one previously imported invalid vote.
for i in (0..supermajority_threshold - 2).map(|i| i + 3) {
let vote =
test_state.issue_statement_with_index(i, candidate_hash, session, false).await;
let vote = test_state
.issue_explicit_statement_with_index(i, candidate_hash, session, false)
.await;
statements.push((vote, ValidatorIndex(i as _)));
}
@@ -1090,11 +1617,13 @@ fn resume_dispute_without_local_statement() {
test_state.activate_leaf_at_session(&mut virtual_overseer, session, 1).await;
let valid_vote =
test_state.issue_statement_with_index(1, candidate_hash, session, true).await;
let valid_vote = test_state
.issue_explicit_statement_with_index(1, candidate_hash, session, true)
.await;
let invalid_vote =
test_state.issue_statement_with_index(2, candidate_hash, session, false).await;
let invalid_vote = test_state
.issue_explicit_statement_with_index(2, candidate_hash, session, false)
.await;
let (pending_confirmation, confirmation_rx) = oneshot::channel();
virtual_overseer
@@ -1146,14 +1675,24 @@ fn resume_dispute_without_local_statement() {
participation_with_distribution(&mut virtual_overseer, &candidate_hash).await;
let valid_vote0 =
test_state.issue_statement_with_index(0, candidate_hash, session, true).await;
let valid_vote3 =
test_state.issue_statement_with_index(3, candidate_hash, session, true).await;
let valid_vote4 =
test_state.issue_statement_with_index(4, candidate_hash, session, true).await;
let valid_vote5 =
test_state.issue_statement_with_index(5, candidate_hash, session, true).await;
let valid_vote0 = test_state
.issue_explicit_statement_with_index(0, candidate_hash, session, true)
.await;
let valid_vote3 = test_state
.issue_explicit_statement_with_index(3, candidate_hash, session, true)
.await;
let valid_vote4 = test_state
.issue_explicit_statement_with_index(4, candidate_hash, session, true)
.await;
let valid_vote5 = test_state
.issue_explicit_statement_with_index(5, candidate_hash, session, true)
.await;
let valid_vote6 = test_state
.issue_explicit_statement_with_index(6, candidate_hash, session, true)
.await;
let valid_vote7 = test_state
.issue_explicit_statement_with_index(7, candidate_hash, session, true)
.await;
let (pending_confirmation, _confirmation_rx) = oneshot::channel();
virtual_overseer
@@ -1167,6 +1706,8 @@ fn resume_dispute_without_local_statement() {
(valid_vote3, ValidatorIndex(3)),
(valid_vote4, ValidatorIndex(4)),
(valid_vote5, ValidatorIndex(5)),
(valid_vote6, ValidatorIndex(6)),
(valid_vote7, ValidatorIndex(7)),
],
pending_confirmation,
},
@@ -1210,14 +1751,17 @@ fn resume_dispute_with_local_statement() {
test_state.activate_leaf_at_session(&mut virtual_overseer, session, 1).await;
let local_valid_vote =
test_state.issue_statement_with_index(0, candidate_hash, session, true).await;
let local_valid_vote = test_state
.issue_explicit_statement_with_index(0, candidate_hash, session, true)
.await;
let valid_vote =
test_state.issue_statement_with_index(1, candidate_hash, session, true).await;
let valid_vote = test_state
.issue_explicit_statement_with_index(1, candidate_hash, session, true)
.await;
let invalid_vote =
test_state.issue_statement_with_index(2, candidate_hash, session, false).await;
let invalid_vote = test_state
.issue_explicit_statement_with_index(2, candidate_hash, session, false)
.await;
let (pending_confirmation, confirmation_rx) = oneshot::channel();
virtual_overseer
@@ -1277,7 +1821,8 @@ fn resume_dispute_with_local_statement() {
fn resume_dispute_without_local_statement_or_local_key() {
let session = 1;
let mut test_state = TestState::default();
test_state.subsystem_keystore = make_keystore(&[Sr25519Keyring::Two]).into();
test_state.subsystem_keystore =
make_keystore(vec![Sr25519Keyring::Two.to_seed()].into_iter()).into();
test_state
.resume(|mut test_state, mut virtual_overseer| {
Box::pin(async move {
@@ -1288,11 +1833,13 @@ fn resume_dispute_without_local_statement_or_local_key() {
test_state.activate_leaf_at_session(&mut virtual_overseer, session, 1).await;
let valid_vote =
test_state.issue_statement_with_index(1, candidate_hash, session, true).await;
let valid_vote = test_state
.issue_explicit_statement_with_index(1, candidate_hash, session, true)
.await;
let invalid_vote =
test_state.issue_statement_with_index(2, candidate_hash, session, false).await;
let invalid_vote = test_state
.issue_explicit_statement_with_index(2, candidate_hash, session, false)
.await;
let (pending_confirmation, confirmation_rx) = oneshot::channel();
virtual_overseer
@@ -1364,14 +1911,17 @@ fn resume_dispute_with_local_statement_without_local_key() {
test_state.activate_leaf_at_session(&mut virtual_overseer, session, 1).await;
let local_valid_vote =
test_state.issue_statement_with_index(0, candidate_hash, session, true).await;
let local_valid_vote = test_state
.issue_explicit_statement_with_index(0, candidate_hash, session, true)
.await;
let valid_vote =
test_state.issue_statement_with_index(1, candidate_hash, session, true).await;
let valid_vote = test_state
.issue_explicit_statement_with_index(1, candidate_hash, session, true)
.await;
let invalid_vote =
test_state.issue_statement_with_index(2, candidate_hash, session, false).await;
let invalid_vote = test_state
.issue_explicit_statement_with_index(2, candidate_hash, session, false)
.await;
let (pending_confirmation, confirmation_rx) = oneshot::channel();
virtual_overseer
@@ -1411,7 +1961,8 @@ fn resume_dispute_with_local_statement_without_local_key() {
})
});
// No keys:
test_state.subsystem_keystore = make_keystore(&[Sr25519Keyring::Two]).into();
test_state.subsystem_keystore =
make_keystore(vec![Sr25519Keyring::Two.to_seed()].into_iter()).into();
// Two should not send a DisputeParticiationMessage::Participate on restart since we gave
// her a non existing key.
test_state.resume(|test_state, mut virtual_overseer| {
@@ -1453,7 +2004,7 @@ fn issue_local_statement_does_cause_distribution_but_not_duplicate_participation
test_state.activate_leaf_at_session(&mut virtual_overseer, session, 1).await;
let other_vote = test_state
.issue_statement_with_index(1, candidate_hash, session, !validity)
.issue_explicit_statement_with_index(1, candidate_hash, session, !validity)
.await;
let (pending_confirmation, confirmation_rx) = oneshot::channel();
@@ -1606,10 +2157,10 @@ fn redundant_votes_ignored() {
test_state.activate_leaf_at_session(&mut virtual_overseer, session, 1).await;
let valid_vote =
test_state.issue_statement_with_index(1, candidate_hash, session, true).await;
test_state.issue_backing_statement_with_index(1, candidate_hash, session).await;
let valid_vote_2 =
test_state.issue_statement_with_index(1, candidate_hash, session, true).await;
test_state.issue_backing_statement_with_index(1, candidate_hash, session).await;
assert!(valid_vote.validator_signature() != valid_vote_2.validator_signature());