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::FullCodec,
37
    parity_scale_codec::{Decode, Encode},
38
    sp_core::H256,
39
    sp_runtime::traits::{Convert, Debug, One, Saturating, Zero},
40
    sp_runtime::DispatchResult,
41
    sp_runtime::Perbill,
42
    sp_staking::{
43
        offence::{OffenceDetails, OnOffenceHandler},
44
        EraIndex, SessionIndex,
45
    },
46
    sp_std::collections::vec_deque::VecDeque,
47
    sp_std::vec,
48
    sp_std::vec::Vec,
49
    tp_traits::{EraIndexProvider, ExternalIndexProvider, InvulnerablesProvider, OnEraStart},
50
};
51

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

            
55
pub use pallet::*;
56

            
57
#[cfg(test)]
58
mod mock;
59

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

            
63
#[cfg(feature = "runtime-benchmarks")]
64
mod benchmarking;
65
pub mod weights;
66

            
67
327
#[frame_support::pallet]
68
pub mod pallet {
69
    use super::*;
70
    pub use crate::weights::WeightInfo;
71

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

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

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

            
100
        /// A conversion from account ID to validator ID.
101
        type ValidatorIdOf: Convert<Self::AccountId, Option<Self::ValidatorId>>;
102

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

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

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

            
127
        /// Interface for interacting with a session pallet.
128
        type SessionInterface: SessionInterface<Self::AccountId>;
129

            
130
        /// Era index provider, used to fetch the active era among other things
131
        type EraIndexProvider: EraIndexProvider;
132

            
133
        /// Invulnerable provider, used to get the invulnerables to know when not to slash
134
        type InvulnerablesProvider: InvulnerablesProvider<Self::ValidatorId>;
135

            
136
        /// Validate a message that will be sent to Ethereum.
137
        type ValidateMessage: ValidateMessage;
138

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

            
144
        /// Provider to retrieve the current external index of validators
145
        type ExternalIndexProvider: ExternalIndexProvider;
146

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

            
151
        /// The weight information of this pallet.
152
        type WeightInfo: WeightInfo;
153
    }
154

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

            
177
4
    #[pallet::pallet]
178
    pub struct Pallet<T>(PhantomData<T>);
179

            
180
    /// All slashing events on validators, mapped by era to the highest slash proportion
181
    /// and slash value of the era.
182
819
    #[pallet::storage]
183
    pub(crate) type ValidatorSlashInEra<T: Config> =
184
        StorageDoubleMap<_, Twox64Concat, EraIndex, Twox64Concat, T::AccountId, Perbill>;
185

            
186
    /// A mapping from still-bonded eras to the first session index of that era.
187
    ///
188
    /// Must contains information for eras for the range:
189
    /// `[active_era - bounding_duration; active_era]`
190
1210
    #[pallet::storage]
191
    #[pallet::unbounded]
192
    pub type BondedEras<T: Config> =
193
        StorageValue<_, Vec<(EraIndex, SessionIndex, u64)>, ValueQuery>;
194

            
195
    /// A counter on the number of slashes we have performed
196
1638
    #[pallet::storage]
197
    #[pallet::getter(fn next_slash_id)]
198
    pub type NextSlashId<T: Config> = StorageValue<_, T::SlashId, ValueQuery>;
199

            
200
    /// All unapplied slashes that are queued for later.
201
730
    #[pallet::storage]
202
    #[pallet::unbounded]
203
    #[pallet::getter(fn slashes)]
204
    pub type Slashes<T: Config> =
205
        StorageMap<_, Twox64Concat, EraIndex, Vec<Slash<T::AccountId, T::SlashId>>, ValueQuery>;
206

            
207
    /// All unreported slashes that will be processed in the future.
208
15479
    #[pallet::storage]
209
    #[pallet::unbounded]
210
    #[pallet::getter(fn unreported_slashes)]
211
    pub type UnreportedSlashesQueue<T: Config> =
212
        StorageValue<_, VecDeque<Slash<T::AccountId, T::SlashId>>, ValueQuery>;
213

            
214
    #[pallet::call]
