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}