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