1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
// Copyright 2017-2022 Parity Technologies (UK) Ltd.
// This file is part of Polkadot.

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

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

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

use parity_scale_codec::{Decode, Encode};

/// Timestamp based on the 1 Jan 1970 UNIX base, which is persistent across node restarts and OS reboots.
pub type Timestamp = u64;

/// The status of dispute.
///
/// As managed by the dispute coordinator.
///
/// NOTE: This status is persisted to the database, any changes have to be versioned and a db
/// migration will be needed.
#[derive(Debug, Clone, Copy, Encode, Decode, PartialEq)]
pub enum DisputeStatus {
	/// The dispute is active and unconcluded.
	#[codec(index = 0)]
	Active,
	/// The dispute has been concluded in favor of the candidate
	/// since the given timestamp.
	#[codec(index = 1)]
	ConcludedFor(Timestamp),
	/// The dispute has been concluded against the candidate
	/// since the given timestamp.
	///
	/// This takes precedence over `ConcludedFor` in the case that
	/// both are true, which is impossible unless a large amount of
	/// validators are participating on both sides.
	#[codec(index = 2)]
	ConcludedAgainst(Timestamp),
	/// Dispute has been confirmed (more than `byzantine_threshold` have already participated/ or
	/// we have seen the candidate included already/participated successfully ourselves).
	#[codec(index = 3)]
	Confirmed,
}

impl DisputeStatus {
	/// Initialize the status to the active state.
	pub fn active() -> DisputeStatus {
		DisputeStatus::Active
	}

	/// Move status to confirmed status, if not yet concluded/confirmed already.
	pub fn confirm(self) -> DisputeStatus {
		match self {
			DisputeStatus::Active => DisputeStatus::Confirmed,
			DisputeStatus::Confirmed => DisputeStatus::Confirmed,
			DisputeStatus::ConcludedFor(_) | DisputeStatus::ConcludedAgainst(_) => self,
		}
	}

	/// Check whether the dispute is not a spam dispute.
	pub fn is_confirmed_concluded(&self) -> bool {
		match self {
			&DisputeStatus::Confirmed |
			&DisputeStatus::ConcludedFor(_) |
			DisputeStatus::ConcludedAgainst(_) => true,
			&DisputeStatus::Active => false,
		}
	}

	/// Concluded valid?
	pub fn has_concluded_for(&self) -> bool {
		match self {
			&DisputeStatus::ConcludedFor(_) => true,
			_ => false,
		}
	}
	/// Concluded invalid?
	pub fn has_concluded_against(&self) -> bool {
		match self {
			&DisputeStatus::ConcludedAgainst(_) => true,
			_ => false,
		}
	}

	/// Transition the status to a new status after observing the dispute has concluded for the candidate.
	/// This may be a no-op if the status was already concluded.
	pub fn conclude_for(self, now: Timestamp) -> DisputeStatus {
		match self {
			DisputeStatus::Active | DisputeStatus::Confirmed => DisputeStatus::ConcludedFor(now),
			DisputeStatus::ConcludedFor(at) => DisputeStatus::ConcludedFor(std::cmp::min(at, now)),
			against => against,
		}
	}

	/// Transition the status to a new status after observing the dispute has concluded against the candidate.
	/// This may be a no-op if the status was already concluded.
	pub fn conclude_against(self, now: Timestamp) -> DisputeStatus {
		match self {
			DisputeStatus::Active | DisputeStatus::Confirmed =>
				DisputeStatus::ConcludedAgainst(now),
			DisputeStatus::ConcludedFor(at) =>
				DisputeStatus::ConcludedAgainst(std::cmp::min(at, now)),
			DisputeStatus::ConcludedAgainst(at) =>
				DisputeStatus::ConcludedAgainst(std::cmp::min(at, now)),
		}
	}

	/// Whether the disputed candidate is possibly invalid.
	pub fn is_possibly_invalid(&self) -> bool {
		match self {
			DisputeStatus::Active |
			DisputeStatus::Confirmed |
			DisputeStatus::ConcludedAgainst(_) => true,
			DisputeStatus::ConcludedFor(_) => false,
		}
	}

	/// Yields the timestamp this dispute concluded at, if any.
	pub fn concluded_at(&self) -> Option<Timestamp> {
		match self {
			DisputeStatus::Active | DisputeStatus::Confirmed => None,
			DisputeStatus::ConcludedFor(at) | DisputeStatus::ConcludedAgainst(at) => Some(*at),
		}
	}
}

/// The choice here is fairly arbitrary. But any dispute that concluded more than a few minutes ago
/// is not worth considering anymore. Changing this value has little to no bearing on consensus,
/// and really only affects the work that the node might do on startup during periods of many
/// disputes.
pub const ACTIVE_DURATION_SECS: Timestamp = 180;

/// Returns true if the dispute has concluded for longer than [`ACTIVE_DURATION_SECS`].
pub fn dispute_is_inactive(status: &DisputeStatus, now: &Timestamp) -> bool {
	let at = status.concluded_at();

	at.is_some() && at.unwrap() + ACTIVE_DURATION_SECS < *now
}