1
// Copyright (C) Parity Technologies (UK) Ltd.
2
// This file is part of Polkadot.
3

            
4
// Polkadot 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
// Polkadot 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 Polkadot.  If not, see <http://www.gnu.org/licenses/>.
16

            
17
//! Runtime component for handling disputes of parachain candidates.
18

            
19
use crate::{
20
	configuration, initializer::SessionChangeNotification, metrics::METRICS, session_info,
21
};
22
use alloc::{collections::btree_set::BTreeSet, vec::Vec};
23
use bitvec::{bitvec, order::Lsb0 as BitOrderLsb0};
24
use codec::{Decode, Encode};
25
use core::cmp::Ordering;
26
use frame_support::{ensure, weights::Weight};
27
use frame_system::pallet_prelude::*;
28
use polkadot_primitives::{
29
	byzantine_threshold, supermajority_threshold, ApprovalVote, ApprovalVoteMultipleCandidates,
30
	CandidateHash, CheckedDisputeStatementSet, CheckedMultiDisputeStatementSet, CompactStatement,
31
	ConsensusLog, DisputeState, DisputeStatement, DisputeStatementSet, ExplicitDisputeStatement,
32
	InvalidDisputeStatementKind, MultiDisputeStatementSet, SessionIndex, SigningContext,
33
	ValidDisputeStatementKind, ValidatorId, ValidatorIndex, ValidatorSignature,
34
};
35
use polkadot_runtime_metrics::get_current_time;
36
use scale_info::TypeInfo;
37
use sp_runtime::{
38
	traits::{AppVerify, One, Saturating, Zero},
39
	DispatchError, RuntimeDebug, SaturatedConversion,
40
};
41

            
42
#[cfg(test)]
43
#[allow(unused_imports)]
44
pub(crate) use self::tests::run_to_block;
45

            
46
pub mod slashing;
47
#[cfg(test)]
48
mod tests;
49

            
50
#[cfg(feature = "runtime-benchmarks")]
51
mod benchmarking;
52

            
53
pub mod migration;
54

            
55
const LOG_TARGET: &str = "runtime::disputes";
56

            
57
/// Whether the dispute is local or remote.
58
#[derive(Encode, Decode, Clone, PartialEq, Eq, RuntimeDebug, TypeInfo)]
59
pub enum DisputeLocation {
60
	Local,
61
	Remote,
62
}
63

            
64
/// The result of a dispute, whether the candidate is deemed valid (for) or invalid (against).
65
#[derive(Encode, Decode, Clone, PartialEq, Eq, RuntimeDebug, TypeInfo)]
66
pub enum DisputeResult {
67
	Valid,
68
	Invalid,
69
}
70

            
71
/// Reward hooks for disputes.
72
pub trait RewardValidators {
73
	// Give each validator a reward, likely small, for participating in the dispute.
74
	fn reward_dispute_statement(
75
		session: SessionIndex,
76
		validators: impl IntoIterator<Item = ValidatorIndex>,
77
	);
78
}
79

            
80
impl RewardValidators for () {
81
	fn reward_dispute_statement(_: SessionIndex, _: impl IntoIterator<Item = ValidatorIndex>) {}
82
}
83

            
84
/// Punishment hooks for disputes.
85
pub trait SlashingHandler<BlockNumber> {
86
	/// Punish a series of validators who were for an invalid parablock. This is
87
	/// expected to be a major punishment.
88
	fn punish_for_invalid(
89
		session: SessionIndex,
90
		candidate_hash: CandidateHash,
91
		losers: impl IntoIterator<Item = ValidatorIndex>,
92
		backers: impl IntoIterator<Item = ValidatorIndex>,
93
	);
94

            
95
	/// Punish a series of validators who were against a valid parablock. This
96
	/// is expected to be a minor punishment.
97
	fn punish_against_valid(
98
		session: SessionIndex,
99
		candidate_hash: CandidateHash,
100
		losers: impl IntoIterator<Item = ValidatorIndex>,
101
		backers: impl IntoIterator<Item = ValidatorIndex>,
102
	);
103

            
104
	/// Called by the initializer to initialize the slashing pallet.
105
	fn initializer_initialize(now: BlockNumber) -> Weight;
106

            
107
	/// Called by the initializer to finalize the slashing pallet.
108
	fn initializer_finalize();
109

            
110
	/// Called by the initializer to note that a new session has started.
111
	fn initializer_on_new_session(session_index: SessionIndex);
112
}
113

            
114
impl<BlockNumber> SlashingHandler<BlockNumber> for () {
115
	fn punish_for_invalid(
116
		_: SessionIndex,
117
		_: CandidateHash,
118
		_: impl IntoIterator<Item = ValidatorIndex>,
119
		_: impl IntoIterator<Item = ValidatorIndex>,
120
	) {
121
	}
122

            
123
	fn punish_against_valid(
124
		_: SessionIndex,
125
		_: CandidateHash,
126
		_: impl IntoIterator<Item = ValidatorIndex>,
127
		_: impl IntoIterator<Item = ValidatorIndex>,
128
	) {
129
	}
130

            
131
	fn initializer_initialize(_now: BlockNumber) -> Weight {
132
		Weight::zero()
133
	}
134

            
135
	fn initializer_finalize() {}
136

            
137
	fn initializer_on_new_session(_: SessionIndex) {}
138
}
139

            
140
/// Provide a `Ordering` for the two provided dispute statement sets according to the
141
/// following prioritization:
142
///  1. Prioritize local disputes over remote disputes
143
///  2. Prioritize older disputes over newer disputes
144
fn dispute_ordering_compare<T: DisputesHandler<BlockNumber>, BlockNumber: Ord>(
145
	a: &DisputeStatementSet,
146
	b: &DisputeStatementSet,
147
) -> Ordering
148
where
149
	T: ?Sized,
