Skip to content
This repository has been archived by the owner on Nov 15, 2023. It is now read-only.

Commit

Permalink
Removes ReportsByKindIndex (#13936)
Browse files Browse the repository at this point in the history
* Removes ReportsByKind

* Minor build fixes

* adds migration

* Addresses review comment

* Uses clear but weight check fails

* Uses unique

* Updates test to commit the change before migration

* Uses reads_writes

* ".git/.scripts/commands/fmt/fmt.sh"

* Fixes build

* Addresses review comments

* ".git/.scripts/commands/fmt/fmt.sh"

* fixes typo

---------

Co-authored-by: command-bot <>
  • Loading branch information
gupnik authored Apr 25, 2023
1 parent 6f0f5a9 commit db6ebf5
Show file tree
Hide file tree
Showing 4 changed files with 113 additions and 87 deletions.
43 changes: 12 additions & 31 deletions frame/offences/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,9 @@ pub mod migration;
mod mock;
mod tests;

use codec::{Decode, Encode};
use core::marker::PhantomData;

use codec::Encode;
use frame_support::weights::Weight;
use sp_runtime::{traits::Hash, Perbill};
use sp_staking::{
Expand All @@ -43,12 +45,17 @@ type OpaqueTimeSlot = Vec<u8>;
/// A type alias for a report identifier.
type ReportIdOf<T> = <T as frame_system::Config>::Hash;

const LOG_TARGET: &str = "runtime::offences";

#[frame_support::pallet]
pub mod pallet {
use super::*;
use frame_support::pallet_prelude::*;

const STORAGE_VERSION: StorageVersion = StorageVersion::new(1);

#[pallet::pallet]
#[pallet::storage_version(STORAGE_VERSION)]
#[pallet::without_storage_info]
pub struct Pallet<T>(_);

Expand Down Expand Up @@ -85,21 +92,6 @@ pub mod pallet {
ValueQuery,
>;

/// Enumerates all reports of a kind along with the time they happened.
///
/// All reports are sorted by the time of offence.
///
/// Note that the actual type of this mapping is `Vec<u8>`, this is because values of
/// different types are not supported at the moment so we are doing the manual serialization.
#[pallet::storage]
pub type ReportsByKindIndex<T> = StorageMap<
_,
Twox64Concat,
Kind,
Vec<u8>, // (O::TimeSlot, ReportIdOf<T>)
ValueQuery,
>;

/// Events type.
#[pallet::event]
#[pallet::generate_deposit(pub(super) fn deposit_event)]
Expand Down Expand Up @@ -190,7 +182,7 @@ impl<T: Config> Pallet<T> {
OffenceDetails { offender, reporters: reporters.clone() },
);

storage.insert(time_slot, report_id);
storage.insert(report_id);
}
}

Expand Down Expand Up @@ -225,38 +217,27 @@ struct TriageOutcome<T: Config> {
struct ReportIndexStorage<T: Config, O: Offence<T::IdentificationTuple>> {
opaque_time_slot: OpaqueTimeSlot,
concurrent_reports: Vec<ReportIdOf<T>>,
same_kind_reports: Vec<(O::TimeSlot, ReportIdOf<T>)>,
_phantom: PhantomData<O>,
}

impl<T: Config, O: Offence<T::IdentificationTuple>> ReportIndexStorage<T, O> {
/// Preload indexes from the storage for the specific `time_slot` and the kind of the offence.
fn load(time_slot: &O::TimeSlot) -> Self {
let opaque_time_slot = time_slot.encode();

let same_kind_reports = ReportsByKindIndex::<T>::get(&O::ID);
let same_kind_reports =
Vec::<(O::TimeSlot, ReportIdOf<T>)>::decode(&mut &same_kind_reports[..])
.unwrap_or_default();

let concurrent_reports = <ConcurrentReportsIndex<T>>::get(&O::ID, &opaque_time_slot);

Self { opaque_time_slot, concurrent_reports, same_kind_reports }
Self { opaque_time_slot, concurrent_reports, _phantom: Default::default() }
}

/// Insert a new report to the index.
fn insert(&mut self, time_slot: &O::TimeSlot, report_id: ReportIdOf<T>) {
// Insert the report id into the list while maintaining the ordering by the time
// slot.
let pos = self.same_kind_reports.partition_point(|(when, _)| when <= time_slot);
self.same_kind_reports.insert(pos, (time_slot.clone(), report_id));

fn insert(&mut self, report_id: ReportIdOf<T>) {
// Update the list of concurrent reports.
self.concurrent_reports.push(report_id);
}

/// Dump the indexes to the storage.
fn save(self) {
ReportsByKindIndex::<T>::insert(&O::ID, self.same_kind_reports.encode());
<ConcurrentReportsIndex<T>>::insert(
&O::ID,
&self.opaque_time_slot,
Expand Down
103 changes: 99 additions & 4 deletions frame/offences/src/migration.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,11 +15,84 @@
// See the License for the specific language governing permissions and
// limitations under the License.

use super::{Config, OffenceDetails, Perbill, SessionIndex};
use frame_support::{pallet_prelude::ValueQuery, storage_alias, traits::Get, weights::Weight};
use super::{Config, Kind, OffenceDetails, Pallet, Perbill, SessionIndex, LOG_TARGET};
use frame_support::{
dispatch::GetStorageVersion,
pallet_prelude::ValueQuery,
storage_alias,
traits::{Get, OnRuntimeUpgrade},
weights::Weight,
Twox64Concat,
};
use sp_staking::offence::{DisableStrategy, OnOffenceHandler};
use sp_std::vec::Vec;

#[cfg(feature = "try-runtime")]
use frame_support::ensure;

mod v0 {
use super::*;

#[storage_alias]
pub type ReportsByKindIndex<T: Config> = StorageMap<
Pallet<T>,
Twox64Concat,
Kind,
Vec<u8>, // (O::TimeSlot, ReportIdOf<T>)
ValueQuery,
>;
}

pub mod v1 {
use frame_support::traits::StorageVersion;

use super::*;

pub struct MigrateToV1<T>(sp_std::marker::PhantomData<T>);
impl<T: Config> OnRuntimeUpgrade for MigrateToV1<T> {
#[cfg(feature = "try-runtime")]
fn pre_upgrade() -> Result<Vec<u8>, &'static str> {
let onchain = Pallet::<T>::on_chain_storage_version();
ensure!(onchain < 1, "pallet_offences::MigrateToV1 migration can be deleted");

log::info!(
target: LOG_TARGET,
"Number of reports to refund and delete: {}",
v0::ReportsByKindIndex::<T>::iter_keys().count()
);

Ok(Vec::new())
}

fn on_runtime_upgrade() -> Weight {
let onchain = Pallet::<T>::on_chain_storage_version();

if onchain > 0 {
log::info!(target: LOG_TARGET, "pallet_offences::MigrateToV1 should be removed");
return T::DbWeight::get().reads(1)
}

let keys_removed = v0::ReportsByKindIndex::<T>::clear(u32::MAX, None).unique as u64;
let weight = T::DbWeight::get().reads_writes(keys_removed, keys_removed);

StorageVersion::new(1).put::<Pallet<T>>();

weight
}

#[cfg(feature = "try-runtime")]
fn post_upgrade(_state: Vec<u8>) -> Result<(), &'static str> {
let onchain = Pallet::<T>::on_chain_storage_version();
ensure!(onchain == 1, "pallet_offences::MigrateToV1 needs to be run");
ensure!(
v0::ReportsByKindIndex::<T>::iter_keys().count() == 0,
"there are some dangling reports that need to be destroyed and refunded"
);
Ok(())
}
}
}

/// Type of data stored as a deferred offence
type DeferredOffenceOf<T> = (
Vec<OffenceDetails<<T as frame_system::Config>::AccountId, <T as Config>::IdentificationTuple>>,
Expand All @@ -36,7 +109,7 @@ type DeferredOffences<T: Config> =
pub fn remove_deferred_storage<T: Config>() -> Weight {
let mut weight = T::DbWeight::get().reads_writes(1, 1);
let deferred = <DeferredOffences<T>>::take();
log::info!(target: "runtime::offences", "have {} deferred offences, applying.", deferred.len());
log::info!(target: LOG_TARGET, "have {} deferred offences, applying.", deferred.len());
for (offences, perbill, session) in deferred.iter() {
let consumed = T::OnOffenceHandler::on_offence(
offences,
Expand All @@ -53,10 +126,32 @@ pub fn remove_deferred_storage<T: Config>() -> Weight {
#[cfg(test)]
mod test {
use super::*;
use crate::mock::{new_test_ext, with_on_offence_fractions, Runtime as T};
use crate::mock::{new_test_ext, with_on_offence_fractions, Runtime as T, KIND};
use codec::Encode;
use sp_runtime::Perbill;
use sp_staking::offence::OffenceDetails;

#[test]
fn migration_to_v1_works() {
let mut ext = new_test_ext();

ext.execute_with(|| {
<v0::ReportsByKindIndex<T>>::insert(KIND, 2u32.encode());
assert!(<v0::ReportsByKindIndex<T>>::iter_values().count() > 0);
});

ext.commit_all().unwrap();

ext.execute_with(|| {
assert_eq!(
v1::MigrateToV1::<T>::on_runtime_upgrade(),
<T as frame_system::Config>::DbWeight::get().reads_writes(1, 1),
);

assert!(<v0::ReportsByKindIndex<T>>::iter_values().count() == 0);
})
}

#[test]
fn should_resubmit_deferred_offences() {
new_test_ext().execute_with(|| {
Expand Down
5 changes: 0 additions & 5 deletions frame/offences/src/mock.rs
Original file line number Diff line number Diff line change
Expand Up @@ -164,8 +164,3 @@ impl offence::Offence<u64> for Offence {
Perbill::from_percent(5 + offenders_count * 100 / self.validator_set_count)
}
}

/// Create the report id for the given `offender` and `time_slot` combination.
pub fn report_id(time_slot: u128, offender: u64) -> H256 {
Offences::report_id::<Offence>(&time_slot, &offender)
}
49 changes: 2 additions & 47 deletions frame/offences/src/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -21,8 +21,8 @@

use super::*;
use crate::mock::{
new_test_ext, offence_reports, report_id, with_on_offence_fractions, Offence, Offences,
RuntimeEvent, System, KIND,
new_test_ext, offence_reports, with_on_offence_fractions, Offence, Offences, RuntimeEvent,
System, KIND,
};
use frame_system::{EventRecord, Phase};
use sp_runtime::Perbill;
Expand Down Expand Up @@ -245,48 +245,3 @@ fn should_properly_count_offences() {
);
});
}

/// We insert offences in sorted order using the time slot in the `same_kind_reports`.
/// This test ensures that it works as expected.
#[test]
fn should_properly_sort_offences() {
new_test_ext().execute_with(|| {
// given
let time_slot = 42;
assert_eq!(offence_reports(KIND, time_slot), vec![]);

let offence1 = Offence { validator_set_count: 5, time_slot, offenders: vec![5] };
let offence2 = Offence { validator_set_count: 5, time_slot, offenders: vec![4] };
let offence3 =
Offence { validator_set_count: 5, time_slot: time_slot + 1, offenders: vec![6, 7] };
let offence4 =
Offence { validator_set_count: 5, time_slot: time_slot - 1, offenders: vec![3] };
Offences::report_offence(vec![], offence1).unwrap();
with_on_offence_fractions(|f| {
assert_eq!(f.clone(), vec![Perbill::from_percent(25)]);
f.clear();
});

// when
// report for the second time
Offences::report_offence(vec![], offence2).unwrap();
Offences::report_offence(vec![], offence3).unwrap();
Offences::report_offence(vec![], offence4).unwrap();

// then
let same_kind_reports = Vec::<(u128, sp_core::H256)>::decode(
&mut &crate::ReportsByKindIndex::<crate::mock::Runtime>::get(KIND)[..],
)
.unwrap();
assert_eq!(
same_kind_reports,
vec![
(time_slot - 1, report_id(time_slot - 1, 3)),
(time_slot, report_id(time_slot, 5)),
(time_slot, report_id(time_slot, 4)),
(time_slot + 1, report_id(time_slot + 1, 6)),
(time_slot + 1, report_id(time_slot + 1, 7)),
]
);
});
}

0 comments on commit db6ebf5

Please sign in to comment.