diff --git a/node/core/dispute-coordinator/src/real/initialized.rs b/node/core/dispute-coordinator/src/real/initialized.rs index 9f9f1305a9a8..d9d71c67e9bb 100644 --- a/node/core/dispute-coordinator/src/real/initialized.rs +++ b/node/core/dispute-coordinator/src/real/initialized.rs @@ -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, diff --git a/node/core/dispute-coordinator/src/real/spam_slots.rs b/node/core/dispute-coordinator/src/real/spam_slots.rs index 6c8707152a68..b58b812b042a 100644 --- a/node/core/dispute-coordinator/src/real/spam_slots.rs +++ b/node/core/dispute-coordinator/src/real/spam_slots.rs @@ -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 diff --git a/node/core/dispute-coordinator/src/real/tests.rs b/node/core/dispute-coordinator/src/real/tests.rs index aed361f5886f..9d031a70b22f 100644 --- a/node/core/dispute-coordinator/src/real/tests.rs +++ b/node/core/dispute-coordinator/src/real/tests.rs @@ -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) -> 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, + validators: Vec, validator_public: Vec, validator_groups: Vec>, master_keystore: Arc, @@ -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(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());