diff --git a/consensus-engine/src/overlay/flat_overlay.rs b/consensus-engine/src/overlay/flat_overlay.rs index 0f8d608a1..83d7c10ff 100644 --- a/consensus-engine/src/overlay/flat_overlay.rs +++ b/consensus-engine/src/overlay/flat_overlay.rs @@ -1,13 +1,11 @@ +use super::threshold::{apply_threshold, default_leader_super_majority_threshold, deser_fraction}; use super::LeaderSelection; use crate::overlay::CommitteeMembership; use crate::{NodeId, Overlay}; -use fraction::{Fraction, ToPrimitive}; +use fraction::Fraction; use serde::{Deserialize, Serialize}; use std::marker::PhantomData; -const LEADER_SUPER_MAJORITY_THRESHOLD_NUM: u64 = 2; -const LEADER_SUPER_MAJORITY_THRESHOLD_DEN: u64 = 3; - #[derive(Clone, Debug, PartialEq)] /// Flat overlay with a single committee and round robin leader selection. pub struct FlatOverlay { @@ -36,12 +34,8 @@ where Self { nodes, leader, - leader_threshold: leader_super_majority_threshold.unwrap_or_else(|| { - Fraction::new( - LEADER_SUPER_MAJORITY_THRESHOLD_NUM, - LEADER_SUPER_MAJORITY_THRESHOLD_DEN, - ) - }), + leader_threshold: leader_super_majority_threshold + .unwrap_or_else(default_leader_super_majority_threshold), _committee_membership: Default::default(), } } @@ -91,11 +85,7 @@ where } fn leader_super_majority_threshold(&self, _id: NodeId) -> usize { - // self.leader_threshold is a tuple of (num, den) where num/den is the super majority threshold - (Fraction::from(self.nodes.len()) * self.leader_threshold) - .floor() - .to_usize() - .unwrap() + apply_threshold(self.nodes.len(), self.leader_threshold) } fn update_leader_selection(&self, f: F) -> Result @@ -125,30 +115,8 @@ pub struct FlatOverlaySettings { pub nodes: Vec, /// A fraction representing the threshold in the form `/' /// Defaults to 2/3 - #[serde(with = "deser")] + #[serde(with = "deser_fraction")] #[serde(skip_serializing_if = "Option::is_none")] pub leader_super_majority_threshold: Option, pub leader: L, } - -mod deser { - use fraction::Fraction; - use serde::{de, Deserialize, Deserializer, Serialize, Serializer}; - use std::str::FromStr; - - pub fn deserialize<'de, D>(deserializer: D) -> Result, D::Error> - where - D: Deserializer<'de>, - { - >::deserialize(deserializer)? - .map(|s| FromStr::from_str(&s).map_err(de::Error::custom)) - .transpose() - } - - pub fn serialize(value: &Option, serializer: S) -> Result - where - S: Serializer, - { - value.map(|v| v.to_string()).serialize(serializer) - } -} diff --git a/consensus-engine/src/overlay/mod.rs b/consensus-engine/src/overlay/mod.rs index 60473a8ee..1a5bcade9 100644 --- a/consensus-engine/src/overlay/mod.rs +++ b/consensus-engine/src/overlay/mod.rs @@ -5,6 +5,7 @@ mod flat_overlay; mod leadership; mod membership; mod random_beacon; +mod threshold; mod tree_overlay; pub use branch_overlay::*; @@ -98,6 +99,7 @@ mod tests { number_of_committees: 1, leader: RoundRobin::new(), committee_membership: FisherYatesShuffle::new(ENTROPY), + leader_super_majority_threshold: None, }); let branch_overlay = BranchOverlay::new(BranchOverlaySettings { current_leader: nodes[0], diff --git a/consensus-engine/src/overlay/threshold.rs b/consensus-engine/src/overlay/threshold.rs new file mode 100644 index 000000000..87283478b --- /dev/null +++ b/consensus-engine/src/overlay/threshold.rs @@ -0,0 +1,41 @@ +use fraction::{Fraction, GenericFraction, ToPrimitive}; + +const LEADER_SUPER_MAJORITY_THRESHOLD_NUM: u64 = 2; +const LEADER_SUPER_MAJORITY_THRESHOLD_DEN: u64 = 3; + +pub fn default_leader_super_majority_threshold() -> GenericFraction { + Fraction::new( + LEADER_SUPER_MAJORITY_THRESHOLD_NUM, + LEADER_SUPER_MAJORITY_THRESHOLD_DEN, + ) +} + +pub fn apply_threshold(size: usize, threshold: GenericFraction) -> usize { + // `threshold` is a tuple of (num, den) where `num/den` is the super majority threshold + (Fraction::from(size) * threshold) + .floor() + .to_usize() + .unwrap() +} + +pub mod deser_fraction { + use fraction::Fraction; + use serde::{de, Deserialize, Deserializer, Serialize, Serializer}; + use std::str::FromStr; + + pub fn deserialize<'de, D>(deserializer: D) -> Result, D::Error> + where + D: Deserializer<'de>, + { + >::deserialize(deserializer)? + .map(|s| FromStr::from_str(&s).map_err(de::Error::custom)) + .transpose() + } + + pub fn serialize(value: &Option, serializer: S) -> Result + where + S: Serializer, + { + value.map(|v| v.to_string()).serialize(serializer) + } +} diff --git a/consensus-engine/src/overlay/tree_overlay/overlay.rs b/consensus-engine/src/overlay/tree_overlay/overlay.rs index 3835a5258..1e6a3c734 100644 --- a/consensus-engine/src/overlay/tree_overlay/overlay.rs +++ b/consensus-engine/src/overlay/tree_overlay/overlay.rs @@ -1,6 +1,10 @@ use super::tree::Tree; +use crate::overlay::threshold::{ + apply_threshold, default_leader_super_majority_threshold, deser_fraction, +}; use crate::overlay::CommitteeMembership; use crate::{overlay::LeaderSelection, Committee, NodeId, Overlay}; +use fraction::Fraction; use serde::{Deserialize, Serialize}; #[derive(Debug, Clone)] @@ -11,6 +15,11 @@ pub struct TreeOverlaySettings { pub number_of_committees: usize, pub leader: L, pub committee_membership: M, + /// A fraction representing the threshold in the form `/' + /// Defaults to 2/3 + #[serde(with = "deser_fraction")] + #[serde(skip_serializing_if = "Option::is_none")] + pub leader_super_majority_threshold: Option, } #[derive(Debug, Clone)] @@ -21,6 +30,7 @@ pub struct TreeOverlay { pub(super) carnot_tree: Tree, pub(super) leader: L, pub(super) committee_membership: M, + pub(super) leader_threshold: Fraction, } impl Overlay for TreeOverlay @@ -40,6 +50,7 @@ where number_of_committees, leader, committee_membership, + leader_super_majority_threshold, } = settings; committee_membership.reshape_committees(&mut nodes); @@ -52,6 +63,8 @@ where carnot_tree, leader, committee_membership, + leader_threshold: leader_super_majority_threshold + .unwrap_or_else(default_leader_super_majority_threshold), } } @@ -158,8 +171,7 @@ where // }); // let root_size = self.root_committee().len(); // let committee_size = root_size + children_size; - let committee_size = self.root_committee().len(); - (committee_size * 2 / 3) + 1 + apply_threshold(self.root_committee().len(), self.leader_threshold) } fn update_leader_selection(&self, f: F) -> Result @@ -186,6 +198,7 @@ where number_of_committees: self.number_of_committees, leader: self.leader.clone(), committee_membership, + leader_super_majority_threshold: Some(self.leader_threshold), }; Self::new(settings) }) @@ -204,6 +217,7 @@ where number_of_committees: self.number_of_committees, leader, committee_membership, + leader_super_majority_threshold: Some(self.leader_threshold), }) } @@ -234,6 +248,7 @@ mod tests { number_of_committees: 3, leader: RoundRobin::new(), committee_membership: FisherYatesShuffle::new(ENTROPY), + leader_super_majority_threshold: None, }); assert_eq!(*overlay.leader(), nodes[0]); @@ -248,6 +263,7 @@ mod tests { number_of_committees: 3, leader: RoundRobin::new(), committee_membership: FisherYatesShuffle::new(ENTROPY), + leader_super_majority_threshold: None, }); let leader = overlay.next_leader(); @@ -265,6 +281,7 @@ mod tests { number_of_committees: 3, leader: RoundRobin::new(), committee_membership: FisherYatesShuffle::new(ENTROPY), + leader_super_majority_threshold: None, }); let mut expected_root = Committee::new(); @@ -283,6 +300,7 @@ mod tests { number_of_committees: 3, leader: RoundRobin::new(), committee_membership: FisherYatesShuffle::new(ENTROPY), + leader_super_majority_threshold: None, }); let mut leaf_committees = overlay @@ -313,6 +331,7 @@ mod tests { number_of_committees: 3, leader: RoundRobin::new(), committee_membership: FisherYatesShuffle::new(ENTROPY), + leader_super_majority_threshold: None, }); assert_eq!(overlay.super_majority_threshold(overlay.nodes[8]), 0); @@ -327,6 +346,7 @@ mod tests { number_of_committees: 3, leader: RoundRobin::new(), committee_membership: FisherYatesShuffle::new(ENTROPY), + leader_super_majority_threshold: None, }); assert_eq!(overlay.super_majority_threshold(overlay.nodes[0]), 3); @@ -341,6 +361,7 @@ mod tests { number_of_committees: 3, leader: RoundRobin::new(), committee_membership: FisherYatesShuffle::new(ENTROPY), + leader_super_majority_threshold: None, }); assert_eq!( diff --git a/simulations/src/bin/app/overlay_node.rs b/simulations/src/bin/app/overlay_node.rs index ad7f4efb8..bffed3038 100644 --- a/simulations/src/bin/app/overlay_node.rs +++ b/simulations/src/bin/app/overlay_node.rs @@ -57,6 +57,7 @@ pub fn to_overlay_node( number_of_committees: tree_settings.number_of_committees, leader: RoundRobin::new(), committee_membership: RandomBeaconState::initial_sad_from_entropy([0; 32]), + leader_super_majority_threshold: None, }; Box::new( CarnotNode::>::new( diff --git a/tests/src/nodes/nomos.rs b/tests/src/nodes/nomos.rs index e68a724af..4adcaee3c 100644 --- a/tests/src/nodes/nomos.rs +++ b/tests/src/nodes/nomos.rs @@ -240,7 +240,7 @@ impl Node for NomosNode { fn create_node_config( nodes: Vec, private_key: [u8; 32], - _threshold: Fraction, + threshold: Fraction, timeout: Duration, #[cfg(feature = "libp2p")] mixnet_node_config: Option, #[cfg(feature = "waku")] _mixnet_node_config: Option, @@ -282,6 +282,10 @@ fn create_node_config( current_leader: [0; 32].into(), number_of_committees: 1, committee_membership: RandomBeaconState::initial_sad_from_entropy([0; 32]), + // By setting the leader_threshold to 1 we ensure that all nodes come + // online before progressing. This is only necessary until we add a way + // to recover poast blocks from other nodes. + leader_super_majority_threshold: Some(threshold), }, timeout, },