casper_node/components/consensus/highway_core/highway/
vertex.rs

1use std::{collections::BTreeSet, fmt::Debug};
2
3use datasize::DataSize;
4use serde::{Deserialize, Deserializer, Serialize, Serializer};
5
6use casper_types::Timestamp;
7
8use crate::components::consensus::{
9    highway_core::{
10        endorsement::SignedEndorsement,
11        highway::{PingError, VertexError},
12        state::Panorama,
13    },
14    traits::{Context, ValidatorSecret},
15    utils::{ValidatorIndex, Validators},
16};
17
18#[allow(clippy::arithmetic_side_effects)]
19mod relaxed {
20    // This module exists solely to exempt the `EnumDiscriminants` macro generated code from the
21    // module-wide `clippy::arithmetic_side_effects` lint.
22
23    use casper_types::Timestamp;
24    use datasize::DataSize;
25    use serde::{Deserialize, Serialize};
26    use strum::EnumDiscriminants;
27
28    use crate::components::consensus::{
29        highway_core::evidence::Evidence, traits::Context, utils::ValidatorIndex,
30    };
31
32    use super::{Endorsements, Ping, SignedWireUnit};
33
34    /// A dependency of a `Vertex` that can be satisfied by one or more other vertices.
35    #[derive(
36        DataSize,
37        Clone,
38        Debug,
39        Eq,
40        PartialEq,
41        PartialOrd,
42        Ord,
43        Hash,
44        Serialize,
45        Deserialize,
46        EnumDiscriminants,
47    )]
48    #[serde(bound(
49        serialize = "C::Hash: Serialize",
50        deserialize = "C::Hash: Deserialize<'de>",
51    ))]
52    #[strum_discriminants(derive(strum::EnumIter))]
53    pub enum Dependency<C>
54    where
55        C: Context,
56    {
57        /// The hash of a unit.
58        Unit(C::Hash),
59        /// The index of the validator against which evidence is needed.
60        Evidence(ValidatorIndex),
61        /// The hash of the unit to be endorsed.
62        Endorsement(C::Hash),
63        /// The ping by a particular validator for a particular timestamp.
64        Ping(ValidatorIndex, Timestamp),
65    }
66
67    /// An element of the protocol state, that might depend on other elements.
68    ///
69    /// It is the vertex in a directed acyclic graph, whose edges are dependencies.
70    #[derive(
71        DataSize, Clone, Debug, Eq, PartialEq, Serialize, Deserialize, Hash, EnumDiscriminants,
72    )]
73    #[serde(bound(
74        serialize = "C::Hash: Serialize",
75        deserialize = "C::Hash: Deserialize<'de>",
76    ))]
77    #[strum_discriminants(derive(strum::EnumIter))]
78    pub enum Vertex<C>
79    where
80        C: Context,
81    {
82        /// A signed unit of the consensus DAG.
83        Unit(SignedWireUnit<C>),
84        /// Evidence of a validator's transgression.
85        Evidence(Evidence<C>),
86        /// Endorsements for a unit.
87        Endorsements(Endorsements<C>),
88        /// A ping conveying the activity of its creator.
89        Ping(Ping<C>),
90    }
91}
92pub use relaxed::{Dependency, DependencyDiscriminants, Vertex, VertexDiscriminants};
93
94impl<C: Context> Dependency<C> {
95    /// Returns whether this identifies a unit, as opposed to other types of vertices.
96    pub fn is_unit(&self) -> bool {
97        matches!(self, Dependency::Unit(_))
98    }
99}
100
101impl<C: Context> Vertex<C> {
102    /// Returns the consensus value mentioned in this vertex, if any.
103    ///
104    /// These need to be validated before passing the vertex into the protocol state. E.g. if
105    /// `C::ConsensusValue` is a transaction, it should be validated first (correct signature,
106    /// structure, gas limit, etc.). If it is a hash of a transaction, the transaction should be
107    /// obtained _and_ validated. Only after that, the vertex can be considered valid.
108    pub fn value(&self) -> Option<&C::ConsensusValue> {
109        match self {
110            Vertex::Unit(swunit) => swunit.wire_unit().value.as_ref(),
111            Vertex::Evidence(_) | Vertex::Endorsements(_) | Vertex::Ping(_) => None,
112        }
113    }
114
115    /// Returns the unit hash of this vertex (if it is a unit).
116    pub fn unit_hash(&self) -> Option<C::Hash> {
117        match self {
118            Vertex::Unit(swunit) => Some(swunit.hash()),
119            Vertex::Evidence(_) | Vertex::Endorsements(_) | Vertex::Ping(_) => None,
120        }
121    }
122
123    /// Returns the seq number of this vertex (if it is a unit).
124    pub fn unit_seq_number(&self) -> Option<u64> {
125        match self {
126            Vertex::Unit(swunit) => Some(swunit.wire_unit().seq_number),
127            _ => None,
128        }
129    }
130
131    /// Returns whether this is evidence, as opposed to other types of vertices.
132    pub fn is_evidence(&self) -> bool {
133        matches!(self, Vertex::Evidence(_))
134    }
135
136    /// Returns a `Timestamp` provided the vertex is a `Vertex::Unit` or `Vertex::Ping`.
137    pub fn timestamp(&self) -> Option<Timestamp> {
138        match self {
139            Vertex::Unit(signed_wire_unit) => Some(signed_wire_unit.wire_unit().timestamp),
140            Vertex::Ping(ping) => Some(ping.timestamp()),
141            Vertex::Evidence(_) | Vertex::Endorsements(_) => None,
142        }
143    }
144
145    /// Returns the creator of this vertex, if one is defined.
146    pub fn creator(&self) -> Option<ValidatorIndex> {
147        match self {
148            Vertex::Unit(signed_wire_unit) => Some(signed_wire_unit.wire_unit().creator),
149            Vertex::Ping(ping) => Some(ping.creator),
150            Vertex::Evidence(_) | Vertex::Endorsements(_) => None,
151        }
152    }
153
154    /// Returns the ID of this vertex.
155    pub fn id(&self) -> Dependency<C> {
156        match self {
157            Vertex::Unit(signed_wire_unit) => Dependency::Unit(signed_wire_unit.hash()),
158            Vertex::Evidence(evidence) => Dependency::Evidence(evidence.perpetrator()),
159            Vertex::Endorsements(endorsement) => Dependency::Endorsement(endorsement.unit),
160            Vertex::Ping(ping) => Dependency::Ping(ping.creator(), ping.timestamp()),
161        }
162    }
163
164    /// Returns a reference to the unit, or `None` if this is not a unit.
165    pub fn unit(&self) -> Option<&SignedWireUnit<C>> {
166        match self {
167            Vertex::Unit(signed_wire_unit) => Some(signed_wire_unit),
168            _ => None,
169        }
170    }
171
172    /// Returns true whether unit is a proposal.
173    pub fn is_proposal(&self) -> bool {
174        self.value().is_some()
175    }
176}
177
178mod specimen_support {
179    use super::{
180        Dependency, DependencyDiscriminants, Endorsements, HashedWireUnit, Ping, SignedEndorsement,
181        SignedWireUnit, Vertex, VertexDiscriminants, WireUnit,
182    };
183    use crate::{
184        components::consensus::ClContext,
185        utils::specimen::{
186            btree_set_distinct_from_prop, largest_variant, vec_prop_specimen, Cache,
187            LargestSpecimen, SizeEstimator,
188        },
189    };
190
191    impl LargestSpecimen for Vertex<ClContext> {
192        fn largest_specimen<E: SizeEstimator>(estimator: &E, cache: &mut Cache) -> Self {
193            largest_variant::<Self, VertexDiscriminants, _, _>(estimator, |variant| match variant {
194                VertexDiscriminants::Unit => {
195                    Vertex::Unit(LargestSpecimen::largest_specimen(estimator, cache))
196                }
197                VertexDiscriminants::Evidence => {
198                    Vertex::Evidence(LargestSpecimen::largest_specimen(estimator, cache))
199                }
200                VertexDiscriminants::Endorsements => {
201                    if estimator.parameter_bool("endorsements_enabled") {
202                        Vertex::Endorsements(LargestSpecimen::largest_specimen(estimator, cache))
203                    } else {
204                        Vertex::Ping(LargestSpecimen::largest_specimen(estimator, cache))
205                    }
206                }
207                VertexDiscriminants::Ping => {
208                    Vertex::Ping(LargestSpecimen::largest_specimen(estimator, cache))
209                }
210            })
211        }
212    }
213
214    impl LargestSpecimen for Dependency<ClContext> {
215        fn largest_specimen<E: SizeEstimator>(estimator: &E, cache: &mut Cache) -> Self {
216            largest_variant::<Self, DependencyDiscriminants, _, _>(estimator, |variant| {
217                match variant {
218                    DependencyDiscriminants::Unit => {
219                        Dependency::Unit(LargestSpecimen::largest_specimen(estimator, cache))
220                    }
221                    DependencyDiscriminants::Evidence => {
222                        Dependency::Evidence(LargestSpecimen::largest_specimen(estimator, cache))
223                    }
224                    DependencyDiscriminants::Endorsement => {
225                        Dependency::Endorsement(LargestSpecimen::largest_specimen(estimator, cache))
226                    }
227                    DependencyDiscriminants::Ping => Dependency::Ping(
228                        LargestSpecimen::largest_specimen(estimator, cache),
229                        LargestSpecimen::largest_specimen(estimator, cache),
230                    ),
231                }
232            })
233        }
234    }
235
236    impl LargestSpecimen for SignedWireUnit<ClContext> {
237        fn largest_specimen<E: SizeEstimator>(estimator: &E, cache: &mut Cache) -> Self {
238            SignedWireUnit {
239                hashed_wire_unit: LargestSpecimen::largest_specimen(estimator, cache),
240                signature: LargestSpecimen::largest_specimen(estimator, cache),
241            }
242        }
243    }
244
245    impl LargestSpecimen for Endorsements<ClContext> {
246        fn largest_specimen<E: SizeEstimator>(estimator: &E, cache: &mut Cache) -> Self {
247            Endorsements {
248                unit: LargestSpecimen::largest_specimen(estimator, cache),
249                endorsers: if estimator.parameter_bool("endorsements_enabled") {
250                    vec_prop_specimen(estimator, "validator_count", cache)
251                } else {
252                    Vec::new()
253                },
254            }
255        }
256    }
257
258    impl LargestSpecimen for SignedEndorsement<ClContext> {
259        fn largest_specimen<E: SizeEstimator>(estimator: &E, cache: &mut Cache) -> Self {
260            SignedEndorsement::new(
261                LargestSpecimen::largest_specimen(estimator, cache),
262                LargestSpecimen::largest_specimen(estimator, cache),
263            )
264        }
265    }
266
267    impl LargestSpecimen for Ping<ClContext> {
268        fn largest_specimen<E: SizeEstimator>(estimator: &E, cache: &mut Cache) -> Self {
269            Ping {
270                creator: LargestSpecimen::largest_specimen(estimator, cache),
271                timestamp: LargestSpecimen::largest_specimen(estimator, cache),
272                instance_id: LargestSpecimen::largest_specimen(estimator, cache),
273                signature: LargestSpecimen::largest_specimen(estimator, cache),
274            }
275        }
276    }
277
278    impl LargestSpecimen for HashedWireUnit<ClContext> {
279        fn largest_specimen<E: SizeEstimator>(estimator: &E, cache: &mut Cache) -> Self {
280            if let Some(item) = cache.get::<Self>() {
281                return item.clone();
282            }
283
284            let hash = LargestSpecimen::largest_specimen(estimator, cache);
285            let wire_unit = LargestSpecimen::largest_specimen(estimator, cache);
286            cache.set(HashedWireUnit { hash, wire_unit }).clone()
287        }
288    }
289
290    impl LargestSpecimen for WireUnit<ClContext> {
291        fn largest_specimen<E: SizeEstimator>(estimator: &E, cache: &mut Cache) -> Self {
292            WireUnit {
293                panorama: LargestSpecimen::largest_specimen(estimator, cache),
294                creator: LargestSpecimen::largest_specimen(estimator, cache),
295                instance_id: LargestSpecimen::largest_specimen(estimator, cache),
296                value: LargestSpecimen::largest_specimen(estimator, cache),
297                seq_number: LargestSpecimen::largest_specimen(estimator, cache),
298                timestamp: LargestSpecimen::largest_specimen(estimator, cache),
299                round_exp: LargestSpecimen::largest_specimen(estimator, cache),
300                endorsed: btree_set_distinct_from_prop(estimator, "validator_count", cache),
301            }
302        }
303    }
304}
305
306/// A `WireUnit` together with its hash and a cryptographic signature by its creator.
307#[derive(DataSize, Clone, Debug, Eq, PartialEq, Serialize, Deserialize, Hash)]
308#[serde(bound(
309    serialize = "C::Hash: Serialize",
310    deserialize = "C::Hash: Deserialize<'de>",
311))]
312pub struct SignedWireUnit<C>
313where
314    C: Context,
315{
316    pub(crate) hashed_wire_unit: HashedWireUnit<C>,
317    pub(crate) signature: C::Signature,
318}
319
320impl<C: Context> SignedWireUnit<C> {
321    pub(crate) fn new(
322        hashed_wire_unit: HashedWireUnit<C>,
323        secret_key: &C::ValidatorSecret,
324    ) -> Self {
325        let signature = secret_key.sign(&hashed_wire_unit.hash);
326        SignedWireUnit {
327            hashed_wire_unit,
328            signature,
329        }
330    }
331
332    /// Returns the inner `WireUnit`.
333    pub fn wire_unit(&self) -> &WireUnit<C> {
334        self.hashed_wire_unit.wire_unit()
335    }
336
337    /// Returns this unit's hash.
338    pub fn hash(&self) -> C::Hash {
339        self.hashed_wire_unit.hash()
340    }
341}
342
343/// A `WireUnit` together with its hash.
344#[derive(Clone, DataSize, Debug, Eq, PartialEq, Hash)]
345pub struct HashedWireUnit<C>
346where
347    C: Context,
348{
349    hash: C::Hash,
350    wire_unit: WireUnit<C>,
351}
352
353impl<C> HashedWireUnit<C>
354where
355    C: Context,
356{
357    /// Computes the unit's hash and creates a new `HashedWireUnit`.
358    pub(crate) fn new(wire_unit: WireUnit<C>) -> Self {
359        let hash = wire_unit.compute_hash();
360        Self::new_with_hash(wire_unit, hash)
361    }
362
363    /// Returns the inner `WireUnit`.
364    pub fn into_inner(self) -> WireUnit<C> {
365        self.wire_unit
366    }
367
368    /// Returns a reference to the inner `WireUnit`.
369    pub fn wire_unit(&self) -> &WireUnit<C> {
370        &self.wire_unit
371    }
372
373    /// Returns this unit's hash.
374    pub fn hash(&self) -> C::Hash {
375        self.hash
376    }
377
378    /// Creates a new `HashedWireUnit`. Make sure the `hash` is correct, and identical with the
379    /// result of `wire_unit.compute_hash`.
380    pub(crate) fn new_with_hash(wire_unit: WireUnit<C>, hash: C::Hash) -> Self {
381        HashedWireUnit { hash, wire_unit }
382    }
383}
384
385impl<C: Context> Serialize for HashedWireUnit<C> {
386    fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
387        self.wire_unit.serialize(serializer)
388    }
389}
390
391impl<'de, C: Context> Deserialize<'de> for HashedWireUnit<C> {
392    fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
393        Ok(HashedWireUnit::new(<_>::deserialize(deserializer)?))
394    }
395}
396
397/// A unit as it is sent over the wire, possibly containing a new block.
398#[derive(DataSize, Clone, Eq, PartialEq, Serialize, Deserialize, Hash)]
399#[serde(bound(
400    serialize = "C::Hash: Serialize",
401    deserialize = "C::Hash: Deserialize<'de>",
402))]
403pub struct WireUnit<C>
404where
405    C: Context,
406{
407    /// The panorama of cited units.
408    pub panorama: Panorama<C>,
409    /// The index of the creator of this unit.
410    pub creator: ValidatorIndex,
411    /// The consensus instance ID for which this unit was created.
412    pub instance_id: C::InstanceId,
413    /// The consensus value included in the unit, if any.
414    pub value: Option<C::ConsensusValue>,
415    /// The sequence number of this unit in the creator's swimlane.
416    pub seq_number: u64,
417    /// Timestamp of when the unit was created.
418    pub timestamp: Timestamp,
419    /// The current round exponent of the unit's creator.
420    pub round_exp: u8,
421    /// The units this unit endorses.
422    pub endorsed: BTreeSet<C::Hash>,
423}
424
425impl<C: Context> Debug for WireUnit<C> {
426    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
427        /// A type whose debug implementation prints ".." (without the quotes).
428        struct Ellipsis;
429
430        impl Debug for Ellipsis {
431            fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
432                write!(f, "..")
433            }
434        }
435
436        f.debug_struct("WireUnit")
437            .field("value", &self.value.as_ref().map(|_| Ellipsis))
438            .field("creator.0", &self.creator.0)
439            .field("instance_id", &self.instance_id)
440            .field("seq_number", &self.seq_number)
441            .field("timestamp", &self.timestamp.millis())
442            .field("panorama", self.panorama.as_ref())
443            .field("round_exp", &self.round_exp)
444            .field("endorsed", &self.endorsed)
445            .finish()
446    }
447}
448
449impl<C: Context> WireUnit<C> {
450    pub(crate) fn into_hashed(self) -> HashedWireUnit<C> {
451        HashedWireUnit::new(self)
452    }
453
454    /// Returns the creator's previous unit.
455    pub fn previous(&self) -> Option<&C::Hash> {
456        self.panorama[self.creator].correct()
457    }
458
459    /// Returns the unit's hash, which is used as a unit identifier.
460    fn compute_hash(&self) -> C::Hash {
461        // TODO: Use serialize_into to avoid allocation?
462        <C as Context>::hash(&bincode::serialize(self).expect("serialize WireUnit"))
463    }
464}
465
466/// A set of endorsements for a unit.
467#[derive(Clone, DataSize, Debug, Eq, PartialEq, Serialize, Deserialize, Hash)]
468#[serde(bound(
469    serialize = "C::Hash: Serialize",
470    deserialize = "C::Hash: Deserialize<'de>",
471))]
472pub struct Endorsements<C>
473where
474    C: Context,
475{
476    /// The endorsed unit.
477    pub unit: C::Hash,
478    /// The endorsements for the unit.
479    pub endorsers: Vec<(ValidatorIndex, C::Signature)>,
480}
481
482impl<C: Context> Endorsements<C> {
483    /// Returns hash of the endorsed vote.
484    pub fn unit(&self) -> &C::Hash {
485        &self.unit
486    }
487
488    /// Returns an iterator over validator indexes that endorsed the `unit`.
489    pub fn validator_ids(&self) -> impl Iterator<Item = ValidatorIndex> + '_ {
490        self.endorsers.iter().map(|(v, _)| *v)
491    }
492}
493
494impl<C: Context> From<SignedEndorsement<C>> for Endorsements<C> {
495    fn from(signed_e: SignedEndorsement<C>) -> Self {
496        Endorsements {
497            unit: *signed_e.unit(),
498            endorsers: vec![(signed_e.validator_idx(), *signed_e.signature())],
499        }
500    }
501}
502
503/// A ping sent by a validator to signal that it is online but has not created new units in a
504/// while.
505#[derive(Clone, DataSize, Debug, Eq, PartialEq, Serialize, Deserialize, Hash)]
506#[serde(bound(
507    serialize = "C::Hash: Serialize",
508    deserialize = "C::Hash: Deserialize<'de>",
509))]
510pub struct Ping<C>
511where
512    C: Context,
513{
514    creator: ValidatorIndex,
515    timestamp: Timestamp,
516    instance_id: C::InstanceId,
517    signature: C::Signature,
518}
519
520impl<C: Context> Ping<C> {
521    /// Creates a new signed ping.
522    pub(crate) fn new(
523        creator: ValidatorIndex,
524        timestamp: Timestamp,
525        instance_id: C::InstanceId,
526        sk: &C::ValidatorSecret,
527    ) -> Self {
528        let signature = sk.sign(&Self::hash(creator, timestamp, instance_id));
529        Ping {
530            creator,
531            timestamp,
532            instance_id,
533            signature,
534        }
535    }
536
537    /// The creator who signals that it is online.
538    pub fn creator(&self) -> ValidatorIndex {
539        self.creator
540    }
541
542    /// The timestamp when the ping was created.
543    pub fn timestamp(&self) -> Timestamp {
544        self.timestamp
545    }
546
547    /// Validates the ping and returns an error if it is not signed by the creator.
548    pub(crate) fn validate(
549        &self,
550        validators: &Validators<C::ValidatorId>,
551        our_instance_id: &C::InstanceId,
552    ) -> Result<(), VertexError> {
553        let Ping {
554            creator,
555            timestamp,
556            instance_id,
557            signature,
558        } = self;
559        if instance_id != our_instance_id {
560            return Err(PingError::InstanceId.into());
561        }
562        let v_id = validators.id(self.creator).ok_or(PingError::Creator)?;
563        let hash = Self::hash(*creator, *timestamp, *instance_id);
564        if !C::verify_signature(&hash, v_id, signature) {
565            return Err(PingError::Signature.into());
566        }
567        Ok(())
568    }
569
570    /// Computes the hash of a ping, i.e. of the creator and timestamp.
571    fn hash(creator: ValidatorIndex, timestamp: Timestamp, instance_id: C::InstanceId) -> C::Hash {
572        let bytes = bincode::serialize(&(creator, timestamp, instance_id)).expect("serialize Ping");
573        <C as Context>::hash(&bytes)
574    }
575}