215
    impl<T: Config> Pallet<T> {
216
        /// Cancel a slash that was deferred for a later era
217
        #[pallet::call_index(0)]
218
        #[pallet::weight(T::WeightInfo::cancel_deferred_slash(slash_indices.len() as u32))]
219
        pub fn cancel_deferred_slash(
220
            origin: OriginFor<T>,
221
            era: EraIndex,
222
            slash_indices: Vec<u32>,
223
5
        ) -> DispatchResult {
224
5
            ensure_root(origin)?;
225

            
226
5
            let active_era = T::EraIndexProvider::active_era().index;
227
5

            
228
5
            // We need to be in the defer period
229
5
            ensure!(
230
5
                era <= active_era
231
5
                    .saturating_add(T::SlashDeferDuration::get().saturating_add(One::one()))
232
5
                    && era > active_era,
233
3
                Error::<T>::DeferPeriodIsOver
234
            );
235

            
236
2
            ensure!(!slash_indices.is_empty(), Error::<T>::EmptyTargets);
237
2
            ensure!(
238
2
                is_sorted_and_unique(&slash_indices),
239
                Error::<T>::NotSortedAndUnique
240
            );
241
            // fetch slashes for the era in which we want to defer
242
2
            let mut era_slashes = Slashes::<T>::get(&era);
243
2

            
244
2
            let last_item = slash_indices[slash_indices.len() - 1];
245
2
            ensure!(
246
2
                (last_item as usize) < era_slashes.len(),
247
                Error::<T>::InvalidSlashIndex
248
            );
249

            
250
            // Remove elements starting from the highest index to avoid shifting issues.
251
2
            for index in slash_indices.into_iter().rev() {
252
2
                era_slashes.remove(index as usize);
253
2
            }
254
            // insert back slashes
255
2
            Slashes::<T>::insert(&era, &era_slashes);
256
2
            Ok(())
257
        }
258

            
259
        #[pallet::call_index(1)]
260
        #[pallet::weight(T::WeightInfo::force_inject_slash())]
261
        pub fn force_inject_slash(
262
            origin: OriginFor<T>,
263
            era: EraIndex,
264
            validator: T::AccountId,
265
            percentage: Perbill,
266
            external_idx: u64,
267
320
        ) -> DispatchResult {
268
320
            ensure_root(origin)?;
269
320
            let active_era = T::EraIndexProvider::active_era().index;
270
320

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

            
273
319
            let slash_defer_duration = T::SlashDeferDuration::get();
274
319

            
275
319
            let _ = T::EraIndexProvider::era_to_session_start(era)
276
319
                .ok_or(Error::<T>::ProvidedNonSlashableEra)?;
277

            
278
318
            let next_slash_id = NextSlashId::<T>::get();
279

            
280
318
            let slash = compute_slash::<T>(
281
318
                percentage,
282
318
                next_slash_id,
283
318
                era,
284
318
                validator,
285
318
                slash_defer_duration,
286
318
                external_idx,
287
318
            )
288
318
            .ok_or(Error::<T>::ErrorComputingSlash)?;
289

            
290
            // If we defer duration is 0, we immediately apply and confirm
291
318
            let era_to_consider = if slash_defer_duration == 0 {
292
314
                era.saturating_add(One::one())
293
            } else {
294
4
                era.saturating_add(slash_defer_duration)
295
4
                    .saturating_add(One::one())
296
            };
297

            
298
318
            Slashes::<T>::mutate(&era_to_consider, |era_slashes| {
299
318
                era_slashes.push(slash);
300
318
            });
301
318

            
302
318
            NextSlashId::<T>::put(next_slash_id.saturating_add(One::one()));
303
318
            Ok(())
304
        }
305

            
306
        #[pallet::call_index(2)]
307
        #[pallet::weight(T::WeightInfo::root_test_send_msg_to_eth())]
308
        pub fn root_test_send_msg_to_eth(
309
            origin: OriginFor<T>,
310
            nonce: H256,
311
            num_msgs: u32,
312
            msg_size: u32,
313
1
        ) -> DispatchResult {
314
1
            ensure_root(origin)?;
315

            
316
1
            for i in 0..num_msgs {
317
                // Make sure each message has a different payload
318
1
                let mut payload = sp_core::blake2_256((nonce, i).encode().as_ref()).to_vec();
319
1
                // Extend with zeros until msg_size is reached
320
1
                payload.resize(msg_size as usize, 0);
321
1
                // Example command, this should be something like "ReportSlashes"
322
1
                let command = Command::Test(payload);
323
1

            
324
1
                // Validate
325
1
                let channel_id: ChannelId = snowbridge_core::PRIMARY_GOVERNANCE_CHANNEL;
326
1

            
327
1
                let outbound_message = Message {
328
1
                    id: None,
329
1
                    channel_id,
330
1
                    command,
331
1
                };
332

            
333
                // validate the message
334
                // Ignore fee because for now only root can send messages
335
1
                let (ticket, _fee) =
336
1
                    T::ValidateMessage::validate(&outbound_message).map_err(|err| {
337
                        log::error!(
338
                            "root_test_send_msg_to_eth: validation of message {i} failed. {err:?}"
339
                        );
340
                        crate::pallet::Error::<T>::EthereumValidateFail
341
1
                    })?;
342

            
343
                // Deliver
344
1
                T::OutboundQueue::deliver(ticket).map_err(|err| {
345
                    log::error!(
346
                        "root_test_send_msg_to_eth: delivery of message {i} failed. {err:?}"
347
                    );
348
                    crate::pallet::Error::<T>::EthereumDeliverFail
349
1
                })?;
350
            }
351

            
352
1
            Ok(())
353
        }
354
    }
