diff --git a/crates/hotshot/src/traits/election/randomized_committee.rs b/crates/hotshot/src/traits/election/randomized_committee.rs index 0292af4862..fcede81ef8 100644 --- a/crates/hotshot/src/traits/election/randomized_committee.rs +++ b/crates/hotshot/src/traits/election/randomized_committee.rs @@ -248,7 +248,7 @@ impl Membership for RandomizedCommittee { } /// Get the voting success threshold for the committee - fn da_success_threshold(&self) -> NonZeroU64 { + fn da_success_threshold(&self, _epoch: ::Epoch) -> NonZeroU64 { NonZeroU64::new(((self.da_stake_table.len() as u64 * 2) / 3) + 1).unwrap() } diff --git a/crates/hotshot/src/traits/election/randomized_committee_members.rs b/crates/hotshot/src/traits/election/randomized_committee_members.rs index 1bd3d1d54d..01f4ad6383 100644 --- a/crates/hotshot/src/traits/election/randomized_committee_members.rs +++ b/crates/hotshot/src/traits/election/randomized_committee_members.rs @@ -14,7 +14,6 @@ use std::{ use hotshot_types::{ traits::{ election::Membership, - network::Topic, node_implementation::{ConsensusTime, NodeType}, signature_key::{SignatureKey, StakeTableEntryType}, }, @@ -37,12 +36,16 @@ pub struct RandomizedCommitteeMembers { /// The nodes on the committee and their stake stake_table: Vec<::StakeTableEntry>, + /// The nodes on the da committee and their stake + da_stake_table: Vec<::StakeTableEntry>, + /// The nodes on the committee and their stake, indexed by public key indexed_stake_table: BTreeMap::StakeTableEntry>, - /// The network topic of the committee - committee_topic: Topic, + /// The nodes on the da committee and their stake, indexed by public key + indexed_da_stake_table: + BTreeMap::StakeTableEntry>, /// Phantom _pd: PhantomData, @@ -53,6 +56,11 @@ impl RandomizedCommitteeMembers::Epoch) -> BTreeSet { CONFIG::execute(epoch.u64(), self.stake_table.len()) } + + /// Creates a set of indices into the da_stake_table which reference the nodes selected for this epoch's da committee + fn make_da_quorum_filter(&self, epoch: ::Epoch) -> BTreeSet { + CONFIG::execute(epoch.u64(), self.da_stake_table.len()) + } } impl Membership @@ -62,19 +70,12 @@ impl Membership /// Create a new election fn new( - eligible_leaders: Vec::SignatureKey>>, committee_members: Vec::SignatureKey>>, - committee_topic: Topic, + da_members: Vec::SignatureKey>>, ) -> Self { - // The only time these two are unequal is in a benchmarking scenario that isn't covered by this test case - assert_eq!( - eligible_leaders, committee_members, - "eligible_leaders should be the same as committee_members!" - ); - // For each eligible leader, get the stake table entry let eligible_leaders: Vec<::StakeTableEntry> = - eligible_leaders + committee_members .iter() .map(|member| member.stake_table_entry.clone()) .filter(|entry| entry.stake() > U256::zero()) @@ -88,6 +89,13 @@ impl Membership .filter(|entry| entry.stake() > U256::zero()) .collect(); + // For each da member, get the stake table entry + let da_members: Vec<::StakeTableEntry> = da_members + .iter() + .map(|member| member.stake_table_entry.clone()) + .filter(|entry| entry.stake() > U256::zero()) + .collect(); + // Index the stake table by public key let indexed_stake_table: BTreeMap< TYPES::SignatureKey, @@ -97,11 +105,21 @@ impl Membership .map(|entry| (TYPES::SignatureKey::public_key(entry), entry.clone())) .collect(); + // Index the stake table by public key + let indexed_da_stake_table: BTreeMap< + TYPES::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, - committee_topic, + indexed_da_stake_table, _pd: PhantomData, } } @@ -121,6 +139,21 @@ impl Membership .collect() } + /// Get the da stake table for the current view + fn da_stake_table( + &self, + epoch: ::Epoch, + ) -> Vec<<::SignatureKey as SignatureKey>::StakeTableEntry> { + let filter = self.make_da_quorum_filter(epoch); + //self.stake_table.clone()s + self.da_stake_table + .iter() + .enumerate() + .filter(|(idx, _)| filter.contains(idx)) + .map(|(_, v)| v.clone()) + .collect() + } + /// Get all members of the committee for the current view fn committee_members( &self, @@ -136,6 +169,21 @@ impl Membership .collect() } + /// Get all members of the committee for the current view + fn da_committee_members( + &self, + _view_number: ::View, + epoch: ::Epoch, + ) -> BTreeSet<::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() + } + /// Get all eligible leaders of the committee for the current view fn committee_leaders( &self, @@ -169,6 +217,30 @@ impl Membership } } + /// Get the da stake table entry for a public key + fn da_stake( + &self, + pub_key: &::SignatureKey, + epoch: ::Epoch, + ) -> Option<::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) { + // Only return the stake if it is above zero + self.indexed_da_stake_table.get(pub_key).cloned() + } else { + // Skip members which aren't included based on the quorum filter + None + } + } + /// Check if a node has stake in the committee fn has_stake( &self, @@ -194,9 +266,29 @@ impl Membership } } - /// Get the network topic for the committee - fn committee_topic(&self) -> Topic { - self.committee_topic.clone() + /// Check if a node has stake in the committee + fn has_da_stake( + &self, + pub_key: &::SignatureKey, + epoch: ::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 { + // Skip members which aren't included based on the quorum filter + false + } } /// Index the vector of public keys with the current view number @@ -225,17 +317,54 @@ impl Membership Ok(TYPES::SignatureKey::public_key(&res)) } + /// Index the vector of public keys with the current view number + fn lookup_da_leader( + &self, + view_number: TYPES::View, + epoch: ::Epoch, + ) -> Result { + let filter = self.make_da_quorum_filter(epoch); + let leader_vec: Vec<_> = self + .da_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)) + } + /// Get the total number of nodes in the committee fn total_nodes(&self, epoch: ::Epoch) -> usize { self.make_quorum_filter(epoch).len() } + /// Get the total number of nodes in the committee + fn da_total_nodes(&self, epoch: ::Epoch) -> usize { + self.make_da_quorum_filter(epoch).len() + } + /// Get the voting success threshold for the committee fn success_threshold(&self, epoch: ::Epoch) -> NonZeroU64 { let len = self.total_nodes(epoch); NonZeroU64::new(((len as u64 * 2) / 3) + 1).unwrap() } + /// Get the voting success threshold for the committee + fn da_success_threshold(&self, epoch: ::Epoch) -> NonZeroU64 { + let len = self.da_total_nodes(epoch); + NonZeroU64::new(((len as u64 * 2) / 3) + 1).unwrap() + } + /// Get the voting failure threshold for the committee fn failure_threshold(&self, epoch: ::Epoch) -> NonZeroU64 { let len = self.total_nodes(epoch); diff --git a/crates/hotshot/src/traits/election/static_committee.rs b/crates/hotshot/src/traits/election/static_committee.rs index 0a33806e97..8505f66857 100644 --- a/crates/hotshot/src/traits/election/static_committee.rs +++ b/crates/hotshot/src/traits/election/static_committee.rs @@ -232,7 +232,7 @@ impl Membership for StaticCommittee { } /// Get the voting success threshold for the committee - fn da_success_threshold(&self) -> NonZeroU64 { + fn da_success_threshold(&self, _epoch: ::Epoch) -> NonZeroU64 { NonZeroU64::new(((self.da_stake_table.len() as u64 * 2) / 3) + 1).unwrap() } @@ -243,10 +243,7 @@ impl Membership for StaticCommittee { /// Get the voting upgrade threshold for the committee fn upgrade_threshold(&self, _epoch: ::Epoch) -> NonZeroU64 { - NonZeroU64::new(max( - (self.stake_table.len() as u64 * 9) / 10, - ((self.stake_table.len() as u64 * 2) / 3) + 1, - )) - .unwrap() + let len = self.stake_table.len(); + NonZeroU64::new(max((len as u64 * 9) / 10, ((len as u64 * 2) / 3) + 1)).unwrap() } } diff --git a/crates/hotshot/src/traits/election/static_committee_leader_two_views.rs b/crates/hotshot/src/traits/election/static_committee_leader_two_views.rs index 695e9ee22c..2d8b1a17bb 100644 --- a/crates/hotshot/src/traits/election/static_committee_leader_two_views.rs +++ b/crates/hotshot/src/traits/election/static_committee_leader_two_views.rs @@ -235,7 +235,7 @@ impl Membership for StaticCommitteeLeaderForTwoViews NonZeroU64 { + fn da_success_threshold(&self, _epoch: ::Epoch) -> NonZeroU64 { NonZeroU64::new(((self.da_stake_table.len() as u64 * 2) / 3) + 1).unwrap() } diff --git a/crates/task-impls/src/helpers.rs b/crates/task-impls/src/helpers.rs index e04a808ca3..242bf3db0d 100644 --- a/crates/task-impls/src/helpers.rs +++ b/crates/task-impls/src/helpers.rs @@ -127,7 +127,7 @@ pub(crate) async fn fetch_proposal( if !justify_qc .is_valid_cert( quorum_membership.stake_table(cur_epoch), - quorum_membership.success_threshold(), + quorum_membership.success_threshold(cur_epoch), upgrade_lock, ) .await @@ -686,7 +686,9 @@ pub(crate) async fn validate_proposal_view_and_certs< validation_info .quorum_membership .stake_table(validation_info.cur_epoch), - validation_info.quorum_membership.success_threshold(), + validation_info + .quorum_membership + .success_threshold(validation_info.cur_epoch), &validation_info.upgrade_lock ) .await, @@ -709,7 +711,9 @@ pub(crate) async fn validate_proposal_view_and_certs< validation_info .quorum_membership .stake_table(validation_info.cur_epoch), - validation_info.quorum_membership.success_threshold(), + validation_info + .quorum_membership + .success_threshold(validation_info.cur_epoch), &validation_info.upgrade_lock ) .await, diff --git a/crates/task-impls/src/quorum_proposal/handlers.rs b/crates/task-impls/src/quorum_proposal/handlers.rs index 0ef9e89d0f..a57143999c 100644 --- a/crates/task-impls/src/quorum_proposal/handlers.rs +++ b/crates/task-impls/src/quorum_proposal/handlers.rs @@ -126,7 +126,8 @@ impl ProposalDependencyHandle { if qc .is_valid_cert( self.quorum_membership.stake_table(TYPES::Epoch::new(0)), - self.quorum_membership.success_threshold(), + self.quorum_membership + .success_threshold(TYPES::Epoch::new(0)), &self.upgrade_lock, ) .await diff --git a/crates/task-impls/src/quorum_proposal/mod.rs b/crates/task-impls/src/quorum_proposal/mod.rs index 2bed728125..e58677667a 100644 --- a/crates/task-impls/src/quorum_proposal/mod.rs +++ b/crates/task-impls/src/quorum_proposal/mod.rs @@ -444,7 +444,7 @@ impl, V: Versions> certificate .is_valid_cert( self.quorum_membership.stake_table(epoch_number), - self.quorum_membership.success_threshold(), + self.quorum_membership.success_threshold(epoch_number), &self.upgrade_lock ) .await, @@ -508,7 +508,7 @@ impl, V: Versions> ensure!( qc.is_valid_cert( self.quorum_membership.stake_table(epoch_number), - self.quorum_membership.success_threshold(), + self.quorum_membership.success_threshold(epoch_number), &self.upgrade_lock ) .await, diff --git a/crates/task-impls/src/quorum_proposal_recv/handlers.rs b/crates/task-impls/src/quorum_proposal_recv/handlers.rs index 73f6addef0..3d5c010058 100644 --- a/crates/task-impls/src/quorum_proposal_recv/handlers.rs +++ b/crates/task-impls/src/quorum_proposal_recv/handlers.rs @@ -157,7 +157,9 @@ pub(crate) async fn handle_quorum_proposal_recv< validation_info .quorum_membership .stake_table(validation_info.cur_epoch), - validation_info.quorum_membership.success_threshold(), + validation_info + .quorum_membership + .success_threshold(validation_info.cur_epoch), &validation_info.upgrade_lock, ) .await diff --git a/crates/task-impls/src/quorum_vote/mod.rs b/crates/task-impls/src/quorum_vote/mod.rs index d1079197f8..75da95add4 100644 --- a/crates/task-impls/src/quorum_vote/mod.rs +++ b/crates/task-impls/src/quorum_vote/mod.rs @@ -478,7 +478,7 @@ impl, V: Versions> QuorumVoteTaskS ensure!( cert.is_valid_cert( self.membership.da_stake_table(cur_epoch), - self.membership.da_success_threshold(), + self.membership.da_success_threshold(cur_epoch), &self.upgrade_lock ) .await, diff --git a/crates/task-impls/src/view_sync.rs b/crates/task-impls/src/view_sync.rs index 2fb74e9fd4..8fce979a4c 100644 --- a/crates/task-impls/src/view_sync.rs +++ b/crates/task-impls/src/view_sync.rs @@ -535,7 +535,7 @@ impl ViewSyncReplicaTaskState { if !certificate .is_valid_cert( self.membership.stake_table(self.cur_epoch), - self.membership.failure_threshold(), + self.membership.failure_threshold(self.cur_epoch), &self.upgrade_lock, ) .await @@ -621,7 +621,7 @@ impl ViewSyncReplicaTaskState { if !certificate .is_valid_cert( self.membership.stake_table(self.cur_epoch), - self.membership.success_threshold(), + self.membership.success_threshold(self.cur_epoch), &self.upgrade_lock, ) .await @@ -718,7 +718,7 @@ impl ViewSyncReplicaTaskState { if !certificate .is_valid_cert( self.membership.stake_table(self.cur_epoch), - self.membership.success_threshold(), + self.membership.success_threshold(self.cur_epoch), &self.upgrade_lock, ) .await diff --git a/crates/testing/tests/tests_1/message.rs b/crates/testing/tests/tests_1/message.rs index 3778aa4737..9536cf0f22 100644 --- a/crates/testing/tests/tests_1/message.rs +++ b/crates/testing/tests/tests_1/message.rs @@ -104,7 +104,7 @@ async fn test_certificate2_validity() { assert!( qc.is_valid_cert( membership.stake_table(EpochNumber::new(0)), - membership.success_threshold(), + membership.success_threshold(EpochNumber::new(0)), &handle.hotshot.upgrade_lock ) .await @@ -113,7 +113,7 @@ async fn test_certificate2_validity() { assert!( qc2.is_valid_cert( membership.stake_table(EpochNumber::new(0)), - membership.success_threshold(), + membership.success_threshold(EpochNumber::new(0)), &handle.hotshot.upgrade_lock ) .await diff --git a/crates/types/src/simple_certificate.rs b/crates/types/src/simple_certificate.rs index 8d544b5cdb..f2cc8cd689 100644 --- a/crates/types/src/simple_certificate.rs +++ b/crates/types/src/simple_certificate.rs @@ -24,7 +24,7 @@ use crate::{ data::serialize_signature2, message::UpgradeLock, simple_vote::{ - DaData, QuorumData, QuorumData2, QuorumMaker, TimeoutData, UpgradeProposalData, + DaData, QuorumData, QuorumData2, QuorumMarker, TimeoutData, UpgradeProposalData, VersionedVoteData, ViewSyncCommitData, ViewSyncFinalizeData, ViewSyncPreCommitData, Voteable, }, @@ -204,8 +204,11 @@ impl> Certificate ) -> usize { membership.da_total_nodes(epoch) } - fn threshold>(membership: &MEMBERSHIP) -> u64 { - membership.da_success_threshold().into() + fn threshold>( + membership: &MEMBERSHIP, + epoch: ::Epoch, + ) -> u64 { + membership.da_success_threshold(epoch).into() } fn data(&self) -> &Self::Voteable { &self.data @@ -222,7 +225,7 @@ impl> Certificate } } -impl> +impl> Certificate for SimpleCertificate { type Voteable = VOTEABLE; diff --git a/crates/types/src/simple_vote.rs b/crates/types/src/simple_vote.rs index 35997d3725..138f730fb9 100644 --- a/crates/types/src/simple_vote.rs +++ b/crates/types/src/simple_vote.rs @@ -25,7 +25,7 @@ use crate::{ }; /// Marker that data should use the quorum cert type -pub(crate) trait QuorumMaker {} +pub(crate) trait QuorumMarker {} #[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Hash, Eq)] /// Data used for a yes vote. @@ -117,13 +117,13 @@ mod sealed { impl Sealed for C {} } -impl QuorumMaker for QuorumData {} -impl QuorumMaker for QuorumData2 {} -impl QuorumMaker for TimeoutData {} -impl QuorumMaker for ViewSyncPreCommitData {} -impl QuorumMaker for ViewSyncCommitData {} -impl QuorumMaker for ViewSyncFinalizeData {} -impl QuorumMaker for UpgradeProposalData {} +impl QuorumMarker for QuorumData {} +impl QuorumMarker for QuorumData2 {} +impl QuorumMarker for TimeoutData {} +impl QuorumMarker for ViewSyncPreCommitData {} +impl QuorumMarker for ViewSyncCommitData {} +impl QuorumMarker for ViewSyncFinalizeData {} +impl QuorumMarker for UpgradeProposalData {} /// A simple yes vote over some votable type. #[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Hash, Eq)] diff --git a/crates/types/src/traits/election.rs b/crates/types/src/traits/election.rs index 5a0c1c365f..efaa4b0004 100644 --- a/crates/types/src/traits/election.rs +++ b/crates/types/src/traits/election.rs @@ -140,7 +140,7 @@ pub trait Membership: Clone + Debug + Send + Sync { fn success_threshold(&self, epoch: TYPES::Epoch) -> NonZeroU64; /// Returns the DA threshold for a specific `Membership` implementation - fn da_success_threshold(&self) -> NonZeroU64; + fn da_success_threshold(&self, epoch: TYPES::Epoch) -> NonZeroU64; /// Returns the threshold for a specific `Membership` implementation fn failure_threshold(&self, epoch: TYPES::Epoch) -> NonZeroU64;