hotshot_example_types/membership/
randomized_committee_members.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/>.
6use std::{
7    collections::{BTreeMap, BTreeSet},
8    marker::PhantomData,
9};
10
11use anyhow::Context;
12use hotshot_types::{
13    drb::DrbResult,
14    traits::signature_key::{
15        LCV1StateSignatureKey, LCV2StateSignatureKey, LCV3StateSignatureKey, SignatureKey,
16        StateSignatureKey,
17    },
18};
19use rand::{rngs::StdRng, Rng};
20use tracing::error;
21
22use crate::membership::{
23    helpers::QuorumFilterConfig,
24    stake_table::{TestDaCommittees, TestStakeTable, TestStakeTableEntry},
25};
26
27#[derive(Clone, Debug, Eq, PartialEq, Hash)]
28pub struct RandomizedCommitteeMembers<
29    PubKey: SignatureKey,
30    StatePubKey: StateSignatureKey + LCV1StateSignatureKey + LCV2StateSignatureKey + LCV3StateSignatureKey,
31    QuorumConfig: QuorumFilterConfig,
32    DaConfig: QuorumFilterConfig,
33> {
34    quorum_members: Vec<TestStakeTableEntry<PubKey, StatePubKey>>,
35
36    da_members: Vec<TestStakeTableEntry<PubKey, StatePubKey>>,
37
38    first_epoch: Option<u64>,
39
40    epochs: BTreeSet<u64>,
41
42    drb_results: BTreeMap<u64, DrbResult>,
43
44    da_committees: TestDaCommittees<PubKey, StatePubKey>,
45
46    _quorum_pd: PhantomData<QuorumConfig>,
47
48    _da_pd: PhantomData<DaConfig>,
49}
50
51impl<
52        PubKey: SignatureKey,
53        StatePubKey: StateSignatureKey + LCV1StateSignatureKey + LCV2StateSignatureKey + LCV3StateSignatureKey,
54        QuorumConfig: QuorumFilterConfig,
55        DaConfig: QuorumFilterConfig,
56    > RandomizedCommitteeMembers<PubKey, StatePubKey, QuorumConfig, DaConfig>
57{
58    /// Creates a set of indices into the stake_table which reference the nodes selected for this epoch's committee
59    fn make_quorum_filter(&self, epoch: u64) -> BTreeSet<usize> {
60        QuorumConfig::execute(epoch, self.quorum_members.len())
61    }
62
63    /// Creates a set of indices into the da_stake_table which reference the nodes selected for this epoch's da committee
64    fn make_da_quorum_filter(&self, epoch: u64) -> BTreeSet<usize> {
65        DaConfig::execute(epoch, self.da_members.len())
66    }
67
68    /// Writes the offsets used for the quorum filter and da_quorum filter to stdout
69    fn debug_display_offsets(&self) {
70        /// Ensures that the quorum filters are only displayed once
71        static START: std::sync::Once = std::sync::Once::new();
72
73        START.call_once(|| {
74            error!(
75                "{} offsets for Quorum filter:",
76                std::any::type_name::<QuorumConfig>()
77            );
78            for epoch in 1..=10 {
79                error!("  epoch {epoch}: {:?}", self.make_quorum_filter(epoch));
80            }
81
82            error!(
83                "{} offsets for DA Quorum filter:",
84                std::any::type_name::<DaConfig>()
85            );
86            for epoch in 1..=10 {
87                error!("  epoch {epoch}: {:?}", self.make_da_quorum_filter(epoch));
88            }
89        });
90    }
91}
92
93impl<
94        PubKey: SignatureKey,
95        StatePubKey: StateSignatureKey + LCV1StateSignatureKey + LCV2StateSignatureKey + LCV3StateSignatureKey,
96        QuorumConfig: QuorumFilterConfig,
97        DaConfig: QuorumFilterConfig,
98    > TestStakeTable<PubKey, StatePubKey>
99    for RandomizedCommitteeMembers<PubKey, StatePubKey, QuorumConfig, DaConfig>
100{
101    fn new(
102        quorum_members: Vec<TestStakeTableEntry<PubKey, StatePubKey>>,
103        da_members: Vec<TestStakeTableEntry<PubKey, StatePubKey>>,
104    ) -> Self {
105        let result = Self {
106            quorum_members,
107            da_members,
108            first_epoch: None,
109            epochs: BTreeSet::new(),
110            drb_results: BTreeMap::new(),
111            da_committees: TestDaCommittees::new(),
112            _quorum_pd: PhantomData,
113            _da_pd: PhantomData,
114        };
115
116        result.debug_display_offsets();
117
118        result
119    }
120
121    fn full_stake_table(&self) -> Vec<TestStakeTableEntry<PubKey, StatePubKey>> {
122        self.quorum_members.clone()
123    }
124
125    fn stake_table(&self, epoch: Option<u64>) -> Vec<TestStakeTableEntry<PubKey, StatePubKey>> {
126        if let Some(epoch) = epoch {
127            let filter = self.make_quorum_filter(epoch);
128            self.quorum_members
129                .iter()
130                .enumerate()
131                .filter(|(idx, _)| filter.contains(idx))
132                .map(|(_, v)| v.clone())
133                .collect()
134        } else {
135            self.quorum_members.clone()
136        }
137    }
138
139    fn da_stake_table(&self, epoch: Option<u64>) -> Vec<TestStakeTableEntry<PubKey, StatePubKey>> {
140        let da_members = self
141            .da_committees
142            .get(epoch)
143            .unwrap_or(self.da_members.clone());
144        if let Some(epoch) = epoch {
145            let filter = self.make_da_quorum_filter(epoch);
146            da_members
147                .into_iter()
148                .enumerate()
149                .filter(|(idx, _)| filter.contains(idx))
150                .map(|(_, v)| v)
151                .collect()
152        } else {
153            da_members.clone()
154        }
155    }
156
157    fn lookup_leader(&self, view_number: u64, epoch: Option<u64>) -> anyhow::Result<PubKey> {
158        let stake_table = self.stake_table(epoch);
159        let mut rng: StdRng = rand::SeedableRng::seed_from_u64(view_number);
160
161        let randomized_view_number: u64 = rng.gen_range(0..=u64::MAX);
162        let index = randomized_view_number as usize % stake_table.len();
163        let leader = stake_table[index].clone();
164
165        tracing::debug!(
166            "RandomizedCommitteeMembers lookup_leader, view_number: {view_number}, epoch: \
167             {epoch:?}, leader: {leader:?}",
168        );
169
170        Ok(leader.signature_key)
171    }
172
173    fn has_stake_table(&self, epoch: u64) -> bool {
174        self.epochs.contains(&epoch)
175    }
176
177    fn has_randomized_stake_table(&self, epoch: u64) -> anyhow::Result<bool> {
178        Ok(self.drb_results.contains_key(&epoch))
179    }
180
181    fn add_epoch_root(&mut self, epoch: u64) {
182        self.epochs.insert(epoch);
183    }
184
185    fn add_drb_result(&mut self, epoch: u64, drb_result: DrbResult) {
186        self.drb_results.insert(epoch, drb_result);
187    }
188
189    fn set_first_epoch(&mut self, epoch: u64, initial_drb_result: DrbResult) {
190        self.first_epoch = Some(epoch);
191
192        self.add_epoch_root(epoch);
193        self.add_epoch_root(epoch + 1);
194
195        self.add_drb_result(epoch, initial_drb_result);
196        self.add_drb_result(epoch + 1, initial_drb_result);
197    }
198
199    fn get_epoch_drb(&self, epoch: u64) -> anyhow::Result<DrbResult> {
200        self.drb_results
201            .get(&epoch)
202            .context("DRB result missing")
203            .copied()
204    }
205
206    fn first_epoch(&self) -> Option<u64> {
207        self.first_epoch
208    }
209
210    fn add_da_committee(
211        &mut self,
212        first_epoch: u64,
213        committee: Vec<TestStakeTableEntry<PubKey, StatePubKey>>,
214    ) {
215        self.da_committees.add(first_epoch, committee);
216    }
217}