150
{
151
	let a_local_block =
152
		<T as DisputesHandler<BlockNumber>>::included_state(a.session, a.candidate_hash);
153
	let b_local_block =
154
		<T as DisputesHandler<BlockNumber>>::included_state(b.session, b.candidate_hash);
155
	match (a_local_block, b_local_block) {
156
		// Prioritize local disputes over remote disputes.
157
		(None, Some(_)) => Ordering::Greater,
158
		(Some(_), None) => Ordering::Less,
159
		// For local disputes, prioritize those that occur at an earlier height.
160
		(Some(a_height), Some(b_height)) =>
161
			a_height.cmp(&b_height).then_with(|| a.candidate_hash.cmp(&b.candidate_hash)),
162
		// Prioritize earlier remote disputes using session as rough proxy.
163
		(None, None) => {
164
			let session_ord = a.session.cmp(&b.session);
165
			if session_ord == Ordering::Equal {
166
				// sort by hash as last resort, to make below dedup work consistently
167
				a.candidate_hash.cmp(&b.candidate_hash)
168
			} else {
169
				session_ord
170
			}
171
		},
172
	}
173
}
174

            
175
/// Hook into disputes handling.
176
///
177
/// Allows decoupling parachains handling from disputes so that it can
178
/// potentially be disabled when instantiating a specific runtime.
179
pub trait DisputesHandler<BlockNumber: Ord> {
180
	/// Whether the chain is frozen, if the chain is frozen it will not accept
181
	/// any new parachain blocks for backing or inclusion.
182
	fn is_frozen() -> bool;
183

            
184
	/// Remove dispute statement duplicates and sort the non-duplicates based on
185
	/// local (lower indices) vs remotes (higher indices) and age (older with lower indices).
186
	///
187
	/// Returns `Ok(())` if no duplicates were present, `Err(())` otherwise.
188
	///
189
	/// Unsorted data does not change the return value, while the node side
190
	/// is generally expected to pass them in sorted.
191
194763
	fn deduplicate_and_sort_dispute_data(
192
194763
		statement_sets: &mut MultiDisputeStatementSet,
193
194763
	) -> Result<(), ()> {
194
194763
		// TODO: Consider trade-of to avoid `O(n * log(n))` average lookups of `included_state`
195
194763
		// TODO: instead make a single pass and store the values lazily.
196
194763
		// TODO: https://github.com/paritytech/polkadot/issues/4527
197
194763
		let n = statement_sets.len();
198
194763

            
199
194763
		statement_sets.sort_by(dispute_ordering_compare::<Self, BlockNumber>);
200
194763
		statement_sets
201
194763
			.dedup_by(|a, b| a.session == b.session && a.candidate_hash == b.candidate_hash);
202
194763

            
203
194763
		// if there were any duplicates, indicate that to the caller.
204
194763
		if n == statement_sets.len() {
205
194763
			Ok(())
206
		} else {
207
			Err(())
208
		}
209
194763
	}
210

            
211
	/// Filter a single dispute statement set.
212
	///
213
	/// Used in cases where more granular control is required, i.e. when
214
	/// accounting for maximum block weight.
215
	fn filter_dispute_data(
216
		statement_set: DisputeStatementSet,
217
		post_conclusion_acceptance_period: BlockNumber,
218
	) -> Option<CheckedDisputeStatementSet>;
219

            
220
	/// Handle sets of dispute statements corresponding to 0 or more candidates.
221
	/// Returns a vector of freshly created disputes.
222
	fn process_checked_multi_dispute_data(
223
		statement_sets: &CheckedMultiDisputeStatementSet,
224
	) -> Result<Vec<(SessionIndex, CandidateHash)>, DispatchError>;
225

            
226
	/// Note that the given candidate has been included.
227
	fn note_included(
228
		session: SessionIndex,
229
		candidate_hash: CandidateHash,
230
		included_in: BlockNumber,
231
	);
232

            
233
	/// Retrieve the included state of a given candidate in a particular session. If it
234
	/// returns `Some`, then we have a local dispute for the given `candidate_hash`.
235
	fn included_state(session: SessionIndex, candidate_hash: CandidateHash) -> Option<BlockNumber>;
236

            
237
	/// Whether the given candidate concluded invalid in a dispute with supermajority.
238
	fn concluded_invalid(session: SessionIndex, candidate_hash: CandidateHash) -> bool;
239

            
240
	/// Called by the initializer to initialize the disputes pallet.
241
	fn initializer_initialize(now: BlockNumber) -> Weight;
242

            
243
	/// Called by the initializer to finalize the disputes pallet.
244
	fn initializer_finalize();
245

            
246
	/// Called by the initializer to note that a new session has started.
247
	fn initializer_on_new_session(notification: &SessionChangeNotification<BlockNumber>);
248
}
249

            
250
impl<BlockNumber: Ord> DisputesHandler<BlockNumber> for () {
251
	fn is_frozen() -> bool {
252
		false
253
	}
254

            
255
	fn deduplicate_and_sort_dispute_data(
256
		statement_sets: &mut MultiDisputeStatementSet,
257
	) -> Result<(), ()> {
258
		statement_sets.clear();
259
		Ok(())
260
	}
261

            
262
	fn filter_dispute_data(
263
		_set: DisputeStatementSet,
264
		_post_conclusion_acceptance_period: BlockNumber,
265
	) -> Option<CheckedDisputeStatementSet> {
266
		None
267
	}
268

            
269
	fn process_checked_multi_dispute_data(
270
		_statement_sets: &CheckedMultiDisputeStatementSet,
271
	) -> Result<Vec<(SessionIndex, CandidateHash)>, DispatchError> {
272
		Ok(Vec::new())
273
	}
274

            
275
	fn note_included(
276
		_session: SessionIndex,
277
		_candidate_hash: CandidateHash,
278
		_included_in: BlockNumber,
279
	) {
280
	}
281

            
282
	fn included_state(
283
		_session: SessionIndex,
284
		_candidate_hash: CandidateHash,
285
	) -> Option<BlockNumber> {
286
		None
287
	}
288

            
289
	fn concluded_invalid(_session: SessionIndex, _candidate_hash: CandidateHash) -> bool {
290
		false
291
	}
292

            
293
	fn initializer_initialize(_now: BlockNumber) -> Weight {
294
		Weight::zero()
295
	}
296

            
297
	fn initializer_finalize() {}
298

            
299
	fn initializer_on_new_session(_notification: &SessionChangeNotification<BlockNumber>) {}
300
}
301

            
302
impl<T: Config> DisputesHandler<BlockNumberFor<T>> for pallet::Pallet<T>
303
where
304
	BlockNumberFor<T>: Ord,
