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_lock::RwLock;
12use committable::{Commitment, Committable};
13use hotshot_utils::anytrace::Result;
14
15use super::node_implementation::NodeType;
16use crate::{
17    data::Leaf2, drb::DrbResult, stake_table::HSStakeTable,
18    traits::signature_key::StakeTableEntryType, PeerConfig,
19};
20
21pub struct NoStakeTableHash;
22
23impl Committable for NoStakeTableHash {
24    fn commit(&self) -> Commitment<Self> {
25        Commitment::from_raw([0u8; 32])
26    }
27}
28
29/// A protocol for determining membership in and participating in a committee.
30pub trait Membership<TYPES: NodeType>: Debug + Send + Sync {
31    /// The error type returned by methods like `lookup_leader`.
32    type Error: std::fmt::Display;
33
34    type StakeTableHash: Committable;
35    /// Create a committee
36    fn new(
37        // Note: eligible_leaders is currently a hack because the DA leader == the quorum leader
38        // but they should not have voting power.
39        stake_committee_members: Vec<PeerConfig<TYPES>>,
40        da_committee_members: Vec<PeerConfig<TYPES>>,
41    ) -> Self;
42
43    fn total_stake(&self, epoch: Option<TYPES::Epoch>) -> U256 {
44        self.stake_table(epoch)
45            .iter()
46            .fold(U256::ZERO, |acc, entry| {
47                acc + entry.stake_table_entry.stake()
48            })
49    }
50
51    fn total_da_stake(&self, epoch: Option<TYPES::Epoch>) -> U256 {
52        self.da_stake_table(epoch)
53            .iter()
54            .fold(U256::ZERO, |acc, entry| {
55                acc + entry.stake_table_entry.stake()
56            })
57    }
58
59    /// Get all participants in the committee (including their stake) for a specific epoch
60    fn stake_table(&self, epoch: Option<TYPES::Epoch>) -> HSStakeTable<TYPES>;
61
62    /// Get all participants in the committee (including their stake) for a specific epoch
63    fn da_stake_table(&self, epoch: Option<TYPES::Epoch>) -> HSStakeTable<TYPES>;
64
65    /// Get all participants in the committee for a specific view for a specific epoch
66    fn committee_members(
67        &self,
68        view_number: TYPES::View,
69        epoch: Option<TYPES::Epoch>,
70    ) -> BTreeSet<TYPES::SignatureKey>;
71
72    /// Get all participants in the committee for a specific view for a specific epoch
73    fn da_committee_members(
74        &self,
75        view_number: TYPES::View,
76        epoch: Option<TYPES::Epoch>,
77    ) -> BTreeSet<TYPES::SignatureKey>;
78
79    /// Get the stake table entry for a public key, returns `None` if the
80    /// key is not in the table for a specific epoch
81    fn stake(
82        &self,
83        pub_key: &TYPES::SignatureKey,
84        epoch: Option<TYPES::Epoch>,
85    ) -> Option<PeerConfig<TYPES>>;
86
87    /// Get the DA stake table entry for a public key, returns `None` if the
88    /// key is not in the table for a specific epoch
89    fn da_stake(
90        &self,
91        pub_key: &TYPES::SignatureKey,
92        epoch: Option<TYPES::Epoch>,
93    ) -> Option<PeerConfig<TYPES>>;
94
95    /// See if a node has stake in the committee in a specific epoch
96    fn has_stake(&self, pub_key: &TYPES::SignatureKey, epoch: Option<TYPES::Epoch>) -> bool;
97
98    /// See if a node has stake in the committee in a specific epoch
99    fn has_da_stake(&self, pub_key: &TYPES::SignatureKey, epoch: Option<TYPES::Epoch>) -> bool;
100
101    /// The leader of the committee for view `view_number` in `epoch`.
102    ///
103    /// Note: this function uses a HotShot-internal error type.
104    /// You should implement `lookup_leader`, rather than implementing this function directly.
105    ///
106    /// # Errors
107    /// Returns an error if the leader cannot be calculated.
108    fn leader(
109        &self,
110        view: TYPES::View,
111        epoch: Option<TYPES::Epoch>,
112    ) -> Result<TYPES::SignatureKey> {
113        use hotshot_utils::anytrace::*;
114
115        self.lookup_leader(view, epoch).wrap().context(info!(
116            "Failed to get leader for view {view} in epoch {epoch:?}"
117        ))
118    }
119
120    /// The leader of the committee for view `view_number` in `epoch`.
121    ///
122    /// Note: There is no such thing as a DA leader, so any consumer
123    /// requiring a leader should call this.
124    ///
125    /// # Errors
126    /// Returns an error if the leader cannot be calculated
127    fn lookup_leader(
128        &self,
129        view: TYPES::View,
130        epoch: Option<TYPES::Epoch>,
131    ) -> std::result::Result<TYPES::SignatureKey, Self::Error>;
132
133    /// Returns the number of total nodes in the committee in an epoch `epoch`
134    fn total_nodes(&self, epoch: Option<TYPES::Epoch>) -> usize;
135
136    /// Returns the number of total DA nodes in the committee in an epoch `epoch`
137    fn da_total_nodes(&self, epoch: Option<TYPES::Epoch>) -> usize;
138
139    /// Returns the threshold for a specific `Membership` implementation
140    fn success_threshold(&self, epoch: Option<TYPES::Epoch>) -> U256;
141
142    /// Returns the DA threshold for a specific `Membership` implementation
143    fn da_success_threshold(&self, epoch: Option<TYPES::Epoch>) -> U256;
144
145    /// Returns the threshold for a specific `Membership` implementation
146    fn failure_threshold(&self, epoch: Option<TYPES::Epoch>) -> U256;
147
148    /// Returns the threshold required to upgrade the network protocol
149    fn upgrade_threshold(&self, epoch: Option<TYPES::Epoch>) -> U256;
150
151    /// Returns if the stake table is available for the given epoch
152    fn has_stake_table(&self, epoch: TYPES::Epoch) -> bool;
153
154    /// Returns if the randomized stake table is available for the given epoch
155    fn has_randomized_stake_table(&self, epoch: TYPES::Epoch) -> anyhow::Result<bool>;
156
157    /// Gets the validated block header and epoch number of the epoch root
158    /// at the given block height
159    fn get_epoch_root(
160        _membership: Arc<RwLock<Self>>,
161        _block_height: u64,
162        _epoch: TYPES::Epoch,
163    ) -> impl std::future::Future<Output = anyhow::Result<Leaf2<TYPES>>> + Send {
164        async move { anyhow::bail!("Not implemented") }
165    }
166
167    /// Gets the DRB result for the given epoch
168    fn get_epoch_drb(
169        _membership: Arc<RwLock<Self>>,
170        _epoch: TYPES::Epoch,
171    ) -> impl std::future::Future<Output = anyhow::Result<DrbResult>> + Send;
172
173    /// Handles notifications that a new epoch root has been created.
174    fn add_epoch_root(
175        _membership: Arc<RwLock<Self>>,
176        _epoch: TYPES::Epoch,
177        _block_header: TYPES::BlockHeader,
178    ) -> impl std::future::Future<Output = anyhow::Result<()>> + Send {
179        async { Ok(()) }
180    }
181
182    /// Called to notify the Membership when a new DRB result has been calculated.
183    /// Observes the same semantics as add_epoch_root
184    fn add_drb_result(&mut self, _epoch: TYPES::Epoch, _drb_result: DrbResult);
185
186    /// Called to notify the Membership that Epochs are enabled.
187    /// Implementations should copy the pre-epoch stake table into epoch and epoch+1
188    /// when this is called. The value of initial_drb_result should be used for DRB
189    /// calculations for epochs (epoch+1) and earlier.
190    fn set_first_epoch(&mut self, _epoch: TYPES::Epoch, _initial_drb_result: DrbResult);
191
192    /// Get first epoch if epochs are enabled, `None` otherwise
193    fn first_epoch(&self) -> Option<TYPES::Epoch> {
194        None
195    }
196
197    /// Returns the commitment of the stake table for the given epoch,
198    /// Errors if the stake table is not available for the given epoch
199    fn stake_table_hash(&self, _epoch: TYPES::Epoch) -> Option<Commitment<Self::StakeTableHash>> {
200        None
201    }
202}
203
204pub fn membership_spawn_add_epoch_root<TYPES: NodeType>(
205    membership: Arc<RwLock<impl Membership<TYPES> + 'static>>,
206    epoch: TYPES::Epoch,
207    block_header: TYPES::BlockHeader,
208) {
209    tokio::spawn(async move {
210        if let Err(e) = Membership::<TYPES>::add_epoch_root(membership, epoch, block_header).await {
211            tracing::error!("Failed to add epoch root for epoch {epoch}: {e}");
212        }
213    });
214}