1
// This file is part of Substrate.
2

            
3
// Copyright (C) Parity Technologies (UK) Ltd.
4
// SPDX-License-Identifier: Apache-2.0
5

            
6
// Licensed under the Apache License, Version 2.0 (the "License");
7
// you may not use this file except in compliance with the License.
8
// You may obtain a copy of the License at
9
//
10
// 	http://www.apache.org/licenses/LICENSE-2.0
11
//
12
// Unless required by applicable law or agreed to in writing, software
13
// distributed under the License is distributed on an "AS IS" BASIS,
14
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15
// See the License for the specific language governing permissions and
16
// limitations under the License.
17

            
18
use super::{Config, Kind, OffenceDetails, Pallet, Perbill, SessionIndex, LOG_TARGET};
19
use alloc::vec::Vec;
20
use frame_support::{
21
	pallet_prelude::ValueQuery,
22
	storage_alias,
23
	traits::{Get, GetStorageVersion, OnRuntimeUpgrade},
24
	weights::Weight,
25
	Twox64Concat,
26
};
27
use sp_staking::offence::OnOffenceHandler;
28

            
29
#[cfg(feature = "try-runtime")]
30
use frame_support::ensure;
31
#[cfg(feature = "try-runtime")]
32
use sp_runtime::TryRuntimeError;
33

            
34
mod v0 {
35
	use super::*;
36

            
37
	#[storage_alias]
38
	pub type ReportsByKindIndex<T: Config> = StorageMap<
39
		Pallet<T>,
40
		Twox64Concat,
41
		Kind,
42
		Vec<u8>, // (O::TimeSlot, ReportIdOf<T>)
43
		ValueQuery,
44
	>;
45
}
46

            
47
pub mod v1 {
48
	use frame_support::traits::StorageVersion;
49

            
50
	use super::*;
51

            
52
	pub struct MigrateToV1<T>(core::marker::PhantomData<T>);
53
	impl<T: Config> OnRuntimeUpgrade for MigrateToV1<T> {
54
		#[cfg(feature = "try-runtime")]
55
		fn pre_upgrade() -> Result<Vec<u8>, TryRuntimeError> {
56
			log::info!(
57
				target: LOG_TARGET,
58
				"Number of reports to refund and delete: {}",
59
				v0::ReportsByKindIndex::<T>::iter_keys().count()
60
			);
61

            
62
			Ok(Vec::new())
63
		}
64

            
65
		fn on_runtime_upgrade() -> Weight {
66
			if Pallet::<T>::on_chain_storage_version() > 0 {
67
				log::info!(target: LOG_TARGET, "pallet_offences::MigrateToV1 should be removed");
68
				return T::DbWeight::get().reads(1)
69
			}
70

            
71
			let keys_removed = v0::ReportsByKindIndex::<T>::clear(u32::MAX, None).unique as u64;
72
			StorageVersion::new(1).put::<Pallet<T>>();
73

            
74
			// + 1 for reading/writing the new storage version
75
			T::DbWeight::get().reads_writes(keys_removed + 1, keys_removed + 1)
76
		}
77

            
78
		#[cfg(feature = "try-runtime")]
79
		fn post_upgrade(_state: Vec<u8>) -> Result<(), TryRuntimeError> {
80
			let onchain = Pallet::<T>::on_chain_storage_version();
81
			ensure!(onchain == 1, "pallet_offences::MigrateToV1 needs to be run");
82
			ensure!(
83
				v0::ReportsByKindIndex::<T>::iter_keys().count() == 0,
84
				"there are some dangling reports that need to be destroyed and refunded"
85
			);
86
			Ok(())
87
		}
88
	}
89
}
90

            
91
/// Type of data stored as a deferred offence
92
type DeferredOffenceOf<T> = (
93
	Vec<OffenceDetails<<T as frame_system::Config>::AccountId, <T as Config>::IdentificationTuple>>,
94
	Vec<Perbill>,
95
	SessionIndex,
96
);
97

            
98
// Deferred reports that have been rejected by the offence handler and need to be submitted
99
// at a later time.
100
#[storage_alias]
101
type DeferredOffences<T: Config> =
102
	StorageValue<crate::Pallet<T>, Vec<DeferredOffenceOf<T>>, ValueQuery>;
103

            
104
pub fn remove_deferred_storage<T: Config>() -> Weight {
105
	let mut weight = T::DbWeight::get().reads_writes(1, 1);
106
	let deferred = <DeferredOffences<T>>::take();
107
	log::info!(target: LOG_TARGET, "have {} deferred offences, applying.", deferred.len());
108
	for (offences, perbill, session) in deferred.iter() {
109
		let consumed = T::OnOffenceHandler::on_offence(offences, perbill, *session);
110
		weight = weight.saturating_add(consumed);
111
	}
112

            
113
	weight
114
}
115

            
116
#[cfg(test)]
117
mod test {
118
	use super::*;
119
	use crate::mock::{new_test_ext, with_on_offence_fractions, Runtime as T, KIND};
120
	use codec::Encode;
121
	use sp_runtime::Perbill;
122
	use sp_staking::offence::OffenceDetails;
123

            
124
	#[test]
125
	fn migration_to_v1_works() {
126
		let mut ext = new_test_ext();
127

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

            
133
		ext.commit_all().unwrap();
134

            
135
		ext.execute_with(|| {
136
			assert_eq!(
137
				v1::MigrateToV1::<T>::on_runtime_upgrade(),
138
				<T as frame_system::Config>::DbWeight::get().reads_writes(2, 2),
139
			);
140

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

            
145
	#[test]
146
	fn should_resubmit_deferred_offences() {
147
		new_test_ext().execute_with(|| {
148
			// given
149
			assert_eq!(<DeferredOffences<T>>::get().len(), 0);
150
			with_on_offence_fractions(|f| {
151
				assert_eq!(f.clone(), vec![]);
152
			});
153

            
154
			let offence_details = OffenceDetails::<
155
				<T as frame_system::Config>::AccountId,
156
				<T as Config>::IdentificationTuple,
157
			> {
158
				offender: 5,
159
				reporters: vec![],
160
			};
161

            
162
			// push deferred offence
163
			<DeferredOffences<T>>::append((
164
				vec![offence_details],
165
				vec![Perbill::from_percent(5 + 1 * 100 / 5)],
166
				1,
167
			));
168

            
169
			// when
170
			assert_eq!(
171
				remove_deferred_storage::<T>(),
172
				<T as frame_system::Config>::DbWeight::get().reads_writes(1, 1),
173
			);
174

            
175
			// then
176
			assert!(!<DeferredOffences<T>>::exists());
177
			with_on_offence_fractions(|f| {
178
				assert_eq!(f.clone(), vec![Perbill::from_percent(5 + 1 * 100 / 5)]);
179
			});
180
		})
181
	}
182
}