From 7f4c074f77ce68875d6ca28ebb91058f782bb76d Mon Sep 17 00:00:00 2001 From: 1xstj <106580853+1xstj@users.noreply.github.com> Date: Fri, 23 Jun 2023 22:08:56 +0530 Subject: [PATCH] [fix] Handle edge-cases in misbehaviour reports (#654) --- pallets/dkg-metadata/src/lib.rs | 54 ++- pallets/dkg-metadata/src/mock.rs | 2 + pallets/dkg-metadata/src/tests.rs | 662 +++++++++++++++++++++++++++++- 3 files changed, 715 insertions(+), 3 deletions(-) diff --git a/pallets/dkg-metadata/src/lib.rs b/pallets/dkg-metadata/src/lib.rs index d0bbd6e63..e3e0e9e72 100644 --- a/pallets/dkg-metadata/src/lib.rs +++ b/pallets/dkg-metadata/src/lib.rs @@ -650,6 +650,12 @@ pub mod pallet { OutOfBounds, /// Cannot retreive signer from ecdsa signature CannotRetreiveSigner, + /// Reported misbehaviour against a non authority + OffenderNotAuthority, + /// Authority is already jailed + AlreadyJailed, + /// We do not have authorities to jail + NotEnoughAuthoritiesToJail, } // Pallets use events to inform users when important changes are made. @@ -1068,6 +1074,16 @@ pub mod pallet { ) -> DispatchResultWithPostInfo { ensure_none(origin)?; let offender = reports.offender.clone(); + + ensure!( + !JailedKeygenAuthorities::::contains_key(&offender), + Error::::AlreadyJailed + ); + ensure!( + !JailedSigningAuthorities::::contains_key(&offender), + Error::::AlreadyJailed + ); + let misbehaviour_type = reports.misbehaviour_type; let authorities = match misbehaviour_type { // We assume genesis ran successfully. Therefore, keygen misbehaviours are from next @@ -1076,6 +1092,10 @@ pub mod pallet { // Signing misbehaviours are from current authorities MisbehaviourType::Sign => Self::authorities(), }; + + // sanity check, is the offender an authority? + ensure!(authorities.contains(&offender), Error::::OffenderNotAuthority); + let valid_reporters = Self::process_misbehaviour_reports(reports, authorities.into()); // Get the threshold for the misbehaviour type let signature_threshold = match misbehaviour_type { @@ -1157,8 +1177,21 @@ pub mod pallet { .try_into() .map_err(|_| Error::::OutOfBounds)?; + // final sanity check, we need atleast two authorities + ensure!( + next_best_authorities.len() >= 2, + Error::::NotEnoughAuthoritiesToJail + ); + NextBestAuthorities::::put(next_best_authorities); + // emit event + Self::deposit_event(Event::MisbehaviourReportsSubmitted { + misbehaviour_type, + reporters: valid_reporters, + offender, + }); + return Ok(().into()) } @@ -1170,7 +1203,20 @@ pub mod pallet { let new_val = u16::try_from(unjailed_authorities.len() - 1) .unwrap_or_default(); Self::update_next_keygen_threshold(new_val); - PendingKeygenThreshold::::put(new_val); + NextKeygenThreshold::::put(new_val); + + if NextSignatureThreshold::::get() >= + NextKeygenThreshold::::get() + { + // drop signature threshold + let next_signature_threshold = + NextSignatureThreshold::::get(); + if next_signature_threshold != 1 { + NextSignatureThreshold::::put( + next_signature_threshold - 1, + ); + } + } } } } @@ -1185,6 +1231,12 @@ pub mod pallet { .try_into() .map_err(|_| Error::::OutOfBounds)?; + // final sanity check, we need atleast two authorities + ensure!( + next_best_authorities.len() >= 2, + Error::::NotEnoughAuthoritiesToJail + ); + NextBestAuthorities::::put(next_best_authorities); }, MisbehaviourType::Sign => { diff --git a/pallets/dkg-metadata/src/mock.rs b/pallets/dkg-metadata/src/mock.rs index cbf9cdd85..3bb991c4b 100644 --- a/pallets/dkg-metadata/src/mock.rs +++ b/pallets/dkg-metadata/src/mock.rs @@ -227,6 +227,8 @@ pub fn new_test_ext_raw_authorities(authorities: Vec<(AccountId, DKGId)>) -> Tes let mut ext = sp_io::TestExternalities::new(t); let keystore = KeyStore::new(); + // set to block 1 to test events + ext.execute_with(|| System::set_block_number(1)); ext.register_extension(KeystoreExt(Arc::new(keystore))); ext } diff --git a/pallets/dkg-metadata/src/tests.rs b/pallets/dkg-metadata/src/tests.rs index 5872f6080..40dd804eb 100644 --- a/pallets/dkg-metadata/src/tests.rs +++ b/pallets/dkg-metadata/src/tests.rs @@ -14,9 +14,16 @@ #![allow(clippy::unwrap_used)] use std::vec; -use crate::mock::*; -use frame_support::{assert_ok, traits::Hooks, weights::Weight, BoundedVec}; +use crate::{ + mock::*, AggregatedMisbehaviourReports, AuthorityReputations, Config, Error, Event, + JailedKeygenAuthorities, NextAuthorities, NextBestAuthorities, NextKeygenThreshold, + NextSignatureThreshold, +}; +use codec::Encode; +use dkg_runtime_primitives::{keccak_256, utils::ecdsa, MisbehaviourType, KEY_TYPE}; +use frame_support::{assert_noop, assert_ok, traits::Hooks, weights::Weight, BoundedVec}; use sp_core::ByteArray; +use sp_io::crypto::{ecdsa_generate, ecdsa_sign_prehashed}; use sp_runtime::traits::Bounded; fn init_block(block: u64) { @@ -27,6 +34,37 @@ fn init_block(block: u64) { DKGMetadata::on_finalize(block); } +fn mock_misbehaviour_report( + pub_key: ecdsa::Public, + offender: T::DKGId, + misbehaviour_type: MisbehaviourType, +) -> BoundedVec { + let session_id: u64 = 1; + let mut payload = Vec::new(); + payload.extend_from_slice(&match misbehaviour_type { + MisbehaviourType::Keygen => [0x01], + MisbehaviourType::Sign => [0x02], + }); + payload.extend_from_slice(session_id.to_be_bytes().as_ref()); + payload.extend_from_slice(offender.clone().as_ref()); + let hash = keccak_256(&payload); + let signature = ecdsa_sign_prehashed(KEY_TYPE, &pub_key, &hash).unwrap(); + + signature.encode().try_into().unwrap() +} + +fn mock_pub_key() -> ecdsa::Public { + ecdsa_generate(KEY_TYPE, None) +} + +fn assert_last_event(generic_event: ::RuntimeEvent) { + frame_system::Pallet::::assert_last_event(generic_event.into()); +} + +fn assert_has_event(generic_event: ::RuntimeEvent) { + frame_system::Pallet::::assert_has_event(generic_event.into()); +} + #[test] fn genesis_session_initializes_authorities() { let want = vec![mock_dkg_id(1), mock_dkg_id(2), mock_dkg_id(3), mock_dkg_id(4)]; @@ -147,3 +185,623 @@ fn refresh_nonce_should_increment_by_one() { assert_eq!(refresh_nonce, 2); }); } + +#[test] +fn misbehaviour_reports_submission_rejects_if_offender_not_authority() { + new_test_ext(vec![1, 2, 3, 4, 5]).execute_with(|| { + // lets use a random offender + let offender: DKGId = DKGId::from(ecdsa_generate(KEY_TYPE, None)); + let mut next_authorities: BoundedVec<_, _> = Default::default(); + let mut reporters: Vec = Vec::new(); + let mut signatures: BoundedVec<_, _> = Default::default(); + let session_id = 1; + let misbehaviour_type = MisbehaviourType::Keygen; + for _ in 1..=5 { + let authority_id = mock_pub_key(); + let sig = + mock_misbehaviour_report::(authority_id, offender.clone(), misbehaviour_type); + signatures.try_push(sig).unwrap(); + let dkg_id = DKGId::from(authority_id); + reporters.push(dkg_id.clone()); + next_authorities.try_push(dkg_id).unwrap(); + } + let threshold = u16::try_from(next_authorities.len() / 2).unwrap() + 1; + NextSignatureThreshold::::put(threshold); + NextAuthorities::::put(&next_authorities); + let aggregated_misbehaviour_reports: AggregatedMisbehaviourReports< + DKGId, + MaxSignatureLength, + MaxReporters, + > = AggregatedMisbehaviourReports { + misbehaviour_type, + session_id, + offender: offender.clone(), + reporters: reporters.clone().try_into().unwrap(), + signatures: signatures.try_into().unwrap(), + }; + + assert_noop!( + DKGMetadata::submit_misbehaviour_reports( + RuntimeOrigin::none(), + aggregated_misbehaviour_reports + ), + Error::::OffenderNotAuthority + ); + }); +} + +#[test] +fn keygen_misbehaviour_reports_work() { + new_test_ext(vec![1, 2, 3, 4, 5]).execute_with(|| { + let session_id = 1; + + // prep the next authorities + let mut next_authorities: BoundedVec<_, _> = Default::default(); + let mut next_authorities_raw: Vec<_> = Default::default(); + for _ in 1..=5 { + let authority_id = mock_pub_key(); + let dkg_id = DKGId::from(authority_id); + next_authorities_raw.push(authority_id); + next_authorities.try_push(dkg_id).unwrap(); + } + + // 1 is the misbehaving party + let offender: DKGId = next_authorities.first().unwrap().clone(); + // lets give the offender some reputation + AuthorityReputations::::insert(offender.clone(), 100); + + let mut reporters: Vec = Vec::new(); + let mut signatures: BoundedVec<_, _> = Default::default(); + let misbehaviour_type = MisbehaviourType::Keygen; + + // everyone except 1 reports the misbehaviour + for authority_id in next_authorities_raw.iter() { + let dkg_id = DKGId::from(authority_id.clone()); + + if dkg_id == offender { + continue + } + + let sig = mock_misbehaviour_report::( + authority_id.clone(), + offender.clone(), + misbehaviour_type, + ); + signatures.try_push(sig).unwrap(); + reporters.push(dkg_id.clone()); + } + + // setup the new threshold so our signature is accepted + let keygen_threshold = 5; + let signature_threshold = 3; + NextKeygenThreshold::::put(keygen_threshold); + NextSignatureThreshold::::put(signature_threshold); + NextAuthorities::::put(&next_authorities); + let next_best_authorities = + DKGMetadata::get_best_authorities(keygen_threshold as usize, &next_authorities); + let mut bounded_next_best_authorities: BoundedVec<_, _> = Default::default(); + for auth in next_best_authorities { + bounded_next_best_authorities.try_push(auth).unwrap(); + } + NextBestAuthorities::::put(&bounded_next_best_authorities); + + let aggregated_misbehaviour_reports: AggregatedMisbehaviourReports< + DKGId, + MaxSignatureLength, + MaxReporters, + > = AggregatedMisbehaviourReports { + misbehaviour_type, + session_id, + offender: offender.clone(), + reporters: reporters.clone().try_into().unwrap(), + signatures: signatures.try_into().unwrap(), + }; + + assert_ok!(DKGMetadata::submit_misbehaviour_reports( + RuntimeOrigin::none(), + aggregated_misbehaviour_reports.clone() + )); + + // succcessful submission event should be present + assert_last_event::( + Event::MisbehaviourReportsSubmitted { + misbehaviour_type, + reporters: reporters.clone().try_into().unwrap(), + offender: offender.clone(), + } + .into(), + ); + + // succcessful submission event should be present + assert_has_event::( + Event::AuthorityJailed { misbehaviour_type, authority: offender.clone() }.into(), + ); + + // lets check the storage is as expected + + // jailed authorities should contain the offender + assert_eq!(JailedKeygenAuthorities::::get(offender.clone()), session_id); + + // offender reputation is dinged by decay percentage + assert_eq!(AuthorityReputations::::get(offender.clone()), 50); + + // next best authorities should contain everyone except the offender + let mut current_next_best_authorities = NextBestAuthorities::::get(); + assert_eq!(current_next_best_authorities.len(), 4); + current_next_best_authorities.retain(|x| x.1 == offender); + assert_eq!(current_next_best_authorities.len(), 0); + + // the threshold should have reduced correctly + assert_eq!(NextKeygenThreshold::::get(), keygen_threshold - 1); + assert_eq!(NextSignatureThreshold::::get(), signature_threshold); + + // sanity check , cannot report the same party twice + assert_noop!( + DKGMetadata::submit_misbehaviour_reports( + RuntimeOrigin::none(), + aggregated_misbehaviour_reports + ), + Error::::AlreadyJailed + ); + }); +} + +// Test that a keygen misbehaviour report will reduce signing threshold if needed +// our goal is to ensure we never end up in a t = n situation +#[test] +fn keygen_misbehaviour_reports_reduce_signature_threshold_if_needed() { + new_test_ext(vec![1, 2, 3, 4, 5]).execute_with(|| { + let session_id = 1; + + // setup the new threshold so our signature is accepted + let keygen_threshold = 5; + let signature_threshold = 3; + NextKeygenThreshold::::put(keygen_threshold); + NextSignatureThreshold::::put(signature_threshold); + + // prep the next authorities + let mut next_authorities: BoundedVec<_, _> = Default::default(); + let mut next_authorities_raw: Vec<_> = Default::default(); + for _ in 1..=5 { + let authority_id = mock_pub_key(); + let dkg_id = DKGId::from(authority_id); + next_authorities_raw.push(authority_id); + next_authorities.try_push(dkg_id).unwrap(); + } + + // load them onchain + NextAuthorities::::put(&next_authorities); + let next_best_authorities = + DKGMetadata::get_best_authorities(keygen_threshold as usize, &next_authorities); + let mut bounded_next_best_authorities: BoundedVec<_, _> = Default::default(); + for auth in next_best_authorities { + bounded_next_best_authorities.try_push(auth).unwrap(); + } + NextBestAuthorities::::put(&bounded_next_best_authorities); + + let mut offenders = vec![]; + + // lets jail two authorities one after other + for i in 0..2 { + let offender: DKGId = next_authorities[i].clone(); + offenders.push(offender.clone()); + // lets give the offender some reputation + AuthorityReputations::::insert(offender.clone(), 100); + + let mut reporters: Vec = Vec::new(); + let mut signatures: BoundedVec<_, _> = Default::default(); + let misbehaviour_type = MisbehaviourType::Keygen; + + // everyone except 1 reports the misbehaviour + for authority_id in next_authorities_raw.iter() { + let dkg_id = DKGId::from(authority_id.clone()); + + if dkg_id == offender { + continue + } + + let sig = mock_misbehaviour_report::( + authority_id.clone(), + offender.clone(), + misbehaviour_type, + ); + signatures.try_push(sig).unwrap(); + reporters.push(dkg_id.clone()); + } + + let aggregated_misbehaviour_reports: AggregatedMisbehaviourReports< + DKGId, + MaxSignatureLength, + MaxReporters, + > = AggregatedMisbehaviourReports { + misbehaviour_type, + session_id, + offender: offender.clone(), + reporters: reporters.clone().try_into().unwrap(), + signatures: signatures.try_into().unwrap(), + }; + + assert_ok!(DKGMetadata::submit_misbehaviour_reports( + RuntimeOrigin::none(), + aggregated_misbehaviour_reports + )); + + // succcessful submission event should be present + assert_last_event::( + Event::MisbehaviourReportsSubmitted { + misbehaviour_type, + reporters: reporters.clone().try_into().unwrap(), + offender: offender.clone(), + } + .into(), + ); + + // succcessful submission event should be present + assert_has_event::( + Event::AuthorityJailed { misbehaviour_type, authority: offender.clone() }.into(), + ); + } + + // lets check the storage is as expected + + // jailed authorities should contain both the offenders + for offender in offenders.iter() { + assert_eq!(JailedKeygenAuthorities::::get(offender), 1); + } + + // next best authorities should contain everyone except the offenders + let mut current_next_best_authorities = NextBestAuthorities::::get(); + assert_eq!(current_next_best_authorities.len() as u16, keygen_threshold - 2); + current_next_best_authorities.retain(|x| offenders.contains(&x.1)); + assert_eq!(current_next_best_authorities.len(), 0); + + // the threshold should have reduced correctly + assert_eq!(NextKeygenThreshold::::get(), keygen_threshold - 2); + + // should correctly drop the signature threshold to ensure we do not end up in t = n + assert_eq!(NextSignatureThreshold::::get(), signature_threshold - 1); + }); +} + +// Test that the signature thresholds are as expected, the misbehaviour report should only be +// accepted if the signature contains t+1 signers +#[test] +fn keygen_misbehaviour_reports_fail_if_not_threshold_plus_1() { + new_test_ext(vec![1, 2, 3, 4, 5]).execute_with(|| { + let session_id = 1; + // setup the new threshold so our signature is accepted + let keygen_threshold = 5; + let signature_threshold = 3; + NextKeygenThreshold::::put(keygen_threshold); + NextSignatureThreshold::::put(signature_threshold); + + // prep the next authorities + let mut next_authorities: BoundedVec<_, _> = Default::default(); + let mut next_authorities_raw: Vec<_> = Default::default(); + for _ in 1..=5 { + let authority_id = mock_pub_key(); + let dkg_id = DKGId::from(authority_id); + next_authorities_raw.push(authority_id); + next_authorities.try_push(dkg_id).unwrap(); + } + NextAuthorities::::put(&next_authorities); + let next_best_authorities = + DKGMetadata::get_best_authorities(keygen_threshold as usize, &next_authorities); + let mut bounded_next_best_authorities: BoundedVec<_, _> = Default::default(); + for auth in next_best_authorities { + bounded_next_best_authorities.try_push(auth).unwrap(); + } + NextBestAuthorities::::put(&bounded_next_best_authorities); + + // 1 is the misbehaving party + let offender: DKGId = next_authorities.first().unwrap().clone(); + // lets give the offender some reputation + AuthorityReputations::::insert(offender.clone(), 100); + + let mut reporters: Vec = Vec::new(); + let mut signatures: BoundedVec<_, _> = Default::default(); + let misbehaviour_type = MisbehaviourType::Keygen; + + // we ideally require threshold + 1 signatures for misbehaviour to be accepted + // lets sign with exactly threshold + for authority_id in next_authorities_raw.iter() { + let dkg_id = DKGId::from(authority_id.clone()); + + if dkg_id == offender { + continue + } + + let sig = mock_misbehaviour_report::( + authority_id.clone(), + offender.clone(), + misbehaviour_type, + ); + signatures.try_push(sig).unwrap(); + reporters.push(dkg_id.clone()); + + if reporters.len() == signature_threshold as usize { + break + } + } + + let aggregated_misbehaviour_reports: AggregatedMisbehaviourReports< + DKGId, + MaxSignatureLength, + MaxReporters, + > = AggregatedMisbehaviourReports { + misbehaviour_type, + session_id, + offender: offender.clone(), + reporters: reporters.clone().try_into().unwrap(), + signatures: signatures.try_into().unwrap(), + }; + + // should not be accepted since we did not have t+1 signers + assert_noop!( + DKGMetadata::submit_misbehaviour_reports( + RuntimeOrigin::none(), + aggregated_misbehaviour_reports.clone() + ), + Error::::InvalidMisbehaviourReports + ); + }); +} + +// Test that the misbehaviour reports does not drop the thresholds if we have more than +// threshold authorities available +#[test] +fn keygen_misbehaviour_reports_does_not_drop_threshold_if_authorities_available() { + new_test_ext(vec![1, 2, 3, 4, 5]).execute_with(|| { + let session_id = 1; + // setup the new threshold so our signature is accepted + // we have 5 authorities, but use lower thresholds for the test + let keygen_threshold = 3; + let signature_threshold = 2; + NextKeygenThreshold::::put(keygen_threshold); + NextSignatureThreshold::::put(signature_threshold); + + // prep the next authorities + let mut next_authorities: BoundedVec<_, _> = Default::default(); + let mut next_authorities_raw: Vec<_> = Default::default(); + for _ in 1..=5 { + let authority_id = mock_pub_key(); + let dkg_id = DKGId::from(authority_id); + next_authorities_raw.push(authority_id); + next_authorities.try_push(dkg_id).unwrap(); + } + NextAuthorities::::put(&next_authorities); + let next_best_authorities = + DKGMetadata::get_best_authorities(keygen_threshold as usize, &next_authorities); + let mut bounded_next_best_authorities: BoundedVec<_, _> = Default::default(); + for auth in next_best_authorities { + bounded_next_best_authorities.try_push(auth).unwrap(); + } + NextBestAuthorities::::put(&bounded_next_best_authorities); + + // 1 is the misbehaving party + let offender: DKGId = next_authorities.first().unwrap().clone(); + // lets give the offender some reputation + AuthorityReputations::::insert(offender.clone(), 100); + + let mut reporters: Vec = Vec::new(); + let mut signatures: BoundedVec<_, _> = Default::default(); + let misbehaviour_type = MisbehaviourType::Keygen; + + for authority_id in next_authorities_raw.iter() { + let dkg_id = DKGId::from(authority_id.clone()); + + if dkg_id == offender { + continue + } + + let sig = mock_misbehaviour_report::( + authority_id.clone(), + offender.clone(), + misbehaviour_type, + ); + signatures.try_push(sig).unwrap(); + reporters.push(dkg_id.clone()); + } + + let aggregated_misbehaviour_reports: AggregatedMisbehaviourReports< + DKGId, + MaxSignatureLength, + MaxReporters, + > = AggregatedMisbehaviourReports { + misbehaviour_type, + session_id, + offender: offender.clone(), + reporters: reporters.clone().try_into().unwrap(), + signatures: signatures.try_into().unwrap(), + }; + + assert_ok!(DKGMetadata::submit_misbehaviour_reports( + RuntimeOrigin::none(), + aggregated_misbehaviour_reports + )); + + // succcessful submission event should be present + assert_last_event::( + Event::MisbehaviourReportsSubmitted { + misbehaviour_type, + reporters: reporters.clone().try_into().unwrap(), + offender: offender.clone(), + } + .into(), + ); + + // succcessful submission event should be present + assert_has_event::( + Event::AuthorityJailed { misbehaviour_type, authority: offender.clone() }.into(), + ); + + // lets check the storage is as expected + + // jailed authorities should contain the offender + assert_eq!(JailedKeygenAuthorities::::get(offender), 1); + + // the threshold should remain unchanged + assert_eq!(NextKeygenThreshold::::get(), keygen_threshold); + assert_eq!(NextSignatureThreshold::::get(), signature_threshold); + }); +} + +// Test that the misbehaviour reports does not drop the thresholds lower than 2 +#[test] +fn keygen_misbehaviour_reports_does_not_drop_threshold_below_2() { + new_test_ext(vec![1, 2, 3]).execute_with(|| { + let session_id = 1; + // setup the new threshold so our signature is accepted + let keygen_threshold = 5; + let signature_threshold = 3; + NextKeygenThreshold::::put(keygen_threshold); + NextSignatureThreshold::::put(signature_threshold); + + // prep the next authorities + let mut next_authorities: BoundedVec<_, _> = Default::default(); + let mut next_authorities_raw: Vec<_> = Default::default(); + for _ in 1..=5 { + let authority_id = mock_pub_key(); + let dkg_id = DKGId::from(authority_id); + next_authorities_raw.push(authority_id); + next_authorities.try_push(dkg_id).unwrap(); + } + + // load them onchain + NextAuthorities::::put(&next_authorities); + let next_best_authorities = + DKGMetadata::get_best_authorities(keygen_threshold as usize, &next_authorities); + let mut bounded_next_best_authorities: BoundedVec<_, _> = Default::default(); + for auth in next_best_authorities { + bounded_next_best_authorities.try_push(auth).unwrap(); + } + NextBestAuthorities::::put(&bounded_next_best_authorities); + + let mut offenders = vec![]; + + // lets jail two authorities one after other + for i in 0..3 { + let offender: DKGId = next_authorities[i].clone(); + offenders.push(offender.clone()); + // lets give the offender some reputation + AuthorityReputations::::insert(offender.clone(), 100); + + let mut reporters: Vec = Vec::new(); + let mut signatures: BoundedVec<_, _> = Default::default(); + let misbehaviour_type = MisbehaviourType::Keygen; + + // everyone except 1 reports the misbehaviour + for authority_id in next_authorities_raw.iter() { + let dkg_id = DKGId::from(authority_id.clone()); + + if dkg_id == offender { + continue + } + + let sig = mock_misbehaviour_report::( + authority_id.clone(), + offender.clone(), + misbehaviour_type, + ); + signatures.try_push(sig).unwrap(); + reporters.push(dkg_id.clone()); + } + + let aggregated_misbehaviour_reports: AggregatedMisbehaviourReports< + DKGId, + MaxSignatureLength, + MaxReporters, + > = AggregatedMisbehaviourReports { + misbehaviour_type, + session_id, + offender: offender.clone(), + reporters: reporters.clone().try_into().unwrap(), + signatures: signatures.try_into().unwrap(), + }; + + assert_ok!(DKGMetadata::submit_misbehaviour_reports( + RuntimeOrigin::none(), + aggregated_misbehaviour_reports + )); + + // succcessful submission event should be present + assert_last_event::( + Event::MisbehaviourReportsSubmitted { + misbehaviour_type, + reporters: reporters.clone().try_into().unwrap(), + offender: offender.clone(), + } + .into(), + ); + + // succcessful submission event should be present + assert_has_event::( + Event::AuthorityJailed { misbehaviour_type, authority: offender.clone() }.into(), + ); + } + + // lets check the storage is as expected + + // jailed authorities should contain both the offenders + for offender in offenders.iter() { + assert_eq!(JailedKeygenAuthorities::::get(offender), 1); + } + + // next best authorities should contain everyone except the offenders + let mut current_next_best_authorities = NextBestAuthorities::::get(); + assert_eq!(current_next_best_authorities.len() as u16, keygen_threshold - 3); + current_next_best_authorities.retain(|x| offenders.contains(&x.1)); + assert_eq!(current_next_best_authorities.len(), 0); + + // the threshold should have reduced correctly, not gone below 2 + assert_eq!(NextKeygenThreshold::::get(), keygen_threshold - 3); + + // should correctly drop the signature threshold to ensure we do not end up in t = n + assert_eq!(NextSignatureThreshold::::get(), signature_threshold - 2); + + // === we are not in effectively a 2-of-1 state, we should not be able to jail anyone else + // ==== + + let offender: DKGId = next_authorities[4].clone(); + let mut reporters: Vec = Vec::new(); + let mut signatures: BoundedVec<_, _> = Default::default(); + let misbehaviour_type = MisbehaviourType::Keygen; + + // everyone except offender reports the misbehaviour + for authority_id in next_authorities_raw.iter() { + let dkg_id = DKGId::from(authority_id.clone()); + + if dkg_id == offender { + continue + } + + let sig = mock_misbehaviour_report::( + authority_id.clone(), + offender.clone(), + misbehaviour_type, + ); + signatures.try_push(sig).unwrap(); + reporters.push(dkg_id.clone()); + } + + let aggregated_misbehaviour_reports: AggregatedMisbehaviourReports< + DKGId, + MaxSignatureLength, + MaxReporters, + > = AggregatedMisbehaviourReports { + misbehaviour_type, + session_id, + offender: offender.clone(), + reporters: reporters.clone().try_into().unwrap(), + signatures: signatures.try_into().unwrap(), + }; + + assert_noop!( + DKGMetadata::submit_misbehaviour_reports( + RuntimeOrigin::none(), + aggregated_misbehaviour_reports + ), + Error::::NotEnoughAuthoritiesToJail + ); + }); +}