1
// Copyright (C) Moondance Labs Ltd.
2
// This file is part of Tanssi.
3

            
4
// Tanssi is free software: you can redistribute it and/or modify
5
// it under the terms of the GNU General Public License as published by
6
// the Free Software Foundation, either version 3 of the License, or
7
// (at your option) any later version.
8

            
9
// Tanssi is distributed in the hope that it will be useful,
10
// but WITHOUT ANY WARRANTY; without even the implied warranty of
11
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12
// GNU General Public License for more details.
13

            
14
// You should have received a copy of the GNU General Public License
15
// along with Tanssi.  If not, see <http://www.gnu.org/licenses/>
16

            
17
//! ExternalValidatorSlashes pallet.
18
//!
19
//! A pallet to store slashes based on offences committed by validators
20
//! Slashes can be cancelled during the DeferPeriod through cancel_deferred_slash
21
//! Slashes can also be forcedly injected via the force_inject_slash extrinsic
22
//! Slashes for a particular era are removed after the bondingPeriod has elapsed
23
//!
24
//! ## OnOffence trait
25
//!
26
//! The pallet also implements the OnOffence trait that reacts to offences being injected by other pallets
27
//! Invulnerables are not slashed and no slashing information is stored for them
28

            
29
#![cfg_attr(not(feature = "std"), no_std)]
30

            
31
use {
32
    frame_support::{pallet_prelude::*, traits::DefensiveSaturating},
33
    frame_system::pallet_prelude::*,
34
    log::log,
35
    pallet_staking::SessionInterface,
36
    parity_scale_codec::{Decode, Encode, FullCodec},
37
    sp_core::H256,
38
    sp_runtime::{
39
        traits::{Convert, Debug, One, Saturating, Zero},
40
        DispatchResult, Perbill,
41
    },
42
    sp_staking::{
43
        offence::{OffenceDetails, OnOffenceHandler},
44
        EraIndex, SessionIndex,
45
    },
46
    sp_std::{collections::vec_deque::VecDeque, vec, vec::Vec},
47
    tp_traits::{
48
        apply, derive_storage_traits, EraIndexProvider, ExternalIndexProvider,
49
        InvulnerablesProvider, OnEraStart,
50
    },
51
};
52

            
53
use {
54
    snowbridge_core::ChannelId,
55
    tp_bridge::{Command, DeliverMessage, Message, SlashData, TicketInfo, ValidateMessage},
56
};
57

            
58
pub use pallet::*;
59

            
60
#[cfg(test)]
61
mod mock;
62

            
63
#[cfg(test)]
64
mod tests;
65

            
66
#[cfg(feature = "runtime-benchmarks")]
67
mod benchmarking;
68
pub mod weights;
69

            
70
327
#[frame_support::pallet]
71
pub mod pallet {
72
    use super::*;
73
    pub use crate::weights::WeightInfo;
74

            
75
    #[pallet::event]
76
128
    #[pallet::generate_deposit(pub(super) fn deposit_event)]
77
    pub enum Event<T: Config> {
78
        /// Removed author data
79
        SlashReported {
80
            validator: T::ValidatorId,
81
            fraction: Perbill,
82
            slash_era: EraIndex,
83
        },
84
6
        /// The slashes message was sent correctly.
85
        SlashesMessageSent {
86
            message_id: H256,
87
            slashes_command: Command,
88
        },
89
    }
90

            
91
    #[pallet::config]
92
    pub trait Config: frame_system::Config {
93
        /// The overarching event type.
94
        type RuntimeEvent: From<Event<Self>> + IsType<<Self as frame_system::Config>::RuntimeEvent>;
95

            
96
        /// A stable ID for a validator.
97
        type ValidatorId: Member
98
            + Parameter
99
            + MaybeSerializeDeserialize
100
            + MaxEncodedLen
101
            + TryFrom<Self::AccountId>;
102

            
103
        /// A conversion from account ID to validator ID.
104
        type ValidatorIdOf: Convert<Self::AccountId, Option<Self::ValidatorId>>;
105

            
106
        /// Number of eras that slashes are deferred by, after computation.
107
        ///
108
        /// This should be less than the bonding duration. Set to 0 if slashes
109
        /// should be applied immediately, without opportunity for intervention.
110
        #[pallet::constant]
111
        type SlashDeferDuration: Get<EraIndex>;
112

            
113
        /// Number of eras that staked funds must remain bonded for.
114
        #[pallet::constant]
115
        type BondingDuration: Get<EraIndex>;
116

            
117
        // SlashId type, used as a counter on the number of slashes
118
        type SlashId: Default
119
            + FullCodec
120
            + TypeInfo
121
            + Copy
122
            + Clone
123
            + Debug
124
            + Eq
125
            + Saturating
126
            + One
127
            + Ord
128
            + MaxEncodedLen;
129

            
130
        /// Interface for interacting with a session pallet.
131
        type SessionInterface: SessionInterface<Self::AccountId>;
132

            
133
        /// Era index provider, used to fetch the active era among other things
134
        type EraIndexProvider: EraIndexProvider;
135

            
136
        /// Invulnerable provider, used to get the invulnerables to know when not to slash
137
        type InvulnerablesProvider: InvulnerablesProvider<Self::ValidatorId>;
138

            
139
        /// Validate a message that will be sent to Ethereum.
140
        type ValidateMessage: ValidateMessage;
141

            
142
        /// Send a message to Ethereum. Needs to be validated first.
143
        type OutboundQueue: DeliverMessage<
144
            Ticket = <<Self as pallet::Config>::ValidateMessage as ValidateMessage>::Ticket,
145
        >;
146

            
147
        /// Provider to retrieve the current external index of validators
148
        type ExternalIndexProvider: ExternalIndexProvider;
149

            
150
        /// How many queued slashes are being processed per block.
151
        #[pallet::constant]
152
        type QueuedSlashesProcessedPerBlock: Get<u32>;
153

            
154
        /// The weight information of this pallet.
155
        type WeightInfo: WeightInfo;
156
    }
157

            
158
20
    #[pallet::error]
159
    pub enum Error<T> {
160
        /// The era for which the slash wants to be cancelled has no slashes
161
        EmptyTargets,
162
        /// No slash was found to be cancelled at the given index
163
        InvalidSlashIndex,
164
        /// Slash indices to be cancelled are not sorted or unique
165
        NotSortedAndUnique,
166
        /// Provided an era in the future
167
        ProvidedFutureEra,
168
        /// Provided an era that is not slashable
169
        ProvidedNonSlashableEra,
170
        /// The slash to be cancelled has already elapsed the DeferPeriod
171
        DeferPeriodIsOver,
172
        /// There was an error computing the slash
173
        ErrorComputingSlash,
174
        /// Failed to validate the message that was going to be sent to Ethereum
175
        EthereumValidateFail,
176
        /// Failed to deliver the message to Ethereum
177
        EthereumDeliverFail,
178
    }
179

            
180
    #[apply(derive_storage_traits)]
181
    #[derive(MaxEncodedLen, Default)]
182
    pub enum SlashingModeOption {
183
        #[default]
184
        Enabled,
185
        LogOnly,
186
        Disabled,
187
    }
188

            
189
4
    #[pallet::pallet]
190
    pub struct Pallet<T>(PhantomData<T>);
191

            
192
    /// All slashing events on validators, mapped by era to the highest slash proportion
193
    /// and slash value of the era.
194
819
    #[pallet::storage]
195
    pub(crate) type ValidatorSlashInEra<T: Config> =
196
        StorageDoubleMap<_, Twox64Concat, EraIndex, Twox64Concat, T::AccountId, Perbill>;
197

            
198
    /// A mapping from still-bonded eras to the first session index of that era.
199
    ///
200
    /// Must contains information for eras for the range:
201
    /// `[active_era - bounding_duration; active_era]`
202
1214
    #[pallet::storage]
203
    #[pallet::unbounded]
204
    pub type BondedEras<T: Config> =
205
        StorageValue<_, Vec<(EraIndex, SessionIndex, u64)>, ValueQuery>;
206

            
207
    /// A counter on the number of slashes we have performed
208
1638
    #[pallet::storage]
209
    #[pallet::getter(fn next_slash_id)]
210
    pub type NextSlashId<T: Config> = StorageValue<_, T::SlashId, ValueQuery>;
211

            
212
    /// All unapplied slashes that are queued for later.
213
731
    #[pallet::storage]
214
    #[pallet::unbounded]
215
    #[pallet::getter(fn slashes)]
216
    pub type Slashes<T: Config> =
217
        StorageMap<_, Twox64Concat, EraIndex, Vec<Slash<T::AccountId, T::SlashId>>, ValueQuery>;
218

            
219
    /// All unreported slashes that will be processed in the future.
220
15491
    #[pallet::storage]
221
    #[pallet::unbounded]
222
    #[pallet::getter(fn unreported_slashes)]
223
    pub type UnreportedSlashesQueue<T: Config> =
224
        StorageValue<_, VecDeque<Slash<T::AccountId, T::SlashId>>, ValueQuery>;
225

            
226
    // Turns slashing on or off
227
182
    #[pallet::storage]
228
    pub type SlashingMode<T: Config> = StorageValue<_, SlashingModeOption, ValueQuery>;
229

            
230
    #[pallet::call]
231
    impl<T: Config> Pallet<T> {
232
        /// Cancel a slash that was deferred for a later era
233
        #[pallet::call_index(0)]
234
        #[pallet::weight(T::WeightInfo::cancel_deferred_slash(slash_indices.len() as u32))]
235
        pub fn cancel_deferred_slash(
236
            origin: OriginFor<T>,
237
            era: EraIndex,
238
            slash_indices: Vec<u32>,
239
5
        ) -> DispatchResult {
240
5
            ensure_root(origin)?;
241

            
242
5
            let active_era = T::EraIndexProvider::active_era().index;
243
5

            
244
5
            // We need to be in the defer period
245
5
            ensure!(
246
5
                era <= active_era
247
5
                    .saturating_add(T::SlashDeferDuration::get().saturating_add(One::one()))
248
5
                    && era > active_era,
249
3
                Error::<T>::DeferPeriodIsOver
250
            );
251

            
252
2
            ensure!(!slash_indices.is_empty(), Error::<T>::EmptyTargets);
253
2
            ensure!(
254
2
                is_sorted_and_unique(&slash_indices),
255
                Error::<T>::NotSortedAndUnique
256
            );
257
            // fetch slashes for the era in which we want to defer
258
2
            let mut era_slashes = Slashes::<T>::get(&era);
259
2

            
260
2
            let last_item = slash_indices[slash_indices.len() - 1];
261
2
            ensure!(
262
2
                (last_item as usize) < era_slashes.len(),
263
                Error::<T>::InvalidSlashIndex
264
            );
265

            
266
            // Remove elements starting from the highest index to avoid shifting issues.
267
2
            for index in slash_indices.into_iter().rev() {
268
2
                era_slashes.remove(index as usize);
269
2
            }
270
            // insert back slashes
271
2
            Slashes::<T>::insert(&era, &era_slashes);
272
2
            Ok(())
273
        }
274

            
275
        #[pallet::call_index(1)]
276
        #[pallet::weight(T::WeightInfo::force_inject_slash())]
277
        pub fn force_inject_slash(
278
            origin: OriginFor<T>,
279
            era: EraIndex,
280
            validator: T::AccountId,
281
            percentage: Perbill,
282
            external_idx: u64,
283
320
        ) -> DispatchResult {
284
320
            ensure_root(origin)?;
285
320
            let active_era = T::EraIndexProvider::active_era().index;
286
320

            
287
320
            ensure!(era <= active_era, Error::<T>::ProvidedFutureEra);
288

            
289
319
            let slash_defer_duration = T::SlashDeferDuration::get();
290
319

            
291
319
            let _ = T::EraIndexProvider::era_to_session_start(era)
292
319
                .ok_or(Error::<T>::ProvidedNonSlashableEra)?;
293

            
294
318
            let next_slash_id = NextSlashId::<T>::get();
295

            
296
318
            let slash = compute_slash::<T>(
297
318
                percentage,
298
318
                next_slash_id,
299
318
                era,
300
318
                validator,
301
318
                slash_defer_duration,
302
318
                external_idx,
303
318
            )
304
318
            .ok_or(Error::<T>::ErrorComputingSlash)?;
305

            
306
            // If we defer duration is 0, we immediately apply and confirm
307
318
            let era_to_consider = if slash_defer_duration == 0 {
308
314
                era.saturating_add(One::one())
309
            } else {
310
4
                era.saturating_add(slash_defer_duration)
311
4
                    .saturating_add(One::one())
312
            };
313

            
314
318
            Slashes::<T>::mutate(&era_to_consider, |era_slashes| {
315
318
                era_slashes.push(slash);
316
318
            });
317
318

            
318
318
            NextSlashId::<T>::put(next_slash_id.saturating_add(One::one()));
319
318
            Ok(())
320
        }
321

            
322
        #[pallet::call_index(2)]
323
        #[pallet::weight(T::WeightInfo::root_test_send_msg_to_eth())]
324
        pub fn root_test_send_msg_to_eth(
325
            origin: OriginFor<T>,
326
            nonce: H256,
327
            num_msgs: u32,
328
            msg_size: u32,
329
1
        ) -> DispatchResult {
330
1
            ensure_root(origin)?;
331

            
332
1
            for i in 0..num_msgs {
333
                // Make sure each message has a different payload
334
1
                let mut payload = sp_core::blake2_256((nonce, i).encode().as_ref()).to_vec();
335
1
                // Extend with zeros until msg_size is reached
336
1
                payload.resize(msg_size as usize, 0);
337
1
                // Example command, this should be something like "ReportSlashes"
338
1
                let command = Command::Test(payload);
339
1

            
340
1
                // Validate
341
1
                let channel_id: ChannelId = snowbridge_core::PRIMARY_GOVERNANCE_CHANNEL;
342
1

            
343
1
                let outbound_message = Message {
344
1
                    id: None,
345
1
                    channel_id,
346
1
                    command,
347
1
                };
348

            
349
                // validate the message
350
                // Ignore fee because for now only root can send messages
351
1
                let (ticket, _fee) =
352
1
                    T::ValidateMessage::validate(&outbound_message).map_err(|err| {
353
                        log::error!(
354
                            "root_test_send_msg_to_eth: validation of message {i} failed. {err:?}"
355
                        );
356
                        crate::pallet::Error::<T>::EthereumValidateFail
357
1
                    })?;
358

            
359
                // Deliver
360
1
                T::OutboundQueue::deliver(ticket).map_err(|err| {
361
                    log::error!(
362
                        "root_test_send_msg_to_eth: delivery of message {i} failed. {err:?}"
363
                    );
364
                    crate::pallet::Error::<T>::EthereumDeliverFail
365
1
                })?;
366
            }
367

            
368
1
            Ok(())
369
        }
370

            
371
        #[pallet::call_index(3)]
372
        #[pallet::weight(T::WeightInfo::set_slashing_mode())]
373
        pub fn set_slashing_mode(origin: OriginFor<T>, mode: SlashingModeOption) -> DispatchResult {
374
            ensure_root(origin)?;
375

            
376
            SlashingMode::<T>::put(mode);
377

            
378
            Ok(())
379
        }
380
    }
381

            
382
3586
    #[pallet::hooks]
383
    impl<T: Config> Hooks<BlockNumberFor<T>> for Pallet<T> {
384
3591
        fn on_initialize(_n: BlockNumberFor<T>) -> Weight {
385
3591
            let processed = Self::process_slashes_queue(T::QueuedSlashesProcessedPerBlock::get());
386
3591
            T::WeightInfo::process_slashes_queue(processed)
387
3591
        }
388
    }
389
}
390

            
391
/// This is intended to be used with `FilterHistoricalOffences`.
392
impl<T: Config>
393
    OnOffenceHandler<T::AccountId, pallet_session::historical::IdentificationTuple<T>, Weight>
