referrerpolicy=no-referrer-when-downgrade

polkadot_node_primitives/disputes/
message.rs

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//! `DisputeMessage` and associated types.
18//!
19//! A `DisputeMessage` is a message that indicates a node participating in a dispute and is used
20//! for interfacing with `DisputeDistribution` to send out our vote in a spam detectable way.
21
22use thiserror::Error;
23
24use codec::{Decode, Encode};
25
26use super::{InvalidDisputeVote, SignedDisputeStatement, ValidDisputeVote};
27use polkadot_primitives::{
28	CandidateReceiptV2 as CandidateReceipt, DisputeStatement, SessionIndex, SessionInfo,
29	ValidatorIndex,
30};
31
32/// A dispute initiating/participating message that have been built from signed
33/// statements.
34///
35/// And most likely has been constructed correctly. This is used with
36/// `DisputeDistributionMessage::SendDispute` for sending out votes.
37///
38/// NOTE: This is sent over the wire, any changes are a change in protocol and need to be
39/// versioned.
40#[derive(Debug, Clone)]
41pub struct DisputeMessage(UncheckedDisputeMessage);
42
43/// A `DisputeMessage` where signatures of statements have not yet been checked.
44#[derive(Clone, Encode, Decode, Debug)]
45pub struct UncheckedDisputeMessage {
46	/// The candidate being disputed.
47	pub candidate_receipt: CandidateReceipt,
48
49	/// The session the candidate appears in.
50	pub session_index: SessionIndex,
51
52	/// The invalid vote data that makes up this dispute.
53	pub invalid_vote: InvalidDisputeVote,
54
55	/// The valid vote that makes this dispute request valid.
56	pub valid_vote: ValidDisputeVote,
57}
58
59/// Things that can go wrong when constructing a `DisputeMessage`.
60#[derive(Error, Debug)]
61pub enum Error {
62	/// The statements concerned different candidates.
63	#[error("Candidate hashes of the two votes did not match up")]
64	CandidateHashMismatch,
65
66	/// The statements concerned different sessions.
67	#[error("Session indices of the two votes did not match up")]
68	SessionIndexMismatch,
69
70	/// The valid statement validator key did not correspond to passed in `SessionInfo`.
71	#[error("Valid statement validator key did not match session information")]
72	InvalidValidKey,
73
74	/// The invalid statement validator key did not correspond to passed in `SessionInfo`.
75	#[error("Invalid statement validator key did not match session information")]
76	InvalidInvalidKey,
77
78	/// Provided receipt had different hash than the `CandidateHash` in the signed statements.
79	#[error("Hash of candidate receipt did not match provided hash")]
80	InvalidCandidateReceipt,
81
82	/// Valid statement should have `ValidDisputeStatementKind`.
83	#[error("Valid statement has kind `invalid`")]
84	ValidStatementHasInvalidKind,
85
86	/// Invalid statement should have `InvalidDisputeStatementKind`.
87	#[error("Invalid statement has kind `valid`")]
88	InvalidStatementHasValidKind,
89
90	/// Provided index could not be found in `SessionInfo`.
91	#[error("The valid statement had an invalid validator index")]
92	ValidStatementInvalidValidatorIndex,
93
94	/// Provided index could not be found in `SessionInfo`.
95	#[error("The invalid statement had an invalid validator index")]
96	InvalidStatementInvalidValidatorIndex,
97}
98
99impl DisputeMessage {
100	/// Build a `SignedDisputeMessage` and check what can be checked.
101	///
102	/// This function checks that:
103	///
104	/// - both statements concern the same candidate
105	/// - both statements concern the same session
106	/// - the invalid statement is indeed an invalid one
107	/// - the valid statement is indeed a valid one
108	/// - The passed `CandidateReceipt` has the correct hash (as signed in the statements).
109	/// - the given validator indices match with the given `ValidatorId`s in the statements, given a
110	///   `SessionInfo`.
111	///
112	/// We don't check whether the given `SessionInfo` matches the `SessionIndex` in the
113	/// statements, because we can't without doing a runtime query. Nevertheless this smart
114	/// constructor gives relative strong guarantees that the resulting `SignedDisputeStatement` is
115	/// valid and good.  Even the passed `SessionInfo` is most likely right if this function
116	/// returns `Some`, because otherwise the passed `ValidatorId`s in the `SessionInfo` at
117	/// their given index would very likely not match the `ValidatorId`s in the statements.
118	///
119	/// So in summary, this smart constructor should be smart enough to prevent from almost all
120	/// programming errors that one could realistically make here.
121	pub fn from_signed_statements(
122		valid_statement: SignedDisputeStatement,
123		valid_index: ValidatorIndex,
124		invalid_statement: SignedDisputeStatement,
125		invalid_index: ValidatorIndex,
126		candidate_receipt: CandidateReceipt,
127		session_info: &SessionInfo,
128	) -> Result<Self, Error> {
129		let candidate_hash = *valid_statement.candidate_hash();
130		// Check statements concern same candidate:
131		if candidate_hash != *invalid_statement.candidate_hash() {
132			return Err(Error::CandidateHashMismatch)
133		}
134
135		let session_index = valid_statement.session_index();
136		if session_index != invalid_statement.session_index() {
137			return Err(Error::SessionIndexMismatch)
138		}
139
140		let valid_id = session_info
141			.validators
142			.get(valid_index)
143			.ok_or(Error::ValidStatementInvalidValidatorIndex)?;
144		let invalid_id = session_info
145			.validators
146			.get(invalid_index)
147			.ok_or(Error::InvalidStatementInvalidValidatorIndex)?;
148
149		if valid_id != valid_statement.validator_public() {
150			return Err(Error::InvalidValidKey)
151		}
152
153		if invalid_id != invalid_statement.validator_public() {
154			return Err(Error::InvalidInvalidKey)
155		}
156
157		if candidate_receipt.hash() != candidate_hash {
158			return Err(Error::InvalidCandidateReceipt)
159		}
160
161		let valid_kind = match valid_statement.statement() {
162			DisputeStatement::Valid(v) => v,
163			_ => return Err(Error::ValidStatementHasInvalidKind),
164		};
165
166		let invalid_kind = match invalid_statement.statement() {
167			DisputeStatement::Invalid(v) => v,
168			_ => return Err(Error::InvalidStatementHasValidKind),
169		};
170
171		let valid_vote = ValidDisputeVote {
172			validator_index: valid_index,
173			signature: valid_statement.validator_signature().clone(),
174			kind: valid_kind.clone(),
175		};
176
177		let invalid_vote = InvalidDisputeVote {
178			validator_index: invalid_index,
179			signature: invalid_statement.validator_signature().clone(),
180			kind: *invalid_kind,
181		};
182
183		Ok(DisputeMessage(UncheckedDisputeMessage {
184			candidate_receipt,
185			session_index,
186			valid_vote,
187			invalid_vote,
188		}))
189	}
190
191	/// Read only access to the candidate receipt.
192	pub fn candidate_receipt(&self) -> &CandidateReceipt {
193		&self.0.candidate_receipt
194	}
195
196	/// Read only access to the `SessionIndex`.
197	pub fn session_index(&self) -> SessionIndex {
198		self.0.session_index
199	}
200
201	/// Read only access to the invalid vote.
202	pub fn invalid_vote(&self) -> &InvalidDisputeVote {
203		&self.0.invalid_vote
204	}
205
206	/// Read only access to the valid vote.
207	pub fn valid_vote(&self) -> &ValidDisputeVote {
208		&self.0.valid_vote
209	}
210}
211
212impl UncheckedDisputeMessage {
213	/// Try to recover the two signed dispute votes from an `UncheckedDisputeMessage`.
214	pub fn try_into_signed_votes(
215		self,
216		session_info: &SessionInfo,
217	) -> Result<
218		(
219			CandidateReceipt,
220			(SignedDisputeStatement, ValidatorIndex),
221			(SignedDisputeStatement, ValidatorIndex),
222		),
223		(),
224	> {
225		let Self { candidate_receipt, session_index, valid_vote, invalid_vote } = self;
226		let candidate_hash = candidate_receipt.hash();
227
228		let vote_valid = {
229			let ValidDisputeVote { validator_index, signature, kind } = valid_vote;
230			let validator_public = session_info.validators.get(validator_index).ok_or(())?.clone();
231
232			(
233				SignedDisputeStatement::new_checked(
234					DisputeStatement::Valid(kind),
235					candidate_hash,
236					session_index,
237					validator_public,
238					signature,
239				)?,
240				validator_index,
241			)
242		};
243
244		let vote_invalid = {
245			let InvalidDisputeVote { validator_index, signature, kind } = invalid_vote;
246			let validator_public = session_info.validators.get(validator_index).ok_or(())?.clone();
247
248			(
249				SignedDisputeStatement::new_checked(
250					DisputeStatement::Invalid(kind),
251					candidate_hash,
252					session_index,
253					validator_public,
254					signature,
255				)?,
256				validator_index,
257			)
258		};
259
260		Ok((candidate_receipt, vote_valid, vote_invalid))
261	}
262}
263
264impl From<DisputeMessage> for UncheckedDisputeMessage {
265	fn from(message: DisputeMessage) -> Self {
266		message.0
267	}
268}