355

            
356
3584
    #[pallet::hooks]
357
    impl<T: Config> Hooks<BlockNumberFor<T>> for Pallet<T> {
358
3589
        fn on_initialize(_n: BlockNumberFor<T>) -> Weight {
359
3589
            let processed = Self::process_slashes_queue(T::QueuedSlashesProcessedPerBlock::get());
360
3589
            T::WeightInfo::process_slashes_queue(processed)
361
3589
        }
362
    }
363
}
364

            
365
/// This is intended to be used with `FilterHistoricalOffences`.
366
impl<T: Config>
367
    OnOffenceHandler<T::AccountId, pallet_session::historical::IdentificationTuple<T>, Weight>
368
    for Pallet<T>
369
where
370
    T: Config<ValidatorId = <T as frame_system::Config>::AccountId>,
371
    T: pallet_session::Config<ValidatorId = <T as frame_system::Config>::AccountId>,
372
    T: pallet_session::historical::Config,
373
    T::SessionHandler: pallet_session::SessionHandler<<T as frame_system::Config>::AccountId>,
374
    T::SessionManager: pallet_session::SessionManager<<T as frame_system::Config>::AccountId>,
375
    <T as pallet::Config>::ValidatorIdOf: Convert<
376
        <T as frame_system::Config>::AccountId,
377
        Option<<T as frame_system::Config>::AccountId>,
378
    >,
