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