diff --git a/crates/task-impls/src/helpers.rs b/crates/task-impls/src/helpers.rs index 1ae16cf21c..a29a6691ae 100644 --- a/crates/task-impls/src/helpers.rs +++ b/crates/task-impls/src/helpers.rs @@ -163,6 +163,51 @@ pub(crate) async fn fetch_proposal( Ok((leaf, view)) } +/// Handles calling add_epoch_root and sync_l1 on Membership if necessary. +async fn decide_from_proposal_add_epoch_root( + proposal: &QuorumProposal2, + leaf_views: &[LeafInfo], + epoch_height: u64, + membership: &Arc>, +) { + let decided_block_number = leaf_views + .last() + .unwrap() + .leaf + .block_header() + .block_number(); + + // Skip if this is not the expected block. + if epoch_height != 0 && (decided_block_number + 3) % epoch_height == 0 { + let next_epoch_number = + TYPES::Epoch::new(epoch_from_block_number(decided_block_number, epoch_height) + 1); + + let write_callback = { + let membership_reader = membership.read().await; + membership_reader + .add_epoch_root(next_epoch_number, proposal.block_header.clone()) + .await + }; + + if let Some(write_callback) = write_callback { + let mut membership_writer = membership.write().await; + write_callback(&mut *membership_writer); + } + + if TYPES::Membership::uses_sync_l1() { + let write_callback = { + let membership_reader = membership.read().await; + membership_reader.sync_l1().await + }; + + if let Some(write_callback) = write_callback { + let mut membership_writer = membership.write().await; + write_callback(&mut *membership_writer); + } + } + } +} + /// Helper type to give names and to the output values of the leaf chain traversal operation. #[derive(Debug)] pub struct LeafChainTraversalOutcome { @@ -202,7 +247,7 @@ impl Default for LeafChainTraversalOutcome { } } -/// calculate the new decided leaf chain based on the rules of hostuff 2 +/// calculate the new decided leaf chain based on the rules of HotStuff 2 /// /// # Panics /// Can't actually panic @@ -211,6 +256,8 @@ pub async fn decide_from_proposal_2( consensus: OuterConsensus, existing_upgrade_cert: Arc>>>, public_key: &TYPES::SignatureKey, + with_epochs: bool, + membership: &Arc>, ) -> LeafChainTraversalOutcome { let mut res = LeafChainTraversalOutcome::default(); let consensus_reader = consensus.read().await; @@ -282,6 +329,14 @@ pub async fn decide_from_proposal_2( res.included_txns = Some(txns); } + if with_epochs { + let epoch_height = consensus_reader.epoch_height; + drop(consensus_reader); + + decide_from_proposal_add_epoch_root(proposal, &res.leaf_views, epoch_height, membership) + .await; + } + res } @@ -317,6 +372,8 @@ pub async fn decide_from_proposal( consensus: OuterConsensus, existing_upgrade_cert: Arc>>>, public_key: &TYPES::SignatureKey, + with_epochs: bool, + membership: &Arc>, ) -> LeafChainTraversalOutcome { let consensus_reader = consensus.read().await; let existing_upgrade_cert_reader = existing_upgrade_cert.read().await; @@ -428,6 +485,14 @@ pub async fn decide_from_proposal( tracing::debug!("Leaf ascension failed; error={e}"); } + if with_epochs { + let epoch_height = consensus_reader.epoch_height; + drop(consensus_reader); + + decide_from_proposal_add_epoch_root(proposal, &res.leaf_views, epoch_height, membership) + .await; + } + res } diff --git a/crates/task-impls/src/quorum_vote/handlers.rs b/crates/task-impls/src/quorum_vote/handlers.rs index 0c9cdbb666..49f481847f 100644 --- a/crates/task-impls/src/quorum_vote/handlers.rs +++ b/crates/task-impls/src/quorum_vote/handlers.rs @@ -167,6 +167,8 @@ pub(crate) async fn handle_quorum_proposal_validated< OuterConsensus::new(Arc::clone(&task_state.consensus.inner_consensus)), Arc::clone(&task_state.upgrade_lock.decided_upgrade_certificate), &task_state.public_key, + version >= V::Epochs::VERSION, + &task_state.membership, ) .await } else { @@ -175,6 +177,8 @@ pub(crate) async fn handle_quorum_proposal_validated< OuterConsensus::new(Arc::clone(&task_state.consensus.inner_consensus)), Arc::clone(&task_state.upgrade_lock.decided_upgrade_certificate), &task_state.public_key, + version >= V::Epochs::VERSION, + &task_state.membership, ) .await }; diff --git a/crates/types/src/traits/election.rs b/crates/types/src/traits/election.rs index 0509918574..021e6b1f6f 100644 --- a/crates/types/src/traits/election.rs +++ b/crates/types/src/traits/election.rs @@ -7,11 +7,13 @@ //! The election trait, used to decide which node is the leader and determine if a vote is valid. use std::{collections::BTreeSet, fmt::Debug, num::NonZeroU64}; +use async_trait::async_trait; use utils::anytrace::Result; use super::node_implementation::NodeType; use crate::{traits::signature_key::SignatureKey, PeerConfig}; +#[async_trait] /// A protocol for determining membership in and participating in a committee. pub trait Membership: Debug + Send + Sync { /// The error type returned by methods like `lookup_leader`. @@ -125,4 +127,32 @@ pub trait Membership: Debug + Send + Sync { /// Returns the threshold required to upgrade the network protocol fn upgrade_threshold(&self, epoch: TYPES::Epoch) -> NonZeroU64; + + #[allow(clippy::type_complexity)] + /// Handles notifications that a new epoch root has been created + /// Is called under a read lock to the Membership. Return a callback + /// with Some to have that callback invoked under a write lock. + async fn add_epoch_root( + &self, + _epoch: TYPES::Epoch, + _block_header: TYPES::BlockHeader, + ) -> Option> { + None + } + + #[must_use] + /// Used to determine if sync_l1 should be called after add_epoch_root. + /// Returning false avoids a redundant read lock for cases where sync_l1 + /// is a no-op. + fn uses_sync_l1() -> bool { + false + } + + #[allow(clippy::type_complexity)] + /// Called after add_epoch_root runs and any callback has been invoked. + /// Causes a read lock to be reacquired for this functionality. If it is + /// not needed, then have uses_sync_l1 return false. + async fn sync_l1(&self) -> Option> { + None + } }