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    data::Leaf2,
19    drb::DrbResult,
20    event::Event,
21    stake_table::HSStakeTable,
22    traits::{node_implementation::NodeImplementation, signature_key::StakeTableEntryType},
23    PeerConfig,
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<TYPES::Epoch>) -> 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<TYPES::Epoch>) -> 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<TYPES::Epoch>) -> 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<TYPES::Epoch>) -> 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: TYPES::View,
89        epoch: Option<TYPES::Epoch>,
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: TYPES::View,
96        epoch: Option<TYPES::Epoch>,
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<TYPES::Epoch>,
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<TYPES::Epoch>,
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<TYPES::Epoch>) -> 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<TYPES::Epoch>) -> 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(
129        &self,
130        view: TYPES::View,
131        epoch: Option<TYPES::Epoch>,
132    ) -> Result<TYPES::SignatureKey> {
133        use hotshot_utils::anytrace::*;
134
135        self.lookup_leader(view, epoch).wrap().context(info!(
136            "Failed to get leader for view {view} in epoch {epoch:?}"
137        ))
138    }
139
140    /// The leader of the committee for view `view_number` in `epoch`.
141    ///
142    /// Note: There is no such thing as a DA leader, so any consumer
143    /// requiring a leader should call this.
144    ///
145    /// # Errors
146    /// Returns an error if the leader cannot be calculated
147    fn lookup_leader(
148        &self,
149        view: TYPES::View,
150        epoch: Option<TYPES::Epoch>,
151    ) -> std::result::Result<TYPES::SignatureKey, Self::Error>;
152
153    /// Returns the number of total nodes in the committee in an epoch `epoch`
154    fn total_nodes(&self, epoch: Option<TYPES::Epoch>) -> usize;
155
156    /// Returns the number of total DA nodes in the committee in an epoch `epoch`
157    fn da_total_nodes(&self, epoch: Option<TYPES::Epoch>) -> usize;
158
159    /// Returns the threshold for a specific `Membership` implementation
160    fn success_threshold(&self, epoch: Option<<TYPES as NodeType>::Epoch>) -> U256 {
161        let total_stake = self.total_stake(epoch);
162        let one = U256::ONE;
163        let two = U256::from(2);
164        let three = U256::from(3);
165        if total_stake < U256::MAX / two {
166            ((total_stake * two) / three) + one
167        } else {
168            ((total_stake / three) * two) + two
169        }
170    }
171
172    /// Returns the DA threshold for a specific `Membership` implementation
173    fn da_success_threshold(&self, epoch: Option<<TYPES as NodeType>::Epoch>) -> U256 {
174        let total_stake = self.total_da_stake(epoch);
175        let one = U256::ONE;
176        let two = U256::from(2);
177        let three = U256::from(3);
178
179        if total_stake < U256::MAX / two {
180            ((total_stake * two) / three) + one
181        } else {
182            ((total_stake / three) * two) + two
183        }
184    }
185
186    /// Returns the threshold for a specific `Membership` implementation
187    fn failure_threshold(&self, epoch: Option<<TYPES as NodeType>::Epoch>) -> U256 {
188        let total_stake = self.total_stake(epoch);
189        let one = U256::ONE;
190        let three = U256::from(3);
191
192        (total_stake / three) + one
193    }
194
195    /// Returns the threshold required to upgrade the network protocol
196    fn upgrade_threshold(&self, epoch: Option<<TYPES as NodeType>::Epoch>) -> U256 {
197        let total_stake = self.total_stake(epoch);
198        let nine = U256::from(9);
199        let ten = U256::from(10);
200
201        let normal_threshold = self.success_threshold(epoch);
202        let higher_threshold = if total_stake < U256::MAX / nine {
203            (total_stake * nine) / ten
204        } else {
205            (total_stake / ten) * nine
206        };
207
208        std::cmp::max(higher_threshold, normal_threshold)
209    }
210
211    /// Returns if the stake table is available for the given epoch
212    fn has_stake_table(&self, epoch: TYPES::Epoch) -> bool;
213
214    /// Returns if the randomized stake table is available for the given epoch
215    fn has_randomized_stake_table(&self, epoch: TYPES::Epoch) -> anyhow::Result<bool>;
216
217    /// Gets the validated block header and epoch number of the epoch root
218    /// at the given block height
219    fn get_epoch_root(
220        _membership: Arc<RwLock<Self>>,
221        _block_height: u64,
222        _epoch: TYPES::Epoch,
223    ) -> impl std::future::Future<Output = anyhow::Result<Leaf2<TYPES>>> + Send;
224
225    /// Gets the DRB result for the given epoch
226    fn get_epoch_drb(
227        _membership: Arc<RwLock<Self>>,
228        _epoch: TYPES::Epoch,
229    ) -> impl std::future::Future<Output = anyhow::Result<DrbResult>> + Send;
230
231    /// Handles notifications that a new epoch root has been created.
232    fn add_epoch_root(
233        _membership: Arc<RwLock<Self>>,
234        _epoch: TYPES::Epoch,
235        _block_header: TYPES::BlockHeader,
236    ) -> impl std::future::Future<Output = anyhow::Result<()>> + Send;
237
238    /// Called to notify the Membership when a new DRB result has been calculated.
239    /// Observes the same semantics as add_epoch_root
240    fn add_drb_result(&mut self, _epoch: TYPES::Epoch, _drb_result: DrbResult);
241
242    /// Called to notify the Membership that Epochs are enabled.
243    /// Implementations should copy the pre-epoch stake table into epoch and epoch+1
244    /// when this is called. The value of initial_drb_result should be used for DRB
245    /// calculations for epochs (epoch+1) and earlier.
246    fn set_first_epoch(&mut self, _epoch: TYPES::Epoch, _initial_drb_result: DrbResult);
247
248    /// Get first epoch if epochs are enabled, `None` otherwise
249    fn first_epoch(&self) -> Option<TYPES::Epoch>;
250
251    /// Returns the commitment of the stake table for the given epoch,
252    /// Errors if the stake table is not available for the given epoch
253    fn stake_table_hash(&self, _epoch: TYPES::Epoch) -> Option<Commitment<Self::StakeTableHash>> {
254        None
255    }
256
257    fn add_da_committee(&mut self, _first_epoch: u64, _da_committee: Vec<PeerConfig<TYPES>>) {}
258}
259
260pub fn membership_spawn_add_epoch_root<TYPES: NodeType>(
261    membership: Arc<RwLock<impl Membership<TYPES> + 'static>>,
262    epoch: TYPES::Epoch,
263    block_header: TYPES::BlockHeader,
264) {
265    tokio::spawn(async move {
266        if let Err(e) = Membership::<TYPES>::add_epoch_root(membership, epoch, block_header).await {
267            tracing::error!("Failed to add epoch root for epoch {epoch}: {e}");
268        }
269    });
270}