use std::{
cmp::max,
collections::{BTreeMap, BTreeSet},
marker::PhantomData,
num::NonZeroU64,
};
use hotshot_types::{
traits::{
election::Membership,
node_implementation::{ConsensusTime, NodeType},
signature_key::{SignatureKey, StakeTableEntryType},
},
PeerConfig,
};
use primitive_types::U256;
use rand::{rngs::StdRng, Rng};
use utils::anytrace::Result;
use crate::traits::election::helpers::QuorumFilterConfig;
#[derive(Clone, Debug, Eq, PartialEq, Hash)]
pub struct RandomizedCommitteeMembers<T: NodeType, C: QuorumFilterConfig> {
eligible_leaders: Vec<<T::SignatureKey as SignatureKey>::StakeTableEntry>,
stake_table: Vec<<T::SignatureKey as SignatureKey>::StakeTableEntry>,
da_stake_table: Vec<<T::SignatureKey as SignatureKey>::StakeTableEntry>,
indexed_stake_table:
BTreeMap<T::SignatureKey, <T::SignatureKey as SignatureKey>::StakeTableEntry>,
indexed_da_stake_table:
BTreeMap<T::SignatureKey, <T::SignatureKey as SignatureKey>::StakeTableEntry>,
_pd: PhantomData<C>,
}
impl<TYPES: NodeType, CONFIG: QuorumFilterConfig> RandomizedCommitteeMembers<TYPES, CONFIG> {
fn make_quorum_filter(&self, epoch: <TYPES as NodeType>::Epoch) -> BTreeSet<usize> {
CONFIG::execute(epoch.u64(), self.stake_table.len())
}
fn make_da_quorum_filter(&self, epoch: <TYPES as NodeType>::Epoch) -> BTreeSet<usize> {
CONFIG::execute(epoch.u64(), self.da_stake_table.len())
}
}
impl<TYPES: NodeType, CONFIG: QuorumFilterConfig> Membership<TYPES>
for RandomizedCommitteeMembers<TYPES, CONFIG>
{
type Error = utils::anytrace::Error;
fn new(
committee_members: Vec<PeerConfig<<TYPES as NodeType>::SignatureKey>>,
da_members: Vec<PeerConfig<<TYPES as NodeType>::SignatureKey>>,
) -> Self {
let eligible_leaders: Vec<<TYPES::SignatureKey as SignatureKey>::StakeTableEntry> =
committee_members
.iter()
.map(|member| member.stake_table_entry.clone())
.filter(|entry| entry.stake() > U256::zero())
.collect();
let members: Vec<<TYPES::SignatureKey as SignatureKey>::StakeTableEntry> =
committee_members
.iter()
.map(|member| member.stake_table_entry.clone())
.filter(|entry| entry.stake() > U256::zero())
.collect();
let da_members: Vec<<TYPES::SignatureKey as SignatureKey>::StakeTableEntry> = da_members
.iter()
.map(|member| member.stake_table_entry.clone())
.filter(|entry| entry.stake() > U256::zero())
.collect();
let indexed_stake_table: BTreeMap<
TYPES::SignatureKey,
<TYPES::SignatureKey as SignatureKey>::StakeTableEntry,
> = members
.iter()
.map(|entry| (TYPES::SignatureKey::public_key(entry), entry.clone()))
.collect();
let indexed_da_stake_table: BTreeMap<
TYPES::SignatureKey,
<TYPES::SignatureKey as SignatureKey>::StakeTableEntry,
> = da_members
.iter()
.map(|entry| (TYPES::SignatureKey::public_key(entry), entry.clone()))
.collect();
Self {
eligible_leaders,
stake_table: members,
da_stake_table: da_members,
indexed_stake_table,
indexed_da_stake_table,
_pd: PhantomData,
}
}
fn stake_table(
&self,
epoch: <TYPES as NodeType>::Epoch,
) -> Vec<<<TYPES as NodeType>::SignatureKey as SignatureKey>::StakeTableEntry> {
let filter = self.make_quorum_filter(epoch);
self.stake_table
.iter()
.enumerate()
.filter(|(idx, _)| filter.contains(idx))
.map(|(_, v)| v.clone())
.collect()
}
fn da_stake_table(
&self,
epoch: <TYPES as NodeType>::Epoch,
) -> Vec<<<TYPES as NodeType>::SignatureKey as SignatureKey>::StakeTableEntry> {
let filter = self.make_da_quorum_filter(epoch);
self.da_stake_table
.iter()
.enumerate()
.filter(|(idx, _)| filter.contains(idx))
.map(|(_, v)| v.clone())
.collect()
}
fn committee_members(
&self,
_view_number: <TYPES as NodeType>::View,
epoch: <TYPES as NodeType>::Epoch,
) -> BTreeSet<<TYPES as NodeType>::SignatureKey> {
let filter = self.make_quorum_filter(epoch);
self.stake_table
.iter()
.enumerate()
.filter(|(idx, _)| filter.contains(idx))
.map(|(_, v)| TYPES::SignatureKey::public_key(v))
.collect()
}
fn da_committee_members(
&self,
_view_number: <TYPES as NodeType>::View,
epoch: <TYPES as NodeType>::Epoch,
) -> BTreeSet<<TYPES as NodeType>::SignatureKey> {
let filter = self.make_da_quorum_filter(epoch);
self.da_stake_table
.iter()
.enumerate()
.filter(|(idx, _)| filter.contains(idx))
.map(|(_, v)| TYPES::SignatureKey::public_key(v))
.collect()
}
fn committee_leaders(
&self,
view_number: <TYPES as NodeType>::View,
epoch: <TYPES as NodeType>::Epoch,
) -> BTreeSet<<TYPES as NodeType>::SignatureKey> {
self.committee_members(view_number, epoch)
}
fn stake(
&self,
pub_key: &<TYPES as NodeType>::SignatureKey,
epoch: <TYPES as NodeType>::Epoch,
) -> Option<<TYPES::SignatureKey as SignatureKey>::StakeTableEntry> {
let filter = self.make_quorum_filter(epoch);
let actual_members: BTreeSet<_> = self
.stake_table
.iter()
.enumerate()
.filter(|(idx, _)| filter.contains(idx))
.map(|(_, v)| TYPES::SignatureKey::public_key(v))
.collect();
if actual_members.contains(pub_key) {
self.indexed_stake_table.get(pub_key).cloned()
} else {
None
}
}
fn da_stake(
&self,
pub_key: &<TYPES as NodeType>::SignatureKey,
epoch: <TYPES as NodeType>::Epoch,
) -> Option<<TYPES::SignatureKey as SignatureKey>::StakeTableEntry> {
let filter = self.make_da_quorum_filter(epoch);
let actual_members: BTreeSet<_> = self
.da_stake_table
.iter()
.enumerate()
.filter(|(idx, _)| filter.contains(idx))
.map(|(_, v)| TYPES::SignatureKey::public_key(v))
.collect();
if actual_members.contains(pub_key) {
self.indexed_da_stake_table.get(pub_key).cloned()
} else {
None
}
}
fn has_stake(
&self,
pub_key: &<TYPES as NodeType>::SignatureKey,
epoch: <TYPES as NodeType>::Epoch,
) -> bool {
let filter = self.make_quorum_filter(epoch);
let actual_members: BTreeSet<_> = self
.stake_table
.iter()
.enumerate()
.filter(|(idx, _)| filter.contains(idx))
.map(|(_, v)| TYPES::SignatureKey::public_key(v))
.collect();
if actual_members.contains(pub_key) {
self.indexed_stake_table
.get(pub_key)
.is_some_and(|x| x.stake() > U256::zero())
} else {
false
}
}
fn has_da_stake(
&self,
pub_key: &<TYPES as NodeType>::SignatureKey,
epoch: <TYPES as NodeType>::Epoch,
) -> bool {
let filter = self.make_da_quorum_filter(epoch);
let actual_members: BTreeSet<_> = self
.da_stake_table
.iter()
.enumerate()
.filter(|(idx, _)| filter.contains(idx))
.map(|(_, v)| TYPES::SignatureKey::public_key(v))
.collect();
if actual_members.contains(pub_key) {
self.indexed_da_stake_table
.get(pub_key)
.is_some_and(|x| x.stake() > U256::zero())
} else {
false
}
}
fn lookup_leader(
&self,
view_number: TYPES::View,
epoch: <TYPES as NodeType>::Epoch,
) -> Result<TYPES::SignatureKey> {
let filter = self.make_quorum_filter(epoch);
let leader_vec: Vec<_> = self
.stake_table
.iter()
.enumerate()
.filter(|(idx, _)| filter.contains(idx))
.map(|(_, v)| v.clone())
.collect();
let mut rng: StdRng = rand::SeedableRng::seed_from_u64(*view_number);
let randomized_view_number: u64 = rng.gen_range(0..=u64::MAX);
#[allow(clippy::cast_possible_truncation)]
let index = randomized_view_number as usize % leader_vec.len();
let res = leader_vec[index].clone();
Ok(TYPES::SignatureKey::public_key(&res))
}
fn total_nodes(&self, epoch: <TYPES as NodeType>::Epoch) -> usize {
self.make_quorum_filter(epoch).len()
}
fn da_total_nodes(&self, epoch: <TYPES as NodeType>::Epoch) -> usize {
self.make_da_quorum_filter(epoch).len()
}
fn success_threshold(&self, epoch: <TYPES as NodeType>::Epoch) -> NonZeroU64 {
let len = self.total_nodes(epoch);
NonZeroU64::new(((len as u64 * 2) / 3) + 1).unwrap()
}
fn da_success_threshold(&self, epoch: <TYPES as NodeType>::Epoch) -> NonZeroU64 {
let len = self.da_total_nodes(epoch);
NonZeroU64::new(((len as u64 * 2) / 3) + 1).unwrap()
}
fn failure_threshold(&self, epoch: <TYPES as NodeType>::Epoch) -> NonZeroU64 {
let len = self.total_nodes(epoch);
NonZeroU64::new(((len as u64) / 3) + 1).unwrap()
}
fn upgrade_threshold(&self, epoch: <TYPES as NodeType>::Epoch) -> NonZeroU64 {
let len = self.total_nodes(epoch);
NonZeroU64::new(max((len as u64 * 9) / 10, ((len as u64 * 2) / 3) + 1)).unwrap()
}
}