hotshot/traits/election/
static_committee_leader_two_views.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
7use std::collections::{BTreeMap, BTreeSet};
8
9use alloy::primitives::U256;
10use anyhow::Context;
11use hotshot_types::{
12    drb::DrbResult,
13    stake_table::HSStakeTable,
14    traits::{
15        election::{Membership, NoStakeTableHash},
16        node_implementation::NodeType,
17        signature_key::{SignatureKey, StakeTableEntryType},
18    },
19    PeerConfig,
20};
21use hotshot_utils::anytrace::Result;
22
23use crate::{Arc, RwLock};
24
25#[derive(Clone, Debug, Eq, PartialEq, Hash)]
26
27/// The static committee election
28pub struct StaticCommitteeLeaderForTwoViews<T: NodeType> {
29    /// The nodes eligible for leadership.
30    /// NOTE: This is currently a hack because the DA leader needs to be the quorum
31    /// leader but without voting rights.
32    eligible_leaders: Vec<PeerConfig<T>>,
33
34    /// The nodes on the committee and their stake
35    stake_table: HSStakeTable<T>,
36
37    /// The nodes on the committee and their stake
38    da_stake_table: HSStakeTable<T>,
39
40    /// The nodes on the committee and their stake, indexed by public key
41    indexed_stake_table: BTreeMap<T::SignatureKey, PeerConfig<T>>,
42
43    /// The nodes on the committee and their stake, indexed by public key
44    indexed_da_stake_table: BTreeMap<T::SignatureKey, PeerConfig<T>>,
45
46    /// `DrbResult`s indexed by epoch
47    drb_results: BTreeMap<T::Epoch, DrbResult>,
48}
49
50impl<TYPES: NodeType> Membership<TYPES> for StaticCommitteeLeaderForTwoViews<TYPES> {
51    type Error = hotshot_utils::anytrace::Error;
52    type StakeTableHash = NoStakeTableHash;
53    /// Create a new election
54    fn new(committee_members: Vec<PeerConfig<TYPES>>, da_members: Vec<PeerConfig<TYPES>>) -> Self {
55        // For each eligible leader, get the stake table entry
56        let eligible_leaders: Vec<PeerConfig<TYPES>> = committee_members
57            .iter()
58            .filter(|&member| member.stake_table_entry.stake() > U256::ZERO)
59            .cloned()
60            .collect();
61
62        // For each member, get the stake table entry
63        let members: Vec<PeerConfig<TYPES>> = committee_members
64            .iter()
65            .filter(|&member| member.stake_table_entry.stake() > U256::ZERO)
66            .cloned()
67            .collect();
68
69        // For each member, get the stake table entry
70        let da_members: Vec<PeerConfig<TYPES>> = da_members
71            .iter()
72            .filter(|&member| member.stake_table_entry.stake() > U256::ZERO)
73            .cloned()
74            .collect();
75
76        // Index the stake table by public key
77        let indexed_stake_table: BTreeMap<TYPES::SignatureKey, PeerConfig<TYPES>> = members
78            .iter()
79            .map(|member| {
80                (
81                    TYPES::SignatureKey::public_key(&member.stake_table_entry),
82                    member.clone(),
83                )
84            })
85            .collect();
86
87        // Index the stake table by public key
88        let indexed_da_stake_table: BTreeMap<TYPES::SignatureKey, PeerConfig<TYPES>> = da_members
89            .iter()
90            .map(|member| {
91                (
92                    TYPES::SignatureKey::public_key(&member.stake_table_entry),
93                    member.clone(),
94                )
95            })
96            .collect();
97
98        Self {
99            eligible_leaders,
100            stake_table: members.into(),
101            da_stake_table: da_members.into(),
102            indexed_stake_table,
103            indexed_da_stake_table,
104            drb_results: BTreeMap::new(),
105        }
106    }
107
108    /// Get the stake table for the current view
109    fn stake_table(&self, _epoch: Option<<TYPES as NodeType>::Epoch>) -> HSStakeTable<TYPES> {
110        self.stake_table.clone()
111    }
112
113    /// Get the stake table for the current view
114    fn da_stake_table(&self, _epoch: Option<<TYPES as NodeType>::Epoch>) -> HSStakeTable<TYPES> {
115        self.da_stake_table.clone()
116    }
117
118    /// Get all members of the committee for the current view
119    fn committee_members(
120        &self,
121        _view_number: <TYPES as NodeType>::View,
122        _epoch: Option<<TYPES as NodeType>::Epoch>,
123    ) -> BTreeSet<<TYPES as NodeType>::SignatureKey> {
124        self.stake_table
125            .iter()
126            .map(|sc| TYPES::SignatureKey::public_key(&sc.stake_table_entry))
127            .collect()
128    }
129
130    /// Get all members of the committee for the current view
131    fn da_committee_members(
132        &self,
133        _view_number: <TYPES as NodeType>::View,
134        _epoch: Option<<TYPES as NodeType>::Epoch>,
135    ) -> BTreeSet<<TYPES as NodeType>::SignatureKey> {
136        self.da_stake_table
137            .iter()
138            .map(|da| TYPES::SignatureKey::public_key(&da.stake_table_entry))
139            .collect()
140    }
141
142    /// Get the stake table entry for a public key
143    fn stake(
144        &self,
145        pub_key: &<TYPES as NodeType>::SignatureKey,
146        _epoch: Option<<TYPES as NodeType>::Epoch>,
147    ) -> Option<PeerConfig<TYPES>> {
148        // Only return the stake if it is above zero
149        self.indexed_stake_table.get(pub_key).cloned()
150    }
151
152    /// Get DA the stake table entry for a public key
153    fn da_stake(
154        &self,
155        pub_key: &<TYPES as NodeType>::SignatureKey,
156        _epoch: Option<<TYPES as NodeType>::Epoch>,
157    ) -> Option<PeerConfig<TYPES>> {
158        // Only return the stake if it is above zero
159        self.indexed_da_stake_table.get(pub_key).cloned()
160    }
161
162    /// Check if a node has stake in the committee
163    fn has_stake(
164        &self,
165        pub_key: &<TYPES as NodeType>::SignatureKey,
166        _epoch: Option<<TYPES as NodeType>::Epoch>,
167    ) -> bool {
168        self.indexed_stake_table
169            .get(pub_key)
170            .is_some_and(|x| x.stake_table_entry.stake() > U256::ZERO)
171    }
172
173    /// Check if a node has stake in the committee
174    fn has_da_stake(
175        &self,
176        pub_key: &<TYPES as NodeType>::SignatureKey,
177        _epoch: Option<<TYPES as NodeType>::Epoch>,
178    ) -> bool {
179        self.indexed_da_stake_table
180            .get(pub_key)
181            .is_some_and(|x| x.stake_table_entry.stake() > U256::ZERO)
182    }
183
184    /// Index the vector of public keys with the current view number
185    fn lookup_leader(
186        &self,
187        view_number: <TYPES as NodeType>::View,
188        _epoch: Option<<TYPES as NodeType>::Epoch>,
189    ) -> Result<TYPES::SignatureKey> {
190        let index =
191            usize::try_from((*view_number / 2) % self.eligible_leaders.len() as u64).unwrap();
192        let res = self.eligible_leaders[index].clone();
193
194        Ok(TYPES::SignatureKey::public_key(&res.stake_table_entry))
195    }
196
197    /// Get the total number of nodes in the committee
198    fn total_nodes(&self, _epoch: Option<<TYPES as NodeType>::Epoch>) -> usize {
199        self.stake_table.len()
200    }
201
202    /// Get the total number of DA nodes in the committee
203    fn da_total_nodes(&self, _epoch: Option<<TYPES as NodeType>::Epoch>) -> usize {
204        self.da_stake_table.len()
205    }
206
207    /// Get the voting success threshold for the committee
208    fn success_threshold(&self, _epoch: Option<<TYPES as NodeType>::Epoch>) -> U256 {
209        U256::from(((self.stake_table.len() as u64 * 2) / 3) + 1)
210    }
211
212    /// Get the voting success threshold for the committee
213    fn da_success_threshold(&self, _epoch: Option<<TYPES as NodeType>::Epoch>) -> U256 {
214        U256::from(((self.da_stake_table.len() as u64 * 2) / 3) + 1)
215    }
216
217    /// Get the voting failure threshold for the committee
218    fn failure_threshold(&self, _epoch: Option<<TYPES as NodeType>::Epoch>) -> U256 {
219        U256::from(((self.stake_table.len() as u64) / 3) + 1)
220    }
221
222    /// Get the voting upgrade threshold for the committee
223    fn upgrade_threshold(&self, _epoch: Option<<TYPES as NodeType>::Epoch>) -> U256 {
224        U256::from(((self.stake_table.len() as u64 * 9) / 10) + 1)
225    }
226    fn has_stake_table(&self, _epoch: TYPES::Epoch) -> bool {
227        true
228    }
229    fn has_randomized_stake_table(&self, _epoch: TYPES::Epoch) -> anyhow::Result<bool> {
230        Ok(true)
231    }
232
233    fn add_drb_result(&mut self, epoch: <TYPES as NodeType>::Epoch, drb_result: DrbResult) {
234        self.drb_results.insert(epoch, drb_result);
235    }
236
237    async fn get_epoch_drb(
238        membership: Arc<RwLock<Self>>,
239        epoch: TYPES::Epoch,
240    ) -> anyhow::Result<DrbResult> {
241        let membership_reader = membership.read().await;
242
243        membership_reader
244            .drb_results
245            .get(&epoch)
246            .context("DRB result missing")
247            .copied()
248    }
249
250    fn set_first_epoch(&mut self, epoch: TYPES::Epoch, initial_drb_result: DrbResult) {
251        self.add_drb_result(epoch, initial_drb_result);
252        self.add_drb_result(epoch + 1, initial_drb_result);
253    }
254}