394
    for Pallet<T>
395
where
396
    T: Config<ValidatorId = <T as frame_system::Config>::AccountId>,
397
    T: pallet_session::Config<ValidatorId = <T as frame_system::Config>::AccountId>,
398
    T: pallet_session::historical::Config,
399
    T::SessionHandler: pallet_session::SessionHandler<<T as frame_system::Config>::AccountId>,
400
    T::SessionManager: pallet_session::SessionManager<<T as frame_system::Config>::AccountId>,
401
    <T as pallet::Config>::ValidatorIdOf: Convert<
402
        <T as frame_system::Config>::AccountId,
403
        Option<<T as frame_system::Config>::AccountId>,
404
    >,
405
{
406
91
    fn on_offence(
407
91
        offenders: &[OffenceDetails<
408
91
            T::AccountId,
409
91
            pallet_session::historical::IdentificationTuple<T>,
410
91
        >],
411
91
        slash_fraction: &[Perbill],
412
91
        slash_session: SessionIndex,
413
91
    ) -> Weight {
414
91
        let slashing_mode = SlashingMode::<T>::get();
415
91
        if slashing_mode == SlashingModeOption::Disabled {
416
            return Weight::default();
417
91
        }
418
91

            
419
91
        let active_era = {
420
91
            let active_era = T::EraIndexProvider::active_era().index;
421
91
            active_era
422
91
        };
423
91
        let active_era_start_session_index = T::EraIndexProvider::era_to_session_start(active_era)
424
91
            .unwrap_or_else(|| {
425
                frame_support::print("Error: start_session_index must be set for current_era");
426
                0
427
91
            });
428

            
429
        // Fast path for active-era report - most likely.
430
        // `slash_session` cannot be in a future active era. It must be in `active_era` or before.
431
91
        let (slash_era, external_idx) = if slash_session >= active_era_start_session_index {
432
37
            (active_era, T::ExternalIndexProvider::get_external_index())
433
        } else {
434
54
            let eras = BondedEras::<T>::get();
435
54

            
436
54
            // Reverse because it's more likely to find reports from recent eras.
437
54
            match eras
438
54
                .iter()
439
54
                .rev()
440
108
                .find(|&(_, sesh, _)| sesh <= &slash_session)
441
            {
442
54
                Some((slash_era, _, external_idx)) => (*slash_era, *external_idx),
443
                // Before bonding period. defensive - should be filtered out.
444
                None => return Weight::default(),
445
            }
446
        };
447

            
448
91
        let slash_defer_duration = T::SlashDeferDuration::get();
449
91

            
450
91
        let invulnerables = T::InvulnerablesProvider::invulnerables();
451
91

            
452
91
        let mut next_slash_id = NextSlashId::<T>::get();
453

            
454
91
        for (details, slash_fraction) in offenders.iter().zip(slash_fraction) {
455
91
            let (stash, _) = &details.offender;
456
91

            
457
91
            // Skip if the validator is invulnerable.
458
91
            if invulnerables.contains(stash) {
459
4
                continue;
460
87
            }
461
87

            
462
87
            Self::deposit_event(Event::<T>::SlashReported {
463
87
                validator: stash.clone(),
464
87
                fraction: *slash_fraction,
465
87
                slash_era,
466
87
            });
467
87

            
468
87
            if slashing_mode == SlashingModeOption::LogOnly {
469
                continue;
470
87
            }
471
87

            
472
87
            let slash = compute_slash::<T>(
473
87
                *slash_fraction,
474
87
                next_slash_id,
475
87
                slash_era,
476
87
                stash.clone(),
477
87
                slash_defer_duration,
478
87
                external_idx,
479
87
            );
480

            
481
87
            if let Some(mut slash) = slash {
482
86
                slash.reporters = details.reporters.clone();
483
86

            
484
86
                // Defer to end of some `slash_defer_duration` from now.
485
86
                log!(
486
86
                    log::Level::Debug,
487
                    "deferring slash of {:?}% happened in {:?} (reported in {:?}) to {:?}",
488
                    slash_fraction,
489
                    slash_era,
490
                    active_era,
491
                    slash_era + slash_defer_duration + 1,
492
                );
493

            
494
                // Cover slash defer duration equal to 0
495
                // Slashes are applied at the end of the current era
496
86
                if slash_defer_duration == 0 {
497
85
                    Slashes::<T>::mutate(active_era.saturating_add(One::one()), move |for_now| {
498
85
                        for_now.push(slash)
499
85
                    });
500
85
                } else {
501
1
                    // Else, slashes are applied after slash_defer_period since the slashed era
502
1
                    Slashes::<T>::mutate(
503
1
                        slash_era
504
1
                            .saturating_add(slash_defer_duration)
505
1
                            .saturating_add(One::one()),
506
1
                        move |for_later| for_later.push(slash),
507
1
                    );
508
1
                }
509

            
510
                // Fix unwrap
511
86
                next_slash_id = next_slash_id.saturating_add(One::one());
512
1
            }
513
        }
514
91
        NextSlashId::<T>::put(next_slash_id);
515
91
        Weight::default()
516
91
    }
517
}
518

            
519
impl<T: Config> OnEraStart for Pallet<T> {
520
274
    fn on_era_start(era_index: EraIndex, session_start: SessionIndex, external_idx: u64) {
521
        // This should be small, as slashes are limited by the num of validators
522
        // let's put 1000 as a conservative measure
523
        const REMOVE_LIMIT: u32 = 1000;
524

            
525
274
        let bonding_duration = T::BondingDuration::get();
526
274

            
527
274
        BondedEras::<T>::mutate(|bonded| {
528
274
            bonded.push((era_index, session_start, external_idx));
529
274

            
530
274
            if era_index > bonding_duration {
531
10
                let first_kept = era_index.defensive_saturating_sub(bonding_duration);
532
10

            
533
10
                // Prune out everything that's from before the first-kept index.
534
10
                let n_to_prune = bonded
535
10
                    .iter()
536
20
                    .take_while(|&&(era_idx, _, _)| era_idx < first_kept)
537
10
                    .count();
538

            
539
                // Kill slashing metadata.
540
10
                for (pruned_era, _, _) in bonded.drain(..n_to_prune) {
541
10
                    let removal_result =
542
10
                        ValidatorSlashInEra::<T>::clear_prefix(&pruned_era, REMOVE_LIMIT, None);
543
10
                    if removal_result.maybe_cursor.is_some() {
544
                        log::error!(
545
                            "Not all validator slashes were remove for era {:?}",
546
                            pruned_era
547
                        );
548
10
                    }
549
10
                    Slashes::<T>::remove(&pruned_era);
550
                }
551

            
552
10
                if let Some(&(_, first_session, _)) = bonded.first() {
553
10
                    T::SessionInterface::prune_historical_up_to(first_session);
554
10
                }
555
264
            }
556
274
        });
557
274

            
558
274
        Self::add_era_slashes_to_queue(era_index);
559
274
    }
560
}
561

            
562
impl<T: Config> Pallet<T> {
563
274
    fn add_era_slashes_to_queue(active_era: EraIndex) {
564
274
        let mut slashes: VecDeque<_> = Slashes::<T>::get(&active_era).into();
565
274

            
566
274
        UnreportedSlashesQueue::<T>::mutate(|queue| queue.append(&mut slashes));
567
274
    }
568

            
569
    /// Returns number of slashes that were sent to ethereum.
570
3591
    fn process_slashes_queue(amount: u32) -> u32 {
571
3591
        let mut slashes_to_send: Vec<_> = vec![];
572
3591
        let era_index = T::EraIndexProvider::active_era().index;
573
3591

            
574
3591
        UnreportedSlashesQueue::<T>::mutate(|queue| {
575
3591
            for _ in 0..amount {
576
3947
                let Some(slash) = queue.pop_front() else {
577
                    // no more slashes to process in the queue
578
3557
                    break;
579
                };
580

            
581
390
                slashes_to_send.push(SlashData {
582
390
                    encoded_validator_id: slash.validator.clone().encode(),
583
390
                    slash_fraction: slash.percentage.deconstruct(),
584
390
                    external_idx: slash.external_idx,
585
390
                });
586
            }
587
3591
        });
588
3591

            
589
3591
        if slashes_to_send.is_empty() {
590
3550
            return 0;
591
41
        }
592
41

            
593
41
        let slashes_count = slashes_to_send.len() as u32;
594
41

            
595
41
        // Build command with slashes.
596
41
        let command = Command::ReportSlashes {
597
41
            era_index,
598
41
            slashes: slashes_to_send,
599
41
        };
600
41

            
601
41
        let channel_id: ChannelId = snowbridge_core::PRIMARY_GOVERNANCE_CHANNEL;
602
41

            
603
41
        let outbound_message = Message {
604
41
            id: None,
605
41
            channel_id,
606
41
            command: command.clone(),
607
41
        };
608
41

            
609
41
        // Validate and deliver the message
610
41
        match T::ValidateMessage::validate(&outbound_message) {
611
41
            Ok((ticket, _fee)) => {
612
41
                let message_id = ticket.message_id();
613
41
                if let Err(err) = T::OutboundQueue::deliver(ticket) {
614
                    log::error!(target: "ext_validators_slashes", "OutboundQueue delivery of message failed. {err:?}");
615
41
                } else {
616
41
                    Self::deposit_event(Event::SlashesMessageSent {
617
41
                        message_id,
618
41
                        slashes_command: command,
619
41
                    });
620
41
                }
621
            }
622
            Err(err) => {
623
                log::error!(target: "ext_validators_slashes", "OutboundQueue validation of message failed. {err:?}");
624
            }
625
        };
626

            
627
41
        slashes_count
628
3591
    }
629
}
630

            
631
/// A pending slash record. The value of the slash has been computed but not applied yet,
632
/// rather deferred for several eras.
633
#[derive(Encode, Decode, RuntimeDebug, TypeInfo, Clone, PartialEq)]
634
pub struct Slash<AccountId, SlashId> {
635
    /// external index identifying a given set of validators
636
    pub external_idx: u64,
637
    /// The stash ID of the offending validator.
638
    pub validator: AccountId,
639
    /// Reporters of the offence; bounty payout recipients.
640
    pub reporters: Vec<AccountId>,
641
    /// The amount of payout.
642
    pub slash_id: SlashId,
643
    pub percentage: Perbill,
644
    // Whether the slash is confirmed or still needs to go through deferred period
645
    pub confirmed: bool,
646
}
647

            
648
impl<AccountId, SlashId: One> Slash<AccountId, SlashId> {
649
    /// Initializes the default object using the given `validator`.
650
    pub fn default_from(validator: AccountId) -> Self {
651
        Self {
652
            external_idx: 0,
653
            validator,
654
            reporters: vec![],
655
            slash_id: One::one(),
656
            percentage: Perbill::from_percent(50),
657
            confirmed: false,
658
        }
659
    }
660
}
661

            
662
/// Computes a slash of a validator and nominators. It returns an unapplied
663
/// record to be applied at some later point. Slashing metadata is updated in storage,
664
/// since unapplied records are only rarely intended to be dropped.
665
///
666
/// The pending slash record returned does not have initialized reporters. Those have
667
/// to be set at a higher level, if any.
668
405
pub(crate) fn compute_slash<T: Config>(
669
405
    slash_fraction: Perbill,
670
405
    slash_id: T::SlashId,
671
405
    slash_era: EraIndex,
672
405
    stash: T::AccountId,
673
405
    slash_defer_duration: EraIndex,
674
405
    external_idx: u64,
675
405
) -> Option<Slash<T::AccountId, T::SlashId>> {
676
405
    let prior_slash_p = ValidatorSlashInEra::<T>::get(&slash_era, &stash).unwrap_or(Zero::zero());
677
405

            
678
405
    // compare slash proportions rather than slash values to avoid issues due to rounding
679
405
    // error.
680
405
    if slash_fraction.deconstruct() > prior_slash_p.deconstruct() {
681
404
        ValidatorSlashInEra::<T>::insert(&slash_era, &stash, &slash_fraction);
682
404
    } else {
683
        // we slash based on the max in era - this new event is not the max,
684
        // so neither the validator or any nominators will need an update.
685
        //
686
        // this does lead to a divergence of our system from the paper, which
687
        // pays out some reward even if the latest report is not max-in-era.
688
        // we opt to avoid the nominator lookups and edits and leave more rewards
689
        // for more drastic misbehavior.
690
1
        return None;
691
    }
692

            
693
404
    let confirmed = slash_defer_duration.is_zero();
694
404
    Some(Slash {
695
404
        external_idx,
696
404
        validator: stash.clone(),
697
404
        percentage: slash_fraction,
698
404
        slash_id,
699
404
        reporters: Vec::new(),
700
404
        confirmed,
701
404
    })
702
405
}
703

            
704
/// Check that list is sorted and has no duplicates.
705
8
fn is_sorted_and_unique(list: &[u32]) -> bool {
706
8
    list.windows(2).all(|w| w[0] < w[1])
707
8
}