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}
85
86impl<Offender: Clone, N> Offence<Offender> for EquivocationOffence<Offender, N>
87where
88 N: Copy + Clone + PartialOrd + Ord + Eq + PartialEq + Encode + Decode,
89{
90 const ID: Kind = *b"beefy:equivocati";
91 type TimeSlot = TimeSlot<N>;
92
93 fn offenders(&self) -> Vec<Offender> {
94 vec![self.offender.clone()]
95 }
96
97 fn session_index(&self) -> SessionIndex {
98 self.session_index
99 }
100
101 fn validator_set_count(&self) -> u32 {
102 self.validator_set_count
103 }
104
105 fn time_slot(&self) -> Self::TimeSlot {
106 self.time_slot
107 }
108
109 fn slash_fraction(&self, offenders_count: u32) -> Perbill {
112 Perbill::from_rational(3 * offenders_count, self.validator_set_count).square()
114 }
115}
116
117pub struct EquivocationReportSystem<T, R, P, L>(core::marker::PhantomData<(T, R, P, L)>);
126
127pub enum EquivocationEvidenceFor<T: Config> {
129 DoubleVotingProof(
130 DoubleVotingProof<
131 BlockNumberFor<T>,
132 T::BeefyId,
133 <T::BeefyId as RuntimeAppPublic>::Signature,
134 >,
135 T::KeyOwnerProof,
136 ),
137 ForkVotingProof(
138 ForkVotingProof<
139 HeaderFor<T>,
140 T::BeefyId,
141 <T::AncestryHelper as AncestryHelper<HeaderFor<T>>>::Proof,
142 >,
143 T::KeyOwnerProof,
144 ),
145 FutureBlockVotingProof(FutureBlockVotingProof<BlockNumberFor<T>, T::BeefyId>, T::KeyOwnerProof),
146}
147
148impl<T: Config> EquivocationEvidenceFor<T> {
149 fn offender_id(&self) -> &T::BeefyId {
151 match self {
152 EquivocationEvidenceFor::DoubleVotingProof(equivocation_proof, _) =>
153 equivocation_proof.offender_id(),
154 EquivocationEvidenceFor::ForkVotingProof(equivocation_proof, _) =>
155 &equivocation_proof.vote.id,
156 EquivocationEvidenceFor::FutureBlockVotingProof(equivocation_proof, _) =>
157 &equivocation_proof.vote.id,
158 }
159 }
160
161 fn round_number(&self) -> &BlockNumberFor<T> {
163 match self {
164 EquivocationEvidenceFor::DoubleVotingProof(equivocation_proof, _) =>
165 equivocation_proof.round_number(),
166 EquivocationEvidenceFor::ForkVotingProof(equivocation_proof, _) =>
167 &equivocation_proof.vote.commitment.block_number,
168 EquivocationEvidenceFor::FutureBlockVotingProof(equivocation_proof, _) =>
169 &equivocation_proof.vote.commitment.block_number,
170 }
171 }
172
173 fn set_id(&self) -> ValidatorSetId {
175 match self {
176 EquivocationEvidenceFor::DoubleVotingProof(equivocation_proof, _) =>
177 equivocation_proof.set_id(),
178 EquivocationEvidenceFor::ForkVotingProof(equivocation_proof, _) =>
179 equivocation_proof.vote.commitment.validator_set_id,
180 EquivocationEvidenceFor::FutureBlockVotingProof(equivocation_proof, _) =>
181 equivocation_proof.vote.commitment.validator_set_id,
182 }
183 }
184
185 fn key_owner_proof(&self) -> &T::KeyOwnerProof {
187 match self {
188 EquivocationEvidenceFor::DoubleVotingProof(_, key_owner_proof) => key_owner_proof,
189 EquivocationEvidenceFor::ForkVotingProof(_, key_owner_proof) => key_owner_proof,
190 EquivocationEvidenceFor::FutureBlockVotingProof(_, key_owner_proof) => key_owner_proof,
191 }
192 }
193
194 fn checked_offender<P>(&self) -> Option<P::IdentificationTuple>
195 where
196 P: KeyOwnerProofSystem<(KeyTypeId, T::BeefyId), Proof = T::KeyOwnerProof>,
197 {
198 let key = (BEEFY_KEY_TYPE, self.offender_id().clone());
199 P::check_proof(key, self.key_owner_proof().clone())
200 }
201
202 fn check_equivocation_proof(self) -> Result<(), Error<T>> {
203 match self {
204 EquivocationEvidenceFor::DoubleVotingProof(equivocation_proof, _) => {
205 if !sp_consensus_beefy::check_double_voting_proof(&equivocation_proof) {
207 return Err(Error::<T>::InvalidDoubleVotingProof);
208 }
209
210 return Ok(())
211 },
212 EquivocationEvidenceFor::ForkVotingProof(equivocation_proof, _) => {
213 let ForkVotingProof { vote, ancestry_proof, header } = equivocation_proof;
214
215 let maybe_validation_context = <T::AncestryHelper as AncestryHelper<
216 HeaderFor<T>,
217 >>::extract_validation_context(header);
218 let validation_context = match maybe_validation_context {
219 Some(validation_context) => validation_context,
220 None => {
221 return Err(Error::<T>::InvalidForkVotingProof);
222 },
223 };
224
225 let is_non_canonical =
226 <T::AncestryHelper as AncestryHelper<HeaderFor<T>>>::is_non_canonical(
227 &vote.commitment,
228 ancestry_proof,
229 validation_context,
230 );
231 if !is_non_canonical {
232 return Err(Error::<T>::InvalidForkVotingProof);
233 }
234
235 let is_signature_valid =
236 check_commitment_signature(&vote.commitment, &vote.id, &vote.signature);
237 if !is_signature_valid {
238 return Err(Error::<T>::InvalidForkVotingProof);
239 }
240
241 Ok(())
242 },
243 EquivocationEvidenceFor::FutureBlockVotingProof(equivocation_proof, _) => {
244 let FutureBlockVotingProof { vote } = equivocation_proof;
245 if vote.commitment.block_number < frame_system::Pallet::<T>::block_number() {
247 return Err(Error::<T>::InvalidFutureBlockVotingProof);
248 }
249
250 let is_signature_valid =
251 check_commitment_signature(&vote.commitment, &vote.id, &vote.signature);
252 if !is_signature_valid {
253 return Err(Error::<T>::InvalidForkVotingProof);
254 }
255
256 Ok(())
257 },
258 }
259 }
260}
261
262impl<T, R, P, L> OffenceReportSystem<Option<T::AccountId>, EquivocationEvidenceFor<T>>
263 for EquivocationReportSystem<T, R, P, L>
264where
265 T: Config + pallet_authorship::Config + frame_system::offchain::CreateInherent<Call<T>>,
266 R: ReportOffence<
267 T::AccountId,
268 P::IdentificationTuple,
269 EquivocationOffence<P::IdentificationTuple, BlockNumberFor<T>>,
270 >,
271 P: KeyOwnerProofSystem<(KeyTypeId, T::BeefyId), Proof = T::KeyOwnerProof>,
272 P::IdentificationTuple: Clone,
273 L: Get<u64>,
274{
275 type Longevity = L;
276
277 fn publish_evidence(evidence: EquivocationEvidenceFor<T>) -> Result<(), ()> {
278 use frame_system::offchain::SubmitTransaction;
279
280 let call: Call<T> = evidence.into();
281 let xt = T::create_inherent(call.into());
282 let res = SubmitTransaction::<T, Call<T>>::submit_transaction(xt);
283 match res {
284 Ok(_) => info!(target: LOG_TARGET, "Submitted equivocation report."),
285 Err(e) => error!(target: LOG_TARGET, "Error submitting equivocation report: {:?}", e),
286 }
287 res
288 }
289
290 fn check_evidence(
291 evidence: EquivocationEvidenceFor<T>,
292 ) -> Result<(), TransactionValidityError> {
293 let offender = evidence.checked_offender::<P>().ok_or(InvalidTransaction::BadProof)?;
294
295 let time_slot = TimeSlot { set_id: evidence.set_id(), round: *evidence.round_number() };
297 if R::is_known_offence(&[offender], &time_slot) {
298 Err(InvalidTransaction::Stale.into())
299 } else {
300 Ok(())
301 }
302 }
303
304 fn process_evidence(
305 reporter: Option<T::AccountId>,
306 evidence: EquivocationEvidenceFor<T>,
307 ) -> Result<(), DispatchError> {
308 let reporter = reporter.or_else(|| pallet_authorship::Pallet::<T>::author());
309
310 let set_id = evidence.set_id();
312 let round = *evidence.round_number();
313 let set_id_session_index = crate::SetIdSession::<T>::get(set_id)
314 .ok_or(Error::<T>::InvalidEquivocationProofSession)?;
315
316 let key_owner_proof = evidence.key_owner_proof();
319 let validator_count = key_owner_proof.validator_count();
320 let session_index = key_owner_proof.session();
321 if session_index != set_id_session_index {
322 return Err(Error::<T>::InvalidEquivocationProofSession.into())
323 }
324
325 let offender =
327 evidence.checked_offender::<P>().ok_or(Error::<T>::InvalidKeyOwnershipProof)?;
328
329 evidence.check_equivocation_proof()?;
330
331 let offence = EquivocationOffence {
332 time_slot: TimeSlot { set_id, round },
333 session_index,
334 validator_set_count: validator_count,
335 offender,
336 };
337 R::report_offence(reporter.into_iter().collect(), offence)
338 .map_err(|_| Error::<T>::DuplicateOffenceReport.into())
339 }
340}
341
342impl<T: Config> Pallet<T> {
347 pub fn validate_unsigned(source: TransactionSource, call: &Call<T>) -> TransactionValidity {
348 match source {
350 TransactionSource::Local | TransactionSource::InBlock => { },
351 _ => {
352 log::warn!(
353 target: LOG_TARGET,
354 "rejecting unsigned report equivocation transaction because it is not local/in-block."
355 );
356 return InvalidTransaction::Call.into()
357 },
358 }
359
360 let evidence = call.to_equivocation_evidence_for().ok_or(InvalidTransaction::Call)?;
361 let tag = (evidence.offender_id().clone(), evidence.set_id(), *evidence.round_number());
362 T::EquivocationReportSystem::check_evidence(evidence)?;
363
364 let longevity =
365 <T::EquivocationReportSystem as OffenceReportSystem<_, _>>::Longevity::get();
366 ValidTransaction::with_tag_prefix("BeefyEquivocation")
367 .priority(TransactionPriority::MAX)
369 .and_provides(tag)
371 .longevity(longevity)
372 .propagate(false)
374 .build()
375 }
376
377 pub fn pre_dispatch(call: &Call<T>) -> Result<(), TransactionValidityError> {
378 let evidence = call.to_equivocation_evidence_for().ok_or(InvalidTransaction::Call)?;
379 T::EquivocationReportSystem::check_evidence(evidence)
380 }
381}