1use alloc::{vec, vec::Vec};
38use codec::{self as codec, Decode, Encode};
39use frame_support::traits::{Get, KeyOwnerProofSystem};
40use frame_system::pallet_prelude::{BlockNumberFor, HeaderFor};
41use log::{error, info};
42use sp_consensus_beefy::{
43 check_commitment_signature, AncestryHelper, DoubleVotingProof, ForkVotingProof,
44 FutureBlockVotingProof, ValidatorSetId, KEY_TYPE as BEEFY_KEY_TYPE,
45};
46use sp_runtime::{
47 transaction_validity::{
48 InvalidTransaction, TransactionPriority, TransactionSource, TransactionValidity,
49 TransactionValidityError, ValidTransaction,
50 },
51 DispatchError, KeyTypeId, Perbill, RuntimeAppPublic,
52};
53use sp_session::{GetSessionNumber, GetValidatorCount};
54use sp_staking::{
55 offence::{Kind, Offence, OffenceReportSystem, ReportOffence},
56 SessionIndex,
57};
58
59use super::{Call, Config, Error, Pallet, LOG_TARGET};
60
61#[derive(Copy, Clone, PartialOrd, Ord, Eq, PartialEq, Encode, Decode)]
63pub struct TimeSlot<N: Copy + Clone + PartialOrd + Ord + Eq + PartialEq + Encode + Decode> {
64 pub set_id: ValidatorSetId,
67 pub round: N,
69}
70
71pub struct EquivocationOffence<Offender, N>
73where
74 N: Copy + Clone + PartialOrd + Ord + Eq + PartialEq + Encode + Decode,
75{
76 pub time_slot: TimeSlot<N>,
78 pub session_index: SessionIndex,
80 pub validator_set_count: u32,
82 pub offender: Offender,
84 maybe_slash_fraction: Option<Perbill>,
86}
87
88impl<Offender: Clone, N> Offence<Offender> for EquivocationOffence<Offender, N>
89where
90 N: Copy + Clone + PartialOrd + Ord + Eq + PartialEq + Encode + Decode,
91{
92 const ID: Kind = *b"beefy:equivocati";
93 type TimeSlot = TimeSlot<N>;
94
95 fn offenders(&self) -> Vec<Offender> {
96 vec![self.offender.clone()]
97 }
98
99 fn session_index(&self) -> SessionIndex {
100 self.session_index
101 }
102
103 fn validator_set_count(&self) -> u32 {
104 self.validator_set_count
105 }
106
107 fn time_slot(&self) -> Self::TimeSlot {
108 self.time_slot
109 }
110
111 fn slash_fraction(&self, offenders_count: u32) -> Perbill {
112 if let Some(slash_fraction) = self.maybe_slash_fraction {
113 return slash_fraction;
114 }
115
116 Perbill::from_rational(3 * offenders_count, self.validator_set_count).square()
120 }
121}
122
123pub struct EquivocationReportSystem<T, R, P, L>(core::marker::PhantomData<(T, R, P, L)>);
132
133pub enum EquivocationEvidenceFor<T: Config> {
135 DoubleVotingProof(
136 DoubleVotingProof<
137 BlockNumberFor<T>,
138 T::BeefyId,
139 <T::BeefyId as RuntimeAppPublic>::Signature,
140 >,
141 T::KeyOwnerProof,
142 ),
143 ForkVotingProof(
144 ForkVotingProof<
145 HeaderFor<T>,
146 T::BeefyId,
147 <T::AncestryHelper as AncestryHelper<HeaderFor<T>>>::Proof,
148 >,
149 T::KeyOwnerProof,
150 ),
151 FutureBlockVotingProof(FutureBlockVotingProof<BlockNumberFor<T>, T::BeefyId>, T::KeyOwnerProof),
152}
153
154impl<T: Config> EquivocationEvidenceFor<T> {
155 fn offender_id(&self) -> &T::BeefyId {
157 match self {
158 EquivocationEvidenceFor::DoubleVotingProof(equivocation_proof, _) =>
159 equivocation_proof.offender_id(),
160 EquivocationEvidenceFor::ForkVotingProof(equivocation_proof, _) =>
161 &equivocation_proof.vote.id,
162 EquivocationEvidenceFor::FutureBlockVotingProof(equivocation_proof, _) =>
163 &equivocation_proof.vote.id,
164 }
165 }
166
167 fn round_number(&self) -> &BlockNumberFor<T> {
169 match self {
170 EquivocationEvidenceFor::DoubleVotingProof(equivocation_proof, _) =>
171 equivocation_proof.round_number(),
172 EquivocationEvidenceFor::ForkVotingProof(equivocation_proof, _) =>
173 &equivocation_proof.vote.commitment.block_number,
174 EquivocationEvidenceFor::FutureBlockVotingProof(equivocation_proof, _) =>
175 &equivocation_proof.vote.commitment.block_number,
176 }
177 }
178
179 fn set_id(&self) -> ValidatorSetId {
181 match self {
182 EquivocationEvidenceFor::DoubleVotingProof(equivocation_proof, _) =>
183 equivocation_proof.set_id(),
184 EquivocationEvidenceFor::ForkVotingProof(equivocation_proof, _) =>
185 equivocation_proof.vote.commitment.validator_set_id,
186 EquivocationEvidenceFor::FutureBlockVotingProof(equivocation_proof, _) =>
187 equivocation_proof.vote.commitment.validator_set_id,
188 }
189 }
190
191 fn key_owner_proof(&self) -> &T::KeyOwnerProof {
193 match self {
194 EquivocationEvidenceFor::DoubleVotingProof(_, key_owner_proof) => key_owner_proof,
195 EquivocationEvidenceFor::ForkVotingProof(_, key_owner_proof) => key_owner_proof,
196 EquivocationEvidenceFor::FutureBlockVotingProof(_, key_owner_proof) => key_owner_proof,
197 }
198 }
199
200 fn checked_offender<P>(&self) -> Option<P::IdentificationTuple>
201 where
202 P: KeyOwnerProofSystem<(KeyTypeId, T::BeefyId), Proof = T::KeyOwnerProof>,
203 {
204 let key = (BEEFY_KEY_TYPE, self.offender_id().clone());
205 P::check_proof(key, self.key_owner_proof().clone())
206 }
207
208 fn check_equivocation_proof(self) -> Result<(), Error<T>> {
209 match self {
210 EquivocationEvidenceFor::DoubleVotingProof(equivocation_proof, _) => {
211 if !sp_consensus_beefy::check_double_voting_proof(&equivocation_proof) {
213 return Err(Error::<T>::InvalidDoubleVotingProof);
214 }
215
216 Ok(())
217 },
218 EquivocationEvidenceFor::ForkVotingProof(equivocation_proof, _) => {
219 let ForkVotingProof { vote, ancestry_proof, header } = equivocation_proof;
220
221 if !<T::AncestryHelper as AncestryHelper<HeaderFor<T>>>::is_proof_optimal(
222 &ancestry_proof,
223 ) {
224 return Err(Error::<T>::InvalidForkVotingProof);
225 }
226
227 let maybe_validation_context = <T::AncestryHelper as AncestryHelper<
228 HeaderFor<T>,
229 >>::extract_validation_context(header);
230 let validation_context = match maybe_validation_context {
231 Some(validation_context) => validation_context,
232 None => {
233 return Err(Error::<T>::InvalidForkVotingProof);
234 },
235 };
236
237 let is_non_canonical =
238 <T::AncestryHelper as AncestryHelper<HeaderFor<T>>>::is_non_canonical(
239 &vote.commitment,
240 ancestry_proof,
241 validation_context,
242 );
243 if !is_non_canonical {
244 return Err(Error::<T>::InvalidForkVotingProof);
245 }
246
247 let is_signature_valid =
248 check_commitment_signature(&vote.commitment, &vote.id, &vote.signature);
249 if !is_signature_valid {
250 return Err(Error::<T>::InvalidForkVotingProof);
251 }
252
253 Ok(())
254 },
255 EquivocationEvidenceFor::FutureBlockVotingProof(equivocation_proof, _) => {
256 let FutureBlockVotingProof { vote } = equivocation_proof;
257 if vote.commitment.block_number < frame_system::Pallet::<T>::block_number() {
259 return Err(Error::<T>::InvalidFutureBlockVotingProof);
260 }
261
262 let is_signature_valid =
263 check_commitment_signature(&vote.commitment, &vote.id, &vote.signature);
264 if !is_signature_valid {
265 return Err(Error::<T>::InvalidForkVotingProof);
266 }
267
268 Ok(())
269 },
270 }
271 }
272
273 fn slash_fraction(&self) -> Option<Perbill> {
274 match self {
275 EquivocationEvidenceFor::DoubleVotingProof(_, _) => None,
276 EquivocationEvidenceFor::ForkVotingProof(_, _) |
277 EquivocationEvidenceFor::FutureBlockVotingProof(_, _) => Some(Perbill::from_percent(50)),
278 }
279 }
280}
281
282impl<T, R, P, L> OffenceReportSystem<Option<T::AccountId>, EquivocationEvidenceFor<T>>
283 for EquivocationReportSystem<T, R, P, L>
284where
285 T: Config + pallet_authorship::Config + frame_system::offchain::CreateBare<Call<T>>,
286 R: ReportOffence<
287 T::AccountId,
288 P::IdentificationTuple,
289 EquivocationOffence<P::IdentificationTuple, BlockNumberFor<T>>,
290 >,
291 P: KeyOwnerProofSystem<(KeyTypeId, T::BeefyId), Proof = T::KeyOwnerProof>,
292 P::IdentificationTuple: Clone,
293 L: Get<u64>,
294{
295 type Longevity = L;
296
297 fn publish_evidence(evidence: EquivocationEvidenceFor<T>) -> Result<(), ()> {
298 use frame_system::offchain::SubmitTransaction;
299
300 let call: Call<T> = evidence.into();
301 let xt = T::create_bare(call.into());
302 let res = SubmitTransaction::<T, Call<T>>::submit_transaction(xt);
303 match res {
304 Ok(_) => info!(target: LOG_TARGET, "Submitted equivocation report."),
305 Err(e) => error!(target: LOG_TARGET, "Error submitting equivocation report: {:?}", e),
306 }
307 res
308 }
309
310 fn check_evidence(
311 evidence: EquivocationEvidenceFor<T>,
312 ) -> Result<(), TransactionValidityError> {
313 let offender = evidence.checked_offender::<P>().ok_or(InvalidTransaction::BadProof)?;
314
315 let time_slot = TimeSlot { set_id: evidence.set_id(), round: *evidence.round_number() };
317 if R::is_known_offence(&[offender], &time_slot) {
318 Err(InvalidTransaction::Stale.into())
319 } else {
320 Ok(())
321 }
322 }
323
324 fn process_evidence(
325 reporter: Option<T::AccountId>,
326 evidence: EquivocationEvidenceFor<T>,
327 ) -> Result<(), DispatchError> {
328 let maybe_slash_fraction = evidence.slash_fraction();
329 let reporter = reporter.or_else(|| pallet_authorship::Pallet::<T>::author());
330
331 let set_id = evidence.set_id();
333 let round = *evidence.round_number();
334 let set_id_session_index = crate::SetIdSession::<T>::get(set_id)
335 .ok_or(Error::<T>::InvalidEquivocationProofSession)?;
336
337 let key_owner_proof = evidence.key_owner_proof();
340 let validator_count = key_owner_proof.validator_count();
341 let session_index = key_owner_proof.session();
342 if session_index != set_id_session_index {
343 return Err(Error::<T>::InvalidEquivocationProofSession.into())
344 }
345
346 let offender =
348 evidence.checked_offender::<P>().ok_or(Error::<T>::InvalidKeyOwnershipProof)?;
349
350 evidence.check_equivocation_proof()?;
351
352 let offence = EquivocationOffence {
353 time_slot: TimeSlot { set_id, round },
354 session_index,
355 validator_set_count: validator_count,
356 offender,
357 maybe_slash_fraction,
358 };
359 R::report_offence(reporter.into_iter().collect(), offence)
360 .map_err(|_| Error::<T>::DuplicateOffenceReport.into())
361 }
362}
363
364impl<T: Config> Pallet<T> {
369 pub fn validate_unsigned(source: TransactionSource, call: &Call<T>) -> TransactionValidity {
370 match source {
372 TransactionSource::Local | TransactionSource::InBlock => { },
373 _ => {
374 log::warn!(
375 target: LOG_TARGET,
376 "rejecting unsigned report equivocation transaction because it is not local/in-block."
377 );
378 return InvalidTransaction::Call.into()
379 },
380 }
381
382 let evidence = call.to_equivocation_evidence_for().ok_or(InvalidTransaction::Call)?;
383 let tag = (evidence.offender_id().clone(), evidence.set_id(), *evidence.round_number());
384 T::EquivocationReportSystem::check_evidence(evidence)?;
385
386 let longevity =
387 <T::EquivocationReportSystem as OffenceReportSystem<_, _>>::Longevity::get();
388 ValidTransaction::with_tag_prefix("BeefyEquivocation")
389 .priority(TransactionPriority::MAX)
391 .and_provides(tag)
393 .longevity(longevity)
394 .propagate(false)
396 .build()
397 }
398
399 pub fn pre_dispatch(call: &Call<T>) -> Result<(), TransactionValidityError> {
400 let evidence = call.to_equivocation_evidence_for().ok_or(InvalidTransaction::Call)?;
401 T::EquivocationReportSystem::check_evidence(evidence)
402 }
403}