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
+2 -2
View File
@@ -575,7 +575,7 @@ impl<T: paras_inherent::Config> BenchBuilder<T> {
/// Fill cores `start..last` with dispute statement sets. The statement sets will have 3/4th of
/// votes be valid, and 1/4th of votes be invalid.
fn create_disputes_with_no_spam(
fn create_disputes(
&self,
start: u32,
last: u32,
@@ -664,7 +664,7 @@ impl<T: paras_inherent::Config> BenchBuilder<T> {
let backed_candidates = builder
.create_backed_candidates(&builder.backed_and_concluding_cores, builder.code_upgrade);
let disputes = builder.create_disputes_with_no_spam(
let disputes = builder.create_disputes(
builder.backed_and_concluding_cores.len() as u32,
used_cores,
builder.dispute_sessions.as_slice(),
@@ -192,8 +192,6 @@ pub struct HostConfiguration<BlockNumber> {
pub dispute_period: SessionIndex,
/// How long after dispute conclusion to accept statements.
pub dispute_post_conclusion_acceptance_period: BlockNumber,
/// The maximum number of dispute spam slots
pub dispute_max_spam_slots: u32,
/// How long it takes for a dispute to conclude by time-out, if no supermajority is reached.
pub dispute_conclusion_by_time_out_period: BlockNumber,
/// The amount of consensus slots that must pass between submitting an assignment and
@@ -263,7 +261,6 @@ impl<BlockNumber: Default + From<u32>> Default for HostConfiguration<BlockNumber
max_validators: None,
dispute_period: 6,
dispute_post_conclusion_acceptance_period: 100.into(),
dispute_max_spam_slots: 2,
dispute_conclusion_by_time_out_period: 200.into(),
n_delay_tranches: Default::default(),
zeroth_delay_tranche_width: Default::default(),
@@ -752,19 +749,6 @@ pub mod pallet {
})
}
/// Set the maximum number of dispute spam slots.
#[pallet::call_index(16)]
#[pallet::weight((
T::WeightInfo::set_config_with_u32(),
DispatchClass::Operational,
))]
pub fn set_dispute_max_spam_slots(origin: OriginFor<T>, new: u32) -> DispatchResult {
ensure_root(origin)?;
Self::schedule_config_update(|config| {
config.dispute_max_spam_slots = new;
})
}
/// Set the dispute conclusion by time out period.
#[pallet::call_index(17)]
#[pallet::weight((
@@ -17,11 +17,7 @@
//! A module that is responsible for migration of storage.
use crate::configuration::{self, Config, Pallet, Store, MAX_POV_SIZE};
use frame_support::{
pallet_prelude::*,
traits::StorageVersion,
weights::{OldWeight, Weight},
};
use frame_support::{pallet_prelude::*, traits::StorageVersion, weights::Weight};
use frame_system::pallet_prelude::BlockNumberFor;
/// The current storage version.
@@ -29,12 +25,15 @@ use frame_system::pallet_prelude::BlockNumberFor;
/// v0-v1: <https://github.com/paritytech/polkadot/pull/3575>
/// v1-v2: <https://github.com/paritytech/polkadot/pull/4420>
/// v2-v3: <https://github.com/paritytech/polkadot/pull/6091>
pub const STORAGE_VERSION: StorageVersion = StorageVersion::new(3);
/// v3-v4: <https://github.com/paritytech/polkadot/pull/6345>
pub const STORAGE_VERSION: StorageVersion = StorageVersion::new(4);
pub mod v3 {
pub mod v4 {
use super::*;
use frame_support::traits::OnRuntimeUpgrade;
use frame_support::{traits::OnRuntimeUpgrade, weights::constants::WEIGHT_REF_TIME_PER_MILLIS};
use primitives::v2::{Balance, SessionIndex};
#[cfg(feature = "try-runtime")]
use sp_std::prelude::*;
// Copied over from configuration.rs @ de9e147695b9f1be8bd44e07861a31e483c8343a and removed
// all the comments, and changed the Weight struct to OldWeight
@@ -51,7 +50,7 @@ pub mod v3 {
pub validation_upgrade_delay: BlockNumber,
pub max_pov_size: u32,
pub max_downward_message_size: u32,
pub ump_service_total_weight: OldWeight,
pub ump_service_total_weight: Weight,
pub hrmp_max_parachain_outbound_channels: u32,
pub hrmp_max_parathread_outbound_channels: u32,
pub hrmp_sender_deposit: Balance,
@@ -79,7 +78,7 @@ pub mod v3 {
pub zeroth_delay_tranche_width: u32,
pub needed_approvals: u32,
pub relay_vrf_modulo_samples: u32,
pub ump_max_individual_weight: OldWeight,
pub ump_max_individual_weight: Weight,
pub pvf_checking_enabled: bool,
pub pvf_voting_ttl: SessionIndex,
pub minimum_validation_upgrade_delay: BlockNumber,
@@ -114,7 +113,7 @@ pub mod v3 {
max_upward_queue_count: Default::default(),
max_upward_queue_size: Default::default(),
max_downward_message_size: Default::default(),
ump_service_total_weight: OldWeight(Default::default()),
ump_service_total_weight: Default::default(),
max_upward_message_size: Default::default(),
max_upward_message_num_per_candidate: Default::default(),
hrmp_sender_deposit: Default::default(),
@@ -127,8 +126,9 @@ pub mod v3 {
hrmp_max_parachain_outbound_channels: Default::default(),
hrmp_max_parathread_outbound_channels: Default::default(),
hrmp_max_message_num_per_candidate: Default::default(),
ump_max_individual_weight: OldWeight(
frame_support::weights::constants::WEIGHT_REF_TIME_PER_MILLIS * 20,
ump_max_individual_weight: Weight::from_parts(
20u64 * WEIGHT_REF_TIME_PER_MILLIS,
MAX_POV_SIZE as u64,
),
pvf_checking_enabled: false,
pvf_voting_ttl: 2u32.into(),
@@ -137,32 +137,51 @@ pub mod v3 {
}
}
pub struct MigrateToV3<T>(sp_std::marker::PhantomData<T>);
impl<T: Config> OnRuntimeUpgrade for MigrateToV3<T> {
fn on_runtime_upgrade() -> Weight {
if StorageVersion::get::<Pallet<T>>() == 2 {
let weight_consumed = migrate_to_v3::<T>();
pub struct MigrateToV4<T>(sp_std::marker::PhantomData<T>);
impl<T: Config> OnRuntimeUpgrade for MigrateToV4<T> {
#[cfg(feature = "try-runtime")]
fn pre_upgrade() -> Result<Vec<u8>, &'static str> {
log::trace!(target: crate::configuration::LOG_TARGET, "Running pre_upgrade()");
log::info!(target: configuration::LOG_TARGET, "MigrateToV3 executed successfully");
ensure!(StorageVersion::get::<Pallet<T>>() == 3, "The migration requires version 3");
Ok(Vec::new())
}
fn on_runtime_upgrade() -> Weight {
if StorageVersion::get::<Pallet<T>>() == 3 {
let weight_consumed = migrate_to_v4::<T>();
log::info!(target: configuration::LOG_TARGET, "MigrateToV4 executed successfully");
STORAGE_VERSION.put::<Pallet<T>>();
weight_consumed
} else {
log::warn!(target: configuration::LOG_TARGET, "MigrateToV3 should be removed.");
log::warn!(target: configuration::LOG_TARGET, "MigrateToV4 should be removed.");
T::DbWeight::get().reads(1)
}
}
#[cfg(feature = "try-runtime")]
fn post_upgrade(_state: Vec<u8>) -> Result<(), &'static str> {
log::trace!(target: crate::configuration::LOG_TARGET, "Running post_upgrade()");
ensure!(
StorageVersion::get::<Pallet<T>>() == STORAGE_VERSION,
"Storage version should be 4 after the migration"
);
Ok(())
}
}
}
fn migrate_to_v3<T: Config>() -> Weight {
fn migrate_to_v4<T: Config>() -> Weight {
// Unusual formatting is justified:
// - make it easier to verify that fields assign what they supposed to assign.
// - this code is transient and will be removed after all migrations are done.
// - this code is important enough to optimize for legibility sacrificing consistency.
#[rustfmt::skip]
let translate =
|pre: v3::OldHostConfiguration<BlockNumberFor<T>>| ->
|pre: v4::OldHostConfiguration<BlockNumberFor<T>>| ->
configuration::HostConfiguration<BlockNumberFor<T>>
{
super::HostConfiguration {
@@ -177,6 +196,7 @@ validation_upgrade_cooldown : pre.validation_upgrade_cooldown,
validation_upgrade_delay : pre.validation_upgrade_delay,
max_pov_size : pre.max_pov_size,
max_downward_message_size : pre.max_downward_message_size,
ump_service_total_weight : pre.ump_service_total_weight,
hrmp_max_parachain_outbound_channels : pre.hrmp_max_parachain_outbound_channels,
hrmp_max_parathread_outbound_channels : pre.hrmp_max_parathread_outbound_channels,
hrmp_sender_deposit : pre.hrmp_sender_deposit,
@@ -197,19 +217,17 @@ max_validators_per_core : pre.max_validators_per_core,
max_validators : pre.max_validators,
dispute_period : pre.dispute_period,
dispute_post_conclusion_acceptance_period: pre.dispute_post_conclusion_acceptance_period,
dispute_max_spam_slots : pre.dispute_max_spam_slots,
dispute_conclusion_by_time_out_period : pre.dispute_conclusion_by_time_out_period,
no_show_slots : pre.no_show_slots,
n_delay_tranches : pre.n_delay_tranches,
zeroth_delay_tranche_width : pre.zeroth_delay_tranche_width,
needed_approvals : pre.needed_approvals,
relay_vrf_modulo_samples : pre.relay_vrf_modulo_samples,
ump_max_individual_weight : pre.ump_max_individual_weight,
pvf_checking_enabled : pre.pvf_checking_enabled,
pvf_voting_ttl : pre.pvf_voting_ttl,
minimum_validation_upgrade_delay : pre.minimum_validation_upgrade_delay,
ump_service_total_weight: Weight::from_ref_time(pre.ump_service_total_weight.0).set_proof_size(MAX_POV_SIZE as u64),
ump_max_individual_weight: Weight::from_ref_time(pre.ump_max_individual_weight.0).set_proof_size(MAX_POV_SIZE as u64),
}
};
@@ -221,7 +239,7 @@ ump_max_individual_weight: Weight::from_ref_time(pre.ump_max_individual_weight.0
// to be unlikely to be caused by this. So we just log. Maybe it'll work out still?
log::error!(
target: configuration::LOG_TARGET,
"unexpected error when performing translation of the configuration type during storage upgrade to v2."
"unexpected error when performing translation of the configuration type during storage upgrade to v4."
);
}
@@ -234,31 +252,43 @@ mod tests {
use crate::mock::{new_test_ext, Test};
#[test]
fn v2_deserialized_from_actual_data() {
// Fetched at Kusama 14,703,780 (0x3b2c305d01bd4adf1973d32a2d55ca1260a55eea8dfb3168e317c57f2841fdf1)
fn v3_deserialized_from_actual_data() {
// Example how to get new `raw_config`:
// We'll obtain the raw_config hes for block
// 15,772,152 (0xf89d3ab5312c5f70d396dc59612f0aa65806c798346f9db4b35278baed2e0e53) on Kusama.
// Steps:
// 1. Go to Polkadot.js -> Developer -> Chain state -> Storage: https://polkadot.js.org/apps/#/chainstate
// 2. Set these parameters:
// 2.1. selected state query: configuration; activeConfig(): PolkadotRuntimeParachainsConfigurationHostConfiguration
// 2.2. blockhash to query at: 0xf89d3ab5312c5f70d396dc59612f0aa65806c798346f9db4b35278baed2e0e53 (the hash of the block)
// 2.3. Note the value of encoded storage key -> 0x06de3d8a54d27e44a9d5ce189618f22db4b49d95320d9021994c850f25b8e385 for the referenced block.
// 2.4. You'll also need the decoded values to update the test.
// 3. Go to Polkadot.js -> Developer -> Chain state -> Raw storage
// 3.1 Enter the encoded storage key and you get the raw config.
// Fetched at Kusama 15,772,152 (0xf89d3ab5312c5f70d396dc59612f0aa65806c798346f9db4b35278baed2e0e53)
//
// This exceeds the maximal line width length, but that's fine, since this is not code and
// doesn't need to be read and also leaving it as one line allows to easily copy it.
let raw_config = hex_literal::hex!["0000a000005000000a00000000c8000000c800000a0000000a000000100e0000580200000000500000c8000000e87648170000001e00000000000000005039278c0400000000000000000000005039278c0400000000000000000000e8030000009001001e00000000000000009001008070000000000000000000000a0000000a0000000a00000001000000010500000001c8000000060000005802000002000000580200000200000059000000000000001e0000002800000000c817a804000000000200000014000000"];
let raw_config = hex_literal::hex!["0000a000005000000a00000000c8000000c800000a0000000a000000100e0000580200000000500000c800000700e8764817020040011e00000000000000005039278c0400000000000000000000005039278c0400000000000000000000e8030000009001001e00000000000000009001008070000000000000000000000a0000000a0000000a00000001000000010500000001c8000000060000005802000002000000580200000200000059000000000000001e000000280000000700c817a80402004001000200000014000000"];
let v2 =
v3::OldHostConfiguration::<primitives::v2::BlockNumber>::decode(&mut &raw_config[..])
let v3 =
v4::OldHostConfiguration::<primitives::v2::BlockNumber>::decode(&mut &raw_config[..])
.unwrap();
// We check only a sample of the values here. If we missed any fields or messed up data types
// that would skew all the fields coming after.
assert_eq!(v2.max_code_size, 10_485_760);
assert_eq!(v2.validation_upgrade_cooldown, 3600);
assert_eq!(v2.max_pov_size, 5_242_880);
assert_eq!(v2.hrmp_channel_max_message_size, 102_400);
assert_eq!(v2.dispute_max_spam_slots, 2);
assert_eq!(v2.n_delay_tranches, 89);
assert_eq!(v2.ump_max_individual_weight, OldWeight(20_000_000_000));
assert_eq!(v2.minimum_validation_upgrade_delay, 20);
assert_eq!(v3.max_code_size, 10_485_760);
assert_eq!(v3.validation_upgrade_cooldown, 3600);
assert_eq!(v3.max_pov_size, 5_242_880);
assert_eq!(v3.hrmp_channel_max_message_size, 102_400);
assert_eq!(v3.n_delay_tranches, 89);
assert_eq!(v3.ump_max_individual_weight, Weight::from_parts(20_000_000_000, 5_242_880));
assert_eq!(v3.minimum_validation_upgrade_delay, 20);
}
#[test]
fn test_migrate_to_v3() {
fn test_migrate_to_v4() {
// Host configuration has lots of fields. However, in this migration we add only a couple of
// fields. The most important part to check are a couple of the last fields. We also pick
// extra fields to check arbitrarily, e.g. depending on their position (i.e. the middle) and
@@ -267,8 +297,8 @@ mod tests {
// We specify only the picked fields and the rest should be provided by the `Default`
// implementation. That implementation is copied over between the two types and should work
// fine.
let v2 = v3::OldHostConfiguration::<primitives::v2::BlockNumber> {
ump_max_individual_weight: OldWeight(0x71616e6f6e0au64),
let v3 = v4::OldHostConfiguration::<primitives::v2::BlockNumber> {
ump_max_individual_weight: Weight::from_parts(0x71616e6f6e0au64, 0x71616e6f6e0au64),
needed_approvals: 69,
thread_availability_period: 55,
hrmp_recipient_deposit: 1337,
@@ -279,64 +309,61 @@ mod tests {
};
new_test_ext(Default::default()).execute_with(|| {
// Implant the v2 version in the state.
// Implant the v3 version in the state.
frame_support::storage::unhashed::put_raw(
&configuration::ActiveConfig::<Test>::hashed_key(),
&v2.encode(),
&v3.encode(),
);
migrate_to_v3::<Test>();
migrate_to_v4::<Test>();
let v3 = configuration::ActiveConfig::<Test>::get();
let v4 = configuration::ActiveConfig::<Test>::get();
#[rustfmt::skip]
{
assert_eq!(v2.max_code_size , v3.max_code_size);
assert_eq!(v2.max_head_data_size , v3.max_head_data_size);
assert_eq!(v2.max_upward_queue_count , v3.max_upward_queue_count);
assert_eq!(v2.max_upward_queue_size , v3.max_upward_queue_size);
assert_eq!(v2.max_upward_message_size , v3.max_upward_message_size);
assert_eq!(v2.max_upward_message_num_per_candidate , v3.max_upward_message_num_per_candidate);
assert_eq!(v2.hrmp_max_message_num_per_candidate , v3.hrmp_max_message_num_per_candidate);
assert_eq!(v2.validation_upgrade_cooldown , v3.validation_upgrade_cooldown);
assert_eq!(v2.validation_upgrade_delay , v3.validation_upgrade_delay);
assert_eq!(v2.max_pov_size , v3.max_pov_size);
assert_eq!(v2.max_downward_message_size , v3.max_downward_message_size);
assert_eq!(v2.hrmp_max_parachain_outbound_channels , v3.hrmp_max_parachain_outbound_channels);
assert_eq!(v2.hrmp_max_parathread_outbound_channels , v3.hrmp_max_parathread_outbound_channels);
assert_eq!(v2.hrmp_sender_deposit , v3.hrmp_sender_deposit);
assert_eq!(v2.hrmp_recipient_deposit , v3.hrmp_recipient_deposit);
assert_eq!(v2.hrmp_channel_max_capacity , v3.hrmp_channel_max_capacity);
assert_eq!(v2.hrmp_channel_max_total_size , v3.hrmp_channel_max_total_size);
assert_eq!(v2.hrmp_max_parachain_inbound_channels , v3.hrmp_max_parachain_inbound_channels);
assert_eq!(v2.hrmp_max_parathread_inbound_channels , v3.hrmp_max_parathread_inbound_channels);
assert_eq!(v2.hrmp_channel_max_message_size , v3.hrmp_channel_max_message_size);
assert_eq!(v2.code_retention_period , v3.code_retention_period);
assert_eq!(v2.parathread_cores , v3.parathread_cores);
assert_eq!(v2.parathread_retries , v3.parathread_retries);
assert_eq!(v2.group_rotation_frequency , v3.group_rotation_frequency);
assert_eq!(v2.chain_availability_period , v3.chain_availability_period);
assert_eq!(v2.thread_availability_period , v3.thread_availability_period);
assert_eq!(v2.scheduling_lookahead , v3.scheduling_lookahead);
assert_eq!(v2.max_validators_per_core , v3.max_validators_per_core);
assert_eq!(v2.max_validators , v3.max_validators);
assert_eq!(v2.dispute_period , v3.dispute_period);
assert_eq!(v2.dispute_post_conclusion_acceptance_period, v3.dispute_post_conclusion_acceptance_period);
assert_eq!(v2.dispute_max_spam_slots , v3.dispute_max_spam_slots);
assert_eq!(v2.dispute_conclusion_by_time_out_period , v3.dispute_conclusion_by_time_out_period);
assert_eq!(v2.no_show_slots , v3.no_show_slots);
assert_eq!(v2.n_delay_tranches , v3.n_delay_tranches);
assert_eq!(v2.zeroth_delay_tranche_width , v3.zeroth_delay_tranche_width);
assert_eq!(v2.needed_approvals , v3.needed_approvals);
assert_eq!(v2.relay_vrf_modulo_samples , v3.relay_vrf_modulo_samples);
assert_eq!(v2.pvf_checking_enabled , v3.pvf_checking_enabled);
assert_eq!(v2.pvf_voting_ttl , v3.pvf_voting_ttl);
assert_eq!(v2.minimum_validation_upgrade_delay , v3.minimum_validation_upgrade_delay);
assert_eq!(v3.max_code_size , v4.max_code_size);
assert_eq!(v3.max_head_data_size , v4.max_head_data_size);
assert_eq!(v3.max_upward_queue_count , v4.max_upward_queue_count);
assert_eq!(v3.max_upward_queue_size , v4.max_upward_queue_size);
assert_eq!(v3.max_upward_message_size , v4.max_upward_message_size);
assert_eq!(v3.max_upward_message_num_per_candidate , v4.max_upward_message_num_per_candidate);
assert_eq!(v3.hrmp_max_message_num_per_candidate , v4.hrmp_max_message_num_per_candidate);
assert_eq!(v3.validation_upgrade_cooldown , v4.validation_upgrade_cooldown);
assert_eq!(v3.validation_upgrade_delay , v4.validation_upgrade_delay);
assert_eq!(v3.max_pov_size , v4.max_pov_size);
assert_eq!(v3.max_downward_message_size , v4.max_downward_message_size);
assert_eq!(v3.ump_service_total_weight , v4.ump_service_total_weight);
assert_eq!(v3.hrmp_max_parachain_outbound_channels , v4.hrmp_max_parachain_outbound_channels);
assert_eq!(v3.hrmp_max_parathread_outbound_channels , v4.hrmp_max_parathread_outbound_channels);
assert_eq!(v3.hrmp_sender_deposit , v4.hrmp_sender_deposit);
assert_eq!(v3.hrmp_recipient_deposit , v4.hrmp_recipient_deposit);
assert_eq!(v3.hrmp_channel_max_capacity , v4.hrmp_channel_max_capacity);
assert_eq!(v3.hrmp_channel_max_total_size , v4.hrmp_channel_max_total_size);
assert_eq!(v3.hrmp_max_parachain_inbound_channels , v4.hrmp_max_parachain_inbound_channels);
assert_eq!(v3.hrmp_max_parathread_inbound_channels , v4.hrmp_max_parathread_inbound_channels);
assert_eq!(v3.hrmp_channel_max_message_size , v4.hrmp_channel_max_message_size);
assert_eq!(v3.code_retention_period , v4.code_retention_period);
assert_eq!(v3.parathread_cores , v4.parathread_cores);
assert_eq!(v3.parathread_retries , v4.parathread_retries);
assert_eq!(v3.group_rotation_frequency , v4.group_rotation_frequency);
assert_eq!(v3.chain_availability_period , v4.chain_availability_period);
assert_eq!(v3.thread_availability_period , v4.thread_availability_period);
assert_eq!(v3.scheduling_lookahead , v4.scheduling_lookahead);
assert_eq!(v3.max_validators_per_core , v4.max_validators_per_core);
assert_eq!(v3.max_validators , v4.max_validators);
assert_eq!(v3.dispute_period , v4.dispute_period);
assert_eq!(v3.dispute_post_conclusion_acceptance_period, v4.dispute_post_conclusion_acceptance_period);
assert_eq!(v3.dispute_conclusion_by_time_out_period , v4.dispute_conclusion_by_time_out_period);
assert_eq!(v3.no_show_slots , v4.no_show_slots);
assert_eq!(v3.n_delay_tranches , v4.n_delay_tranches);
assert_eq!(v3.zeroth_delay_tranche_width , v4.zeroth_delay_tranche_width);
assert_eq!(v3.needed_approvals , v4.needed_approvals);
assert_eq!(v3.relay_vrf_modulo_samples , v4.relay_vrf_modulo_samples);
assert_eq!(v3.ump_max_individual_weight , v4.ump_max_individual_weight);
assert_eq!(v3.pvf_checking_enabled , v4.pvf_checking_enabled);
assert_eq!(v3.pvf_voting_ttl , v4.pvf_voting_ttl);
assert_eq!(v3.minimum_validation_upgrade_delay , v4.minimum_validation_upgrade_delay);
assert_eq!(v2.ump_service_total_weight, OldWeight(v3.ump_service_total_weight.ref_time()));
assert_eq!(v2.ump_max_individual_weight, OldWeight(v3.ump_max_individual_weight.ref_time()));
assert_eq!(v3.ump_service_total_weight.proof_size(), MAX_POV_SIZE as u64);
assert_eq!(v3.ump_max_individual_weight.proof_size(), MAX_POV_SIZE as u64);
}; // ; makes this a statement. `rustfmt::skip` cannot be put on an expression.
});
}
@@ -309,7 +309,6 @@ fn setting_pending_config_members() {
max_validators: None,
dispute_period: 239,
dispute_post_conclusion_acceptance_period: 10,
dispute_max_spam_slots: 2,
dispute_conclusion_by_time_out_period: 512,
no_show_slots: 240,
n_delay_tranches: 241,
@@ -402,11 +401,6 @@ fn setting_pending_config_members() {
new_config.dispute_post_conclusion_acceptance_period,
)
.unwrap();
Configuration::set_dispute_max_spam_slots(
RuntimeOrigin::root(),
new_config.dispute_max_spam_slots,
)
.unwrap();
Configuration::set_dispute_conclusion_by_time_out_period(
RuntimeOrigin::root(),
new_config.dispute_conclusion_by_time_out_period,
+31 -224
View File
@@ -46,6 +46,10 @@ mod tests;
#[cfg(feature = "runtime-benchmarks")]
mod benchmarking;
pub mod migration;
const LOG_TARGET: &str = "runtime::disputes";
/// Whether the dispute is local or remote.
#[derive(Encode, Decode, Clone, PartialEq, Eq, RuntimeDebug, TypeInfo)]
pub enum DisputeLocation {
@@ -262,7 +266,6 @@ pub trait DisputesHandler<BlockNumber: Ord> {
/// accounting for maximum block weight.
fn filter_dispute_data(
statement_set: DisputeStatementSet,
max_spam_slots: u32,
post_conclusion_acceptance_period: BlockNumber,
verify_sigs: VerifyDisputeSignatures,
) -> Option<CheckedDisputeStatementSet>;
@@ -311,7 +314,6 @@ impl<BlockNumber: Ord> DisputesHandler<BlockNumber> for () {
fn filter_dispute_data(
_set: DisputeStatementSet,
_max_spam_slots: u32,
_post_conclusion_acceptance_period: BlockNumber,
_verify_sigs: VerifyDisputeSignatures,
) -> Option<CheckedDisputeStatementSet> {
@@ -361,14 +363,12 @@ where
fn filter_dispute_data(
set: DisputeStatementSet,
max_spam_slots: u32,
post_conclusion_acceptance_period: T::BlockNumber,
verify_sigs: VerifyDisputeSignatures,
) -> Option<CheckedDisputeStatementSet> {
pallet::Pallet::<T>::filter_dispute_data(
&set,
post_conclusion_acceptance_period,
max_spam_slots,
verify_sigs,
)
.filter_statement_set(set)
@@ -471,14 +471,6 @@ pub mod pallet {
T::BlockNumber,
>;
/// Maps session indices to a vector indicating the number of potentially-spam disputes
/// each validator is participating in. Potentially-spam disputes are remote disputes which have
/// fewer than `byzantine_threshold + 1` validators.
///
/// The i'th entry of the vector corresponds to the i'th validator in the session.
#[pallet::storage]
pub(super) type SpamSlots<T> = StorageMap<_, Twox64Concat, SessionIndex, Vec<u32>>;
/// Whether the chain is frozen. Starts as `None`. When this is `Some`,
/// the chain will not accept any new parachain blocks for backing or inclusion,
/// and its value indicates the last valid block number in the chain.
@@ -517,10 +509,10 @@ pub mod pallet {
InvalidSignature,
/// Validator vote submitted more than once to dispute.
DuplicateStatement,
/// Too many spam slots used by some specific validator.
PotentialSpam,
/// A dispute where there are only votes on one side.
SingleSidedDispute,
/// Unconfirmed dispute statement sets provided
UnconfirmedDispute,
}
#[pallet::call]
@@ -574,19 +566,9 @@ impl DisputeStateFlags {
}
}
#[derive(PartialEq, RuntimeDebug)]
enum SpamSlotChange {
/// Add a `+1` to the spam slot for a particular validator index in this session.
Inc,
/// Subtract `-1` ...
Dec,
}
struct ImportSummary<BlockNumber> {
/// The new state, with all votes imported.
state: DisputeState<BlockNumber>,
/// Changes to spam slots. Validator index paired with directional change.
spam_slot_changes: Vec<(ValidatorIndex, SpamSlotChange)>,
/// Validators to slash for being (wrongly) on the AGAINST side.
slash_against: Vec<ValidatorIndex>,
/// Validators to slash for being (wrongly) on the FOR side.
@@ -699,37 +681,7 @@ impl<BlockNumber: Clone> DisputeStateImporter<BlockNumber> {
let pre_post_contains = |flags| (pre_flags.contains(flags), post_flags.contains(flags));
// 1. Act on confirmed flag state to inform spam slots changes.
let spam_slot_changes: Vec<_> = match pre_post_contains(DisputeStateFlags::CONFIRMED) {
(false, false) => {
// increment spam slots for all new participants.
self.new_participants
.iter_ones()
.map(|i| (ValidatorIndex(i as _), SpamSlotChange::Inc))
.collect()
},
(false, true) => {
// all participants, which are not new participants
let prev_participants = (self.state.validators_for.clone() |
self.state.validators_against.clone()) &
!self.new_participants.clone();
prev_participants
.iter_ones()
.map(|i| (ValidatorIndex(i as _), SpamSlotChange::Dec))
.collect()
},
(true, false) => {
log::error!("Dispute statements are never removed. This is a bug");
Vec::new()
},
(true, true) => {
// No change, nothing to do.
Vec::new()
},
};
// 2. Check for fresh FOR supermajority. Only if not already concluded.
// 1. Check for fresh FOR supermajority. Only if not already concluded.
let slash_against =
if let (false, true) = pre_post_contains(DisputeStateFlags::FOR_SUPERMAJORITY) {
if self.state.concluded_at.is_none() {
@@ -746,7 +698,7 @@ impl<BlockNumber: Clone> DisputeStateImporter<BlockNumber> {
Vec::new()
};
// 3. Check for fresh AGAINST supermajority.
// 2. Check for fresh AGAINST supermajority.
let slash_for =
if let (false, true) = pre_post_contains(DisputeStateFlags::AGAINST_SUPERMAJORITY) {
if self.state.concluded_at.is_none() {
@@ -761,7 +713,6 @@ impl<BlockNumber: Clone> DisputeStateImporter<BlockNumber> {
ImportSummary {
state: self.state,
spam_slot_changes,
slash_against,
slash_for,
new_participants: self.new_participants,
@@ -831,32 +782,7 @@ impl<T: Config> Pallet<T> {
dispute.concluded_at = Some(now);
<Disputes<T>>::insert(session_index, candidate_hash, &dispute);
if <Included<T>>::contains_key(&session_index, &candidate_hash) {
// Local disputes don't count towards spam.
weight += T::DbWeight::get().reads_writes(1, 1);
continue
}
// mildly punish all validators involved. they've failed to make
// data available to others, so this is most likely spam.
SpamSlots::<T>::mutate(session_index, |spam_slots| {
let spam_slots = match spam_slots {
Some(ref mut s) => s,
None => return,
};
// also reduce spam slots for all validators involved, if the dispute was unconfirmed.
// this does open us up to more spam, but only for validators who are willing
// to be punished more.
//
// it would be unexpected for any change here to occur when the dispute has not concluded
// in time, as a dispute guaranteed to have at least one honest participant should
// conclude quickly.
let _participating = decrement_spam(spam_slots, &dispute);
});
weight += T::DbWeight::get().reads_writes(2, 2);
weight += T::DbWeight::get().writes(1);
}
}
@@ -894,7 +820,6 @@ impl<T: Config> Pallet<T> {
// TODO: https://github.com/paritytech/polkadot/issues/3469
#[allow(deprecated)]
<Included<T>>::remove_prefix(to_prune, None);
SpamSlots::<T>::remove(to_prune);
}
*last_pruned = Some(pruning_target);
@@ -938,10 +863,10 @@ impl<T: Config> Pallet<T> {
//
// Votes which are duplicate or already known by the chain are filtered out.
// The entire set is removed if the dispute is both, ancient and concluded.
// Disputes without enough votes to get confirmed are also filtered out.
fn filter_dispute_data(
set: &DisputeStatementSet,
post_conclusion_acceptance_period: <T as frame_system::Config>::BlockNumber,
max_spam_slots: u32,
verify_sigs: VerifyDisputeSignatures,
) -> StatementSetFilter {
let mut filter = StatementSetFilter::RemoveIndices(Vec::new());
@@ -960,29 +885,26 @@ impl<T: Config> Pallet<T> {
let n_validators = session_info.validators.len();
// Check for ancient.
let (first_votes, dispute_state) = {
let dispute_state = {
if let Some(dispute_state) = <Disputes<T>>::get(&set.session, &set.candidate_hash) {
if dispute_state.concluded_at.as_ref().map_or(false, |c| c < &oldest_accepted) {
return StatementSetFilter::RemoveAll
}
(false, dispute_state)
dispute_state
} else {
// No state in storage, this indicates it's the first dispute statement set as well.
(
true,
DisputeState {
validators_for: bitvec![u8, BitOrderLsb0; 0; n_validators],
validators_against: bitvec![u8, BitOrderLsb0; 0; n_validators],
start: now,
concluded_at: None,
},
)
DisputeState {
validators_for: bitvec![u8, BitOrderLsb0; 0; n_validators],
validators_against: bitvec![u8, BitOrderLsb0; 0; n_validators],
start: now,
concluded_at: None,
}
}
};
// Check and import all votes.
let mut summary = {
let summary = {
let mut importer = DisputeStateImporter::new(dispute_state, now);
for (i, (statement, validator_index, signature)) in set.statements.iter().enumerate() {
// assure the validator index and is present in the session info
@@ -1039,99 +961,11 @@ impl<T: Config> Pallet<T> {
return StatementSetFilter::RemoveAll
}
// Apply spam slot changes. Bail early if too many occupied.
let is_local = <Included<T>>::contains_key(&set.session, &set.candidate_hash);
if !is_local {
let mut spam_slots: Vec<u32> =
SpamSlots::<T>::get(&set.session).unwrap_or_else(|| vec![0; n_validators]);
let mut spam_filter_struck = false;
for (validator_index, spam_slot_change) in summary.spam_slot_changes {
let spam_slot = spam_slots
.get_mut(validator_index.0 as usize)
.expect("index is in-bounds, as checked above; qed");
if let SpamSlotChange::Inc = spam_slot_change {
if *spam_slot >= max_spam_slots {
spam_filter_struck = true;
// Find the vote by this validator and filter it out.
let first_index_in_set = set
.statements
.iter()
.position(|(_statement, v_i, _signature)| &validator_index == v_i)
.expect(
"spam slots are only incremented when a new statement \
from a validator is included; qed",
);
// Note that there may be many votes by the validator in the statement
// set. There are not supposed to be, but the purpose of this function
// is to filter out invalid submissions, after all.
//
// This is fine - we only need to handle the first one, because all
// subsequent votes' indices have been added to the filter already
// by the duplicate checks above. It's only the first one which
// may not already have been filtered out.
filter.remove_index(first_index_in_set);
// Removing individual statments can cause the dispute to become onesided.
// Checking that (again) is done after the loop. Remove the bit indices.
summary.new_participants.set(validator_index.0 as _, false);
}
// It's also worth noting that the `DisputeStateImporter`
// which produces these spam slot updates only produces
// one spam slot update per validator because it rejects
// duplicate votes.
//
// So we don't need to worry about spam slots being
// updated incorrectly after receiving duplicates.
*spam_slot += 1;
} else {
*spam_slot = spam_slot.saturating_sub(1);
}
}
// We write the spam slots here because sequential calls to
// `filter_dispute_data` have a dependency on each other.
//
// For example, if a validator V occupies 1 spam slot and
// max is 2, then 2 sequential calls incrementing spam slot
// cannot be allowed.
//
// However, 3 sequential calls, where the first increments,
// the second decrements, and the third increments would be allowed.
SpamSlots::<T>::insert(&set.session, spam_slots);
// This is only relevant in cases where it's the first vote and the state
// would hence hold a onesided dispute. If a onesided dispute can never be
// started, by induction, we can never enter a state of a one sided dispute.
if spam_filter_struck && first_votes {
let mut vote_for_count = 0_u64;
let mut vote_against_count = 0_u64;
// Since this is the first set of statements for the dispute,
// it's sufficient to count the votes in the statement set after they
set.statements.iter().for_each(|(statement, v_i, _signature)| {
if Some(true) ==
summary.new_participants.get(v_i.0 as usize).map(|b| *b.as_ref())
{
match statement {
// `summary.new_flags` contains the spam free votes.
// Note that this does not distinguish between pro or con votes,
// since allowing both of them, even if the spam threshold would be reached
// is a good thing.
// Overflow of the counters is no concern, disputes are limited by weight.
DisputeStatement::Valid(_) => vote_for_count += 1,
DisputeStatement::Invalid(_) => vote_against_count += 1,
}
}
});
if vote_for_count.is_zero() || vote_against_count.is_zero() {
// It wasn't one-sided before the spam filters, but now it is,
// so we need to be thorough and not import that dispute.
return StatementSetFilter::RemoveAll
}
}
// Reject disputes containing less votes than needed for confirmation.
if (summary.state.validators_for.clone() | &summary.state.validators_against).count_ones() <=
byzantine_threshold(summary.state.validators_for.len())
{
return StatementSetFilter::RemoveAll
}
filter
@@ -1201,13 +1035,17 @@ impl<T: Config> Pallet<T> {
Error::<T>::SingleSidedDispute,
);
// Reject disputes containing less votes than needed for confirmation.
ensure!(
(summary.state.validators_for.clone() | &summary.state.validators_against).count_ones() >
byzantine_threshold(summary.state.validators_for.len()),
Error::<T>::UnconfirmedDispute,
);
let DisputeStatementSet { ref session, ref candidate_hash, .. } = set;
let session = *session;
let candidate_hash = *candidate_hash;
// we can omit spam slot checks, `fn filter_disputes_data` is
// always called before calling this `fn`.
if fresh {
let is_local = <Included<T>>::contains_key(&session, &candidate_hash);
@@ -1283,15 +1121,7 @@ impl<T: Config> Pallet<T> {
<Included<T>>::insert(&session, &candidate_hash, revert_to);
// If we just included a block locally which has a live dispute, decrement spam slots
// for any involved validators, if the dispute is not already confirmed by f + 1.
if let Some(state) = <Disputes<T>>::get(&session, candidate_hash) {
SpamSlots::<T>::mutate(&session, |spam_slots| {
if let Some(ref mut spam_slots) = *spam_slots {
decrement_spam(spam_slots, &state);
}
});
if has_supermajority_against(&state) {
Self::revert_and_freeze(revert_to);
}
@@ -1337,29 +1167,6 @@ fn has_supermajority_against<BlockNumber>(dispute: &DisputeState<BlockNumber>) -
dispute.validators_against.count_ones() >= supermajority_threshold
}
// If the dispute had not enough validators to confirm, decrement spam slots for all the participating
// validators.
//
// Returns the set of participating validators as a bitvec.
fn decrement_spam<BlockNumber>(
spam_slots: &mut [u32],
dispute: &DisputeState<BlockNumber>,
) -> bitvec::vec::BitVec<u8, BitOrderLsb0> {
let byzantine_threshold = byzantine_threshold(spam_slots.len());
let participating = dispute.validators_for.clone() | dispute.validators_against.clone();
let decrement_spam = participating.count_ones() <= byzantine_threshold;
for validator_index in participating.iter_ones() {
if decrement_spam {
if let Some(occupied) = spam_slots.get_mut(validator_index as usize) {
*occupied = occupied.saturating_sub(1);
}
}
}
participating
}
fn check_signature(
validator_public: &ValidatorId,
candidate_hash: CandidateHash,
@@ -0,0 +1,98 @@
// Copyright 2017-2022 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 <http://www.gnu.org/licenses/>.
//! Storage migration(s) related to disputes pallet
use frame_support::traits::StorageVersion;
/// The current storage version.
const STORAGE_VERSION: StorageVersion = StorageVersion::new(1);
pub mod v1 {
use super::*;
use crate::disputes::{Config, Pallet};
use frame_support::{
pallet_prelude::*, storage_alias, traits::OnRuntimeUpgrade, weights::Weight,
};
use primitives::v2::SessionIndex;
use sp_std::prelude::*;
#[storage_alias]
type SpamSlots<T: Config> = StorageMap<Pallet<T>, Twox64Concat, SessionIndex, Vec<u32>>;
pub struct MigrateToV1<T>(sp_std::marker::PhantomData<T>);
impl<T: Config> OnRuntimeUpgrade for MigrateToV1<T> {
fn on_runtime_upgrade() -> Weight {
let mut weight: Weight = Weight::zero();
if StorageVersion::get::<Pallet<T>>() < STORAGE_VERSION {
log::info!(target: crate::disputes::LOG_TARGET, "Migrating disputes storage to v1");
weight += migrate_to_v1::<T>();
STORAGE_VERSION.put::<Pallet<T>>();
weight = weight.saturating_add(T::DbWeight::get().reads_writes(1, 1));
} else {
log::info!(
target: crate::disputes::LOG_TARGET,
"Disputes storage up to date - no need for migration"
);
}
weight
}
#[cfg(feature = "try-runtime")]
fn pre_upgrade() -> Result<Vec<u8>, &'static str> {
log::trace!(
target: crate::disputes::LOG_TARGET,
"SpamSlots before migration: {}",
SpamSlots::<T>::iter().count()
);
ensure!(
StorageVersion::get::<Pallet<T>>() == 0,
"Storage version should be less than `1` before the migration",
);
Ok(Vec::new())
}
#[cfg(feature = "try-runtime")]
fn post_upgrade(_state: Vec<u8>) -> Result<(), &'static str> {
log::trace!(target: crate::disputes::LOG_TARGET, "Running post_upgrade()");
ensure!(
StorageVersion::get::<Pallet<T>>() == STORAGE_VERSION,
"Storage version should be `1` after the migration"
);
ensure!(
SpamSlots::<T>::iter().count() == 0,
"SpamSlots should be empty after the migration"
);
Ok(())
}
}
/// Migrates the pallet storage to the most recent version, checking and setting the `StorageVersion`.
pub fn migrate_to_v1<T: Config>() -> Weight {
let mut weight: Weight = Weight::zero();
// SpamSlots should not contain too many keys so removing everything at once should be safe
let res = SpamSlots::<T>::clear(u32::MAX, None);
// `loops` is the number of iterations => used to calculate read weights
// `backend` is the number of keys removed from the backend => used to calculate write weights
weight = weight
.saturating_add(T::DbWeight::get().reads_writes(res.loops as u64, res.backend as u64));
weight
}
}
+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(|| {
@@ -349,7 +349,6 @@ impl<T: Config> Pallet<T> {
let (checked_disputes, total_consumed_weight) = {
// Obtain config params..
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;
@@ -363,7 +362,6 @@ impl<T: Config> Pallet<T> {
let dispute_set_validity_check = move |set| {
T::DisputesHandler::filter_dispute_data(
set,
max_spam_slots,
post_conclusion_acceptance_period,
verify_dispute_sigs,
)
@@ -595,7 +593,6 @@ impl<T: Config> Pallet<T> {
}
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;
// TODO: Better if we can convert this to `with_transactional` and handle an error if
@@ -609,7 +606,6 @@ impl<T: Config> Pallet<T> {
let dispute_statement_set_valid = move |set: DisputeStatementSet| {
T::DisputesHandler::filter_dispute_data(
set,
max_spam_slots,
post_conclusion_acceptance_period,
// `DisputeCoordinator` on the node side only forwards
// valid dispute statement sets and hence this does not