379
{
380
91
    fn on_offence(
381
91
        offenders: &[OffenceDetails<
382
91
            T::AccountId,
383
91
            pallet_session::historical::IdentificationTuple<T>,
384
91
        >],
385
91
        slash_fraction: &[Perbill],
386
91
        slash_session: SessionIndex,
387
91
    ) -> Weight {
388
91
        let active_era = {
389
91
            let active_era = T::EraIndexProvider::active_era().index;
390
91
            active_era
391
91
        };
392
91
        let active_era_start_session_index = T::EraIndexProvider::era_to_session_start(active_era)
393
91
            .unwrap_or_else(|| {
394
                frame_support::print("Error: start_session_index must be set for current_era");
395
                0
396
91
            });
397

            
398
        // Fast path for active-era report - most likely.
399
        // `slash_session` cannot be in a future active era. It must be in `active_era` or before.
400
91
        let (slash_era, external_idx) = if slash_session >= active_era_start_session_index {
401
37
            (active_era, T::ExternalIndexProvider::get_external_index())
402
        } else {
403
54
            let eras = BondedEras::<T>::get();
404
54

            
405
54
            // Reverse because it's more likely to find reports from recent eras.
406
54
            match eras
407
54
                .iter()
408
54
                .rev()
409
108
                .find(|&(_, sesh, _)| sesh <= &slash_session)
410
            {
411
54
                Some((slash_era, _, external_idx)) => (*slash_era, *external_idx),
412
                // Before bonding period. defensive - should be filtered out.
413
                None => return Weight::default(),
414
            }
415
        };
416

            
417
91
        let slash_defer_duration = T::SlashDeferDuration::get();
418
91

            
419
91
        let invulnerables = T::InvulnerablesProvider::invulnerables();
420
91

            
421
91
        let mut next_slash_id = NextSlashId::<T>::get();
422

            
423
91
        for (details, slash_fraction) in offenders.iter().zip(slash_fraction) {
424
91
            let (stash, _) = &details.offender;
425
91

            
426
91
            // Skip if the validator is invulnerable.
427
91
            if invulnerables.contains(stash) {
428
4
                continue;
429
87
            }
430
87

            
431
87
            let slash = compute_slash::<T>(
432
87
                *slash_fraction,
433
87
                next_slash_id,
434
87
                slash_era,
435
87
                stash.clone(),
436
87
                slash_defer_duration,
437
87
                external_idx,
438
87
            );
439
87

            
440
87
            Self::deposit_event(Event::<T>::SlashReported {
441
87
                validator: stash.clone(),
442
87
                fraction: *slash_fraction,
443
87
                slash_era,
444
87
            });
445

            
446
87
            if let Some(mut slash) = slash {
447
86
                slash.reporters = details.reporters.clone();
448
86

            
449
86
                // Defer to end of some `slash_defer_duration` from now.
450
86
                log!(
451
86
                    log::Level::Debug,
452
                    "deferring slash of {:?}% happened in {:?} (reported in {:?}) to {:?}",
453
                    slash_fraction,
454
                    slash_era,
455
                    active_era,
456
                    slash_era + slash_defer_duration + 1,
457
                );
458

            
459
                // Cover slash defer duration equal to 0
460
                // Slashes are applied at the end of the current era
461
86
                if slash_defer_duration == 0 {
462
85
                    Slashes::<T>::mutate(active_era.saturating_add(One::one()), move |for_now| {
463
85
                        for_now.push(slash)
464
85
                    });
465
85
                } else {
466
1
                    // Else, slashes are applied after slash_defer_period since the slashed era
467
1
                    Slashes::<T>::mutate(
468
1
                        slash_era
469
1
                            .saturating_add(slash_defer_duration)
470
1
                            .saturating_add(One::one()),
471
1
                        move |for_later| for_later.push(slash),
472
1
                    );
473
1
                }
474

            
475
                // Fix unwrap
476
86
                next_slash_id = next_slash_id.saturating_add(One::one());
477
1
            }
478
        }
479
91
        NextSlashId::<T>::put(next_slash_id);
480
91
        Weight::default()
481
91
    }
482
}
483

            
484
impl<T: Config> OnEraStart for Pallet<T> {
485
273
    fn on_era_start(era_index: EraIndex, session_start: SessionIndex, external_idx: u64) {
486
        // This should be small, as slashes are limited by the num of validators
487
        // let's put 1000 as a conservative measure
488
        const REMOVE_LIMIT: u32 = 1000;
489

            
490
273
        let bonding_duration = T::BondingDuration::get();
491
273

            
492
273
        BondedEras::<T>::mutate(|bonded| {
493
273
            bonded.push((era_index, session_start, external_idx));
494
273

            
495
273
            if era_index > bonding_duration {
496
10
                let first_kept = era_index.defensive_saturating_sub(bonding_duration);
497
10

            
498
10
                // Prune out everything that's from before the first-kept index.
499
10
                let n_to_prune = bonded
500
10
                    .iter()
501
20
                    .take_while(|&&(era_idx, _, _)| era_idx < first_kept)
502
10
                    .count();
503

            
504
                // Kill slashing metadata.
505
10
                for (pruned_era, _, _) in bonded.drain(..n_to_prune) {
506
10
                    let removal_result =
507
10
                        ValidatorSlashInEra::<T>::clear_prefix(&pruned_era, REMOVE_LIMIT, None);
508
10
                    if removal_result.maybe_cursor.is_some() {
509
                        log::error!(
510
                            "Not all validator slashes were remove for era {:?}",
511
                            pruned_era
512
                        );
513
10
                    }
514
10
                    Slashes::<T>::remove(&pruned_era);
515
                }
516

            
517
10
                if let Some(&(_, first_session, _)) = bonded.first() {
518
10
                    T::SessionInterface::prune_historical_up_to(first_session);
519
10
                }
520
263
            }
521
273
        });
522
273

            
523
273
        Self::add_era_slashes_to_queue(era_index);
524
273
    }
525
}
526

            
527
impl<T: Config> Pallet<T> {
528
273
    fn add_era_slashes_to_queue(active_era: EraIndex) {
529
273
        let mut slashes: VecDeque<_> = Slashes::<T>::get(&active_era).into();
530
273

            
531
273
        UnreportedSlashesQueue::<T>::mutate(|queue| queue.append(&mut slashes));
532
273
    }
533

            
534
    /// Returns number of slashes that were sent to ethereum.
535
3589
    fn process_slashes_queue(amount: u32) -> u32 {
536
3589
        let mut slashes_to_send: Vec<_> = vec![];
537
3589
        let era_index = T::EraIndexProvider::active_era().index;
538
3589

            
539
3589
        UnreportedSlashesQueue::<T>::mutate(|queue| {
540
3589
            for _ in 0..amount {
541
3945
                let Some(slash) = queue.pop_front() else {
542
                    // no more slashes to process in the queue
543
3555
                    break;
544
                };
545

            
546
390
                slashes_to_send.push(SlashData {
547
390
                    encoded_validator_id: slash.validator.clone().encode(),
548
390
                    slash_fraction: slash.percentage.deconstruct(),
549
390
                    external_idx: slash.external_idx,
550
390
                });
551
            }
552
3589
        });
553
3589

            
554
3589
        if slashes_to_send.is_empty() {
555
3548
            return 0;
556
41
        }
557
41

            
558
41
        let slashes_count = slashes_to_send.len() as u32;
559
41

            
560
41
        // Build command with slashes.
561
41
        let command = Command::ReportSlashes {
562
41
            era_index,
563
41
            slashes: slashes_to_send,
564
41
        };
565
41

            
566
41
        let channel_id: ChannelId = snowbridge_core::PRIMARY_GOVERNANCE_CHANNEL;
567
41

            
568
41
        let outbound_message = Message {
569
41
            id: None,
570
41
            channel_id,
571
41
            command: command.clone(),
572
41
        };
573
41

            
574
41
        // Validate and deliver the message
575
41
        match T::ValidateMessage::validate(&outbound_message) {
576
41
            Ok((ticket, _fee)) => {
577
41
                let message_id = ticket.message_id();
578
41
                if let Err(err) = T::OutboundQueue::deliver(ticket) {
579
                    log::error!(target: "ext_validators_slashes", "OutboundQueue delivery of message failed. {err:?}");
580
41
                } else {
581
41
                    Self::deposit_event(Event::SlashesMessageSent {
582
41
                        message_id,
583
41
                        slashes_command: command,
584
41
                    });
585
41
                }
586
            }
587
            Err(err) => {
588
                log::error!(target: "ext_validators_slashes", "OutboundQueue validation of message failed. {err:?}");
589
            }
590
        };
591

            
592
41
        slashes_count
593
3589
    }
594
}
595

            
596
/// A pending slash record. The value of the slash has been computed but not applied yet,
597
/// rather deferred for several eras.
598
#[derive(Encode, Decode, RuntimeDebug, TypeInfo, Clone, PartialEq)]
599
pub struct Slash<AccountId, SlashId> {
600
    /// external index identifying a given set of validators
601
    pub external_idx: u64,
602
    /// The stash ID of the offending validator.
603
    pub validator: AccountId,
604
    /// Reporters of the offence; bounty payout recipients.
605
    pub reporters: Vec<AccountId>,
606
    /// The amount of payout.
607
    pub slash_id: SlashId,
608
    pub percentage: Perbill,
609
    // Whether the slash is confirmed or still needs to go through deferred period
610
    pub confirmed: bool,
611
}
612

            
613
impl<AccountId, SlashId: One> Slash<AccountId, SlashId> {
614
    /// Initializes the default object using the given `validator`.
615
    pub fn default_from(validator: AccountId) -> Self {
616
        Self {
617
            external_idx: 0,
618
            validator,
619
            reporters: vec![],
620
            slash_id: One::one(),
621
            percentage: Perbill::from_percent(50),
622
            confirmed: false,
623
        }
624
    }
625
}
626

            
627
/// Computes a slash of a validator and nominators. It returns an unapplied
628
/// record to be applied at some later point. Slashing metadata is updated in storage,
629
/// since unapplied records are only rarely intended to be dropped.
630
///
631
/// The pending slash record returned does not have initialized reporters. Those have
632
/// to be set at a higher level, if any.
633
405
pub(crate) fn compute_slash<T: Config>(
634
405
    slash_fraction: Perbill,
635
405
    slash_id: T::SlashId,
636
405
    slash_era: EraIndex,
637
405
    stash: T::AccountId,
638
405
    slash_defer_duration: EraIndex,
639
405
    external_idx: u64,
640
405
) -> Option<Slash<T::AccountId, T::SlashId>> {
641
405
    let prior_slash_p = ValidatorSlashInEra::<T>::get(&slash_era, &stash).unwrap_or(Zero::zero());
642
405

            
643
405
    // compare slash proportions rather than slash values to avoid issues due to rounding
644
405
    // error.
645
405
    if slash_fraction.deconstruct() > prior_slash_p.deconstruct() {
646
404
        ValidatorSlashInEra::<T>::insert(&slash_era, &stash, &slash_fraction);
647
404
    } else {
648
        // we slash based on the max in era - this new event is not the max,
649
        // so neither the validator or any nominators will need an update.
650
        //
651
        // this does lead to a divergence of our system from the paper, which
652
        // pays out some reward even if the latest report is not max-in-era.
653
        // we opt to avoid the nominator lookups and edits and leave more rewards
654
        // for more drastic misbehavior.
655
1
        return None;
656
    }
657

            
658
404
    let confirmed = slash_defer_duration.is_zero();
659
404
    Some(Slash {
660
404
        external_idx,
661
404
        validator: stash.clone(),
662
404
        percentage: slash_fraction,
663
404
        slash_id,
664
404
        reporters: Vec::new(),
665
404
        confirmed,
666
404
    })
667
405
}
668

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