hotshot_types/traits/
election.rs

1// Copyright (c) 2021-2024 Espresso Systems (espressosys.com)
2// This file is part of the HotShot repository.
3
4// You should have received a copy of the MIT License
5// along with the HotShot repository. If not, see <https://mit-license.org/>.
6
7//! The election trait, used to decide which node is the leader and determine if a vote is valid.
8use std::{collections::BTreeSet, fmt::Debug, sync::Arc};
9
10use alloy::primitives::U256;
11use async_broadcast::Receiver;
12use async_lock::RwLock;
13use committable::{Commitment, Committable};
14use hotshot_utils::anytrace::Result;
15
16use super::node_implementation::NodeType;
17use crate::{
18    PeerConfig,
19    data::{EpochNumber, Leaf2, ViewNumber},
20    drb::DrbResult,
21    event::Event,
22    stake_table::{HSStakeTable, supermajority_threshold},
23    traits::{node_implementation::NodeImplementation, signature_key::StakeTableEntryType},
24};
25
26pub struct NoStakeTableHash;
27
28impl Committable for NoStakeTableHash {
29    fn commit(&self) -> Commitment<Self> {
30        Commitment::from_raw([0u8; 32])
31    }
32}
33
34/// A protocol for determining membership in and participating in a committee.
35pub trait Membership<TYPES: NodeType>: Debug + Send + Sync {
36    /// The error type returned by methods like `lookup_leader`.
37    type Error: std::fmt::Display;
38
39    /// Storage type used by the underlying fetcher
40    type Storage;
41
42    type StakeTableHash: Committable;
43
44    /// Create a committee
45    fn new<I: NodeImplementation<TYPES>>(
46        // Note: eligible_leaders is currently a hack because the DA leader == the quorum leader
47        // but they should not have voting power.
48        stake_committee_members: Vec<PeerConfig<TYPES>>,
49        da_committee_members: Vec<PeerConfig<TYPES>>,
50        storage: Self::Storage,
51        network: Arc<<I as NodeImplementation<TYPES>>::Network>,
52        public_key: TYPES::SignatureKey,
53        epoch_height: u64,
54    ) -> Self;
55
56    fn set_external_channel(
57        &mut self,
58        _external_channel: Receiver<Event<TYPES>>,
59    ) -> impl std::future::Future<Output = ()> + Send {
60        async {}
61    }
62
63    fn total_stake(&self, epoch: Option<EpochNumber>) -> U256 {
64        self.stake_table(epoch)
65            .iter()
66            .fold(U256::ZERO, |acc, entry| {
67                acc + entry.stake_table_entry.stake()
68            })
69    }
70
71    fn total_da_stake(&self, epoch: Option<EpochNumber>) -> U256 {
72        self.da_stake_table(epoch)
73            .iter()
74            .fold(U256::ZERO, |acc, entry| {
75                acc + entry.stake_table_entry.stake()
76            })
77    }
78
79    /// Get all participants in the committee (including their stake) for a specific epoch
80    fn stake_table(&self, epoch: Option<EpochNumber>) -> HSStakeTable<TYPES>;
81
82    /// Get all participants in the committee (including their stake) for a specific epoch
83    fn da_stake_table(&self, epoch: Option<EpochNumber>) -> HSStakeTable<TYPES>;
84
85    /// Get all participants in the committee for a specific view for a specific epoch
86    fn committee_members(
87        &self,
88        view_number: ViewNumber,
89        epoch: Option<EpochNumber>,
90    ) -> BTreeSet<TYPES::SignatureKey>;
91
92    /// Get all participants in the committee for a specific view for a specific epoch
93    fn da_committee_members(
94        &self,
95        view_number: ViewNumber,
96        epoch: Option<EpochNumber>,
97    ) -> BTreeSet<TYPES::SignatureKey>;
98
99    /// Get the stake table entry for a public key, returns `None` if the
100    /// key is not in the table for a specific epoch
101    fn stake(
102        &self,
103        pub_key: &TYPES::SignatureKey,
104        epoch: Option<EpochNumber>,
105    ) -> Option<PeerConfig<TYPES>>;
106
107    /// Get the DA stake table entry for a public key, returns `None` if the
108    /// key is not in the table for a specific epoch
109    fn da_stake(
110        &self,
111        pub_key: &TYPES::SignatureKey,
112        epoch: Option<EpochNumber>,
113    ) -> Option<PeerConfig<TYPES>>;
114
115    /// See if a node has stake in the committee in a specific epoch
116    fn has_stake(&self, pub_key: &TYPES::SignatureKey, epoch: Option<EpochNumber>) -> bool;
117
118    /// See if a node has stake in the committee in a specific epoch
119    fn has_da_stake(&self, pub_key: &TYPES::SignatureKey, epoch: Option<EpochNumber>) -> bool;
120
121    /// The leader of the committee for view `view_number` in `epoch`.
122    ///
123    /// Note: this function uses a HotShot-internal error type.
124    /// You should implement `lookup_leader`, rather than implementing this function directly.
125    ///
126    /// # Errors
127    /// Returns an error if the leader cannot be calculated.
128    fn leader(&self, view: ViewNumber, epoch: Option<EpochNumber>) -> Result<TYPES::SignatureKey> {
129        use hotshot_utils::anytrace::*;
130
131        self.lookup_leader(view, epoch).wrap().context(info!(
132            "Failed to get leader for view {view} in epoch {epoch:?}"
133        ))
134    }
135
136    /// The leader of the committee for view `view_number` in `epoch`.
137    ///
138    /// Note: There is no such thing as a DA leader, so any consumer
139    /// requiring a leader should call this.
140    ///
141    /// # Errors
142    /// Returns an error if the leader cannot be calculated
143    fn lookup_leader(
144        &self,
145        view: ViewNumber,
146        epoch: Option<EpochNumber>,
147    ) -> std::result::Result<TYPES::SignatureKey, Self::Error>;
148
149    /// Returns the number of total nodes in the committee in an epoch `epoch`
150    fn total_nodes(&self, epoch: Option<EpochNumber>) -> usize;
151
152    /// Returns the number of total DA nodes in the committee in an epoch `epoch`
153    fn da_total_nodes(&self, epoch: Option<EpochNumber>) -> usize;
154
155    /// Returns the threshold for a specific `Membership` implementation
156    fn success_threshold(&self, epoch: Option<EpochNumber>) -> U256 {
157        let total_stake = self.total_stake(epoch);
158        supermajority_threshold(total_stake)
159    }
160
161    /// Returns the DA threshold for a specific `Membership` implementation
162    fn da_success_threshold(&self, epoch: Option<EpochNumber>) -> U256 {
163        let total_stake = self.total_da_stake(epoch);
164        let one = U256::ONE;
165        let two = U256::from(2);
166        let three = U256::from(3);
167
168        if total_stake < U256::MAX / two {
169            ((total_stake * two) / three) + one
170        } else {
171            ((total_stake / three) * two) + two
172        }
173    }
174
175    /// Returns the threshold for a specific `Membership` implementation
176    fn failure_threshold(&self, epoch: Option<EpochNumber>) -> U256 {
177        let total_stake = self.total_stake(epoch);
178        let one = U256::ONE;
179        let three = U256::from(3);
180
181        (total_stake / three) + one
182    }
183
184    /// Returns the threshold required to upgrade the network protocol
185    fn upgrade_threshold(&self, epoch: Option<EpochNumber>) -> U256 {
186        let total_stake = self.total_stake(epoch);
187        let nine = U256::from(9);
188        let ten = U256::from(10);
189
190        let normal_threshold = self.success_threshold(epoch);
191        let higher_threshold = if total_stake < U256::MAX / nine {
192            (total_stake * nine) / ten
193        } else {
194            (total_stake / ten) * nine
195        };
196
197        std::cmp::max(higher_threshold, normal_threshold)
198    }
199
200    /// Returns if the stake table is available for the given epoch
201    fn has_stake_table(&self, epoch: EpochNumber) -> bool;
202
203    /// Returns if the randomized stake table is available for the given epoch
204    fn has_randomized_stake_table(&self, epoch: EpochNumber) -> anyhow::Result<bool>;
205
206    /// Gets the validated block header and epoch number of the epoch root
207    /// at the given block height
208    fn get_epoch_root(
209        _membership: Arc<RwLock<Self>>,
210        _epoch: EpochNumber,
211    ) -> impl std::future::Future<Output = anyhow::Result<Leaf2<TYPES>>> + Send;
212
213    /// Gets the DRB result for the given epoch
214    fn get_epoch_drb(
215        _membership: Arc<RwLock<Self>>,
216        _epoch: EpochNumber,
217    ) -> impl std::future::Future<Output = anyhow::Result<DrbResult>> + Send;
218
219    /// Handles notifications that a new epoch root has been created.
220    fn add_epoch_root(
221        _membership: Arc<RwLock<Self>>,
222        _block_header: TYPES::BlockHeader,
223    ) -> impl std::future::Future<Output = anyhow::Result<()>> + Send;
224
225    /// Called to notify the Membership when a new DRB result has been calculated.
226    /// Observes the same semantics as add_epoch_root
227    fn add_drb_result(&mut self, _epoch: EpochNumber, _drb_result: DrbResult);
228
229    /// Called to notify the Membership that Epochs are enabled.
230    /// Implementations should copy the pre-epoch stake table into epoch and epoch+1
231    /// when this is called. The value of initial_drb_result should be used for DRB
232    /// calculations for epochs (epoch+1) and earlier.
233    fn set_first_epoch(&mut self, _epoch: EpochNumber, _initial_drb_result: DrbResult);
234
235    /// Get first epoch if epochs are enabled, `None` otherwise
236    fn first_epoch(&self) -> Option<EpochNumber>;
237
238    /// Returns the commitment of the stake table for the given epoch,
239    /// Errors if the stake table is not available for the given epoch
240    fn stake_table_hash(&self, _epoch: EpochNumber) -> Option<Commitment<Self::StakeTableHash>> {
241        None
242    }
243
244    fn add_da_committee(&mut self, _first_epoch: u64, _da_committee: Vec<PeerConfig<TYPES>>);
245}