// Copyright (C) Parity Technologies (UK) Ltd. // This file is part of Polkadot. // Polkadot is free software: you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by // the Free Software Foundation, either version 3 of the License, or // (at your option) any later version. // Polkadot is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License for more details. // You should have received a copy of the GNU General Public License // along with Polkadot. If not, see . use super::*; use crate::{ configuration::HostConfiguration, disputes::DisputesHandler, mock::{ new_test_ext, AccountId, AllPalletsWithSystem, Initializer, MockGenesisConfig, System, Test, PUNISH_BACKERS_FOR, PUNISH_VALIDATORS_AGAINST, PUNISH_VALIDATORS_FOR, REWARD_VALIDATORS, }, }; use frame_support::{ assert_err, assert_noop, assert_ok, traits::{OnFinalize, OnInitialize}, }; use primitives::BlockNumber; use sp_core::{crypto::CryptoType, Pair}; const VOTE_FOR: VoteKind = VoteKind::ExplicitValid; const VOTE_AGAINST: VoteKind = VoteKind::Invalid; const VOTE_BACKING: VoteKind = VoteKind::Backing; fn filter_dispute_set(stmts: MultiDisputeStatementSet) -> CheckedMultiDisputeStatementSet { let config = >::config(); let post_conclusion_acceptance_period = config.dispute_post_conclusion_acceptance_period; stmts .into_iter() .filter_map(|set| { let filter = Pallet::::filter_dispute_data( &set, post_conclusion_acceptance_period, VerifyDisputeSignatures::Skip, ); filter.filter_statement_set(set) }) .collect::>() } // All arguments for `initializer::on_new_session` type NewSession<'a> = ( bool, SessionIndex, Vec<(&'a AccountId, ValidatorId)>, Option>, ); // Run to specific block, while calling disputes pallet hooks manually, because disputes is not // integrated in initializer yet. pub(crate) fn run_to_block<'a>( to: BlockNumber, new_session: impl Fn(BlockNumber) -> Option>, ) { while System::block_number() < to { let b = System::block_number(); if b != 0 { // circumvent requirement to have bitfields and headers in block for testing purposes crate::paras_inherent::Included::::set(Some(())); AllPalletsWithSystem::on_finalize(b); System::finalize(); } System::reset_events(); System::initialize(&(b + 1), &Default::default(), &Default::default()); AllPalletsWithSystem::on_initialize(b + 1); if let Some(new_session) = new_session(b + 1) { Initializer::test_trigger_on_new_session( new_session.0, new_session.1, new_session.2.into_iter(), new_session.3.map(|q| q.into_iter()), ); } } } #[test] fn test_contains_duplicates_in_sorted_iter() { // We here use the implicit ascending sorting and builtin equality of integers let v = vec![1, 2, 3, 5, 5, 8]; assert_eq!(true, contains_duplicates_in_sorted_iter(&v, |a, b| a == b)); let v = vec![1, 2, 3, 4]; assert_eq!(false, contains_duplicates_in_sorted_iter(&v, |a, b| a == b)); } #[test] fn test_dispute_state_flag_from_state() { assert_eq!( DisputeStateFlags::from_state(&DisputeState { validators_for: bitvec![u8, BitOrderLsb0; 0, 0, 0, 0, 0, 0, 0, 0], validators_against: bitvec![u8, BitOrderLsb0; 0, 0, 0, 0, 0, 0, 0, 0], start: 0, concluded_at: None, }), DisputeStateFlags::default(), ); assert_eq!( DisputeStateFlags::from_state(&DisputeState { validators_for: bitvec![u8, BitOrderLsb0; 1, 1, 1, 1, 1, 0, 0], validators_against: bitvec![u8, BitOrderLsb0; 0, 0, 0, 0, 0, 0, 0], start: 0, concluded_at: None, }), DisputeStateFlags::FOR_SUPERMAJORITY | DisputeStateFlags::CONFIRMED, ); assert_eq!( DisputeStateFlags::from_state(&DisputeState { validators_for: bitvec![u8, BitOrderLsb0; 0, 0, 0, 0, 0, 0, 0], validators_against: bitvec![u8, BitOrderLsb0; 1, 1, 1, 1, 1, 0, 0], start: 0, concluded_at: None, }), DisputeStateFlags::AGAINST_SUPERMAJORITY | DisputeStateFlags::CONFIRMED, ); } #[test] fn test_import_new_participant() { let mut importer = DisputeStateImporter::new( DisputeState { validators_for: bitvec![u8, BitOrderLsb0; 1, 0, 0, 0, 0, 0, 0, 0], validators_against: bitvec![u8, BitOrderLsb0; 0, 0, 0, 0, 0, 0, 0, 0], start: 0, concluded_at: None, }, BTreeSet::new(), 0, ); assert_err!( importer.import(ValidatorIndex(9), VOTE_FOR), VoteImportError::ValidatorIndexOutOfBounds, ); assert_err!(importer.import(ValidatorIndex(0), VOTE_FOR), VoteImportError::DuplicateStatement); assert_ok!(importer.import(ValidatorIndex(0), VOTE_AGAINST)); assert_ok!(importer.import(ValidatorIndex(2), VOTE_FOR)); assert_err!(importer.import(ValidatorIndex(2), VOTE_FOR), VoteImportError::DuplicateStatement); assert_ok!(importer.import(ValidatorIndex(2), VOTE_AGAINST)); assert_err!( importer.import(ValidatorIndex(2), VOTE_AGAINST), VoteImportError::DuplicateStatement ); let summary = importer.finish(); assert_eq!(summary.new_flags, DisputeStateFlags::default()); assert_eq!( summary.state, DisputeState { validators_for: bitvec![u8, BitOrderLsb0; 1, 0, 1, 0, 0, 0, 0, 0], validators_against: bitvec![u8, BitOrderLsb0; 1, 0, 1, 0, 0, 0, 0, 0], start: 0, concluded_at: None, }, ); assert!(summary.slash_for.is_empty()); assert!(summary.slash_against.is_empty()); assert_eq!(summary.new_participants, bitvec![u8, BitOrderLsb0; 0, 0, 1, 0, 0, 0, 0, 0]); } #[test] fn test_import_prev_participant_confirmed() { let mut importer = DisputeStateImporter::new( DisputeState { validators_for: bitvec![u8, BitOrderLsb0; 1, 0, 0, 0, 0, 0, 0, 0], validators_against: bitvec![u8, BitOrderLsb0; 0, 1, 0, 0, 0, 0, 0, 0], start: 0, concluded_at: None, }, BTreeSet::new(), 0, ); assert_ok!(importer.import(ValidatorIndex(2), VOTE_FOR)); let summary = importer.finish(); assert_eq!( summary.state, DisputeState { validators_for: bitvec![u8, BitOrderLsb0; 1, 0, 1, 0, 0, 0, 0, 0], validators_against: bitvec![u8, BitOrderLsb0; 0, 1, 0, 0, 0, 0, 0, 0], start: 0, concluded_at: None, }, ); assert!(summary.slash_for.is_empty()); assert!(summary.slash_against.is_empty()); assert_eq!(summary.new_participants, bitvec![u8, BitOrderLsb0; 0, 0, 1, 0, 0, 0, 0, 0]); assert_eq!(summary.new_flags, DisputeStateFlags::CONFIRMED); } #[test] fn test_import_prev_participant_confirmed_slash_for() { let mut importer = DisputeStateImporter::new( DisputeState { validators_for: bitvec![u8, BitOrderLsb0; 1, 0, 0, 0, 0, 0, 0, 0], validators_against: bitvec![u8, BitOrderLsb0; 0, 1, 0, 0, 0, 0, 0, 0], start: 0, concluded_at: None, }, BTreeSet::new(), 0, ); assert_ok!(importer.import(ValidatorIndex(2), VOTE_FOR)); assert_ok!(importer.import(ValidatorIndex(2), VOTE_AGAINST)); assert_ok!(importer.import(ValidatorIndex(3), VOTE_AGAINST)); assert_ok!(importer.import(ValidatorIndex(4), VOTE_AGAINST)); assert_ok!(importer.import(ValidatorIndex(5), VOTE_AGAINST)); assert_ok!(importer.import(ValidatorIndex(6), VOTE_AGAINST)); let summary = importer.finish(); assert_eq!( summary.state, DisputeState { validators_for: bitvec![u8, BitOrderLsb0; 1, 0, 1, 0, 0, 0, 0, 0], validators_against: bitvec![u8, BitOrderLsb0; 0, 1, 1, 1, 1, 1, 1, 0], start: 0, concluded_at: Some(0), }, ); assert_eq!(summary.slash_for, vec![ValidatorIndex(0), ValidatorIndex(2)]); assert!(summary.slash_against.is_empty()); assert_eq!(summary.new_participants, bitvec![u8, BitOrderLsb0; 0, 0, 1, 1, 1, 1, 1, 0]); assert_eq!( summary.new_flags, DisputeStateFlags::CONFIRMED | DisputeStateFlags::AGAINST_SUPERMAJORITY, ); } #[test] fn test_import_slash_against() { let mut importer = DisputeStateImporter::new( DisputeState { validators_for: bitvec![u8, BitOrderLsb0; 1, 0, 1, 0, 0, 0, 0, 0], validators_against: bitvec![u8, BitOrderLsb0; 0, 1, 0, 0, 0, 0, 0, 0], start: 0, concluded_at: None, }, BTreeSet::new(), 0, ); assert_ok!(importer.import(ValidatorIndex(3), VOTE_FOR)); assert_ok!(importer.import(ValidatorIndex(4), VOTE_FOR)); assert_ok!(importer.import(ValidatorIndex(5), VOTE_AGAINST)); assert_ok!(importer.import(ValidatorIndex(6), VOTE_FOR)); assert_ok!(importer.import(ValidatorIndex(7), VOTE_FOR)); let summary = importer.finish(); assert_eq!( summary.state, DisputeState { validators_for: bitvec![u8, BitOrderLsb0; 1, 0, 1, 1, 1, 0, 1, 1], validators_against: bitvec![u8, BitOrderLsb0; 0, 1, 0, 0, 0, 1, 0, 0], start: 0, concluded_at: Some(0), }, ); assert!(summary.slash_for.is_empty()); assert_eq!(summary.slash_against, vec![ValidatorIndex(1), ValidatorIndex(5)]); assert_eq!(summary.new_participants, bitvec![u8, BitOrderLsb0; 0, 0, 0, 1, 1, 1, 1, 1]); assert_eq!(summary.new_flags, DisputeStateFlags::FOR_SUPERMAJORITY); } #[test] fn test_import_backing_votes() { let mut importer = DisputeStateImporter::new( DisputeState { validators_for: bitvec![u8, BitOrderLsb0; 1, 0, 1, 0, 0, 0, 0, 0], validators_against: bitvec![u8, BitOrderLsb0; 0, 1, 0, 0, 0, 0, 0, 0], start: 0, concluded_at: None, }, BTreeSet::from_iter([ValidatorIndex(0)]), 0, ); assert_ok!(importer.import(ValidatorIndex(3), VOTE_FOR)); assert_ok!(importer.import(ValidatorIndex(3), VOTE_BACKING)); assert_ok!(importer.import(ValidatorIndex(3), VOTE_AGAINST)); assert_ok!(importer.import(ValidatorIndex(6), VOTE_FOR)); assert_ok!(importer.import(ValidatorIndex(7), VOTE_BACKING)); // Don't import backing vote twice assert_err!( importer.import(ValidatorIndex(0), VOTE_BACKING), VoteImportError::DuplicateStatement, ); // Don't import explicit votes after backing assert_err!(importer.import(ValidatorIndex(7), VOTE_FOR), VoteImportError::MaliciousBacker,); let summary = importer.finish(); assert_eq!( summary.state, DisputeState { validators_for: bitvec![u8, BitOrderLsb0; 1, 0, 1, 1, 0, 0, 1, 1], validators_against: bitvec![u8, BitOrderLsb0; 0, 1, 0, 1, 0, 0, 0, 0], start: 0, concluded_at: None, }, ); assert_eq!( summary.backers, BTreeSet::from_iter([ValidatorIndex(0), ValidatorIndex(3), ValidatorIndex(7),]), ); } // Test pruning works #[test] fn test_initializer_on_new_session() { let dispute_period = 3; let mock_genesis_config = MockGenesisConfig { configuration: crate::configuration::GenesisConfig { config: HostConfiguration { dispute_period, ..Default::default() }, ..Default::default() }, ..Default::default() }; new_test_ext(mock_genesis_config).execute_with(|| { let v0 = ::Pair::generate().0; let candidate_hash = CandidateHash(sp_core::H256::repeat_byte(1)); Pallet::::note_included(0, candidate_hash.clone(), 0); Pallet::::note_included(1, candidate_hash.clone(), 1); Pallet::::note_included(2, candidate_hash.clone(), 2); Pallet::::note_included(3, candidate_hash.clone(), 3); Pallet::::note_included(4, candidate_hash.clone(), 4); Pallet::::note_included(5, candidate_hash.clone(), 5); Pallet::::note_included(6, candidate_hash.clone(), 5); run_to_block(7, |b| { // a new session at each block Some((true, b, vec![(&0, v0.public())], Some(vec![(&0, v0.public())]))) }); // current session is 7, // we keep for dispute_period + 1 session and we remove in on_finalize // thus we keep info for session 3, 4, 5, 6, 7. assert_eq!(Included::::iter_prefix(0).count(), 0); assert_eq!(Included::::iter_prefix(1).count(), 0); assert_eq!(Included::::iter_prefix(2).count(), 0); assert_eq!(Included::::iter_prefix(3).count(), 1); assert_eq!(Included::::iter_prefix(4).count(), 1); assert_eq!(Included::::iter_prefix(5).count(), 1); assert_eq!(Included::::iter_prefix(6).count(), 1); }); } #[test] fn test_provide_data_duplicate_error() { new_test_ext(Default::default()).execute_with(|| { let candidate_hash_1 = CandidateHash(sp_core::H256::repeat_byte(1)); let candidate_hash_2 = CandidateHash(sp_core::H256::repeat_byte(2)); let mut stmts = vec![ DisputeStatementSet { candidate_hash: candidate_hash_2, session: 2, statements: vec![], }, DisputeStatementSet { candidate_hash: candidate_hash_1, session: 1, statements: vec![], }, DisputeStatementSet { candidate_hash: candidate_hash_2, session: 2, statements: vec![], }, ]; assert!(Pallet::::deduplicate_and_sort_dispute_data(&mut stmts).is_err()); assert_eq!(stmts.len(), 2); }) } #[test] fn test_provide_multi_dispute_is_providing() { new_test_ext(Default::default()).execute_with(|| { let v0 = ::Pair::generate().0; let v1 = ::Pair::generate().0; run_to_block(3, |b| { // a new session at each block if b == 1 { Some(( true, b, vec![(&0, v0.public()), (&1, v1.public())], Some(vec![(&0, v0.public()), (&1, v1.public())]), )) } else { Some((true, b, vec![(&1, v1.public())], Some(vec![(&1, v1.public())]))) } }); let candidate_hash = CandidateHash(sp_core::H256::repeat_byte(1)); let inclusion_parent = sp_core::H256::repeat_byte(0xff); let session = 1; let stmts = vec![DisputeStatementSet { candidate_hash: candidate_hash.clone(), session, statements: vec![ ( DisputeStatement::Valid(ValidDisputeStatementKind::BackingValid( inclusion_parent, )), ValidatorIndex(0), v0.sign(&CompactStatement::Valid(candidate_hash).signing_payload( &SigningContext { session_index: session, parent_hash: inclusion_parent }, )), ), ( DisputeStatement::Invalid(InvalidDisputeStatementKind::Explicit), ValidatorIndex(1), v1.sign( &ExplicitDisputeStatement { valid: false, candidate_hash: candidate_hash.clone(), session, } .signing_payload(), ), ), ], }]; assert_ok!( Pallet::::process_checked_multi_dispute_data( &stmts .into_iter() .map(CheckedDisputeStatementSet::unchecked_from_unchecked) .collect() ), vec![(1, candidate_hash.clone())], ); }) } #[test] fn test_disputes_with_missing_backing_votes_are_rejected() { new_test_ext(Default::default()).execute_with(|| { let v0 = ::Pair::generate().0; let v1 = ::Pair::generate().0; run_to_block(3, |b| { // a new session at each block if b == 1 { Some(( true, b, vec![(&0, v0.public()), (&1, v1.public())], Some(vec![(&0, v0.public()), (&1, v1.public())]), )) } else { Some((true, b, vec![(&1, v1.public())], Some(vec![(&1, v1.public())]))) } }); let candidate_hash = CandidateHash(sp_core::H256::repeat_byte(1)); let session = 1; let stmts = vec![DisputeStatementSet { candidate_hash: candidate_hash.clone(), session, statements: vec![ ( DisputeStatement::Valid(ValidDisputeStatementKind::Explicit), ValidatorIndex(0), v0.sign( &ExplicitDisputeStatement { valid: true, candidate_hash: candidate_hash.clone(), session, } .signing_payload(), ), ), ( DisputeStatement::Invalid(InvalidDisputeStatementKind::Explicit), ValidatorIndex(1), v1.sign( &ExplicitDisputeStatement { valid: false, candidate_hash: candidate_hash.clone(), session, } .signing_payload(), ), ), ], }]; assert!(Pallet::::process_checked_multi_dispute_data( &stmts .into_iter() .map(CheckedDisputeStatementSet::unchecked_from_unchecked) .collect() ) .is_err(),); }) } #[test] fn test_freeze_on_note_included() { new_test_ext(Default::default()).execute_with(|| { let v0 = ::Pair::generate().0; let v1 = ::Pair::generate().0; run_to_block(6, |b| { // a new session at each block Some(( true, b, vec![(&0, v0.public()), (&1, v1.public())], Some(vec![(&0, v0.public()), (&1, v1.public())]), )) }); let candidate_hash = CandidateHash(sp_core::H256::repeat_byte(1)); let inclusion_parent = sp_core::H256::repeat_byte(0xff); let session = 3; // v0 votes for 3 let stmts = vec![DisputeStatementSet { candidate_hash: candidate_hash.clone(), session: 3, statements: vec![ ( DisputeStatement::Invalid(InvalidDisputeStatementKind::Explicit), ValidatorIndex(0), v0.sign( &ExplicitDisputeStatement { valid: false, candidate_hash: candidate_hash.clone(), session: 3, } .signing_payload(), ), ), ( DisputeStatement::Invalid(InvalidDisputeStatementKind::Explicit), ValidatorIndex(1), v1.sign( &ExplicitDisputeStatement { valid: false, candidate_hash: candidate_hash.clone(), session: 3, } .signing_payload(), ), ), ( DisputeStatement::Valid(ValidDisputeStatementKind::BackingValid( inclusion_parent, )), ValidatorIndex(1), v0.sign(&CompactStatement::Valid(candidate_hash).signing_payload( &SigningContext { session_index: session, parent_hash: inclusion_parent }, )), ), ], }]; assert!(Pallet::::process_checked_multi_dispute_data( &stmts .into_iter() .map(CheckedDisputeStatementSet::unchecked_from_unchecked) .collect() ) .is_ok()); Pallet::::note_included(3, candidate_hash.clone(), 3); assert_eq!(Frozen::::get(), Some(2)); }); } #[test] fn test_freeze_provided_against_supermajority_for_included() { new_test_ext(Default::default()).execute_with(|| { let v0 = ::Pair::generate().0; let v1 = ::Pair::generate().0; run_to_block(6, |b| { // a new session at each block Some(( true, b, vec![(&0, v0.public()), (&1, v1.public())], Some(vec![(&0, v0.public()), (&1, v1.public())]), )) }); let candidate_hash = CandidateHash(sp_core::H256::repeat_byte(1)); let inclusion_parent = sp_core::H256::repeat_byte(0xff); let session = 3; // v0 votes for 3 let stmts = vec![DisputeStatementSet { candidate_hash: candidate_hash.clone(), session, statements: vec![ ( DisputeStatement::Invalid(InvalidDisputeStatementKind::Explicit), ValidatorIndex(0), v0.sign( &ExplicitDisputeStatement { valid: false, candidate_hash: candidate_hash.clone(), session, } .signing_payload(), ), ), ( DisputeStatement::Invalid(InvalidDisputeStatementKind::Explicit), ValidatorIndex(1), v1.sign( &ExplicitDisputeStatement { valid: false, candidate_hash: candidate_hash.clone(), session, } .signing_payload(), ), ), ( DisputeStatement::Valid(ValidDisputeStatementKind::BackingValid( inclusion_parent, )), ValidatorIndex(1), v0.sign(&CompactStatement::Valid(candidate_hash).signing_payload( &SigningContext { session_index: session, parent_hash: inclusion_parent }, )), ), ], }]; Pallet::::note_included(3, candidate_hash.clone(), 3); assert!(Pallet::::process_checked_multi_dispute_data( &stmts .into_iter() .map(CheckedDisputeStatementSet::unchecked_from_unchecked) .collect() ) .is_ok()); assert_eq!(Frozen::::get(), Some(2)); }); } mod unconfirmed_disputes { use super::*; use assert_matches::assert_matches; use sp_runtime::ModuleError; // Shared initialization code between `test_unconfirmed_are_ignored` and `test_unconfirmed_disputes_cause_block_import_error` fn generate_dispute_statement_set_and_run_to_block() -> DisputeStatementSet { // 7 validators needed for byzantine threshold of 2. let v0 = ::Pair::generate().0; let v1 = ::Pair::generate().0; let v2 = ::Pair::generate().0; let v3 = ::Pair::generate().0; let v4 = ::Pair::generate().0; let v5 = ::Pair::generate().0; let v6 = ::Pair::generate().0; // Mapping between key pair and `ValidatorIndex` // v0 -> 0 // v1 -> 3 // v2 -> 6 // v3 -> 5 // v4 -> 1 // v5 -> 4 // v6 -> 2 run_to_block(6, |b| { // a new session at each block Some(( true, b, vec![ (&0, v0.public()), (&1, v1.public()), (&2, v2.public()), (&3, v3.public()), (&4, v4.public()), (&5, v5.public()), (&6, v6.public()), ], Some(vec![ (&0, v0.public()), (&1, v1.public()), (&2, v2.public()), (&3, v3.public()), (&4, v4.public()), (&5, v5.public()), (&6, v6.public()), ]), )) }); let candidate_hash = CandidateHash(sp_core::H256::repeat_byte(1)); // v0 votes for 4, v1 votes against 4. DisputeStatementSet { candidate_hash: candidate_hash.clone(), session: 4, statements: vec![ ( DisputeStatement::Valid(ValidDisputeStatementKind::Explicit), ValidatorIndex(0), v0.sign( &ExplicitDisputeStatement { valid: true, candidate_hash: candidate_hash.clone(), session: 4, } .signing_payload(), ), ), ( DisputeStatement::Invalid(InvalidDisputeStatementKind::Explicit), ValidatorIndex(3), v1.sign( &ExplicitDisputeStatement { valid: false, candidate_hash: candidate_hash.clone(), session: 4, } .signing_payload(), ), ), ], } } #[test] fn test_unconfirmed_are_ignored() { new_test_ext(Default::default()).execute_with(|| { let stmts = vec![generate_dispute_statement_set_and_run_to_block()]; let stmts = filter_dispute_set(stmts); // Not confirmed => should be filtered out assert_ok!(Pallet::::process_checked_multi_dispute_data(&stmts), vec![],); }); } #[test] fn test_unconfirmed_disputes_cause_block_import_error() { new_test_ext(Default::default()).execute_with(|| { let stmts = generate_dispute_statement_set_and_run_to_block(); let stmts = vec![CheckedDisputeStatementSet::unchecked_from_unchecked(stmts)]; assert_matches!( Pallet::::process_checked_multi_dispute_data(&stmts), Err(DispatchError::Module(ModuleError{index: _, error: _, message})) => assert_eq!(message, Some("UnconfirmedDispute")) ); }); } } // tests for: // * provide_multi_dispute: with success scenario // * disputes: correctness of datas // * could_be_invalid: correctness of datas // * ensure rewards and punishment are correctly called. #[test] fn test_provide_multi_dispute_success_and_other() { new_test_ext(Default::default()).execute_with(|| { // 7 validators needed for byzantine threshold of 2. let v0 = ::Pair::generate().0; let v1 = ::Pair::generate().0; let v2 = ::Pair::generate().0; let v3 = ::Pair::generate().0; let v4 = ::Pair::generate().0; let v5 = ::Pair::generate().0; let v6 = ::Pair::generate().0; // Mapping between key pair and `ValidatorIndex` // v0 -> 0 // v1 -> 3 // v2 -> 6 // v3 -> 5 // v4 -> 1 // v5 -> 4 // v6 -> 2 run_to_block(6, |b| { // a new session at each block Some(( true, b, vec![ (&0, v0.public()), (&1, v1.public()), (&2, v2.public()), (&3, v3.public()), (&4, v4.public()), (&5, v5.public()), (&6, v6.public()), ], Some(vec![ (&0, v0.public()), (&1, v1.public()), (&2, v2.public()), (&3, v3.public()), (&4, v4.public()), (&5, v5.public()), (&6, v6.public()), ]), )) }); let candidate_hash = CandidateHash(sp_core::H256::repeat_byte(1)); let inclusion_parent = sp_core::H256::repeat_byte(0xff); let session = 3; // v0 and v1 vote for 3, v6 votes against let stmts = vec![DisputeStatementSet { candidate_hash: candidate_hash.clone(), session, statements: vec![ ( DisputeStatement::Valid(ValidDisputeStatementKind::BackingValid( inclusion_parent, )), ValidatorIndex(0), v0.sign(&CompactStatement::Valid(candidate_hash).signing_payload( &SigningContext { session_index: session, parent_hash: inclusion_parent }, )), ), ( DisputeStatement::Invalid(InvalidDisputeStatementKind::Explicit), ValidatorIndex(2), v6.sign( &ExplicitDisputeStatement { valid: false, candidate_hash: candidate_hash.clone(), session, } .signing_payload(), ), ), ( DisputeStatement::Valid(ValidDisputeStatementKind::Explicit), ValidatorIndex(3), v1.sign( &ExplicitDisputeStatement { valid: true, candidate_hash: candidate_hash.clone(), session: 3, } .signing_payload(), ), ), ], }]; let stmts = filter_dispute_set(stmts); assert_ok!( Pallet::::process_checked_multi_dispute_data(&stmts), vec![(3, candidate_hash.clone())], ); // v3 votes against 3 and for 5, v2 and v6 vote against 5. let stmts = vec![ DisputeStatementSet { candidate_hash: candidate_hash.clone(), session: 3, statements: vec![( DisputeStatement::Invalid(InvalidDisputeStatementKind::Explicit), ValidatorIndex(5), v3.sign( &ExplicitDisputeStatement { valid: false, candidate_hash: candidate_hash.clone(), session: 3, } .signing_payload(), ), )], }, DisputeStatementSet { candidate_hash: candidate_hash.clone(), session: 5, statements: vec![ ( DisputeStatement::Valid(ValidDisputeStatementKind::BackingValid( inclusion_parent, )), ValidatorIndex(5), v3.sign(&CompactStatement::Valid(candidate_hash).signing_payload( &SigningContext { session_index: 5, parent_hash: inclusion_parent }, )), ), ( DisputeStatement::Invalid(InvalidDisputeStatementKind::Explicit), ValidatorIndex(6), v2.sign( &ExplicitDisputeStatement { valid: false, candidate_hash: candidate_hash.clone(), session: 5, } .signing_payload(), ), ), ( DisputeStatement::Invalid(InvalidDisputeStatementKind::Explicit), ValidatorIndex(2), v6.sign( &ExplicitDisputeStatement { valid: false, candidate_hash: candidate_hash.clone(), session: 5, } .signing_payload(), ), ), ], }, ]; let stmts = filter_dispute_set(stmts); assert_ok!( Pallet::::process_checked_multi_dispute_data(&stmts), vec![(5, candidate_hash.clone())], ); // v2 votes for 3 let stmts = vec![DisputeStatementSet { candidate_hash: candidate_hash.clone(), session: 3, statements: vec![( DisputeStatement::Valid(ValidDisputeStatementKind::Explicit), ValidatorIndex(6), v2.sign( &ExplicitDisputeStatement { valid: true, candidate_hash: candidate_hash.clone(), session: 3, } .signing_payload(), ), )], }]; let stmts = filter_dispute_set(stmts); assert_ok!(Pallet::::process_checked_multi_dispute_data(&stmts), vec![]); let stmts = vec![ // 0, 4, and 5 vote against 5 DisputeStatementSet { candidate_hash: candidate_hash.clone(), session: 5, statements: vec![ ( DisputeStatement::Invalid(InvalidDisputeStatementKind::Explicit), ValidatorIndex(0), v0.sign( &ExplicitDisputeStatement { valid: false, candidate_hash: candidate_hash.clone(), session: 5, } .signing_payload(), ), ), ( DisputeStatement::Invalid(InvalidDisputeStatementKind::Explicit), ValidatorIndex(1), v4.sign( &ExplicitDisputeStatement { valid: false, candidate_hash: candidate_hash.clone(), session: 5, } .signing_payload(), ), ), ( DisputeStatement::Invalid(InvalidDisputeStatementKind::Explicit), ValidatorIndex(4), v5.sign( &ExplicitDisputeStatement { valid: false, candidate_hash: candidate_hash.clone(), session: 5, } .signing_payload(), ), ), ], }, // 4 and 5 vote for 3 DisputeStatementSet { candidate_hash: candidate_hash.clone(), session: 3, statements: vec![ ( DisputeStatement::Valid(ValidDisputeStatementKind::Explicit), ValidatorIndex(1), v4.sign( &ExplicitDisputeStatement { valid: true, candidate_hash: candidate_hash.clone(), session: 3, } .signing_payload(), ), ), ( DisputeStatement::Valid(ValidDisputeStatementKind::Explicit), ValidatorIndex(4), v5.sign( &ExplicitDisputeStatement { valid: true, candidate_hash: candidate_hash.clone(), session: 3, } .signing_payload(), ), ), ], }, ]; let stmts = filter_dispute_set(stmts); assert_ok!(Pallet::::process_checked_multi_dispute_data(&stmts), vec![]); assert_eq!( Pallet::::disputes(), vec![ ( 5, candidate_hash.clone(), DisputeState { validators_for: bitvec![u8, BitOrderLsb0; 0, 0, 0, 0, 0, 1, 0], validators_against: bitvec![u8, BitOrderLsb0; 1, 1, 1, 0, 1, 0, 1], start: 6, concluded_at: Some(6), // 5 vote against } ), ( 3, candidate_hash.clone(), DisputeState { validators_for: bitvec![u8, BitOrderLsb0; 1, 1, 0, 1, 1, 0, 1], validators_against: bitvec![u8, BitOrderLsb0; 0, 0, 1, 0, 0, 1, 0], start: 6, concluded_at: Some(6), // 5 vote for } ), ] ); assert!(!Pallet::::concluded_invalid(3, candidate_hash.clone())); assert!(!Pallet::::concluded_invalid(4, candidate_hash.clone())); assert!(Pallet::::concluded_invalid(5, candidate_hash.clone())); // Ensure the `reward_validator` function was correctly called assert_eq!( REWARD_VALIDATORS.with(|r| r.borrow().clone()), vec![ (3, vec![ValidatorIndex(0), ValidatorIndex(2), ValidatorIndex(3)]), (3, vec![ValidatorIndex(5)]), (5, vec![ValidatorIndex(2), ValidatorIndex(5), ValidatorIndex(6)]), (3, vec![ValidatorIndex(6)]), (5, vec![ValidatorIndex(0), ValidatorIndex(1), ValidatorIndex(4)]), (3, vec![ValidatorIndex(1), ValidatorIndex(4)]), ], ); // Ensure punishment against is called assert_eq!( PUNISH_VALIDATORS_AGAINST.with(|r| r.borrow().clone()), vec![ (3, vec![]), (3, vec![]), (5, vec![]), (3, vec![]), (5, vec![]), (3, vec![ValidatorIndex(2), ValidatorIndex(5)]), ], ); // Ensure punishment for is called assert_eq!( PUNISH_VALIDATORS_FOR.with(|r| r.borrow().clone()), vec![ (3, vec![]), (3, vec![]), (5, vec![]), (3, vec![]), (5, vec![ValidatorIndex(5)]), (3, vec![]), ], ); }) } /// In this setup we have only one dispute concluding AGAINST. /// There are some votes imported post dispute conclusion. /// We make sure these votes are accounted for in punishment. #[test] fn test_punish_post_conclusion() { new_test_ext(Default::default()).execute_with(|| { // supermajority threshold is 5 let v0 = ::Pair::generate().0; let v1 = ::Pair::generate().0; let v2 = ::Pair::generate().0; let v3 = ::Pair::generate().0; let v4 = ::Pair::generate().0; let v5 = ::Pair::generate().0; let v6 = ::Pair::generate().0; // Mapping between key pair and `ValidatorIndex` // v0 -> 0 // v1 -> 3 // v2 -> 6 // v3 -> 5 // v4 -> 1 // v5 -> 4 // v6 -> 2 run_to_block(6, |b| { // a new session at each block Some(( true, b, vec![ (&0, v0.public()), (&1, v1.public()), (&2, v2.public()), (&3, v3.public()), (&4, v4.public()), (&5, v5.public()), (&6, v6.public()), ], Some(vec![ (&0, v0.public()), (&1, v1.public()), (&2, v2.public()), (&3, v3.public()), (&4, v4.public()), (&5, v5.public()), (&6, v6.public()), ]), )) }); let candidate_hash = CandidateHash(sp_core::H256::repeat_byte(1)); let inclusion_parent = sp_core::H256::repeat_byte(0xff); let session = 3; let stmts = vec![DisputeStatementSet { candidate_hash: candidate_hash.clone(), session, statements: vec![ ( DisputeStatement::Valid(ValidDisputeStatementKind::BackingValid( inclusion_parent, )), ValidatorIndex(0), v0.sign(&CompactStatement::Valid(candidate_hash).signing_payload( &SigningContext { session_index: session, parent_hash: inclusion_parent }, )), ), ( DisputeStatement::Invalid(InvalidDisputeStatementKind::Explicit), ValidatorIndex(1), v4.sign( &ExplicitDisputeStatement { valid: false, candidate_hash: candidate_hash.clone(), session, } .signing_payload(), ), ), ( DisputeStatement::Invalid(InvalidDisputeStatementKind::Explicit), ValidatorIndex(2), v6.sign( &ExplicitDisputeStatement { valid: false, candidate_hash: candidate_hash.clone(), session, } .signing_payload(), ), ), ( DisputeStatement::Invalid(InvalidDisputeStatementKind::Explicit), ValidatorIndex(6), v2.sign( &ExplicitDisputeStatement { valid: false, candidate_hash: candidate_hash.clone(), session, } .signing_payload(), ), ), ( DisputeStatement::Invalid(InvalidDisputeStatementKind::Explicit), ValidatorIndex(4), v5.sign( &ExplicitDisputeStatement { valid: false, candidate_hash: candidate_hash.clone(), session, } .signing_payload(), ), ), ( DisputeStatement::Invalid(InvalidDisputeStatementKind::Explicit), ValidatorIndex(5), v3.sign( &ExplicitDisputeStatement { valid: false, candidate_hash: candidate_hash.clone(), session, } .signing_payload(), ), ), ( DisputeStatement::Valid(ValidDisputeStatementKind::ApprovalChecking), ValidatorIndex(3), v1.sign(&ApprovalVote(candidate_hash).signing_payload(session)), ), ], }]; let stmts = filter_dispute_set(stmts); assert_ok!( Pallet::::process_checked_multi_dispute_data(&stmts), vec![(session, candidate_hash)], ); assert_eq!( PUNISH_VALIDATORS_FOR.with(|r| r.borrow().clone()), vec![(session, vec![ValidatorIndex(0), ValidatorIndex(3)]),], ); assert_eq!( PUNISH_BACKERS_FOR.with(|r| r.borrow().clone()), vec![(session, vec![ValidatorIndex(0)]),], ); // someone reveals 3 backing vote, 6 votes against let stmts = vec![DisputeStatementSet { candidate_hash: candidate_hash.clone(), session, statements: vec![ ( DisputeStatement::Valid(ValidDisputeStatementKind::BackingValid( inclusion_parent, )), ValidatorIndex(3), v1.sign(&CompactStatement::Valid(candidate_hash).signing_payload( &SigningContext { session_index: session, parent_hash: inclusion_parent }, )), ), ( DisputeStatement::Invalid(InvalidDisputeStatementKind::Explicit), ValidatorIndex(6), v2.sign( &ExplicitDisputeStatement { valid: false, candidate_hash: candidate_hash.clone(), session, } .signing_payload(), ), ), ], }]; let stmts = filter_dispute_set(stmts); assert_ok!(Pallet::::process_checked_multi_dispute_data(&stmts), vec![],); // Ensure punishment for is called assert_eq!( PUNISH_VALIDATORS_FOR.with(|r| r.borrow().clone()), vec![ (session, vec![ValidatorIndex(0), ValidatorIndex(3)]), (session, vec![ValidatorIndex(3)]), ], ); assert_eq!( PUNISH_BACKERS_FOR.with(|r| r.borrow().clone()), vec![ (session, vec![ValidatorIndex(0)]), (session, vec![ValidatorIndex(0), ValidatorIndex(3)]) ], ); assert_eq!( PUNISH_VALIDATORS_AGAINST.with(|r| r.borrow().clone()), vec![(session, vec![]), (session, vec![]),], ); }) } #[test] fn test_revert_and_freeze() { new_test_ext(Default::default()).execute_with(|| { // events are ignored for genesis block System::set_block_number(1); Frozen::::put(Some(0)); assert_noop!( { Pallet::::revert_and_freeze(0); Result::<(), ()>::Err(()) // Just a small trick in order to use `assert_noop`. }, (), ); Frozen::::kill(); Pallet::::revert_and_freeze(0); assert_eq!(Frozen::::get(), Some(0)); assert_eq!(System::digest().logs[0], ConsensusLog::Revert(1).into()); System::assert_has_event(Event::Revert(1).into()); }) } #[test] fn test_revert_and_freeze_merges() { new_test_ext(Default::default()).execute_with(|| { Frozen::::put(Some(10)); assert_noop!( { Pallet::::revert_and_freeze(10); Result::<(), ()>::Err(()) // Just a small trick in order to use `assert_noop`. }, (), ); Pallet::::revert_and_freeze(8); assert_eq!(Frozen::::get(), Some(8)); }) } #[test] fn test_has_supermajority_against() { assert_eq!( has_supermajority_against(&DisputeState { validators_for: bitvec![u8, BitOrderLsb0; 1, 1, 0, 0, 0, 0, 0, 0], validators_against: bitvec![u8, BitOrderLsb0; 1, 1, 1, 1, 1, 0, 0, 0], start: 0, concluded_at: None, }), false, ); assert_eq!( has_supermajority_against(&DisputeState { validators_for: bitvec![u8, BitOrderLsb0; 1, 1, 0, 0, 0, 0, 0, 0], validators_against: bitvec![u8, BitOrderLsb0; 1, 1, 1, 1, 1, 1, 0, 0], start: 0, concluded_at: None, }), true, ); } #[test] fn test_check_signature() { let validator_id = ::Pair::generate().0; let wrong_validator_id = ::Pair::generate().0; let session = 0; let wrong_session = 1; let candidate_hash = CandidateHash(sp_core::H256::repeat_byte(1)); let wrong_candidate_hash = CandidateHash(sp_core::H256::repeat_byte(2)); let inclusion_parent = sp_core::H256::repeat_byte(3); let wrong_inclusion_parent = sp_core::H256::repeat_byte(4); let statement_1 = DisputeStatement::Valid(ValidDisputeStatementKind::Explicit); let statement_2 = DisputeStatement::Valid(ValidDisputeStatementKind::BackingSeconded( inclusion_parent.clone(), )); let wrong_statement_2 = DisputeStatement::Valid(ValidDisputeStatementKind::BackingSeconded( wrong_inclusion_parent.clone(), )); let statement_3 = DisputeStatement::Valid(ValidDisputeStatementKind::BackingValid(inclusion_parent.clone())); let wrong_statement_3 = DisputeStatement::Valid(ValidDisputeStatementKind::BackingValid( wrong_inclusion_parent.clone(), )); let statement_4 = DisputeStatement::Valid(ValidDisputeStatementKind::ApprovalChecking); let statement_5 = DisputeStatement::Invalid(InvalidDisputeStatementKind::Explicit); let signed_1 = validator_id.sign( &ExplicitDisputeStatement { valid: true, candidate_hash: candidate_hash.clone(), session } .signing_payload(), ); let signed_2 = validator_id.sign(&CompactStatement::Seconded(candidate_hash.clone()).signing_payload( &SigningContext { session_index: session, parent_hash: inclusion_parent.clone() }, )); let signed_3 = validator_id.sign(&CompactStatement::Valid(candidate_hash.clone()).signing_payload( &SigningContext { session_index: session, parent_hash: inclusion_parent.clone() }, )); let signed_4 = validator_id.sign(&ApprovalVote(candidate_hash.clone()).signing_payload(session)); let signed_5 = validator_id.sign( &ExplicitDisputeStatement { valid: false, candidate_hash: candidate_hash.clone(), session } .signing_payload(), ); assert!(check_signature( &validator_id.public(), candidate_hash, session, &statement_1, &signed_1 ) .is_ok()); assert!(check_signature( &wrong_validator_id.public(), candidate_hash, session, &statement_1, &signed_1 ) .is_err()); assert!(check_signature( &validator_id.public(), wrong_candidate_hash, session, &statement_1, &signed_1 ) .is_err()); assert!(check_signature( &validator_id.public(), candidate_hash, wrong_session, &statement_1, &signed_1 ) .is_err()); assert!(check_signature( &validator_id.public(), candidate_hash, session, &statement_2, &signed_1 ) .is_err()); assert!(check_signature( &validator_id.public(), candidate_hash, session, &statement_3, &signed_1 ) .is_err()); assert!(check_signature( &validator_id.public(), candidate_hash, session, &statement_4, &signed_1 ) .is_err()); assert!(check_signature( &validator_id.public(), candidate_hash, session, &statement_5, &signed_1 ) .is_err()); assert!(check_signature( &validator_id.public(), candidate_hash, session, &statement_2, &signed_2 ) .is_ok()); assert!(check_signature( &wrong_validator_id.public(), candidate_hash, session, &statement_2, &signed_2 ) .is_err()); assert!(check_signature( &validator_id.public(), wrong_candidate_hash, session, &statement_2, &signed_2 ) .is_err()); assert!(check_signature( &validator_id.public(), candidate_hash, wrong_session, &statement_2, &signed_2 ) .is_err()); assert!(check_signature( &validator_id.public(), candidate_hash, session, &wrong_statement_2, &signed_2 ) .is_err()); assert!(check_signature( &validator_id.public(), candidate_hash, session, &statement_1, &signed_2 ) .is_err()); assert!(check_signature( &validator_id.public(), candidate_hash, session, &statement_3, &signed_2 ) .is_err()); assert!(check_signature( &validator_id.public(), candidate_hash, session, &statement_4, &signed_2 ) .is_err()); assert!(check_signature( &validator_id.public(), candidate_hash, session, &statement_5, &signed_2 ) .is_err()); assert!(check_signature( &validator_id.public(), candidate_hash, session, &statement_3, &signed_3 ) .is_ok()); assert!(check_signature( &wrong_validator_id.public(), candidate_hash, session, &statement_3, &signed_3 ) .is_err()); assert!(check_signature( &validator_id.public(), wrong_candidate_hash, session, &statement_3, &signed_3 ) .is_err()); assert!(check_signature( &validator_id.public(), candidate_hash, wrong_session, &statement_3, &signed_3 ) .is_err()); assert!(check_signature( &validator_id.public(), candidate_hash, session, &wrong_statement_3, &signed_3 ) .is_err()); assert!(check_signature( &validator_id.public(), candidate_hash, session, &statement_1, &signed_3 ) .is_err()); assert!(check_signature( &validator_id.public(), candidate_hash, session, &statement_2, &signed_3 ) .is_err()); assert!(check_signature( &validator_id.public(), candidate_hash, session, &statement_4, &signed_3 ) .is_err()); assert!(check_signature( &validator_id.public(), candidate_hash, session, &statement_5, &signed_3 ) .is_err()); assert!(check_signature( &validator_id.public(), candidate_hash, session, &statement_4, &signed_4 ) .is_ok()); assert!(check_signature( &wrong_validator_id.public(), candidate_hash, session, &statement_4, &signed_4 ) .is_err()); assert!(check_signature( &validator_id.public(), wrong_candidate_hash, session, &statement_4, &signed_4 ) .is_err()); assert!(check_signature( &validator_id.public(), candidate_hash, wrong_session, &statement_4, &signed_4 ) .is_err()); assert!(check_signature( &validator_id.public(), candidate_hash, session, &statement_1, &signed_4 ) .is_err()); assert!(check_signature( &validator_id.public(), candidate_hash, session, &statement_2, &signed_4 ) .is_err()); assert!(check_signature( &validator_id.public(), candidate_hash, session, &statement_3, &signed_4 ) .is_err()); assert!(check_signature( &validator_id.public(), candidate_hash, session, &statement_5, &signed_4 ) .is_err()); assert!(check_signature( &validator_id.public(), candidate_hash, session, &statement_5, &signed_5 ) .is_ok()); assert!(check_signature( &wrong_validator_id.public(), candidate_hash, session, &statement_5, &signed_5 ) .is_err()); assert!(check_signature( &validator_id.public(), wrong_candidate_hash, session, &statement_5, &signed_5 ) .is_err()); assert!(check_signature( &validator_id.public(), candidate_hash, wrong_session, &statement_5, &signed_5 ) .is_err()); assert!(check_signature( &validator_id.public(), candidate_hash, session, &statement_1, &signed_5 ) .is_err()); assert!(check_signature( &validator_id.public(), candidate_hash, session, &statement_2, &signed_5 ) .is_err()); assert!(check_signature( &validator_id.public(), candidate_hash, session, &statement_3, &signed_5 ) .is_err()); assert!(check_signature( &validator_id.public(), candidate_hash, session, &statement_4, &signed_5 ) .is_err()); } #[test] fn deduplication_and_sorting_works() { new_test_ext(Default::default()).execute_with(|| { let v0 = ::Pair::generate().0; let v1 = ::Pair::generate().0; let v2 = ::Pair::generate().0; let v3 = ::Pair::generate().0; run_to_block(3, |b| { // a new session at each block Some(( true, b, vec![(&0, v0.public()), (&1, v1.public()), (&2, v2.public()), (&3, v3.public())], Some(vec![ (&0, v0.public()), (&1, v1.public()), (&2, v2.public()), (&3, v3.public()), ]), )) }); let candidate_hash_a = CandidateHash(sp_core::H256::repeat_byte(1)); let candidate_hash_b = CandidateHash(sp_core::H256::repeat_byte(2)); let candidate_hash_c = CandidateHash(sp_core::H256::repeat_byte(3)); let create_explicit_statement = |vidx: ValidatorIndex, validator: &::Pair, c_hash: &CandidateHash, valid, session| { let payload = ExplicitDisputeStatement { valid, candidate_hash: c_hash.clone(), session } .signing_payload(); let sig = validator.sign(&payload); (DisputeStatement::Valid(ValidDisputeStatementKind::Explicit), vidx, sig.clone()) }; let explicit_triple_a = create_explicit_statement(ValidatorIndex(0), &v0, &candidate_hash_a, true, 1); let explicit_triple_a_bad = create_explicit_statement(ValidatorIndex(1), &v1, &candidate_hash_a, false, 1); let explicit_triple_b = create_explicit_statement(ValidatorIndex(0), &v0, &candidate_hash_b, true, 2); let explicit_triple_b_bad = create_explicit_statement(ValidatorIndex(1), &v1, &candidate_hash_b, false, 2); let explicit_triple_c = create_explicit_statement(ValidatorIndex(0), &v0, &candidate_hash_c, true, 2); let explicit_triple_c_bad = create_explicit_statement(ValidatorIndex(1), &v1, &candidate_hash_c, false, 2); let mut disputes = vec![ DisputeStatementSet { candidate_hash: candidate_hash_b.clone(), session: 2, statements: vec![explicit_triple_b.clone(), explicit_triple_b_bad.clone()], }, // same session as above DisputeStatementSet { candidate_hash: candidate_hash_c.clone(), session: 2, statements: vec![explicit_triple_c, explicit_triple_c_bad], }, // the duplicate set DisputeStatementSet { candidate_hash: candidate_hash_b.clone(), session: 2, statements: vec![explicit_triple_b.clone(), explicit_triple_b_bad.clone()], }, DisputeStatementSet { candidate_hash: candidate_hash_a.clone(), session: 1, statements: vec![explicit_triple_a, explicit_triple_a_bad], }, ]; let disputes_orig = disputes.clone(); as DisputesHandler< ::BlockNumber, >>::deduplicate_and_sort_dispute_data(&mut disputes).unwrap_err(); // assert ordering of local only disputes, and at the same time, and being free of duplicates assert_eq!(disputes_orig.len(), disputes.len() + 1); let are_these_equal = |a: &DisputeStatementSet, b: &DisputeStatementSet| { use core::cmp::Ordering; // we only have local disputes here, so sorting of those adheres to the // simplified sorting logic let cmp = a.session.cmp(&b.session).then_with(|| a.candidate_hash.cmp(&b.candidate_hash)); assert_ne!(cmp, Ordering::Greater); cmp == Ordering::Equal }; assert_eq!(false, contains_duplicates_in_sorted_iter(&disputes, are_these_equal)); }) } fn apply_filter_all>( sets: I, ) -> Vec { let config = >::config(); let post_conclusion_acceptance_period = config.dispute_post_conclusion_acceptance_period; let mut acc = Vec::::new(); for dispute_statement in sets { if let Some(checked) = as DisputesHandler<::BlockNumber>>::filter_dispute_data( dispute_statement, post_conclusion_acceptance_period, VerifyDisputeSignatures::Yes, ) { acc.push(checked); } } acc } #[test] fn filter_removes_duplicates_within_set() { new_test_ext(Default::default()).execute_with(|| { let v0 = ::Pair::generate().0; let v1 = ::Pair::generate().0; run_to_block(3, |b| { // a new session at each block Some(( true, b, vec![(&0, v0.public()), (&1, v1.public())], Some(vec![(&0, v0.public()), (&1, v1.public())]), )) }); let candidate_hash = CandidateHash(sp_core::H256::repeat_byte(1)); let payload = ExplicitDisputeStatement { valid: true, candidate_hash: candidate_hash.clone(), session: 1, } .signing_payload(); let payload_against = ExplicitDisputeStatement { valid: false, candidate_hash: candidate_hash.clone(), session: 1, } .signing_payload(); let sig_a = v0.sign(&payload); let sig_b = v0.sign(&payload); let sig_c = v0.sign(&payload); let sig_d = v1.sign(&payload_against); let statements = DisputeStatementSet { candidate_hash: candidate_hash.clone(), session: 1, statements: vec![ ( DisputeStatement::Valid(ValidDisputeStatementKind::Explicit), ValidatorIndex(0), sig_a.clone(), ), ( DisputeStatement::Valid(ValidDisputeStatementKind::Explicit), ValidatorIndex(0), sig_b, ), ( DisputeStatement::Valid(ValidDisputeStatementKind::Explicit), ValidatorIndex(0), sig_c, ), ( DisputeStatement::Invalid(InvalidDisputeStatementKind::Explicit), ValidatorIndex(1), sig_d.clone(), ), ], }; let post_conclusion_acceptance_period = 10; let statements = as DisputesHandler< ::BlockNumber, >>::filter_dispute_data( statements, post_conclusion_acceptance_period, VerifyDisputeSignatures::Yes, ); assert_eq!( statements, Some(CheckedDisputeStatementSet::unchecked_from_unchecked(DisputeStatementSet { candidate_hash: candidate_hash.clone(), session: 1, statements: vec![ ( DisputeStatement::Valid(ValidDisputeStatementKind::Explicit), ValidatorIndex(0), sig_a, ), ( DisputeStatement::Invalid(InvalidDisputeStatementKind::Explicit), ValidatorIndex(1), sig_d, ), ] })) ); }) } #[test] fn filter_bad_signatures_correctly_detects_single_sided() { new_test_ext(Default::default()).execute_with(|| { let v0 = ::Pair::generate().0; let v1 = ::Pair::generate().0; let v2 = ::Pair::generate().0; let v3 = ::Pair::generate().0; run_to_block(3, |b| { // a new session at each block Some(( true, b, vec![(&0, v0.public()), (&1, v1.public()), (&2, v2.public()), (&3, v3.public())], Some(vec![ (&0, v0.public()), (&1, v1.public()), (&2, v2.public()), (&3, v3.public()), ]), )) }); let candidate_hash_a = CandidateHash(sp_core::H256::repeat_byte(1)); let payload = |c_hash: &CandidateHash, valid| { ExplicitDisputeStatement { valid, candidate_hash: c_hash.clone(), session: 1 } .signing_payload() }; let payload_a = payload(&candidate_hash_a, true); let payload_a_bad = payload(&candidate_hash_a, false); let sig_0 = v0.sign(&payload_a); let sig_1 = v1.sign(&payload_a_bad); let statements = vec![DisputeStatementSet { candidate_hash: candidate_hash_a.clone(), session: 1, statements: vec![ ( DisputeStatement::Valid(ValidDisputeStatementKind::Explicit), ValidatorIndex(0), sig_0.clone(), ), ( DisputeStatement::Invalid(InvalidDisputeStatementKind::Explicit), ValidatorIndex(2), sig_1.clone(), ), ], }]; let statements = apply_filter_all::(statements); assert!(statements.is_empty()); }) } #[test] fn filter_removes_session_out_of_bounds() { new_test_ext(Default::default()).execute_with(|| { let v0 = ::Pair::generate().0; run_to_block(3, |b| { // a new session at each block Some((true, b, vec![(&0, v0.public())], Some(vec![(&0, v0.public())]))) }); let candidate_hash = CandidateHash(sp_core::H256::repeat_byte(1)); let payload = ExplicitDisputeStatement { valid: true, candidate_hash: candidate_hash.clone(), session: 1, } .signing_payload(); let sig_a = v0.sign(&payload); let statements = vec![DisputeStatementSet { candidate_hash: candidate_hash.clone(), session: 100, statements: vec![( DisputeStatement::Valid(ValidDisputeStatementKind::Explicit), ValidatorIndex(0), sig_a, )], }]; let statements = apply_filter_all::(statements); assert!(statements.is_empty()); }) } #[test] fn filter_removes_concluded_ancient() { let dispute_post_conclusion_acceptance_period = 2; let mock_genesis_config = MockGenesisConfig { configuration: crate::configuration::GenesisConfig { config: HostConfiguration { dispute_post_conclusion_acceptance_period, ..Default::default() }, ..Default::default() }, ..Default::default() }; new_test_ext(mock_genesis_config).execute_with(|| { let v0 = ::Pair::generate().0; run_to_block(3, |b| { // a new session at each block Some((true, b, vec![(&0, v0.public())], Some(vec![(&0, v0.public())]))) }); let candidate_hash_a = CandidateHash(sp_core::H256::repeat_byte(1)); let candidate_hash_b = CandidateHash(sp_core::H256::repeat_byte(2)); >::insert( &1, &candidate_hash_a, DisputeState { validators_for: bitvec![u8, BitOrderLsb0; 0; 4], validators_against: bitvec![u8, BitOrderLsb0; 1; 4], start: 0, concluded_at: Some(0), }, ); >::insert( &1, &candidate_hash_b, DisputeState { validators_for: bitvec![u8, BitOrderLsb0; 0; 4], validators_against: bitvec![u8, BitOrderLsb0; 1; 4], start: 0, concluded_at: Some(1), }, ); let payload_a = ExplicitDisputeStatement { valid: true, candidate_hash: candidate_hash_a.clone(), session: 1, } .signing_payload(); let payload_b = ExplicitDisputeStatement { valid: true, candidate_hash: candidate_hash_b.clone(), session: 1, } .signing_payload(); let sig_a = v0.sign(&payload_a); let sig_b = v0.sign(&payload_b); let statements = vec![ DisputeStatementSet { candidate_hash: candidate_hash_a.clone(), session: 1, statements: vec![( DisputeStatement::Valid(ValidDisputeStatementKind::Explicit), ValidatorIndex(0), sig_a, )], }, DisputeStatementSet { candidate_hash: candidate_hash_b.clone(), session: 1, statements: vec![( DisputeStatement::Valid(ValidDisputeStatementKind::Explicit), ValidatorIndex(0), sig_b.clone(), )], }, ]; let statements = apply_filter_all::(statements); assert_eq!( statements, vec![CheckedDisputeStatementSet::unchecked_from_unchecked(DisputeStatementSet { candidate_hash: candidate_hash_b.clone(), session: 1, statements: vec![( DisputeStatement::Valid(ValidDisputeStatementKind::Explicit), ValidatorIndex(0), sig_b, ),] })] ); }) } #[test] fn filter_removes_duplicate_statements_sets() { new_test_ext(Default::default()).execute_with(|| { let v0 = ::Pair::generate().0; let v1 = ::Pair::generate().0; run_to_block(3, |b| { // a new session at each block Some(( true, b, vec![(&0, v0.public()), (&1, v1.public())], Some(vec![(&0, v0.public()), (&1, v1.public())]), )) }); let candidate_hash_a = CandidateHash(sp_core::H256::repeat_byte(1)); let payload = ExplicitDisputeStatement { valid: true, candidate_hash: candidate_hash_a.clone(), session: 1, } .signing_payload(); let payload_against = ExplicitDisputeStatement { valid: false, candidate_hash: candidate_hash_a.clone(), session: 1, } .signing_payload(); let sig_a = v0.sign(&payload); let sig_a_against = v1.sign(&payload_against); let statements = vec![ ( DisputeStatement::Valid(ValidDisputeStatementKind::Explicit), ValidatorIndex(0), sig_a.clone(), ), ( DisputeStatement::Invalid(InvalidDisputeStatementKind::Explicit), ValidatorIndex(1), sig_a_against.clone(), ), ]; let mut sets = vec![ DisputeStatementSet { candidate_hash: candidate_hash_a.clone(), session: 1, statements: statements.clone(), }, DisputeStatementSet { candidate_hash: candidate_hash_a.clone(), session: 1, statements: statements.clone(), }, ]; // `Err(())` indicates presence of duplicates assert!( as DisputesHandler< ::BlockNumber, >>::deduplicate_and_sort_dispute_data(&mut sets) .is_err()); assert_eq!( sets, vec![DisputeStatementSet { candidate_hash: candidate_hash_a.clone(), session: 1, statements, }] ); }) } #[test] fn assure_no_duplicate_statements_sets_are_fine() { new_test_ext(Default::default()).execute_with(|| { let v0 = ::Pair::generate().0; let v1 = ::Pair::generate().0; run_to_block(3, |b| { // a new session at each block Some(( true, b, vec![(&0, v0.public()), (&1, v1.public())], Some(vec![(&0, v0.public()), (&1, v1.public())]), )) }); let candidate_hash_a = CandidateHash(sp_core::H256::repeat_byte(1)); let payload = ExplicitDisputeStatement { valid: true, candidate_hash: candidate_hash_a.clone(), session: 1, } .signing_payload(); let payload_against = ExplicitDisputeStatement { valid: false, candidate_hash: candidate_hash_a.clone(), session: 1, } .signing_payload(); let sig_a = v0.sign(&payload); let sig_a_against = v1.sign(&payload_against); let statements = vec![ ( DisputeStatement::Valid(ValidDisputeStatementKind::Explicit), ValidatorIndex(0), sig_a.clone(), ), ( DisputeStatement::Invalid(InvalidDisputeStatementKind::Explicit), ValidatorIndex(1), sig_a_against.clone(), ), ]; let sets = vec![ DisputeStatementSet { candidate_hash: candidate_hash_a.clone(), session: 1, statements: statements.clone(), }, DisputeStatementSet { candidate_hash: candidate_hash_a.clone(), session: 2, statements: statements.clone(), }, ]; // `Err(())` indicates presence of duplicates assert!( as DisputesHandler< ::BlockNumber, >>::assure_deduplicated_and_sorted(&sets) .is_ok()); }) } #[test] fn assure_detects_duplicate_statements_sets() { new_test_ext(Default::default()).execute_with(|| { let v0 = ::Pair::generate().0; let v1 = ::Pair::generate().0; run_to_block(3, |b| { // a new session at each block Some(( true, b, vec![(&0, v0.public()), (&1, v1.public())], Some(vec![(&0, v0.public()), (&1, v1.public())]), )) }); let candidate_hash_a = CandidateHash(sp_core::H256::repeat_byte(1)); let payload = ExplicitDisputeStatement { valid: true, candidate_hash: candidate_hash_a.clone(), session: 1, } .signing_payload(); let payload_against = ExplicitDisputeStatement { valid: false, candidate_hash: candidate_hash_a.clone(), session: 1, } .signing_payload(); let sig_a = v0.sign(&payload); let sig_a_against = v1.sign(&payload_against); let statements = vec![ ( DisputeStatement::Valid(ValidDisputeStatementKind::Explicit), ValidatorIndex(0), sig_a.clone(), ), ( DisputeStatement::Invalid(InvalidDisputeStatementKind::Explicit), ValidatorIndex(1), sig_a_against.clone(), ), ]; let sets = vec![ DisputeStatementSet { candidate_hash: candidate_hash_a.clone(), session: 1, statements: statements.clone(), }, DisputeStatementSet { candidate_hash: candidate_hash_a.clone(), session: 1, statements: statements.clone(), }, ]; // `Err(())` indicates presence of duplicates assert!( as DisputesHandler< ::BlockNumber, >>::assure_deduplicated_and_sorted(&sets) .is_err()); }) } #[test] fn filter_ignores_single_sided() { new_test_ext(Default::default()).execute_with(|| { let v0 = ::Pair::generate().0; run_to_block(3, |b| { // a new session at each block Some((true, b, vec![(&0, v0.public())], Some(vec![(&0, v0.public())]))) }); let candidate_hash_a = CandidateHash(sp_core::H256::repeat_byte(1)); let payload = ExplicitDisputeStatement { valid: true, candidate_hash: candidate_hash_a.clone(), session: 1, } .signing_payload(); let sig_a = v0.sign(&payload); let statements = vec![DisputeStatementSet { candidate_hash: candidate_hash_a.clone(), session: 1, statements: vec![( DisputeStatement::Valid(ValidDisputeStatementKind::Explicit), ValidatorIndex(0), sig_a.clone(), )], }]; let statements = apply_filter_all::(statements); assert!(statements.is_empty()); }) } #[test] fn import_ignores_single_sided() { new_test_ext(Default::default()).execute_with(|| { let v0 = ::Pair::generate().0; run_to_block(3, |b| { // a new session at each block Some((true, b, vec![(&0, v0.public())], Some(vec![(&0, v0.public())]))) }); let candidate_hash_a = CandidateHash(sp_core::H256::repeat_byte(1)); let payload = ExplicitDisputeStatement { valid: true, candidate_hash: candidate_hash_a.clone(), session: 1, } .signing_payload(); let sig_a = v0.sign(&payload); let statements = vec![DisputeStatementSet { candidate_hash: candidate_hash_a.clone(), session: 1, statements: vec![( DisputeStatement::Valid(ValidDisputeStatementKind::Explicit), ValidatorIndex(0), sig_a.clone(), )], }]; let statements = apply_filter_all::(statements); assert!(statements.is_empty()); }) }