305
{
306
194763
	fn is_frozen() -> bool {
307
194763
		pallet::Pallet::<T>::is_frozen()
308
194763
	}
309

            
310
	fn filter_dispute_data(
311
		set: DisputeStatementSet,
312
		post_conclusion_acceptance_period: BlockNumberFor<T>,
313
	) -> Option<CheckedDisputeStatementSet> {
314
		pallet::Pallet::<T>::filter_dispute_data(&set, post_conclusion_acceptance_period)
315
			.filter_statement_set(set)
316
	}
317

            
318
194763
	fn process_checked_multi_dispute_data(
319
194763
		statement_sets: &CheckedMultiDisputeStatementSet,
320
194763
	) -> Result<Vec<(SessionIndex, CandidateHash)>, DispatchError> {
321
194763
		pallet::Pallet::<T>::process_checked_multi_dispute_data(statement_sets)
322
194763
	}
323

            
324
	fn note_included(
325
		session: SessionIndex,
326
		candidate_hash: CandidateHash,
327
		included_in: BlockNumberFor<T>,
328
	) {
329
		pallet::Pallet::<T>::note_included(session, candidate_hash, included_in)
330
	}
331

            
332
	fn included_state(
333
		session: SessionIndex,
334
		candidate_hash: CandidateHash,
335
	) -> Option<BlockNumberFor<T>> {
336
		pallet::Pallet::<T>::included_state(session, candidate_hash)
337
	}
338

            
339
	fn concluded_invalid(session: SessionIndex, candidate_hash: CandidateHash) -> bool {
340
		pallet::Pallet::<T>::concluded_invalid(session, candidate_hash)
341
	}
342

            
343
194763
	fn initializer_initialize(now: BlockNumberFor<T>) -> Weight {
344
194763
		pallet::Pallet::<T>::initializer_initialize(now)
345
194763
	}
346

            
347
194763
	fn initializer_finalize() {
348
194763
		pallet::Pallet::<T>::initializer_finalize()
349
194763
	}
350

            
351
135234
	fn initializer_on_new_session(notification: &SessionChangeNotification<BlockNumberFor<T>>) {
352
135234
		pallet::Pallet::<T>::initializer_on_new_session(notification)
353
135234
	}
354
}
355

            
356
pub trait WeightInfo {
357
	fn force_unfreeze() -> Weight;
358
}
359

            
360
pub struct TestWeightInfo;
361
impl WeightInfo for TestWeightInfo {
362
	fn force_unfreeze() -> Weight {
363
		Weight::zero()
364
	}
365
}
366

            
367
pub use pallet::*;
368
137
#[frame_support::pallet]
369
pub mod pallet {
370
	use super::*;
371
	use frame_support::pallet_prelude::*;
372

            
373
	#[pallet::config]
374
	pub trait Config: frame_system::Config + configuration::Config + session_info::Config {
375
		type RuntimeEvent: From<Event<Self>> + IsType<<Self as frame_system::Config>::RuntimeEvent>;
376
		type RewardValidators: RewardValidators;
377
		type SlashingHandler: SlashingHandler<BlockNumberFor<Self>>;
378

            
379
		/// Weight information for extrinsics in this pallet.
380
		type WeightInfo: WeightInfo;
381
	}
382

            
383
	/// The in-code storage version.
384
	const STORAGE_VERSION: StorageVersion = StorageVersion::new(1);
385

            
386
554790
	#[pallet::pallet]
387
	#[pallet::without_storage_info]
388
	#[pallet::storage_version(STORAGE_VERSION)]
389
	pub struct Pallet<T>(_);
390

            
391
	/// The last pruned session, if any. All data stored by this module
392
	/// references sessions.
393
304908
	#[pallet::storage]
394
	pub(super) type LastPrunedSession<T> = StorageValue<_, SessionIndex>;
395

            
396
	/// All ongoing or concluded disputes for the last several sessions.
397
76227
	#[pallet::storage]
398
	pub(super) type Disputes<T: Config> = StorageDoubleMap<
399
		_,
400
		Twox64Concat,
401
		SessionIndex,
402
		Blake2_128Concat,
403
		CandidateHash,
404
		DisputeState<BlockNumberFor<T>>,
405
	>;
406

            
407
	/// Backing votes stored for each dispute.
408
	/// This storage is used for slashing.
409
76227
	#[pallet::storage]
410
	pub(super) type BackersOnDisputes<T: Config> = StorageDoubleMap<
411
		_,
412
		Twox64Concat,
413
		SessionIndex,
414
		Blake2_128Concat,
415
		CandidateHash,
416
		BTreeSet<ValidatorIndex>,
417
	>;
418

            
419
	/// All included blocks on the chain, as well as the block number in this chain that
420
	/// should be reverted back to if the candidate is disputed and determined to be invalid.
421
76227
	#[pallet::storage]
422
	pub(super) type Included<T: Config> = StorageDoubleMap<
423
		_,
424
		Twox64Concat,
425
		SessionIndex,
426
		Blake2_128Concat,
427
		CandidateHash,
428
		BlockNumberFor<T>,
429
	>;
430

            
431
	/// Whether the chain is frozen. Starts as `None`. When this is `Some`,
432
	/// the chain will not accept any new parachain blocks for backing or inclusion,
433
	/// and its value indicates the last valid block number in the chain.
434
	/// It can only be set back to `None` by governance intervention.
435
389526
	#[pallet::storage]
436
	pub type Frozen<T: Config> = StorageValue<_, Option<BlockNumberFor<T>>, ValueQuery>;
437

            
438
	#[pallet::event]
439
	#[pallet::generate_deposit(pub fn deposit_event)]
440
	pub enum Event<T: Config> {
441
		/// A dispute has been initiated. \[candidate hash, dispute location\]
442
		DisputeInitiated(CandidateHash, DisputeLocation),
443
		/// A dispute has concluded for or against a candidate.
444
		/// `\[para id, candidate hash, dispute result\]`
445
		DisputeConcluded(CandidateHash, DisputeResult),
446
		/// A dispute has concluded with supermajority against a candidate.
447
		/// Block authors should no longer build on top of this head and should
448
		/// instead revert the block at the given height. This should be the
449
		/// number of the child of the last known valid block in the chain.
450
		Revert(BlockNumberFor<T>),
451
	}
452

            
453
	#[pallet::error]
454
	pub enum Error<T> {
455
		/// Duplicate dispute statement sets provided.
456
		DuplicateDisputeStatementSets,
457
		/// Ancient dispute statement provided.
458
		AncientDisputeStatement,
459
		/// Validator index on statement is out of bounds for session.
460
		ValidatorIndexOutOfBounds,
461
		/// Invalid signature on statement.
462
		InvalidSignature,
463
		/// Validator vote submitted more than once to dispute.
464
		DuplicateStatement,
465
		/// A dispute where there are only votes on one side.
466
		SingleSidedDispute,
467
		/// A dispute vote from a malicious backer.
468
		MaliciousBacker,
469
		/// No backing votes were provides along dispute statements.
470
		MissingBackingVotes,
471
		/// Unconfirmed dispute statement sets provided.
472
		UnconfirmedDispute,
473
	}
474

            
475
552
	#[pallet::call]
476
	impl<T: Config> Pallet<T> {
477
		#[pallet::call_index(0)]
478
		#[pallet::weight(<T as Config>::WeightInfo::force_unfreeze())]
479
135
		pub fn force_unfreeze(origin: OriginFor<T>) -> DispatchResult {
480
135
			ensure_root(origin)?;
481
			Frozen::<T>::set(None);
482
			Ok(())
483
		}
484
	}
485
}
486

            
487
bitflags::bitflags! {
488
	#[derive(Default)]
489
	struct DisputeStateFlags: u8 {
490
		/// The byzantine threshold of `f + 1` votes (and hence participating validators) was reached.
491
		const CONFIRMED = 0b0001;
492
		/// Is the supermajority for validity of the dispute reached.
493
		const FOR_SUPERMAJORITY = 0b0010;
494
		/// Is the supermajority against the validity of the block reached.
495
		const AGAINST_SUPERMAJORITY = 0b0100;
496
		/// Is there f+1 against the validity of the block reached
497
		const AGAINST_BYZANTINE = 0b1000;
498
	}
499
}
500

            
501
impl DisputeStateFlags {
502
	fn from_state<BlockNumber>(state: &DisputeState<BlockNumber>) -> Self {
503
		// Only correct since `DisputeState` is _always_ initialized
504
		// with the validator set based on the session.
505
		let n = state.validators_for.len();
506

            
507
		let byzantine_threshold = byzantine_threshold(n);
508
		let supermajority_threshold = supermajority_threshold(n);
509

            
510
		let mut flags = DisputeStateFlags::default();
511
		let all_participants = state.validators_for.clone() | state.validators_against.clone();
512
		if all_participants.count_ones() > byzantine_threshold {
513
			flags |= DisputeStateFlags::CONFIRMED;
514
		}
515

            
516
		if state.validators_for.count_ones() >= supermajority_threshold {
517
			flags |= DisputeStateFlags::FOR_SUPERMAJORITY;
518
		}
519

            
520
		if state.validators_against.count_ones() > byzantine_threshold {
521
			flags |= DisputeStateFlags::AGAINST_BYZANTINE;
522
		}
523

            
524
		if state.validators_against.count_ones() >= supermajority_threshold {
525
			flags |= DisputeStateFlags::AGAINST_SUPERMAJORITY;
526
		}
527

            
528
		flags
529
	}
530
}
531

            
532
struct ImportSummary<BlockNumber> {
533
	/// The new state, with all votes imported.
534
	state: DisputeState<BlockNumber>,
535
	/// List of validators who backed the candidate being disputed.
536
	backers: BTreeSet<ValidatorIndex>,
537
	/// Validators to slash for being (wrongly) on the AGAINST side.
538
	slash_against: Vec<ValidatorIndex>,
539
	/// Validators to slash for being (wrongly) on the FOR side.
540
	slash_for: Vec<ValidatorIndex>,
541
	// New participants in the dispute.
542
	new_participants: bitvec::vec::BitVec<u8, BitOrderLsb0>,
543
	// Difference in state flags from previous.
544
	new_flags: DisputeStateFlags,
545
}
546

            
547
#[derive(RuntimeDebug, PartialEq, Eq)]
548
enum VoteImportError {
549
	/// Validator index was outside the range of valid validator indices in the given session.
550
	ValidatorIndexOutOfBounds,
551
	/// Found a duplicate statement in the dispute statement set.
552
	DuplicateStatement,
553
	/// Found an explicit valid statement after backing statement.
554
	/// Backers should not participate in explicit voting so this is
555
	/// only possible on malicious backers.
556
	MaliciousBacker,
557
}
558

            
559
#[derive(RuntimeDebug, Copy, Clone, PartialEq, Eq)]
560
enum VoteKind {
561
	/// A backing vote that is counted as "for" vote in dispute resolution.
562
	Backing,
563
	/// Either an approval vote or and explicit dispute "for" vote.
564
	ExplicitValid,
565
	/// An explicit dispute "against" vote.
566
	Invalid,
567
}
568

            
569
impl From<&DisputeStatement> for VoteKind {
570
	fn from(statement: &DisputeStatement) -> Self {
571
		if statement.is_backing() {
572
			Self::Backing
573
		} else if statement.indicates_validity() {
574
			Self::ExplicitValid
575
		} else {
576
			Self::Invalid
577
		}
578
	}
579
}
580

            
581
impl VoteKind {
582
	fn is_valid(&self) -> bool {
583
		match self {
584
			Self::Backing | Self::ExplicitValid => true,
585
			Self::Invalid => false,
586
		}
587
	}
588

            
589
	fn is_backing(&self) -> bool {
590
		match self {
591
			Self::Backing => true,
592
			Self::Invalid | Self::ExplicitValid => false,
593
		}
594
	}
595
}
596

            
597
impl<T: Config> From<VoteImportError> for Error<T> {
598
	fn from(e: VoteImportError) -> Self {
599
		match e {
600
			VoteImportError::ValidatorIndexOutOfBounds => Error::<T>::ValidatorIndexOutOfBounds,
601
			VoteImportError::DuplicateStatement => Error::<T>::DuplicateStatement,
602
			VoteImportError::MaliciousBacker => Error::<T>::MaliciousBacker,
603
		}
604
	}
605
}
606

            
607
/// A transport statement bit change for a single validator.
608
#[derive(RuntimeDebug, PartialEq, Eq)]
609
struct ImportUndo {
610
	/// The validator index to which to associate the statement import.
611
	validator_index: ValidatorIndex,
612
	/// The kind and direction of the vote.
613
	vote_kind: VoteKind,
614
	/// Has the validator participated before, i.e. in backing or
615
	/// with an opposing vote.
616
	new_participant: bool,
617
}
618

            
619
struct DisputeStateImporter<BlockNumber> {
620
	state: DisputeState<BlockNumber>,
621
	backers: BTreeSet<ValidatorIndex>,
622
	now: BlockNumber,
623
	new_participants: bitvec::vec::BitVec<u8, BitOrderLsb0>,
624
	pre_flags: DisputeStateFlags,
625
	pre_state: DisputeState<BlockNumber>,
626
	// The list of backing votes before importing the batch of votes. This field should be
627
	// initialized as empty on the first import of the dispute votes and should remain non-empty
628
	// afterwards.
629
	//
630
	// If a dispute has concluded and the candidate was found invalid, we may want to slash as many
631
	// backers as possible. This list allows us to slash these backers once their votes have been
632
	// imported post dispute conclusion.
633
	pre_backers: BTreeSet<ValidatorIndex>,
634
}
635

            
636
impl<BlockNumber: Clone> DisputeStateImporter<BlockNumber> {
637
	fn new(
638
		state: DisputeState<BlockNumber>,
639
		backers: BTreeSet<ValidatorIndex>,
640
		now: BlockNumber,
641
	) -> Self {
642
		let pre_flags = DisputeStateFlags::from_state(&state);
643
		let new_participants = bitvec::bitvec![u8, BitOrderLsb0; 0; state.validators_for.len()];
644
		// consistency checks
645
		for i in backers.iter() {
646
			debug_assert_eq!(state.validators_for.get(i.0 as usize).map(|b| *b), Some(true));
647
		}
648
		let pre_state = state.clone();
649
		let pre_backers = backers.clone();
650

            
651
		DisputeStateImporter {
652
			state,
653
			backers,
654
			now,
655
			new_participants,
656
			pre_flags,
657
			pre_state,
658
			pre_backers,
659
		}
660
	}
661

            
662
	fn import(
663
		&mut self,
664
		validator: ValidatorIndex,
665
		kind: VoteKind,
666
	) -> Result<ImportUndo, VoteImportError> {
667
		let (bits, other_bits) = if kind.is_valid() {
668
			(&mut self.state.validators_for, &mut self.state.validators_against)
669
		} else {
670
			(&mut self.state.validators_against, &mut self.state.validators_for)
671
		};
672

            
673
		// out of bounds or already participated
674
		match bits.get(validator.0 as usize).map(|b| *b) {
675
			None => return Err(VoteImportError::ValidatorIndexOutOfBounds),
676
			Some(true) => {
677
				// We allow backing statements to be imported after an
678
				// explicit "for" vote, but not the other way around.
679
				match (kind.is_backing(), self.backers.contains(&validator)) {
680
					(true, true) | (false, false) =>
681
						return Err(VoteImportError::DuplicateStatement),
682
					(false, true) => return Err(VoteImportError::MaliciousBacker),
683
					(true, false) => {},
684
				}
685
			},
686
			Some(false) => {},
687
		}
688

            
689
		// consistency check
690
		debug_assert!((validator.0 as usize) < self.new_participants.len());
691

            
692
		let mut undo =
693
			ImportUndo { validator_index: validator, vote_kind: kind, new_participant: false };
694

            
695
		bits.set(validator.0 as usize, true);
696
		if kind.is_backing() {
697
			let is_new = self.backers.insert(validator);
698
			// invariant check
699
			debug_assert!(is_new);
700
		}
701

            
702
		// New participants tracks those validators by index, which didn't appear on either
703
		// side of the dispute until now (so they make a first appearance).
704
		// To verify this we need to assure they also were not part of the opposing side before.
705
		if other_bits.get(validator.0 as usize).map_or(false, |b| !*b) {
706
			undo.new_participant = true;
707
			self.new_participants.set(validator.0 as usize, true);
708
		}
709

            
710
		Ok(undo)
711
	}
712

            
713
	/// Revert a done transaction.
714
	fn undo(&mut self, undo: ImportUndo) {
715
		if undo.vote_kind.is_valid() {
716
			self.state.validators_for.set(undo.validator_index.0 as usize, false);
717
		} else {
718
			self.state.validators_against.set(undo.validator_index.0 as usize, false);
719
		}
720

            
721
		if undo.vote_kind.is_backing() {
722
			self.backers.remove(&undo.validator_index);
723
		}
724

            
725
		if undo.new_participant {
726
			self.new_participants.set(undo.validator_index.0 as usize, false);
727
		}
728
	}
729

            
730
	/// Collect all dispute votes.
731
	fn finish(mut self) -> ImportSummary<BlockNumber> {
732
		let pre_flags = self.pre_flags;
733
		let post_flags = DisputeStateFlags::from_state(&self.state);
734

            
735
		let pre_post_contains = |flags| (pre_flags.contains(flags), post_flags.contains(flags));
736

            
737
		// 1. Check for FOR supermajority.
738
		let slash_against = match pre_post_contains(DisputeStateFlags::FOR_SUPERMAJORITY) {
739
			(false, true) => {
740
				if self.state.concluded_at.is_none() {
741
					self.state.concluded_at = Some(self.now.clone());
742
				}
743

            
744
				// provide AGAINST voters to slash.
745
				self.state
746
					.validators_against
747
					.iter_ones()
748
					.map(|i| ValidatorIndex(i as _))
749
					.collect()
750
			},
751
			(true, true) => {
752
				// provide new AGAINST voters to slash.
753
				self.state
754
					.validators_against
755
					.iter_ones()
756
					.filter(|i| self.pre_state.validators_against.get(*i).map_or(false, |b| !*b))
757
					.map(|i| ValidatorIndex(i as _))
758
					.collect()
759
			},
760
			(true, false) => {
761
				log::error!("Dispute statements are never removed. This is a bug");
762
				Vec::new()
763
			},
764
			(false, false) => Vec::new(),
765
		};
766

            
767
		// 2. Check for AGAINST supermajority.
768
		let slash_for = match pre_post_contains(DisputeStateFlags::AGAINST_SUPERMAJORITY) {
769
			(false, true) => {
770
				if self.state.concluded_at.is_none() {
771
					self.state.concluded_at = Some(self.now.clone());
772
				}
773

            
774
				// provide FOR voters to slash.
775
				self.state.validators_for.iter_ones().map(|i| ValidatorIndex(i as _)).collect()
776
			},
777
			(true, true) => {
778
				// provide new FOR voters to slash including new backers
779
				// who might have voted FOR before
780
				let new_backing_vote = |i: &ValidatorIndex| -> bool {
781
					!self.pre_backers.contains(i) && self.backers.contains(i)
782
				};
783
				self.state
784
					.validators_for
785
					.iter_ones()
786
					.filter(|i| {
787
						self.pre_state.validators_for.get(*i).map_or(false, |b| !*b) ||
788
							new_backing_vote(&ValidatorIndex(*i as _))
789
					})
790
					.map(|i| ValidatorIndex(i as _))
791
					.collect()
792
			},
793
			(true, false) => {
794
				log::error!("Dispute statements are never removed. This is a bug");
795
				Vec::new()
796
			},
797
			(false, false) => Vec::new(),
798
		};
799

            
800
		ImportSummary {
801
			state: self.state,
802
			backers: self.backers,
803
			slash_against,
804
			slash_for,
805
			new_participants: self.new_participants,
806
			new_flags: post_flags - pre_flags,
807
		}
808
	}
809
}
810

            
811
// A filter on a dispute statement set.
812
#[derive(PartialEq)]
813
#[cfg_attr(test, derive(Debug))]
814
enum StatementSetFilter {
815
	// Remove the entire dispute statement set.
816
	RemoveAll,
817
	// Remove the votes with given index from the statement set.
818
	RemoveIndices(Vec<usize>),
819
}
820

            
821
impl StatementSetFilter {
822
	fn filter_statement_set(
823
		self,
824
		mut statement_set: DisputeStatementSet,
825
	) -> Option<CheckedDisputeStatementSet> {
826
		match self {
827
			StatementSetFilter::RemoveAll => None,
828
			StatementSetFilter::RemoveIndices(mut indices) => {
829
				indices.sort();
830
				indices.dedup();
831

            
832
				// reverse order ensures correctness
833
				for index in indices.into_iter().rev() {
834
					// `swap_remove` guarantees linear complexity.
835
					statement_set.statements.swap_remove(index);
836
				}
837

            
838
				if statement_set.statements.is_empty() {
839
					None
840
				} else {
841
					// we just checked correctness when filtering.
842
					Some(CheckedDisputeStatementSet::unchecked_from_unchecked(statement_set))
843
				}
844
			},
845
		}
846
	}
847

            
848
	fn remove_index(&mut self, i: usize) {
849
		if let StatementSetFilter::RemoveIndices(ref mut indices) = *self {
850
			indices.push(i)
851
		}
852
	}
853
}
854

            
855
impl<T: Config> Pallet<T> {
856
	/// Called by the initializer to initialize the disputes module.
857
194763
	pub(crate) fn initializer_initialize(_now: BlockNumberFor<T>) -> Weight {
858
194763
		Weight::zero()
859
194763
	}
860

            
861
	/// Called by the initializer to finalize the disputes pallet.
862
194763
	pub(crate) fn initializer_finalize() {}
863

            
864
	/// Called by the initializer to note a new session in the disputes pallet.
865
135234
	pub(crate) fn initializer_on_new_session(
866
135234
		notification: &SessionChangeNotification<BlockNumberFor<T>>,
867
135234
	) {
868
135234
		let config = configuration::ActiveConfig::<T>::get();
869
135234

            
870
135234
		if notification.session_index <= config.dispute_period + 1 {
871
59007
			return
872
76227
		}
873
76227

            
874
76227
		let pruning_target = notification.session_index - config.dispute_period - 1;
875
76227

            
876
101636
		LastPrunedSession::<T>::mutate(|last_pruned| {
877
76227
			let to_prune = if let Some(last_pruned) = last_pruned {
878
73692
				*last_pruned + 1..=pruning_target
879
			} else {
880
2535
				pruning_target..=pruning_target
881
			};
882

            
883
152454
			for to_prune in to_prune {
884
76227
				// This should be small, as disputes are rare, so `None` is fine.
885
76227
				#[allow(deprecated)]
886
76227
				Disputes::<T>::remove_prefix(to_prune, None);
887
76227
				#[allow(deprecated)]
888
76227
				BackersOnDisputes::<T>::remove_prefix(to_prune, None);
889
76227

            
890
76227
				// This is larger, and will be extracted to the `shared` pallet for more proper
891
76227
				// pruning. TODO: https://github.com/paritytech/polkadot/issues/3469
892
76227
				#[allow(deprecated)]
893
76227
				Included::<T>::remove_prefix(to_prune, None);
894
76227
			}
895

            
896
76227
			*last_pruned = Some(pruning_target);
897
101636
		});
898
135234
	}
899

            
900
	/// Handle sets of dispute statements corresponding to 0 or more candidates.
901
	/// Returns a vector of freshly created disputes.
902
	///
903
	/// Assumes `statement_sets` were already de-duplicated.
904
	///
905
	/// # Warning
906
	///
907
	/// This functions modifies the state when failing. It is expected to be called in inherent,
908
	/// and to fail the extrinsic on error. As invalid inherents are not allowed, the dirty state
909
	/// is not committed.
910
194763
	pub(crate) fn process_checked_multi_dispute_data(
911
194763
		statement_sets: &CheckedMultiDisputeStatementSet,
912
194763
	) -> Result<Vec<(SessionIndex, CandidateHash)>, DispatchError> {
913
194763
		let config = configuration::ActiveConfig::<T>::get();
914
194763

            
915
194763
		let mut fresh = Vec::with_capacity(statement_sets.len());
916
194763
		for statement_set in statement_sets {
917
			let dispute_target = {
918
				let statement_set = statement_set.as_ref();
919
				(statement_set.session, statement_set.candidate_hash)
920
			};
921
			if Self::process_checked_dispute_data(
922
				statement_set,
923
				config.dispute_post_conclusion_acceptance_period,
924
			)? {
925
				fresh.push(dispute_target);
926
			}
927
		}
928

            
929
194763
		Ok(fresh)
930
194763
	}
931

            
932
	// Given a statement set, this produces a filter to be applied to the statement set.
933
	// It either removes the entire dispute statement set or some specific votes from it.
934
	//
935
	// Votes which are duplicate or already known by the chain are filtered out.
936
	// The entire set is removed if the dispute is both, ancient and concluded.
937
	// Disputes without enough votes to get confirmed are also filtered out.
938
	fn filter_dispute_data(
939
		set: &DisputeStatementSet,
940
		post_conclusion_acceptance_period: BlockNumberFor<T>,
941
	) -> StatementSetFilter {
942
		let mut filter = StatementSetFilter::RemoveIndices(Vec::new());
943

            
944
		// Dispute statement sets on any dispute which concluded
945
		// before this point are to be rejected.
946
		let now = frame_system::Pallet::<T>::block_number();
947
		let oldest_accepted = now.saturating_sub(post_conclusion_acceptance_period);
948

            
949
		// Load session info to access validators
950
		let session_info = match session_info::Sessions::<T>::get(set.session) {
951
			Some(s) => s,
952
			None => return StatementSetFilter::RemoveAll,
953
		};
954

            
955
		let config = configuration::ActiveConfig::<T>::get();
956

            
957
		let n_validators = session_info.validators.len();
958

            
959
		// Check for ancient.
960
		let dispute_state = {
961
			if let Some(dispute_state) = Disputes::<T>::get(&set.session, &set.candidate_hash) {
962
				if dispute_state.concluded_at.as_ref().map_or(false, |c| c < &oldest_accepted) {
963
					return StatementSetFilter::RemoveAll
964
				}
965

            
966
				dispute_state
967
			} else {
968
				// No state in storage, this indicates it's the first dispute statement set as well.
969
				DisputeState {
970
					validators_for: bitvec![u8, BitOrderLsb0; 0; n_validators],
971
					validators_against: bitvec![u8, BitOrderLsb0; 0; n_validators],
972
					start: now,
973
					concluded_at: None,
974
				}
975
			}
976
		};
977

            
978
		let backers =
979
			BackersOnDisputes::<T>::get(&set.session, &set.candidate_hash).unwrap_or_default();
980

            
981
		// Check and import all votes.
982
		let summary = {
983
			let mut importer = DisputeStateImporter::new(dispute_state, backers, now);
984
			for (i, (statement, validator_index, signature)) in set.statements.iter().enumerate() {
985
				// ensure the validator index is present in the session info
986
				// and the signature is valid
987
				let validator_public = match session_info.validators.get(*validator_index) {
988
					None => {
989
						filter.remove_index(i);
990
						continue
991
					},
992
					Some(v) => v,
993
				};
994

            
995
				let kind = VoteKind::from(statement);
996

            
997
				let undo = match importer.import(*validator_index, kind) {
998
					Ok(u) => u,
999
					Err(_) => {
						filter.remove_index(i);
						continue
					},
				};
				// Check signature after attempting import.
				//
				// Since we expect that this filter will be applied to
				// disputes long after they're concluded, 99% of the time,
				// the duplicate filter above will catch them before needing
				// to do a heavy signature check.
				//
				// This is only really important until the post-conclusion acceptance threshold
				// is reached, and then no part of this loop will be hit.
				if let Err(()) = check_signature(
					&validator_public,
					set.candidate_hash,
					set.session,
					statement,
					signature,
					// This is here to prevent malicious nodes of generating
					// `ValidDisputeStatementKind::ApprovalCheckingMultipleCandidates` before that
					// is enabled, via setting `max_approval_coalesce_count` in the parachain host
					// config.
					config.approval_voting_params.max_approval_coalesce_count > 1,
				) {
					log::warn!("Failed to check dispute signature");
					importer.undo(undo);
					filter.remove_index(i);
					continue
				};
			}
			importer.finish()
		};
		// Reject disputes which don't have at least one vote on each side.
		if summary.state.validators_for.count_ones() == 0 ||
			summary.state.validators_against.count_ones() == 0
		{
			return StatementSetFilter::RemoveAll
		}
		// Reject disputes containing less votes than needed for confirmation.
		if (summary.state.validators_for.clone() | &summary.state.validators_against).count_ones() <=
			byzantine_threshold(summary.state.validators_for.len())
		{
			return StatementSetFilter::RemoveAll
		}
		filter
	}
	/// Handle a set of dispute statements corresponding to a single candidate.
	///
	/// Fails if the dispute data is invalid. Returns a Boolean indicating whether the
	/// dispute is fresh.
	fn process_checked_dispute_data(
		set: &CheckedDisputeStatementSet,
		dispute_post_conclusion_acceptance_period: BlockNumberFor<T>,
	) -> Result<bool, DispatchError> {
		// Dispute statement sets on any dispute which concluded
		// before this point are to be rejected.
		let now = frame_system::Pallet::<T>::block_number();
		let oldest_accepted = now.saturating_sub(dispute_post_conclusion_acceptance_period);
		let set = set.as_ref();
		// Load session info to access validators
		let session_info = match session_info::Sessions::<T>::get(set.session) {
			Some(s) => s,
			None => return Err(Error::<T>::AncientDisputeStatement.into()),
		};
		let n_validators = session_info.validators.len();
		// Check for ancient.
		let (fresh, dispute_state) = {
			if let Some(dispute_state) = Disputes::<T>::get(&set.session, &set.candidate_hash) {
				ensure!(
					dispute_state.concluded_at.as_ref().map_or(true, |c| c >= &oldest_accepted),
					Error::<T>::AncientDisputeStatement,
				);
				(false, dispute_state)
			} else {
				(
					true,
					DisputeState {
						validators_for: bitvec![u8, BitOrderLsb0; 0; n_validators],
						validators_against: bitvec![u8, BitOrderLsb0; 0; n_validators],
						start: now,
						concluded_at: None,
					},
				)
			}
		};
		let backers =
			BackersOnDisputes::<T>::get(&set.session, &set.candidate_hash).unwrap_or_default();
		// Import all votes. They were pre-checked.
		let summary = {
			let mut importer = DisputeStateImporter::new(dispute_state, backers, now);
			for (statement, validator_index, _signature) in &set.statements {
				let kind = VoteKind::from(statement);
				importer.import(*validator_index, kind).map_err(Error::<T>::from)?;
			}
			importer.finish()
		};
		// Reject disputes which don't have at least one vote on each side.
		ensure!(
			summary.state.validators_for.count_ones() > 0 &&
				summary.state.validators_against.count_ones() > 0,
			Error::<T>::SingleSidedDispute,
		);
		// Reject disputes containing less votes than needed for confirmation.
		ensure!(
			(summary.state.validators_for.clone() | &summary.state.validators_against).count_ones() >
				byzantine_threshold(summary.state.validators_for.len()),
			Error::<T>::UnconfirmedDispute,
		);
		let backers = summary.backers;
		// Reject statements with no accompanying backing votes.
		ensure!(!backers.is_empty(), Error::<T>::MissingBackingVotes);
		BackersOnDisputes::<T>::insert(&set.session, &set.candidate_hash, backers.clone());
		// AUDIT: from now on, no error should be returned.
		let DisputeStatementSet { ref session, ref candidate_hash, .. } = set;
		let session = *session;
		let candidate_hash = *candidate_hash;
		if fresh {
			let is_local = Included::<T>::contains_key(&session, &candidate_hash);
			Self::deposit_event(Event::DisputeInitiated(
				candidate_hash,
				if is_local { DisputeLocation::Local } else { DisputeLocation::Remote },
			));
		}
		{
			if summary.new_flags.contains(DisputeStateFlags::FOR_SUPERMAJORITY) {
				Self::deposit_event(Event::DisputeConcluded(candidate_hash, DisputeResult::Valid));
			}
			// It is possible, although unexpected, for a dispute to conclude twice.
			// This would require f+1 validators to vote in both directions.
			// A dispute cannot conclude more than once in each direction.
			if summary.new_flags.contains(DisputeStateFlags::AGAINST_SUPERMAJORITY) {
				Self::deposit_event(Event::DisputeConcluded(
					candidate_hash,
					DisputeResult::Invalid,
				));
			}
		}
		// Reward statements.
		T::RewardValidators::reward_dispute_statement(
			session,
			summary.new_participants.iter_ones().map(|i| ValidatorIndex(i as _)),
		);
		// Slash participants on a losing side.
		{
			// a valid candidate, according to 2/3. Punish those on the 'against' side.
			T::SlashingHandler::punish_against_valid(
				session,
				candidate_hash,
				summary.slash_against,
				backers.clone(),
			);
			// an invalid candidate, according to 2/3. Punish those on the 'for' side.
			T::SlashingHandler::punish_for_invalid(
				session,
				candidate_hash,
				summary.slash_for,
				backers,
			);
		}
		Disputes::<T>::insert(&session, &candidate_hash, &summary.state);
		// Freeze if the INVALID votes against some local candidate are above the byzantine
		// threshold
		if summary.new_flags.contains(DisputeStateFlags::AGAINST_BYZANTINE) {
			if let Some(revert_to) = Included::<T>::get(&session, &candidate_hash) {
				Self::revert_and_freeze(revert_to);
			}
		}
		Ok(fresh)
	}
	#[allow(unused)]
	pub(crate) fn disputes() -> Vec<(SessionIndex, CandidateHash, DisputeState<BlockNumberFor<T>>)>
	{
		Disputes::<T>::iter().collect()
	}
	pub(crate) fn note_included(
		session: SessionIndex,
		candidate_hash: CandidateHash,
		included_in: BlockNumberFor<T>,
	) {
		if included_in.is_zero() {
			return
		}
		let revert_to = included_in - One::one();
		Included::<T>::insert(&session, &candidate_hash, revert_to);
		if let Some(state) = Disputes::<T>::get(&session, candidate_hash) {
			if has_supermajority_against(&state) {
				Self::revert_and_freeze(revert_to);
			}
		}
	}
	pub(crate) fn included_state(
		session: SessionIndex,
		candidate_hash: CandidateHash,
	) -> Option<BlockNumberFor<T>> {
		Included::<T>::get(session, candidate_hash)
	}
	pub(crate) fn concluded_invalid(session: SessionIndex, candidate_hash: CandidateHash) -> bool {
		Disputes::<T>::get(&session, &candidate_hash).map_or(false, |dispute| {
			// A dispute that has concluded with supermajority-against.
			has_supermajority_against(&dispute)
		})
	}
194763
	pub(crate) fn is_frozen() -> bool {
194763
		Frozen::<T>::get().is_some()
194763
	}
	pub(crate) fn revert_and_freeze(revert_to: BlockNumberFor<T>) {
		if Frozen::<T>::get().map_or(true, |last| last > revert_to) {
			Frozen::<T>::set(Some(revert_to));
			// The `Revert` log is about reverting a block, not reverting to a block.
			// If we want to revert to block X in the current chain, we need to revert
			// block X+1.
			let revert = revert_to + One::one();
			Self::deposit_event(Event::Revert(revert));
			frame_system::Pallet::<T>::deposit_log(
				ConsensusLog::Revert(revert.saturated_into()).into(),
			);
		}
	}
}
fn has_supermajority_against<BlockNumber>(dispute: &DisputeState<BlockNumber>) -> bool {
	let supermajority_threshold = supermajority_threshold(dispute.validators_against.len());
	dispute.validators_against.count_ones() >= supermajority_threshold
}
fn check_signature(
	validator_public: &ValidatorId,
	candidate_hash: CandidateHash,
	session: SessionIndex,
	statement: &DisputeStatement,
	validator_signature: &ValidatorSignature,
	approval_multiple_candidates_enabled: bool,
) -> Result<(), ()> {
	let payload = match statement {
		DisputeStatement::Valid(ValidDisputeStatementKind::Explicit) =>
			ExplicitDisputeStatement { valid: true, candidate_hash, session }.signing_payload(),
		DisputeStatement::Valid(ValidDisputeStatementKind::BackingSeconded(inclusion_parent)) =>
			CompactStatement::Seconded(candidate_hash).signing_payload(&SigningContext {
				session_index: session,
				parent_hash: *inclusion_parent,
			}),
		DisputeStatement::Valid(ValidDisputeStatementKind::BackingValid(inclusion_parent)) =>
			CompactStatement::Valid(candidate_hash).signing_payload(&SigningContext {
				session_index: session,
				parent_hash: *inclusion_parent,
			}),
		DisputeStatement::Valid(ValidDisputeStatementKind::ApprovalChecking) =>
			ApprovalVote(candidate_hash).signing_payload(session),
		DisputeStatement::Valid(ValidDisputeStatementKind::ApprovalCheckingMultipleCandidates(
			candidates,
		)) =>
			if approval_multiple_candidates_enabled && candidates.contains(&candidate_hash) {
				ApprovalVoteMultipleCandidates(candidates).signing_payload(session)
			} else {
				return Err(())
			},
		DisputeStatement::Invalid(InvalidDisputeStatementKind::Explicit) =>
			ExplicitDisputeStatement { valid: false, candidate_hash, session }.signing_payload(),
	};
	let start = get_current_time();
	let res =
		if validator_signature.verify(&payload[..], &validator_public) { Ok(()) } else { Err(()) };
	let end = get_current_time();
	METRICS.on_signature_check_complete(end.saturating_sub(start)); // ns
	res
}