Skip to main content

hotshot_new_protocol/vote/
accumulate.rs

1use std::{any::type_name, mem};
2
3use committable::Committable;
4use hotshot::types::SignatureKey;
5use hotshot_types::{
6    epoch_membership::EpochMembership,
7    message::UpgradeLock,
8    simple_vote::{HasEpoch, VersionedVoteData},
9    stake_table::StakeTableEntries,
10    traits::node_implementation::NodeType,
11    vote::{Certificate, Vote, VoteAccumulator},
12};
13use hotshot_utils::anytrace;
14use tracing::{info, warn};
15
16/// A [`VoteAccumulator`] that validates certificates before emitting them.
17///
18/// Votes are accumulated without checking their signatures. If the resulting
19/// certificate is invalid, votes with invalid signatures are discarded, the
20/// remaining ones are accumulated again, and every subsequent vote is verified
21/// before it is accumulated.
22pub struct CheckedAccumulator<T, V, C>
23where
24    T: NodeType,
25    V: Vote<T>,
26    C: Certificate<T, V::Commitment, Voteable = V::Commitment> + HasEpoch,
27{
28    accumulator: VoteAccumulator<T, V, C>,
29
30    /// Votes accumulated so far, kept for recovery from an invalid certificate.
31    votes: Vec<V>,
32
33    /// Verify votes before accumulating them?
34    ///
35    /// Set after an invalid certificate.
36    verify_votes: bool,
37
38    membership: EpochMembership<T>,
39
40    upgrade_lock: UpgradeLock<T>,
41}
42
43impl<T, V, C> CheckedAccumulator<T, V, C>
44where
45    T: NodeType,
46    V: Vote<T>,
47    C: Certificate<T, V::Commitment, Voteable = V::Commitment> + HasEpoch,
48{
49    pub fn new(membership: EpochMembership<T>, lock: UpgradeLock<T>) -> Self {
50        Self {
51            accumulator: VoteAccumulator::new(lock.clone()),
52            votes: Vec::new(),
53            verify_votes: false,
54            membership,
55            upgrade_lock: lock,
56        }
57    }
58
59    /// Accumulate a vote.
60    ///
61    /// Returns a valid certificate once enough votes have been collected.
62    pub fn add(&mut self, vote: V) -> Option<C> {
63        if self.verify_votes {
64            if !self.is_valid_vote(&vote) {
65                return None;
66            }
67            let cert = self
68                .accumulator
69                .accumulate(&vote, self.membership.clone())?;
70            debug_assert!(self.validate(&cert).is_ok());
71            info!(view = %cert.view_number(), cert = type_name::<C>(), "certificate formed");
72            return Some(cert);
73        }
74
75        let Some(cert) = self.accumulator.accumulate(&vote, self.membership.clone()) else {
76            self.votes.push(vote);
77            return None;
78        };
79
80        match self.validate(&cert) {
81            Ok(()) => {
82                info!(view = %cert.view_number(), cert = type_name::<C>(), "certificate formed");
83                Some(cert)
84            },
85            Err(err) => {
86                warn!(view = %cert.view_number(), %err, "invalid certificate formed");
87                self.votes.push(vote);
88                self.recover()
89            },
90        }
91    }
92
93    /// Discard votes with invalid signatures and accumulate the rest again.
94    fn recover(&mut self) -> Option<C> {
95        self.verify_votes = true;
96        self.accumulator.clear();
97        for vote in mem::take(&mut self.votes) {
98            if !self.is_valid_vote(&vote) {
99                continue;
100            }
101            if let Some(cert) = self.accumulator.accumulate(&vote, self.membership.clone()) {
102                debug_assert!(self.validate(&cert).is_ok());
103                info!(view = %cert.view_number(), cert = type_name::<C>(), "certificate formed");
104                return Some(cert);
105            }
106        }
107        None
108    }
109
110    /// Check the certificate's aggregate signature against the stake table.
111    fn validate(&self, cert: &C) -> anytrace::Result<()> {
112        let table = StakeTableEntries::from(C::stake_table(&self.membership));
113        let thresh = C::threshold(&self.membership);
114        cert.is_valid_cert(&table.0, thresh, &self.upgrade_lock)
115    }
116
117    /// Check the vote's signature.
118    fn is_valid_vote(&self, vote: &V) -> bool {
119        let commit = match VersionedVoteData::new(
120            vote.date().clone(),
121            vote.view_number(),
122            &self.upgrade_lock,
123        ) {
124            Ok(data) => data.commit(),
125            Err(err) => {
126                warn!(%err, "failed to generate versioned vote data");
127                return false;
128            },
129        };
130        let valid = vote
131            .signing_key()
132            .validate(&vote.signature(), commit.as_ref());
133        if !valid {
134            warn!(
135                view = %vote.view_number(),
136                cert = type_name::<C>(),
137                signer = %vote.signing_key(),
138                "invalid vote"
139            );
140        }
141        valid
142    }
143}