1use alloc::{boxed::Box, vec, vec::Vec};
39use codec::{self as codec, Decode, Encode};
40use frame_support::traits::{Get, KeyOwnerProofSystem};
41use frame_system::pallet_prelude::BlockNumberFor;
42use log::{error, info};
43use sp_consensus_grandpa::{AuthorityId, EquivocationProof, RoundNumber, SetId, KEY_TYPE};
44use sp_runtime::{
45 transaction_validity::{
46 InvalidTransaction, TransactionPriority, TransactionSource, TransactionValidity,
47 TransactionValidityError, ValidTransaction,
48 },
49 DispatchError, KeyTypeId, Perbill,
50};
51use sp_session::{GetSessionNumber, GetValidatorCount};
52use sp_staking::{
53 offence::{Kind, Offence, OffenceReportSystem, ReportOffence},
54 SessionIndex,
55};
56
57use super::{Call, Config, Error, Pallet, LOG_TARGET};
58
59#[derive(Copy, Clone, PartialOrd, Ord, Eq, PartialEq, Encode, Decode)]
61pub struct TimeSlot {
62 pub set_id: SetId,
65 pub round: RoundNumber,
67}
68
69pub struct EquivocationOffence<Offender> {
71 pub time_slot: TimeSlot,
73 pub session_index: SessionIndex,
75 pub validator_set_count: u32,
77 pub offender: Offender,
79}
80
81impl<Offender: Clone> Offence<Offender> for EquivocationOffence<Offender> {
82 const ID: Kind = *b"grandpa:equivoca";
83 type TimeSlot = TimeSlot;
84
85 fn offenders(&self) -> Vec<Offender> {
86 vec![self.offender.clone()]
87 }
88
89 fn session_index(&self) -> SessionIndex {
90 self.session_index
91 }
92
93 fn validator_set_count(&self) -> u32 {
94 self.validator_set_count
95 }
96
97 fn time_slot(&self) -> Self::TimeSlot {
98 self.time_slot
99 }
100
101 fn slash_fraction(&self, offenders_count: u32) -> Perbill {
104 Perbill::from_rational(3 * offenders_count, self.validator_set_count).square()
106 }
107}
108
109pub struct EquivocationReportSystem<T, R, P, L>(core::marker::PhantomData<(T, R, P, L)>);
118
119impl<T, R, P, L>
120 OffenceReportSystem<
121 Option<T::AccountId>,
122 (EquivocationProof<T::Hash, BlockNumberFor<T>>, T::KeyOwnerProof),
123 > for EquivocationReportSystem<T, R, P, L>
124where
125 T: Config + pallet_authorship::Config + frame_system::offchain::CreateBare<Call<T>>,
126 R: ReportOffence<
127 T::AccountId,
128 P::IdentificationTuple,
129 EquivocationOffence<P::IdentificationTuple>,
130 >,
131 P: KeyOwnerProofSystem<(KeyTypeId, AuthorityId), Proof = T::KeyOwnerProof>,
132 P::IdentificationTuple: Clone,
133 L: Get<u64>,
134{
135 type Longevity = L;
136
137 fn publish_evidence(
138 evidence: (EquivocationProof<T::Hash, BlockNumberFor<T>>, T::KeyOwnerProof),
139 ) -> Result<(), ()> {
140 use frame_system::offchain::SubmitTransaction;
141 let (equivocation_proof, key_owner_proof) = evidence;
142
143 let call = Call::report_equivocation_unsigned {
144 equivocation_proof: Box::new(equivocation_proof),
145 key_owner_proof,
146 };
147 let xt = T::create_bare(call.into());
148 let res = SubmitTransaction::<T, Call<T>>::submit_transaction(xt);
149 match res {
150 Ok(_) => info!(target: LOG_TARGET, "Submitted equivocation report"),
151 Err(e) => error!(target: LOG_TARGET, "Error submitting equivocation report: {:?}", e),
152 }
153 res
154 }
155
156 fn check_evidence(
157 evidence: (EquivocationProof<T::Hash, BlockNumberFor<T>>, T::KeyOwnerProof),
158 ) -> Result<(), TransactionValidityError> {
159 let (equivocation_proof, key_owner_proof) = evidence;
160
161 let key = (KEY_TYPE, equivocation_proof.offender().clone());
163 let offender = P::check_proof(key, key_owner_proof).ok_or(InvalidTransaction::BadProof)?;
164
165 let time_slot =
167 TimeSlot { set_id: equivocation_proof.set_id(), round: equivocation_proof.round() };
168 if R::is_known_offence(&[offender], &time_slot) {
169 Err(InvalidTransaction::Stale.into())
170 } else {
171 Ok(())
172 }
173 }
174
175 fn process_evidence(
176 reporter: Option<T::AccountId>,
177 evidence: (EquivocationProof<T::Hash, BlockNumberFor<T>>, T::KeyOwnerProof),
178 ) -> Result<(), DispatchError> {
179 let (equivocation_proof, key_owner_proof) = evidence;
180 let reporter = reporter.or_else(|| pallet_authorship::Pallet::<T>::author());
181 let offender = equivocation_proof.offender().clone();
182
183 let set_id = equivocation_proof.set_id();
188 let round = equivocation_proof.round();
189 let session_index = key_owner_proof.session();
190 let validator_set_count = key_owner_proof.validator_count();
191
192 if !sp_consensus_grandpa::check_equivocation_proof(equivocation_proof) {
194 return Err(Error::<T>::InvalidEquivocationProof.into())
195 }
196
197 let offender = P::check_proof((KEY_TYPE, offender), key_owner_proof)
199 .ok_or(Error::<T>::InvalidKeyOwnershipProof)?;
200
201 let previous_set_id_session_index = if set_id != 0 {
204 let idx = crate::SetIdSession::<T>::get(set_id - 1)
205 .ok_or(Error::<T>::InvalidEquivocationProof)?;
206 Some(idx)
207 } else {
208 None
209 };
210
211 let set_id_session_index =
212 crate::SetIdSession::<T>::get(set_id).ok_or(Error::<T>::InvalidEquivocationProof)?;
213
214 if session_index > set_id_session_index ||
217 previous_set_id_session_index
218 .map(|previous_index| session_index <= previous_index)
219 .unwrap_or(false)
220 {
221 return Err(Error::<T>::InvalidEquivocationProof.into())
222 }
223
224 let offence = EquivocationOffence {
225 time_slot: TimeSlot { set_id, round },
226 session_index,
227 offender,
228 validator_set_count,
229 };
230
231 R::report_offence(reporter.into_iter().collect(), offence)
232 .map_err(|_| Error::<T>::DuplicateOffenceReport)?;
233
234 Ok(())
235 }
236}
237
238impl<T: Config> Pallet<T> {
243 pub fn validate_unsigned(source: TransactionSource, call: &Call<T>) -> TransactionValidity {
244 if let Call::report_equivocation_unsigned { equivocation_proof, key_owner_proof } = call {
245 match source {
247 TransactionSource::Local | TransactionSource::InBlock => { },
248 _ => {
249 log::warn!(
250 target: LOG_TARGET,
251 "rejecting unsigned report equivocation transaction because it is not local/in-block."
252 );
253
254 return InvalidTransaction::Call.into()
255 },
256 }
257
258 let evidence = (*equivocation_proof.clone(), key_owner_proof.clone());
260 T::EquivocationReportSystem::check_evidence(evidence)?;
261
262 let longevity =
263 <T::EquivocationReportSystem as OffenceReportSystem<_, _>>::Longevity::get();
264
265 ValidTransaction::with_tag_prefix("GrandpaEquivocation")
266 .priority(TransactionPriority::max_value())
268 .and_provides((
270 equivocation_proof.offender().clone(),
271 equivocation_proof.set_id(),
272 equivocation_proof.round(),
273 ))
274 .longevity(longevity)
275 .propagate(false)
277 .build()
278 } else {
279 InvalidTransaction::Call.into()
280 }
281 }
282
283 pub fn pre_dispatch(call: &Call<T>) -> Result<(), TransactionValidityError> {
284 if let Call::report_equivocation_unsigned { equivocation_proof, key_owner_proof } = call {
285 let evidence = (*equivocation_proof.clone(), key_owner_proof.clone());
286 T::EquivocationReportSystem::check_evidence(evidence)
287 } else {
288 Err(InvalidTransaction::Call.into())
289 }
290 }
291}