disputes pallet: Remove spam slots (#6345)

* disputes pallet: Filter disputes with votes less than supermajority threshold

* Remove `max_spam_slots` usages

* Remove `SpamSlots`

* Remove `SpamSlotChange`

* Remove `Error<T>::PotentialSpam` and stale comments

* `create_disputes_with_no_spam` -> `create_disputes`

* Make tests compile - wip commit

* Rework `test_dispute_timeout`. Rename `update_spam_slots` to `filter_dispute_set`

* Remove `dispute_statement_becoming_onesided_due_to_spamslots_is_accepted` and `filter_correctly_accounts_spam_slots` -> they bring no value with removed spam slots

* Fix `test_provide_multi_dispute_success_and_other`

* Remove an old comment

* Remove spam slots from tests - clean todo comments

* Remove test - `test_decrement_spam`

* todo comments

* Update TODO comments

* Extract `test_unconfirmed_are_ignored` as separate test case

* Remove dead code

* Fix `test_unconfirmed_are_ignored`

* Remove dead code in `filter_dispute_data`

* Fix weights (related to commit "Remove `SpamSlots`")

* Disputes migration - first try

* Remove `dispute_max_spam_slots` + storage migration

* Fix `HostConfig` migration tests

* Deprecate `SpamSlots`

* Code review feedback

* add weight for storage version update
* fix bound for clear()

* Fix weights in disputes migration

* Revert "Deprecate `SpamSlots`"

This reverts commit 8c4d967c7b061abd76ba8b551223918c0b9e6370.

* Make mod migration public

* Remove `SpamSlots` from disputes pallet and use `storage_alias` in the migration

* Fix call to `clear()` for `SpamSlots` in migration

* Update migration and add a `try-runtime` test

* Add `pre_upgrade` `try-runtime` test

* Fix some test names in `HostConfiguration` migration

* Link spamslots migration in all runtimes

* Add `test_unconfirmed_disputes_cause_block_import_error`

* Update guide

- Remove `SpamSlots` related information from roadmap/implementers-guide/src/runtime/disputes.md
- Add 'Disputes filtering' to Runtime section of the Implementor's guide

* Update runtime/parachains/src/configuration/migration.rs

Co-authored-by: Marcin S. <marcin@bytedude.com>

* Code review feedback - update logs

* Code review feedback: fix weights

* Update runtime/parachains/src/disputes.rs

Co-authored-by: s0me0ne-unkn0wn <48632512+s0me0ne-unkn0wn@users.noreply.github.com>

* Additional logs in disputes migration

* Fix merge conflicts

* Add version checks in try-runtime tests

* Fix a compilation warning`

Co-authored-by: Marcin S. <marcin@bytedude.com>
Co-authored-by: s0me0ne-unkn0wn <48632512+s0me0ne-unkn0wn@users.noreply.github.com>
This commit is contained in:
Tsvetomir Dimitrov
2023-01-07 15:56:14 +02:00
committed by GitHub
parent 715e98268a
commit ed9a1a400e
13 changed files with 492 additions and 927 deletions
+198 -560
View File
@@ -23,7 +23,6 @@ use crate::{
Test, PUNISH_VALIDATORS_AGAINST, PUNISH_VALIDATORS_FOR, REWARD_VALIDATORS,
},
};
use assert_matches::assert_matches;
use frame_support::{
assert_err, assert_noop, assert_ok,
traits::{OnFinalize, OnInitialize},
@@ -31,20 +30,16 @@ use frame_support::{
use primitives::v2::BlockNumber;
use sp_core::{crypto::CryptoType, Pair};
/// Filtering updates the spam slots, as such update them.
fn update_spam_slots(stmts: MultiDisputeStatementSet) -> CheckedMultiDisputeStatementSet {
fn filter_dispute_set(stmts: MultiDisputeStatementSet) -> CheckedMultiDisputeStatementSet {
let config = <configuration::Pallet<Test>>::config();
let max_spam_slots = config.dispute_max_spam_slots;
let post_conclusion_acceptance_period = config.dispute_post_conclusion_acceptance_period;
stmts
.into_iter()
.filter_map(|set| {
// updates spam slots implicitly
let filter = Pallet::<Test>::filter_dispute_data(
&set,
post_conclusion_acceptance_period,
max_spam_slots,
VerifyDisputeSignatures::Skip,
);
filter.filter_statement_set(set)
@@ -135,7 +130,7 @@ fn test_dispute_state_flag_from_state() {
}
#[test]
fn test_import_new_participant_spam_inc() {
fn test_import_new_participant() {
let mut importer = DisputeStateImporter::new(
DisputeState {
validators_for: bitvec![u8, BitOrderLsb0; 1, 0, 0, 0, 0, 0, 0, 0],
@@ -171,14 +166,13 @@ fn test_import_new_participant_spam_inc() {
concluded_at: None,
},
);
assert_eq!(summary.spam_slot_changes, vec![(ValidatorIndex(2), SpamSlotChange::Inc)]);
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_spam_dec_confirmed() {
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],
@@ -201,10 +195,7 @@ fn test_import_prev_participant_spam_dec_confirmed() {
concluded_at: None,
},
);
assert_eq!(
summary.spam_slot_changes,
vec![(ValidatorIndex(0), SpamSlotChange::Dec), (ValidatorIndex(1), SpamSlotChange::Dec),],
);
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]);
@@ -212,7 +203,7 @@ fn test_import_prev_participant_spam_dec_confirmed() {
}
#[test]
fn test_import_prev_participant_spam_dec_confirmed_slash_for() {
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],
@@ -240,10 +231,7 @@ fn test_import_prev_participant_spam_dec_confirmed_slash_for() {
concluded_at: Some(0),
},
);
assert_eq!(
summary.spam_slot_changes,
vec![(ValidatorIndex(0), SpamSlotChange::Dec), (ValidatorIndex(1), SpamSlotChange::Dec),],
);
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]);
@@ -281,224 +269,12 @@ fn test_import_slash_against() {
concluded_at: Some(0),
},
);
assert!(summary.spam_slot_changes.is_empty());
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);
}
fn generate_dispute_statement_set_entry(
session: u32,
candidate_hash: CandidateHash,
statement: DisputeStatement,
validator: &<ValidatorId as CryptoType>::Pair,
) -> (DisputeStatement, ValidatorSignature) {
let valid = match &statement {
DisputeStatement::Valid(_) => true,
_ => false,
};
let signature_bytes = validator
.sign(&ExplicitDisputeStatement { valid, candidate_hash, session }.signing_payload());
let signature = ValidatorSignature::try_from(signature_bytes).unwrap();
(statement, signature)
}
fn generate_dispute_statement_set(
session: SessionIndex,
candidate_hash: CandidateHash,
validators: &[<ValidatorId as CryptoType>::Pair],
vidxs: Vec<(usize, DisputeStatement)>,
) -> DisputeStatementSet {
let statements = vidxs
.into_iter()
.map(|(v_i, statement)| {
let validator_index = ValidatorIndex(v_i as u32);
let (statement, signature) = generate_dispute_statement_set_entry(
session,
candidate_hash.clone(),
statement,
&validators[v_i],
);
(statement, validator_index, signature)
})
.collect::<Vec<(DisputeStatement, ValidatorIndex, ValidatorSignature)>>();
DisputeStatementSet { candidate_hash: candidate_hash.clone(), session, statements }
}
#[test]
fn dispute_statement_becoming_onesided_due_to_spamslots_is_accepted() {
let dispute_conclusion_by_time_out_period = 3;
let start = 10;
let session = start - 1;
let dispute_max_spam_slots = 2;
let post_conclusion_acceptance_period = 3;
let mock_genesis_config = MockGenesisConfig {
configuration: crate::configuration::GenesisConfig {
config: HostConfiguration {
dispute_conclusion_by_time_out_period,
dispute_max_spam_slots,
..Default::default()
},
..Default::default()
},
..Default::default()
};
new_test_ext(mock_genesis_config).execute_with(|| {
// We need 6 validators for the byzantine threshold to be 2
static ACCOUNT_IDS: &[AccountId] = &[0, 1, 2, 3, 4, 5, 6, 7];
let validators = std::iter::repeat(())
.take(7)
.map(|_| <ValidatorId as CryptoType>::Pair::generate().0)
.collect::<Vec<_>>();
let validators = &validators;
// a new session at each block, but always the same validators
let session_change_callback = |block_number: u32| -> Option<NewSession<'_>> {
let session_validators =
Vec::from_iter(ACCOUNT_IDS.iter().zip(validators.iter().map(|pair| pair.public())));
Some((true, block_number, session_validators.clone(), Some(session_validators)))
};
run_to_block(start, session_change_callback);
// Must be _foreign_ parachain candidate
// otherwise slots do not trigger.
let candidate_hash_a = CandidateHash(sp_core::H256::repeat_byte(0xA));
let candidate_hash_b = CandidateHash(sp_core::H256::repeat_byte(0xB));
let candidate_hash_c = CandidateHash(sp_core::H256::repeat_byte(0xC));
let candidate_hash_d = CandidateHash(sp_core::H256::repeat_byte(0xD));
let stmts = vec![
// a
generate_dispute_statement_set(
session,
candidate_hash_a,
validators,
vec![
(3, DisputeStatement::Valid(ValidDisputeStatementKind::Explicit)),
(6, DisputeStatement::Invalid(InvalidDisputeStatementKind::Explicit)),
],
),
// b
generate_dispute_statement_set(
session,
candidate_hash_b,
validators,
vec![
(1, DisputeStatement::Valid(ValidDisputeStatementKind::Explicit)),
(6, DisputeStatement::Invalid(InvalidDisputeStatementKind::Explicit)),
],
),
// c
generate_dispute_statement_set(
session,
candidate_hash_c,
validators,
vec![
(2, DisputeStatement::Valid(ValidDisputeStatementKind::Explicit)),
(6, DisputeStatement::Invalid(InvalidDisputeStatementKind::Explicit)),
],
),
// d
generate_dispute_statement_set(
session,
candidate_hash_d,
validators,
vec![
(4, DisputeStatement::Valid(ValidDisputeStatementKind::Explicit)),
(5, DisputeStatement::Invalid(InvalidDisputeStatementKind::Explicit)),
],
),
generate_dispute_statement_set(
session,
candidate_hash_d,
validators,
vec![(6, DisputeStatement::Invalid(InvalidDisputeStatementKind::Explicit))],
),
];
// no filtering happens, host config for `dispute_max_spam_slots: 2` is the default
// updates spam slots implicitly
let set = stmts[0].clone();
let filter = Pallet::<Test>::filter_dispute_data(
&set,
post_conclusion_acceptance_period,
dispute_max_spam_slots,
VerifyDisputeSignatures::Skip,
);
assert_matches!(&filter, StatementSetFilter::RemoveIndices(v) if v.is_empty());
assert_matches!(filter.filter_statement_set(set.clone()), Some(modified) => {
assert_eq!(&set, modified.as_ref());
});
assert_eq!(SpamSlots::<Test>::get(session), Some(vec![0, 0, 0, 1, 0, 0, 1]));
// <----->
// 2nd, still ok? Should be
let set = stmts[1].clone();
let filter = Pallet::<Test>::filter_dispute_data(
&set,
post_conclusion_acceptance_period,
dispute_max_spam_slots,
VerifyDisputeSignatures::Skip,
);
assert_matches!(&filter, StatementSetFilter::RemoveIndices(v) if v.is_empty());
assert_matches!(filter.filter_statement_set(set.clone()), Some(modified) => {
assert_eq!(&set, modified.as_ref());
});
assert_eq!(SpamSlots::<Test>::get(session), Some(vec![0, 1, 0, 1, 0, 0, 2]));
// <----->
// now this is the third spammy participation of validator 6 and hence
let set = stmts[2].clone();
let filter = Pallet::<Test>::filter_dispute_data(
&set,
post_conclusion_acceptance_period,
dispute_max_spam_slots,
VerifyDisputeSignatures::Skip,
);
assert_matches!(&filter, StatementSetFilter::RemoveAll);
// no need to apply the filter,
// we don't do anything with the result, and spam slots were updated already
// <----->
// now there is no pariticipation in this dispute being initiated
// only validator 4 and 5 are part of it
// with 3 validators it's not a an unconfirmed dispute anymore
// so validator 6, while being considered spammy should work again
let set = stmts[3].clone();
let filter = Pallet::<Test>::filter_dispute_data(
&set,
post_conclusion_acceptance_period,
dispute_max_spam_slots,
VerifyDisputeSignatures::Skip,
);
assert_matches!(&filter, StatementSetFilter::RemoveIndices(v) if v.is_empty());
// no need to apply the filter,
// we don't do anything with the result, and spam slots were updated already
// <----->
// it's a spammy participant, so a new dispute will not be accepted being initiated by a spammer
let set = stmts[4].clone();
let filter = Pallet::<Test>::filter_dispute_data(
&set,
post_conclusion_acceptance_period,
dispute_max_spam_slots,
VerifyDisputeSignatures::Skip,
);
assert_matches!(&filter, StatementSetFilter::RemoveAll);
assert_matches!(filter.filter_statement_set(set.clone()), None);
assert_eq!(SpamSlots::<Test>::get(session), Some(vec![0, 1, 1, 1, 1, 1, 3]));
});
}
// Test that dispute timeout is handled correctly.
#[test]
fn test_dispute_timeout() {
@@ -517,7 +293,7 @@ fn test_dispute_timeout() {
};
new_test_ext(mock_genesis_config).execute_with(|| {
// We need 6 validators for the byzantine threshold to be 2
// We need 7 validators for the byzantine threshold to be 2
let v0 = <ValidatorId as CryptoType>::Pair::generate().0;
let v1 = <ValidatorId as CryptoType>::Pair::generate().0;
let v2 = <ValidatorId as CryptoType>::Pair::generate().0;
@@ -554,10 +330,12 @@ fn test_dispute_timeout() {
let candidate_hash = CandidateHash(sp_core::H256::repeat_byte(1));
// v0 votes for 3, v6 against.
// v0 and v1 vote for 3, v2 against. We need f+1 votes (3) so that the dispute is
// confirmed. Otherwise It will be filtered out.
let session = start - 1;
let stmts = vec![DisputeStatementSet {
candidate_hash: candidate_hash.clone(),
session: start - 1,
session,
statements: vec![
(
DisputeStatement::Valid(ValidDisputeStatementKind::Explicit),
@@ -571,6 +349,18 @@ fn test_dispute_timeout() {
.signing_payload(),
),
),
(
DisputeStatement::Valid(ValidDisputeStatementKind::Explicit),
ValidatorIndex(1),
v1.sign(
&ExplicitDisputeStatement {
valid: true,
candidate_hash: candidate_hash.clone(),
session: start - 1,
}
.signing_payload(),
),
),
(
DisputeStatement::Invalid(InvalidDisputeStatementKind::Explicit),
ValidatorIndex(6),
@@ -586,8 +376,7 @@ fn test_dispute_timeout() {
],
}];
let stmts = update_spam_slots(stmts);
assert_eq!(SpamSlots::<Test>::get(start - 1), Some(vec![1, 0, 0, 0, 0, 0, 1]));
let stmts = filter_dispute_set(stmts);
assert_ok!(
Pallet::<Test>::process_checked_multi_dispute_data(stmts),
@@ -596,11 +385,20 @@ fn test_dispute_timeout() {
// Run to timeout period
run_to_block(start + dispute_conclusion_by_time_out_period, |_| None);
assert_eq!(SpamSlots::<Test>::get(start - 1), Some(vec![1, 0, 0, 0, 0, 0, 1]));
assert!(<Disputes<Test>>::get(&session, &candidate_hash)
.expect("dispute should exist")
.concluded_at
.is_none());
// Run to timeout + 1 in order to executive on_finalize(timeout)
run_to_block(start + dispute_conclusion_by_time_out_period + 1, |_| None);
assert_eq!(SpamSlots::<Test>::get(start - 1), Some(vec![0, 0, 0, 0, 0, 0, 0]));
assert_eq!(
<Disputes<Test>>::get(&session, &candidate_hash)
.expect("dispute should exist")
.concluded_at
.expect("dispute should have concluded"),
start + dispute_conclusion_by_time_out_period + 1
);
});
}
@@ -888,16 +686,13 @@ fn test_freeze_provided_against_supermajority_for_included() {
});
}
// tests for:
// * provide_multi_dispute: with success scenario
// * disputes: correctness of datas
// * could_be_invalid: correctness of datas
// * note_included: decrement spam correctly
// * spam slots: correctly incremented and decremented
// * ensure rewards and punishment are correctly called.
#[test]
fn test_provide_multi_dispute_success_and_other() {
new_test_ext(Default::default()).execute_with(|| {
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 = <ValidatorId as CryptoType>::Pair::generate().0;
let v1 = <ValidatorId as CryptoType>::Pair::generate().0;
@@ -907,6 +702,7 @@ fn test_provide_multi_dispute_success_and_other() {
let v5 = <ValidatorId as CryptoType>::Pair::generate().0;
let v6 = <ValidatorId as CryptoType>::Pair::generate().0;
// Mapping between key pair and `ValidatorIndex`
// v0 -> 0
// v1 -> 3
// v2 -> 6
@@ -943,7 +739,120 @@ fn test_provide_multi_dispute_success_and_other() {
let candidate_hash = CandidateHash(sp_core::H256::repeat_byte(1));
// v0 votes for 3, v6 votes against
// 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::<Test>::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::<Test>::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 = <ValidatorId as CryptoType>::Pair::generate().0;
let v1 = <ValidatorId as CryptoType>::Pair::generate().0;
let v2 = <ValidatorId as CryptoType>::Pair::generate().0;
let v3 = <ValidatorId as CryptoType>::Pair::generate().0;
let v4 = <ValidatorId as CryptoType>::Pair::generate().0;
let v5 = <ValidatorId as CryptoType>::Pair::generate().0;
let v6 = <ValidatorId as CryptoType>::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 and v1 vote for 3, v6 votes against
let stmts = vec![DisputeStatementSet {
candidate_hash: candidate_hash.clone(),
session: 3,
@@ -972,53 +881,7 @@ fn test_provide_multi_dispute_success_and_other() {
.signing_payload(),
),
),
],
}];
let stmts = update_spam_slots(stmts);
assert_eq!(SpamSlots::<Test>::get(3), Some(vec![1, 0, 1, 0, 0, 0, 0]));
assert_ok!(
Pallet::<Test>::process_checked_multi_dispute_data(stmts),
vec![(3, candidate_hash.clone())],
);
// v1 votes for 4 and for 3, v6 votes against 4.
let stmts = vec![
DisputeStatementSet {
candidate_hash: candidate_hash.clone(),
session: 4,
statements: vec![
(
DisputeStatement::Valid(ValidDisputeStatementKind::Explicit),
ValidatorIndex(3),
v1.sign(
&ExplicitDisputeStatement {
valid: true,
candidate_hash: candidate_hash.clone(),
session: 4,
}
.signing_payload(),
),
),
(
DisputeStatement::Invalid(InvalidDisputeStatementKind::Explicit),
ValidatorIndex(2),
v6.sign(
&ExplicitDisputeStatement {
valid: false,
candidate_hash: candidate_hash.clone(),
session: 4,
}
.signing_payload(),
),
),
],
},
DisputeStatementSet {
candidate_hash: candidate_hash.clone(),
session: 3,
statements: vec![(
(
DisputeStatement::Valid(ValidDisputeStatementKind::Explicit),
ValidatorIndex(3),
v1.sign(
@@ -1029,20 +892,18 @@ fn test_provide_multi_dispute_success_and_other() {
}
.signing_payload(),
),
)],
},
];
),
],
}];
let stmts = update_spam_slots(stmts);
let stmts = filter_dispute_set(stmts);
assert_ok!(
Pallet::<Test>::process_checked_multi_dispute_data(stmts),
vec![(4, candidate_hash.clone())],
vec![(3, candidate_hash.clone())],
);
assert_eq!(SpamSlots::<Test>::get(3), Some(vec![0, 0, 0, 0, 0, 0, 0])); // Confirmed as no longer spam
assert_eq!(SpamSlots::<Test>::get(4), Some(vec![0, 0, 1, 1, 0, 0, 0]));
// v3 votes against 3 and for 5, v6 votes against 5.
// v3 votes against 3 and for 5, v2 and v6 vote against 5.
let stmts = vec![
DisputeStatementSet {
candidate_hash: candidate_hash.clone(),
@@ -1076,6 +937,18 @@ fn test_provide_multi_dispute_success_and_other() {
.signing_payload(),
),
),
(
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),
@@ -1092,55 +965,31 @@ fn test_provide_multi_dispute_success_and_other() {
},
];
let stmts = update_spam_slots(stmts);
let stmts = filter_dispute_set(stmts);
assert_ok!(
Pallet::<Test>::process_checked_multi_dispute_data(stmts),
vec![(5, candidate_hash.clone())],
);
assert_eq!(SpamSlots::<Test>::get(3), Some(vec![0, 0, 0, 0, 0, 0, 0]));
assert_eq!(SpamSlots::<Test>::get(4), Some(vec![0, 0, 1, 1, 0, 0, 0]));
assert_eq!(SpamSlots::<Test>::get(5), Some(vec![0, 0, 1, 0, 0, 1, 0]));
// v2 votes for 3 and against 5
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(),
),
)],
},
DisputeStatementSet {
candidate_hash: candidate_hash.clone(),
session: 5,
statements: vec![(
DisputeStatement::Invalid(InvalidDisputeStatementKind::Explicit),
ValidatorIndex(6),
v2.sign(
&ExplicitDisputeStatement {
valid: false,
candidate_hash: candidate_hash.clone(),
session: 5,
}
.signing_payload(),
),
)],
},
];
let stmts = update_spam_slots(stmts);
// 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::<Test>::process_checked_multi_dispute_data(stmts), vec![]);
assert_eq!(SpamSlots::<Test>::get(3), Some(vec![0, 0, 0, 0, 0, 0, 0]));
assert_eq!(SpamSlots::<Test>::get(4), Some(vec![0, 0, 1, 1, 0, 0, 0]));
assert_eq!(SpamSlots::<Test>::get(5), Some(vec![0, 0, 0, 0, 0, 0, 0]));
let stmts = vec![
// 0, 4, and 5 vote against 5
@@ -1218,7 +1067,7 @@ fn test_provide_multi_dispute_success_and_other() {
],
},
];
let stmts = update_spam_slots(stmts);
let stmts = filter_dispute_set(stmts);
assert_ok!(Pallet::<Test>::process_checked_multi_dispute_data(stmts), vec![]);
assert_eq!(
@@ -1244,16 +1093,6 @@ fn test_provide_multi_dispute_success_and_other() {
concluded_at: Some(6), // 5 vote for
}
),
(
4,
candidate_hash.clone(),
DisputeState {
validators_for: bitvec![u8, BitOrderLsb0; 0, 0, 0, 1, 0, 0, 0],
validators_against: bitvec![u8, BitOrderLsb0; 0, 0, 1, 0, 0, 0, 0],
start: 6,
concluded_at: None,
}
),
]
);
@@ -1261,22 +1100,14 @@ fn test_provide_multi_dispute_success_and_other() {
assert!(!Pallet::<Test>::concluded_invalid(4, candidate_hash.clone()));
assert!(Pallet::<Test>::concluded_invalid(5, candidate_hash.clone()));
// Ensure inclusion removes spam slots
assert_eq!(SpamSlots::<Test>::get(4), Some(vec![0, 0, 1, 1, 0, 0, 0]));
Pallet::<Test>::note_included(4, candidate_hash.clone(), 4);
assert_eq!(SpamSlots::<Test>::get(4), Some(vec![0, 0, 0, 0, 0, 0, 0]));
// Ensure the `reward_validator` function was correctly called
assert_eq!(
REWARD_VALIDATORS.with(|r| r.borrow().clone()),
vec![
(3, vec![ValidatorIndex(0), ValidatorIndex(2)]),
(4, vec![ValidatorIndex(2), ValidatorIndex(3)]),
(3, vec![ValidatorIndex(3)]),
(3, vec![ValidatorIndex(0), ValidatorIndex(2), ValidatorIndex(3)]),
(3, vec![ValidatorIndex(5)]),
(5, vec![ValidatorIndex(2), ValidatorIndex(5)]),
(5, vec![ValidatorIndex(2), ValidatorIndex(5), ValidatorIndex(6)]),
(3, vec![ValidatorIndex(6)]),
(5, vec![ValidatorIndex(6)]),
(5, vec![ValidatorIndex(0), ValidatorIndex(1), ValidatorIndex(4)]),
(3, vec![ValidatorIndex(1), ValidatorIndex(4)]),
],
@@ -1286,14 +1117,11 @@ fn test_provide_multi_dispute_success_and_other() {
assert_eq!(
PUNISH_VALIDATORS_AGAINST.with(|r| r.borrow().clone()),
vec![
(3, vec![]),
(4, vec![]),
(3, vec![]),
(3, vec![]),
(5, vec![]),
(3, vec![]),
(5, vec![]),
(5, vec![]),
(3, vec![ValidatorIndex(2), ValidatorIndex(5)]),
],
);
@@ -1302,13 +1130,10 @@ fn test_provide_multi_dispute_success_and_other() {
assert_eq!(
PUNISH_VALIDATORS_FOR.with(|r| r.borrow().clone()),
vec![
(3, vec![]),
(4, vec![]),
(3, vec![]),
(3, vec![]),
(5, vec![]),
(3, vec![]),
(5, vec![]),
(5, vec![ValidatorIndex(5)]),
(3, vec![]),
],
@@ -1380,44 +1205,6 @@ fn test_has_supermajority_against() {
);
}
#[test]
fn test_decrement_spam() {
let original_spam_slots = vec![0, 1, 2, 3, 4, 5, 6, 7];
// Test confirm is no-op
let mut spam_slots = original_spam_slots.clone();
let dispute_state_confirm = DisputeState {
validators_for: bitvec![u8, BitOrderLsb0; 1, 1, 0, 0, 0, 0, 0, 0],
validators_against: bitvec![u8, BitOrderLsb0; 1, 0, 1, 0, 0, 0, 0, 0],
start: 0,
concluded_at: None,
};
assert_eq!(DisputeStateFlags::from_state(&dispute_state_confirm), DisputeStateFlags::CONFIRMED);
assert_eq!(
decrement_spam(spam_slots.as_mut(), &dispute_state_confirm),
bitvec![u8, BitOrderLsb0; 1, 1, 1, 0, 0, 0, 0, 0],
);
assert_eq!(spam_slots, original_spam_slots);
// Test not confirm is decreasing spam
let mut spam_slots = original_spam_slots.clone();
let dispute_state_no_confirm = DisputeState {
validators_for: bitvec![u8, BitOrderLsb0; 1, 0, 0, 0, 0, 0, 0, 0],
validators_against: bitvec![u8, BitOrderLsb0; 1, 0, 1, 0, 0, 0, 0, 0],
start: 0,
concluded_at: None,
};
assert_eq!(
DisputeStateFlags::from_state(&dispute_state_no_confirm),
DisputeStateFlags::default()
);
assert_eq!(
decrement_spam(spam_slots.as_mut(), &dispute_state_no_confirm),
bitvec![u8, BitOrderLsb0; 1, 0, 1, 0, 0, 0, 0, 0],
);
assert_eq!(spam_slots, vec![0, 1, 1, 3, 4, 5, 6, 7]);
}
#[test]
fn test_check_signature() {
let validator_id = <ValidatorId as CryptoType>::Pair::generate().0;
@@ -1912,14 +1699,12 @@ fn apply_filter_all<T: Config, I: IntoIterator<Item = DisputeStatementSet>>(
sets: I,
) -> Vec<CheckedDisputeStatementSet> {
let config = <configuration::Pallet<T>>::config();
let max_spam_slots = config.dispute_max_spam_slots;
let post_conclusion_acceptance_period = config.dispute_post_conclusion_acceptance_period;
let mut acc = Vec::<CheckedDisputeStatementSet>::new();
for dispute_statement in sets {
if let Some(checked) = <Pallet<T> as DisputesHandler<<T>::BlockNumber>>::filter_dispute_data(
dispute_statement,
max_spam_slots,
post_conclusion_acceptance_period,
VerifyDisputeSignatures::Yes,
) {
@@ -1993,13 +1778,11 @@ fn filter_removes_duplicates_within_set() {
],
};
let max_spam_slots = 10;
let post_conclusion_acceptance_period = 10;
let statements = <Pallet<Test> as DisputesHandler<
<Test as frame_system::Config>::BlockNumber,
>>::filter_dispute_data(
statements,
max_spam_slots,
post_conclusion_acceptance_period,
VerifyDisputeSignatures::Yes,
);
@@ -2085,151 +1868,6 @@ fn filter_bad_signatures_correctly_detects_single_sided() {
})
}
#[test]
fn filter_correctly_accounts_spam_slots() {
let dispute_max_spam_slots = 2;
let mock_genesis_config = MockGenesisConfig {
configuration: crate::configuration::GenesisConfig {
config: HostConfiguration { dispute_max_spam_slots, ..Default::default() },
..Default::default()
},
..Default::default()
};
new_test_ext(mock_genesis_config).execute_with(|| {
// We need 7 validators for the byzantine threshold to be 2
let v0 = <ValidatorId as CryptoType>::Pair::generate().0;
let v1 = <ValidatorId as CryptoType>::Pair::generate().0;
let v2 = <ValidatorId as CryptoType>::Pair::generate().0;
let v3 = <ValidatorId as CryptoType>::Pair::generate().0;
let v4 = <ValidatorId as CryptoType>::Pair::generate().0;
let v5 = <ValidatorId as CryptoType>::Pair::generate().0;
let v6 = <ValidatorId as CryptoType>::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()),
(&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_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 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_b = payload(&candidate_hash_b, true);
let payload_c = payload(&candidate_hash_c, true);
let payload_a_bad = payload(&candidate_hash_a, false);
let payload_b_bad = payload(&candidate_hash_b, false);
let payload_c_bad = payload(&candidate_hash_c, false);
let sig_0a = v0.sign(&payload_a);
let sig_0b = v0.sign(&payload_b);
let sig_0c = v0.sign(&payload_c);
let sig_1b = v1.sign(&payload_b);
let sig_2a = v2.sign(&payload_a_bad);
let sig_2b = v2.sign(&payload_b_bad);
let sig_2c = v2.sign(&payload_c_bad);
let statements = vec![
// validators 0 and 2 get 1 spam slot from this.
DisputeStatementSet {
candidate_hash: candidate_hash_a.clone(),
session: 1,
statements: vec![
(
DisputeStatement::Valid(ValidDisputeStatementKind::Explicit),
ValidatorIndex(0),
sig_0a.clone(),
),
(
DisputeStatement::Invalid(InvalidDisputeStatementKind::Explicit),
ValidatorIndex(6),
sig_2a.clone(),
),
],
},
// Validators 0, 2, and 3 get no spam slots for this
DisputeStatementSet {
candidate_hash: candidate_hash_b.clone(),
session: 1,
statements: vec![
(
DisputeStatement::Valid(ValidDisputeStatementKind::Explicit),
ValidatorIndex(0),
sig_0b.clone(),
),
(
DisputeStatement::Valid(ValidDisputeStatementKind::Explicit),
ValidatorIndex(3),
sig_1b.clone(),
),
(
DisputeStatement::Invalid(InvalidDisputeStatementKind::Explicit),
ValidatorIndex(6),
sig_2b.clone(),
),
],
},
// Validators 0 and 2 get an extra spam slot for this.
DisputeStatementSet {
candidate_hash: candidate_hash_c.clone(),
session: 1,
statements: vec![
(
DisputeStatement::Valid(ValidDisputeStatementKind::Explicit),
ValidatorIndex(0),
sig_0c.clone(),
),
(
DisputeStatement::Invalid(InvalidDisputeStatementKind::Explicit),
ValidatorIndex(6),
sig_2c.clone(),
),
],
},
];
let old_statements = statements
.clone()
.into_iter()
.map(CheckedDisputeStatementSet::unchecked_from_unchecked)
.collect::<Vec<_>>();
let statements = apply_filter_all::<Test, _>(statements);
assert_eq!(statements, old_statements);
})
}
#[test]
fn filter_removes_session_out_of_bounds() {
new_test_ext(Default::default()).execute_with(|| {