tendermint/
evidence.rs

1//! Evidence of malfeasance by validators (i.e. signing conflicting votes).
2
3use core::slice;
4
5use serde::{Deserialize, Serialize};
6use tendermint_proto::google::protobuf::Duration as RawDuration;
7use tendermint_proto::Protobuf;
8
9use crate::{
10    block::{signed_header::SignedHeader, Height},
11    error::Error,
12    prelude::*,
13    serializers, validator,
14    vote::Power,
15    Time, Vote,
16};
17
18/// Evidence of malfeasance by validators (i.e. signing conflicting votes or light client attack).
19#[derive(Clone, Debug, PartialEq, Eq)]
20pub enum Evidence {
21    /// Duplicate vote evidence
22    DuplicateVote(Box<DuplicateVoteEvidence>),
23
24    /// LightClient attack evidence
25    LightClientAttack(Box<LightClientAttackEvidence>),
26}
27
28impl From<LightClientAttackEvidence> for Evidence {
29    fn from(ev: LightClientAttackEvidence) -> Self {
30        Self::LightClientAttack(Box::new(ev))
31    }
32}
33
34impl From<DuplicateVoteEvidence> for Evidence {
35    fn from(ev: DuplicateVoteEvidence) -> Self {
36        Self::DuplicateVote(Box::new(ev))
37    }
38}
39
40/// Duplicate vote evidence
41#[derive(Clone, Debug, PartialEq, Eq)]
42pub struct DuplicateVoteEvidence {
43    pub vote_a: Vote,
44    pub vote_b: Vote,
45    pub total_voting_power: Power,
46    pub validator_power: Power,
47    pub timestamp: Time,
48}
49
50impl DuplicateVoteEvidence {
51    /// constructor
52    pub fn new(vote_a: Vote, vote_b: Vote) -> Result<Self, Error> {
53        if vote_a.height != vote_b.height {
54            return Err(Error::invalid_evidence());
55        }
56
57        // Todo: make more assumptions about what is considered a valid evidence for duplicate vote
58        Ok(Self {
59            vote_a,
60            vote_b,
61            total_voting_power: Default::default(),
62            validator_power: Default::default(),
63            timestamp: Time::unix_epoch(),
64        })
65    }
66
67    /// Get votes
68    pub fn votes(&self) -> (&Vote, &Vote) {
69        (&self.vote_a, &self.vote_b)
70    }
71}
72
73/// Conflicting block detected in light client attack
74#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
75pub struct ConflictingBlock {
76    pub signed_header: SignedHeader,
77    pub validator_set: validator::Set,
78}
79
80/// Light client attack evidence
81#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
82pub struct LightClientAttackEvidence {
83    pub conflicting_block: ConflictingBlock,
84    pub common_height: Height,
85    pub byzantine_validators: Vec<validator::Info>,
86    pub total_voting_power: Power,
87    pub timestamp: Time,
88}
89
90/// A list of `Evidence`.
91///
92/// <https://github.com/tendermint/spec/blob/d46cd7f573a2c6a2399fcab2cde981330aa63f37/spec/core/data_structures.md#evidencedata>
93#[derive(Clone, Debug, Default, PartialEq, Eq)]
94pub struct List(Vec<Evidence>);
95
96impl List {
97    /// Create a new evidence data collection
98    pub fn new<I>(into_evidence: I) -> List
99    where
100        I: Into<Vec<Evidence>>,
101    {
102        List(into_evidence.into())
103    }
104
105    /// Convert this evidence data into a vector
106    pub fn into_vec(self) -> Vec<Evidence> {
107        self.0
108    }
109
110    /// Iterate over the evidence data
111    pub fn iter(&self) -> slice::Iter<'_, Evidence> {
112        self.0.iter()
113    }
114}
115
116impl AsRef<[Evidence]> for List {
117    fn as_ref(&self) -> &[Evidence] {
118        &self.0
119    }
120}
121
122/// EvidenceParams determine how we handle evidence of malfeasance.
123///
124/// [Tendermint documentation](https://docs.tendermint.com/master/spec/core/data_structures.html#evidenceparams)
125#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
126pub struct Params {
127    /// Max age of evidence, in blocks.
128    #[serde(with = "serializers::from_str")]
129    pub max_age_num_blocks: u64,
130
131    /// Max age of evidence, in time.
132    ///
133    /// It should correspond with an app's "unbonding period" or other similar
134    /// mechanism for handling [Nothing-At-Stake attacks][nas].
135    ///
136    /// [nas]: https://github.com/ethereum/wiki/wiki/Proof-of-Stake-FAQ#what-is-the-nothing-at-stake-problem-and-how-can-it-be-fixed
137    pub max_age_duration: Duration,
138
139    /// This sets the maximum size of total evidence in bytes that can be
140    /// committed in a single block, and should fall comfortably under the max
141    /// block bytes. The default is 1048576 or 1MB.
142    #[serde(with = "serializers::from_str", default)]
143    pub max_bytes: i64,
144}
145
146// =============================================================================
147// Protobuf conversions
148// =============================================================================
149
150tendermint_pb_modules! {
151    use pb::types as raw;
152
153    use super::{List, LightClientAttackEvidence, DuplicateVoteEvidence, ConflictingBlock, Evidence, Params};
154    use crate::{error::Error, prelude::*};
155
156    impl Protobuf<raw::Evidence> for Evidence {}
157
158    impl TryFrom<raw::Evidence> for Evidence {
159        type Error = Error;
160
161        fn try_from(value: raw::Evidence) -> Result<Self, Self::Error> {
162            match value.sum.ok_or_else(Error::invalid_evidence)? {
163                raw::evidence::Sum::DuplicateVoteEvidence(ev) => {
164                    Ok(Evidence::DuplicateVote(Box::new(ev.try_into()?)))
165                },
166                raw::evidence::Sum::LightClientAttackEvidence(ev) => {
167                    Ok(Evidence::LightClientAttack(Box::new(ev.try_into()?)))
168                },
169            }
170        }
171    }
172
173    impl From<Evidence> for raw::Evidence {
174        fn from(value: Evidence) -> Self {
175            match value {
176                Evidence::DuplicateVote(ev) => raw::Evidence {
177                    sum: Some(raw::evidence::Sum::DuplicateVoteEvidence((*ev).into())),
178                },
179                Evidence::LightClientAttack(ev) => raw::Evidence {
180                    sum: Some(raw::evidence::Sum::LightClientAttackEvidence((*ev).into())),
181                },
182            }
183        }
184    }
185
186    impl Protobuf<raw::DuplicateVoteEvidence> for DuplicateVoteEvidence {}
187
188    impl TryFrom<raw::DuplicateVoteEvidence> for DuplicateVoteEvidence {
189        type Error = Error;
190
191        fn try_from(value: raw::DuplicateVoteEvidence) -> Result<Self, Self::Error> {
192            Ok(Self {
193                vote_a: value
194                    .vote_a
195                    .ok_or_else(Error::missing_evidence)?
196                    .try_into()?,
197                vote_b: value
198                    .vote_b
199                    .ok_or_else(Error::missing_evidence)?
200                    .try_into()?,
201                total_voting_power: value.total_voting_power.try_into()?,
202                validator_power: value.validator_power.try_into()?,
203                timestamp: value
204                    .timestamp
205                    .ok_or_else(Error::missing_timestamp)?
206                    .try_into()?,
207            })
208        }
209    }
210
211    impl From<DuplicateVoteEvidence> for raw::DuplicateVoteEvidence {
212        fn from(value: DuplicateVoteEvidence) -> Self {
213            raw::DuplicateVoteEvidence {
214                vote_a: Some(value.vote_a.into()),
215                vote_b: Some(value.vote_b.into()),
216                total_voting_power: value.total_voting_power.into(),
217                validator_power: value.total_voting_power.into(),
218                timestamp: Some(value.timestamp.into()),
219            }
220        }
221    }
222
223    impl Protobuf<raw::LightBlock> for ConflictingBlock {}
224
225    impl TryFrom<raw::LightBlock> for ConflictingBlock {
226        type Error = Error;
227
228        fn try_from(value: raw::LightBlock) -> Result<Self, Self::Error> {
229            Ok(ConflictingBlock {
230                signed_header: value
231                    .signed_header
232                    .ok_or_else(Error::missing_evidence)?
233                    .try_into()?,
234                validator_set: value
235                    .validator_set
236                    .ok_or_else(Error::missing_evidence)?
237                    .try_into()?,
238            })
239        }
240    }
241
242    impl From<ConflictingBlock> for raw::LightBlock {
243        fn from(value: ConflictingBlock) -> Self {
244            raw::LightBlock {
245                signed_header: Some(value.signed_header.into()),
246                validator_set: Some(value.validator_set.into()),
247            }
248        }
249    }
250
251    impl Protobuf<raw::LightClientAttackEvidence> for LightClientAttackEvidence {}
252
253    impl TryFrom<raw::LightClientAttackEvidence> for LightClientAttackEvidence {
254        type Error = Error;
255
256        fn try_from(ev: raw::LightClientAttackEvidence) -> Result<Self, Self::Error> {
257            Ok(LightClientAttackEvidence {
258                conflicting_block: ev
259                    .conflicting_block
260                    .ok_or_else(Error::missing_evidence)?
261                    .try_into()?,
262                common_height: ev.common_height.try_into()?,
263                byzantine_validators: ev
264                    .byzantine_validators
265                    .into_iter()
266                    .map(TryInto::try_into)
267                    .collect::<Result<Vec<_>, _>>()?,
268                total_voting_power: ev.total_voting_power.try_into()?,
269                timestamp: ev
270                    .timestamp
271                    .ok_or_else(Error::missing_timestamp)?
272                    .try_into()?,
273            })
274        }
275    }
276
277    impl From<LightClientAttackEvidence> for raw::LightClientAttackEvidence {
278        fn from(ev: LightClientAttackEvidence) -> Self {
279            raw::LightClientAttackEvidence {
280                conflicting_block: Some(ev.conflicting_block.into()),
281                common_height: ev.common_height.into(),
282                byzantine_validators: ev
283                    .byzantine_validators
284                    .into_iter()
285                    .map(Into::into)
286                    .collect(),
287                total_voting_power: ev.total_voting_power.into(),
288                timestamp: Some(ev.timestamp.into()),
289            }
290        }
291    }
292
293    impl Protobuf<raw::EvidenceList> for List {}
294
295    impl TryFrom<raw::EvidenceList> for List {
296        type Error = Error;
297        fn try_from(value: raw::EvidenceList) -> Result<Self, Self::Error> {
298            let evidence = value
299                .evidence
300                .into_iter()
301                .map(TryInto::try_into)
302                .collect::<Result<Vec<_>, _>>()?;
303            Ok(Self(evidence))
304        }
305    }
306
307    impl From<List> for raw::EvidenceList {
308        fn from(value: List) -> Self {
309            raw::EvidenceList {
310                evidence: value.0.into_iter().map(Into::into).collect(),
311            }
312        }
313    }
314
315    impl Protobuf<raw::EvidenceParams> for Params {}
316
317    impl TryFrom<raw::EvidenceParams> for Params {
318        type Error = Error;
319
320        fn try_from(value: raw::EvidenceParams) -> Result<Self, Self::Error> {
321            Ok(Self {
322                max_age_num_blocks: value
323                    .max_age_num_blocks
324                    .try_into()
325                    .map_err(Error::negative_max_age_num)?,
326                max_age_duration: value
327                    .max_age_duration
328                    .ok_or_else(Error::missing_max_age_duration)?
329                    .try_into()?,
330                max_bytes: value.max_bytes,
331            })
332        }
333    }
334
335    impl From<Params> for raw::EvidenceParams {
336        fn from(value: Params) -> Self {
337            Self {
338                // Todo: Implement proper domain types so this becomes infallible
339                max_age_num_blocks: value.max_age_num_blocks.try_into().unwrap(),
340                max_age_duration: Some(value.max_age_duration.into()),
341                max_bytes: value.max_bytes,
342            }
343        }
344    }
345}
346
347/// Duration is a wrapper around core::time::Duration
348/// essentially, to keep the usages look cleaner
349/// i.e. you can avoid using serde annotations everywhere
350/// Todo: harmonize google::protobuf::Duration, core::time::Duration and this. Too many structs.
351/// <https://github.com/informalsystems/tendermint-rs/issues/741>
352#[derive(Copy, Clone, Debug, Eq, PartialEq, Deserialize, Serialize)]
353pub struct Duration(#[serde(with = "serializers::time_duration")] pub core::time::Duration);
354
355impl From<Duration> for core::time::Duration {
356    fn from(d: Duration) -> core::time::Duration {
357        d.0
358    }
359}
360
361impl Protobuf<RawDuration> for Duration {}
362
363impl TryFrom<RawDuration> for Duration {
364    type Error = Error;
365
366    fn try_from(value: RawDuration) -> Result<Self, Self::Error> {
367        Ok(Self(core::time::Duration::new(
368            value.seconds.try_into().map_err(Error::integer_overflow)?,
369            value.nanos.try_into().map_err(Error::integer_overflow)?,
370        )))
371    }
372}
373
374impl From<Duration> for RawDuration {
375    fn from(value: Duration) -> Self {
376        // Todo: make the struct into a proper domaintype so this becomes infallible.
377        Self {
378            seconds: value.0.as_secs() as i64,
379            nanos: value.0.subsec_nanos() as i32,
380        }
381    }
382}