1use core::fmt;
2
3use crate::{
4 BlockNumber, RoundNumber, Validator, ValidatorSet, Signature, AggregateSignature,
5 SignatureScheme, BlockHash, Block, Blockchain, ValidRound, Data, Message, MessageError,
6};
7
8#[cfg(test)]
9mod tests;
10
11#[derive(Clone)]
17#[cfg_attr(test, derive(PartialEq, Eq))]
18#[cfg_attr(feature = "alloc", derive(borsh::BorshSerialize, borsh::BorshDeserialize))]
19pub(crate) enum EquivocatingData<S: Signature, A: AggregateSignature, H: BlockHash> {
20 Proposal {
21 first_valid_round: Option<ValidRound<A>>,
22 first_proposal: H,
23 second_valid_round: Option<ValidRound<A>>,
24 second_proposal: H,
25 },
26 Prevote {
27 first_block: Option<H>,
28 second_block: Option<H>,
29 },
30 Precommit {
31 first_block_and_precommit_signature: Option<(H, S)>,
32 second_block_and_precommit_signature: Option<(H, S)>,
33 },
34}
35
36impl<S: Signature, A: AggregateSignature, H: fmt::Debug + BlockHash> fmt::Debug
37 for EquivocatingData<S, A, H>
38{
39 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
40 match self {
41 Self::Proposal { first_valid_round, first_proposal, second_valid_round, second_proposal } => {
42 formatter
43 .debug_struct("EquivocatingData::Proposal")
44 .field("first_valid_round", first_valid_round)
45 .field("first_proposal", first_proposal)
46 .field("second_valid_round", second_valid_round)
47 .field("second_proposal", second_proposal)
48 .finish_non_exhaustive()
49 }
50 Self::Prevote { first_block, second_block } => formatter
51 .debug_struct("EquivocatingData::Prevote")
52 .field("first_block", first_block)
53 .field("second_block", second_block)
54 .finish_non_exhaustive(),
55 Self::Precommit {
56 first_block_and_precommit_signature,
57 second_block_and_precommit_signature,
58 } => formatter
59 .debug_struct("EquivocatingData::Precommit")
60 .field(
61 "first_block",
62 &first_block_and_precommit_signature.as_ref().map(|(block, _precommit_signature)| block),
63 )
64 .field(
65 "second_block",
66 &second_block_and_precommit_signature.as_ref().map(|(block, _precommit_signature)| block),
67 )
68 .finish_non_exhaustive(),
69 }
70 }
71}
72
73#[derive(Clone, Copy, PartialEq, Eq)]
80#[cfg_attr(test, derive(Debug))]
81pub(crate) struct OpaqueBlockHash<'hash>(&'hash [u8]);
82impl AsRef<[u8]> for OpaqueBlockHash<'_> {
83 fn as_ref(&self) -> &[u8] {
84 self.0
85 }
86}
87
88#[derive(Clone)]
93#[cfg_attr(test, derive(PartialEq, Eq))]
94#[repr(transparent)]
95pub(crate) struct StubBlock<'hash>(&'hash [u8]);
96
97#[cfg(test)]
98impl<'hash> StubBlock<'hash> {
99 pub(crate) fn from(hash: &'hash [u8]) -> Self {
100 Self(hash)
101 }
102}
103impl<'hash> Block for StubBlock<'hash> {
104 type Hash = OpaqueBlockHash<'hash>;
105 fn hash(&self) -> Self::Hash {
106 OpaqueBlockHash(self.0)
107 }
108}
109
110impl<S: Signature, A: AggregateSignature, H: BlockHash> EquivocatingData<S, A, H> {
111 fn split(&self) -> (Data<S, A, StubBlock<'_>>, Data<S, A, StubBlock<'_>>) {
112 match self {
113 EquivocatingData::Proposal {
114 first_valid_round,
115 first_proposal,
116 second_valid_round,
117 second_proposal,
118 } => (
119 Data::Proposal {
120 valid_round: first_valid_round.clone(),
121 proposal: StubBlock(first_proposal.as_ref()),
122 },
123 Data::Proposal {
124 valid_round: second_valid_round.clone(),
125 proposal: StubBlock(second_proposal.as_ref()),
126 },
127 ),
128 EquivocatingData::Prevote { first_block, second_block } => (
129 Data::Prevote { block: first_block.as_ref().map(|hash| OpaqueBlockHash(hash.as_ref())) },
130 Data::Prevote { block: second_block.as_ref().map(|hash| OpaqueBlockHash(hash.as_ref())) },
131 ),
132 EquivocatingData::Precommit {
133 first_block_and_precommit_signature,
134 second_block_and_precommit_signature,
135 } => (
136 Data::Precommit {
137 block_and_precommit_signature: first_block_and_precommit_signature
138 .as_ref()
139 .map(|(block, signature)| (OpaqueBlockHash(block.as_ref()), signature.clone())),
140 },
141 Data::Precommit {
142 block_and_precommit_signature: second_block_and_precommit_signature
143 .as_ref()
144 .map(|(block, signature)| (OpaqueBlockHash(block.as_ref()), signature.clone())),
145 },
146 ),
147 }
148 }
149}
150
151#[derive(Clone)]
153#[cfg_attr(test, derive(PartialEq, Eq))]
154#[cfg_attr(feature = "alloc", derive(borsh::BorshSerialize, borsh::BorshDeserialize))]
155pub(crate) enum Evidence<S: Signature, A: AggregateSignature, H: BlockHash> {
156 Equivocation { data: EquivocatingData<S, A, H>, first_signature: S, second_signature: S },
158 InvalidProposal { valid_round: Option<ValidRound<A>>, proposal: H, signature: S },
160 InvalidPrecommit { block: H, precommit_signature: S, signature: S },
162}
163
164impl<S: Signature, A: AggregateSignature, H: fmt::Debug + BlockHash> fmt::Debug
165 for Evidence<S, A, H>
166{
167 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
168 match self {
169 Self::Equivocation { data, first_signature: _, second_signature: _ } => {
170 formatter.debug_struct("Evidence::Equivocation").field("data", data).finish_non_exhaustive()
171 }
172 Self::InvalidProposal { valid_round, proposal, signature: _ } => formatter
173 .debug_struct("Evidence::InvalidProposal")
174 .field("valid_round", valid_round)
175 .field("proposal", proposal)
176 .finish_non_exhaustive(),
177 Self::InvalidPrecommit { block, precommit_signature: _, signature: _ } => formatter
178 .debug_struct("Evidence::InvalidPrecommit")
179 .field("block", block)
180 .finish_non_exhaustive(),
181 }
182 }
183}
184
185#[derive(Clone)]
189#[cfg_attr(test, derive(PartialEq, Eq))]
190#[cfg_attr(feature = "alloc", derive(borsh::BorshSerialize, borsh::BorshDeserialize))]
191pub struct SlashReason<S: Signature, A: AggregateSignature, H: BlockHash> {
192 pub(crate) block_number: BlockNumber,
193 pub(crate) round_number: RoundNumber,
194 pub(crate) evidence: Evidence<S, A, H>,
195}
196
197pub type SlashReasonFor<B> = SlashReason<
199 <<B as Blockchain>::SignatureScheme as SignatureScheme>::Signature,
200 <<B as Blockchain>::SignatureScheme as SignatureScheme>::AggregateSignature,
201 <<B as Blockchain>::Block as Block>::Hash,
202>;
203
204impl<S: Signature, A: AggregateSignature, H: fmt::Debug + BlockHash> fmt::Debug
205 for SlashReason<S, A, H>
206{
207 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
208 let Self { block_number, round_number, evidence } = self;
209 formatter
210 .debug_struct("SlashReason")
211 .field("block_number", block_number)
212 .field("round_number", round_number)
213 .field("evidence", evidence)
214 .finish()
215 }
216}
217
218#[derive(Clone, Debug)]
220pub struct InvalidReason;
221
222impl<S: Signature, A: AggregateSignature, H: BlockHash> SlashReason<S, A, H> {
223 pub(crate) fn equivocation<V: Validator, B: Block<Hash = H>>(
229 first_message: Message<V, S, A, B>,
230 second_message: Message<V, S, A, B>,
231 ) -> Option<Self> {
232 Some(Self {
233 block_number: first_message.block_number,
234 round_number: first_message.round_number,
235 evidence: Evidence::Equivocation {
236 data: match (first_message.data, second_message.data) {
237 (
238 Data::Proposal { valid_round: first_valid_round, proposal: first_proposal },
239 Data::Proposal { valid_round: second_valid_round, proposal: second_proposal },
240 ) => EquivocatingData::Proposal {
241 first_valid_round,
242 first_proposal: first_proposal.hash(),
243 second_valid_round,
244 second_proposal: second_proposal.hash(),
245 },
246 (Data::Prevote { block: first_block }, Data::Prevote { block: second_block }) => {
247 EquivocatingData::Prevote { first_block, second_block }
248 }
249 (
250 Data::Precommit { block_and_precommit_signature: first_block_and_precommit_signature },
251 Data::Precommit { block_and_precommit_signature: second_block_and_precommit_signature },
252 ) => EquivocatingData::Precommit {
253 first_block_and_precommit_signature,
254 second_block_and_precommit_signature,
255 },
256 _ => None?,
257 },
258 first_signature: first_message.signature,
259 second_signature: second_message.signature,
260 },
261 })
262 }
263
264 pub fn verify<V: Validator>(
266 &self,
267 genesis: impl AsRef<[u8]>,
268 validator_set: &(impl ?Sized + ValidatorSet<Validator = V>),
269 signature_scheme: &(impl ?Sized
270 + SignatureScheme<Validator = V, Signature = S, AggregateSignature = A>),
271 validator: V,
272 ) -> Result<(), InvalidReason> {
273 match &self.evidence {
274 Evidence::Equivocation { data, first_signature, second_signature } => {
275 let (data1, data2) = data.split();
276
277 if data1 == data2 {
279 Err(InvalidReason)?;
280 }
281
282 for (data, signature) in [(data1, first_signature), (data2, second_signature)] {
284 let verify_message = |data, signature| {
285 if !signature_scheme.verify(
286 &validator,
287 Message::<V, S, A, _>::signature_message(
288 genesis.as_ref(),
289 self.block_number,
290 self.round_number,
291 &data,
292 ),
293 signature,
294 ) {
295 Err(InvalidReason)?;
300 }
301
302 Ok(())
303 };
304
305 verify_message(data, signature)?;
306 }
307 }
308 Evidence::InvalidProposal { valid_round, proposal, signature } => {
309 if !matches!(
310 (Message {
311 validator,
312 block_number: self.block_number,
313 round_number: self.round_number,
314 data: Data::Proposal {
315 valid_round: valid_round.clone(),
316 proposal: StubBlock(proposal.as_ref()),
317 },
318 signature: signature.clone(),
319 })
320 .static_verificiation(genesis, validator_set, signature_scheme),
321 Err(MessageError::Invalid(SlashReason { .. }))
322 ) {
323 Err(InvalidReason)?;
324 }
325 }
326 Evidence::InvalidPrecommit { block, precommit_signature, signature } => {
327 if !matches!(
328 (Message::<_, _, _, StubBlock<'_>> {
329 validator,
330 block_number: self.block_number,
331 round_number: self.round_number,
332 data: Data::Precommit {
333 block_and_precommit_signature: Some((
334 StubBlock(block.as_ref()).hash(),
335 precommit_signature.clone()
336 )),
337 },
338 signature: signature.clone(),
339 })
340 .static_verificiation(genesis, validator_set, signature_scheme),
341 Err(MessageError::Invalid(SlashReason { .. }))
342 ) {
343 Err(InvalidReason)?;
344 }
345 }
346 }
347
348 Ok(())
349 }
350}