use std::{sync::mpsc::Sender, time::SystemTime};
use ed25519_dalek::VerifyingKey;
use crate::{
app::{
App, ProduceBlockRequest, ProduceBlockResponse, ValidateBlockRequest, ValidateBlockResponse,
},
block_tree::{
accessors::internal::{BlockTreeError, BlockTreeSingleton},
invariants::{repropose_block, safe_block, safe_nudge, safe_pc},
pluggables::KVStore,
},
events::{
CollectPCEvent, Event, InsertBlockEvent, NewViewEvent, NudgeEvent, PhaseVoteEvent,
ProposeEvent, ReceiveNewViewEvent, ReceiveNudgeEvent, ReceivePhaseVoteEvent,
ReceiveProposalEvent, StartViewEvent,
},
hotstuff::{
messages::{HotStuffMessage, NewView, Nudge, PhaseVote, Proposal},
roles::{is_phase_voter, is_proposer, new_view_recipients},
types::{Phase, PhaseVoteCollector},
},
networking::{
network::{Network, ValidatorSetUpdateHandle},
sending::SenderHandle,
},
pacemaker::implementation::ViewInfo,
types::{
block::Block,
crypto_primitives::Keypair,
data_types::{BlockHeight, ChainID},
signed_messages::{ActiveCollectorPair, Certificate, SignedMessage},
validator_set::ValidatorSetState,
},
};
use super::roles::phase_vote_recipient;
pub(crate) struct HotStuff<N: Network> {
config: HotStuffConfiguration,
view_info: ViewInfo,
proposal_status: ProposalStatus,
phase_vote_collectors: ActiveCollectorPair<PhaseVoteCollector>,
sender_handle: SenderHandle<N>,
validator_set_update_handle: ValidatorSetUpdateHandle<N>,
event_publisher: Option<Sender<Event>>,
}
impl<N: Network> HotStuff<N> {
pub(crate) fn new(
config: HotStuffConfiguration,
view_info: ViewInfo,
sender_handle: SenderHandle<N>,
validator_set_update_handle: ValidatorSetUpdateHandle<N>,
init_validator_set_state: ValidatorSetState,
event_publisher: Option<Sender<Event>>,
) -> Self {
let phase_vote_collectors = <ActiveCollectorPair<PhaseVoteCollector>>::new(
config.chain_id,
view_info.view,
&init_validator_set_state,
);
let proposal_status = ProposalStatus::WaitingForProposal;
Self {
config,
view_info,
proposal_status,
phase_vote_collectors,
sender_handle,
validator_set_update_handle,
event_publisher,
}
}
pub(crate) fn is_view_outdated(&self, new_view_info: &ViewInfo) -> bool {
new_view_info.view != self.view_info.view
}
pub(crate) fn enter_view<K: KVStore>(
&mut self,
new_view_info: ViewInfo,
block_tree: &mut BlockTreeSingleton<K>,
app: &mut impl App<K>,
) -> Result<(), HotStuffError> {
let validator_set_state = block_tree.validator_set_state()?;
let new_view = NewView {
chain_id: self.config.chain_id,
view: self.view_info.view,
highest_pc: block_tree.highest_pc()?,
};
match new_view_recipients(&new_view, &validator_set_state) {
(committed_vs_leader, None) => self
.sender_handle
.send::<HotStuffMessage>(committed_vs_leader, new_view.clone().into()),
(committed_vs_leader, Some(prev_vs_leader)) => {
self.sender_handle
.send::<HotStuffMessage>(committed_vs_leader, new_view.clone().into());
self.sender_handle
.send::<HotStuffMessage>(prev_vs_leader, new_view.clone().into());
}
}
Event::NewView(NewViewEvent {
timestamp: SystemTime::now(),
new_view,
})
.publish(&self.event_publisher);
self.view_info = new_view_info;
self.proposal_status = ProposalStatus::WaitingForProposal;
self.phase_vote_collectors = <ActiveCollectorPair<PhaseVoteCollector>>::new(
self.config.chain_id,
self.view_info.view,
&validator_set_state,
);
block_tree.set_highest_view_entered(self.view_info.view)?;
Event::StartView(StartViewEvent {
timestamp: SystemTime::now(),
view: self.view_info.view.clone(),
})
.publish(&self.event_publisher);
if is_proposer(
&self.config.keypair.public(),
self.view_info.view,
&validator_set_state,
) {
if let Some(block_hash) = repropose_block(self.view_info.view, block_tree)? {
let block = block_tree
.block(&block_hash)?
.ok_or(BlockTreeError::BlockExpectedButNotFound { block: block_hash })?;
let proposal = Proposal {
chain_id: self.config.chain_id,
view: self.view_info.view,
block,
};
self.sender_handle
.broadcast::<HotStuffMessage>(proposal.clone().into());
Event::Propose(ProposeEvent {
timestamp: SystemTime::now(),
proposal,
})
.publish(&self.event_publisher);
return Ok(());
}
let highest_pc = block_tree.highest_pc()?;
match highest_pc.phase {
Phase::Generic | Phase::Decide => {
let (parent_block, child_height) = if highest_pc.is_genesis_pc() {
(None, BlockHeight::new(0))
} else {
let parent_height = block_tree.block_height(&highest_pc.block)?.ok_or(
BlockTreeError::BlockExpectedButNotFound {
block: highest_pc.block,
},
)?;
(Some(highest_pc.block), parent_height + 1)
};
let produce_block_request = ProduceBlockRequest::new(
self.view_info.view,
parent_block,
block_tree.app_view(parent_block.as_ref())?,
);
let ProduceBlockResponse {
data,
data_hash,
app_state_updates: _,
validator_set_updates: _,
} = app.produce_block(produce_block_request);
let block = Block::new(child_height, highest_pc, data_hash, data);
let proposal = Proposal {
chain_id: self.config.chain_id,
view: self.view_info.view,
block,
};
self.sender_handle
.broadcast::<HotStuffMessage>(proposal.clone().into());
Event::Propose(ProposeEvent {
timestamp: SystemTime::now(),
proposal,
})
.publish(&self.event_publisher);
}
Phase::Prepare | Phase::Precommit | Phase::Commit => {
let nudge = Nudge {
chain_id: self.config.chain_id,
view: self.view_info.view,
justify: highest_pc,
};
self.sender_handle
.broadcast::<HotStuffMessage>(nudge.clone().into());
Event::Nudge(NudgeEvent {
timestamp: SystemTime::now(),
nudge,
})
.publish(&self.event_publisher)
}
}
}
Ok(())
}
pub(crate) fn on_receive_msg<K: KVStore>(
&mut self,
msg: HotStuffMessage,
origin: &VerifyingKey,
block_tree: &mut BlockTreeSingleton<K>,
app: &mut impl App<K>,
) -> Result<(), HotStuffError> {
if matches!(msg, HotStuffMessage::Proposal(_)) || matches!(msg, HotStuffMessage::Nudge(_)) {
let validator_set_state = block_tree.validator_set_state()?;
if !is_proposer(origin, self.view_info.view, &validator_set_state) {
return Ok(());
}
if self.proposal_status.has_one_leader_proposed(origin)
|| self.proposal_status.have_all_leaders_proposed()
{
return Ok(());
}
}
match msg {
HotStuffMessage::Proposal(proposal) => {
self.on_receive_proposal(proposal, origin, block_tree, app)
}
HotStuffMessage::Nudge(nudge) => self.on_receive_nudge(nudge, origin, block_tree),
HotStuffMessage::PhaseVote(vote) => {
self.on_receive_phase_vote(vote, origin, block_tree)
}
HotStuffMessage::NewView(new_view) => {
self.on_receive_new_view(new_view, origin, block_tree)
}
}
}
fn on_receive_proposal<K: KVStore>(
&mut self,
proposal: Proposal,
origin: &VerifyingKey,
block_tree: &mut BlockTreeSingleton<K>,
app: &mut impl App<K>,
) -> Result<(), HotStuffError> {
Event::ReceiveProposal(ReceiveProposalEvent {
timestamp: SystemTime::now(),
origin: *origin,
proposal: proposal.clone(),
})
.publish(&self.event_publisher);
if !proposal.block.is_correct(block_tree)?
|| !safe_block(&proposal.block, block_tree, self.config.chain_id)?
{
match self.proposal_status {
ProposalStatus::WaitingForProposal => {
self.proposal_status = ProposalStatus::OneLeaderProposed { leader: *origin }
}
ProposalStatus::OneLeaderProposed { leader: _ } => {
self.proposal_status = ProposalStatus::AllLeadersProposed
}
_ => {}
}
return Ok(());
}
let parent_block = if proposal.block.justify.is_genesis_pc() {
None
} else {
Some(&proposal.block.justify.block)
};
let validate_block_request =
ValidateBlockRequest::new(&proposal.block, block_tree.app_view(parent_block)?);
if let ValidateBlockResponse::Valid {
app_state_updates,
validator_set_updates,
} = app.validate_block(validate_block_request)
{
block_tree.insert(
&proposal.block,
app_state_updates.as_ref(),
validator_set_updates.as_ref(),
)?;
Event::InsertBlock(InsertBlockEvent {
timestamp: SystemTime::now(),
block: proposal.block.clone(),
})
.publish(&self.event_publisher);
let committed_validator_set_updates =
block_tree.update(&proposal.block.justify, &self.event_publisher)?;
if let Some(vs_updates) = committed_validator_set_updates {
self.validator_set_update_handle
.update_validator_set(vs_updates)
}
let validator_set_state = block_tree.validator_set_state()?;
let _ = self
.phase_vote_collectors
.update_validator_sets(&validator_set_state);
if is_phase_voter(
&self.config.keypair.public(),
&validator_set_state,
&proposal.block.justify,
) && (block_tree.highest_view_voted()?.is_none()
|| block_tree.highest_view_voted()?.unwrap() < self.view_info.view)
{
let vote_phase = if validator_set_updates.is_some() {
Phase::Prepare
} else {
Phase::Generic
};
let phase_vote = PhaseVote::new(
&self.config.keypair,
self.config.chain_id,
self.view_info.view,
proposal.block.hash,
vote_phase,
);
let vote_recipient = phase_vote_recipient(&phase_vote, &validator_set_state);
self.sender_handle
.send::<HotStuffMessage>(vote_recipient, phase_vote.clone().into());
block_tree.set_highest_view_phase_voted(self.view_info.view)?;
Event::PhaseVote(PhaseVoteEvent {
timestamp: SystemTime::now(),
vote: phase_vote.clone(),
})
.publish(&self.event_publisher)
}
}
match self.proposal_status {
ProposalStatus::WaitingForProposal => {
self.proposal_status = ProposalStatus::OneLeaderProposed { leader: *origin }
}
ProposalStatus::OneLeaderProposed { leader: _ } => {
self.proposal_status = ProposalStatus::AllLeadersProposed
}
_ => {}
}
Ok(())
}
fn on_receive_nudge<K: KVStore>(
&mut self,
nudge: Nudge,
origin: &VerifyingKey,
block_tree: &mut BlockTreeSingleton<K>,
) -> Result<(), HotStuffError> {
Event::ReceiveNudge(ReceiveNudgeEvent {
timestamp: SystemTime::now(),
origin: *origin,
nudge: nudge.clone(),
})
.publish(&self.event_publisher);
if !nudge.justify.is_correct(block_tree)?
|| !safe_nudge(
&nudge,
self.view_info.view,
block_tree,
self.config.chain_id,
)?
{
match self.proposal_status {
ProposalStatus::WaitingForProposal => {
self.proposal_status = ProposalStatus::OneLeaderProposed { leader: *origin }
}
ProposalStatus::OneLeaderProposed { leader: _ } => {
self.proposal_status = ProposalStatus::AllLeadersProposed
}
_ => {}
}
return Ok(());
}
let committed_validator_set_updates =
block_tree.update(&nudge.justify, &self.event_publisher)?;
if let Some(vs_updates) = committed_validator_set_updates {
self.validator_set_update_handle
.update_validator_set(vs_updates)
}
let validator_set_state = block_tree.validator_set_state()?;
let _ = self
.phase_vote_collectors
.update_validator_sets(&validator_set_state);
if is_phase_voter(
&self.config.keypair.public(),
&validator_set_state,
&nudge.justify,
) && (block_tree.highest_view_voted()?.is_none()
|| block_tree.highest_view_voted()?.unwrap() < self.view_info.view)
{
let vote_phase = match nudge.justify.phase {
Phase::Prepare => Phase::Precommit,
Phase::Precommit => Phase::Commit,
Phase::Commit => Phase::Decide,
_ => unreachable!("if `safe_nudge` check passed then `vote_phase` should be either `Precommit`, `Commit`, or `Decide`"),
};
let vote = PhaseVote::new(
&self.config.keypair,
self.config.chain_id,
self.view_info.view,
nudge.justify.block,
vote_phase,
);
let vote_recipient = phase_vote_recipient(&vote, &validator_set_state);
self.sender_handle
.send::<HotStuffMessage>(vote_recipient, vote.clone().into());
block_tree.set_highest_view_phase_voted(self.view_info.view)?;
Event::PhaseVote(PhaseVoteEvent {
timestamp: SystemTime::now(),
vote: vote.clone(),
})
.publish(&self.event_publisher);
}
match self.proposal_status {
ProposalStatus::WaitingForProposal => {
self.proposal_status = ProposalStatus::OneLeaderProposed { leader: *origin }
}
ProposalStatus::OneLeaderProposed { leader: _ } => {
self.proposal_status = ProposalStatus::AllLeadersProposed
}
_ => {}
}
Ok(())
}
fn on_receive_phase_vote<K: KVStore>(
&mut self,
phase_vote: PhaseVote,
signer: &VerifyingKey,
block_tree: &mut BlockTreeSingleton<K>,
) -> Result<(), HotStuffError> {
Event::ReceivePhaseVote(ReceivePhaseVoteEvent {
timestamp: SystemTime::now(),
origin: *signer,
phase_vote: phase_vote.clone(),
})
.publish(&self.event_publisher);
if phase_vote.is_correct(signer) {
if let Some(new_pc) = self.phase_vote_collectors.collect(signer, phase_vote) {
Event::CollectPC(CollectPCEvent {
timestamp: SystemTime::now(),
phase_certificate: new_pc.clone(),
})
.publish(&self.event_publisher);
if !new_pc.is_correct(block_tree)?
|| !safe_pc(&new_pc, block_tree, self.config.chain_id)?
{
return Ok(());
}
let committed_validator_set_updates =
block_tree.update(&new_pc, &self.event_publisher)?;
if let Some(vs_updates) = committed_validator_set_updates {
self.validator_set_update_handle
.update_validator_set(vs_updates)
}
let validator_set_state = block_tree.validator_set_state()?;
let _ = self
.phase_vote_collectors
.update_validator_sets(&validator_set_state);
}
}
Ok(())
}
fn on_receive_new_view<K: KVStore>(
&mut self,
new_view: NewView,
origin: &VerifyingKey,
block_tree: &mut BlockTreeSingleton<K>,
) -> Result<(), HotStuffError> {
Event::ReceiveNewView(ReceiveNewViewEvent {
timestamp: SystemTime::now(),
origin: *origin,
new_view: new_view.clone(),
})
.publish(&self.event_publisher);
if new_view.highest_pc.is_correct(block_tree)?
&& safe_pc(&new_view.highest_pc, block_tree, self.config.chain_id)?
{
let committed_validator_set_updates =
block_tree.update(&new_view.highest_pc, &self.event_publisher)?;
if let Some(vs_updates) = committed_validator_set_updates {
self.validator_set_update_handle
.update_validator_set(vs_updates)
}
let validator_set_state = block_tree.validator_set_state()?;
let _ = self
.phase_vote_collectors
.update_validator_sets(&validator_set_state);
}
Ok(())
}
}
#[derive(Clone)]
pub(crate) struct HotStuffConfiguration {
pub(crate) chain_id: ChainID,
pub(crate) keypair: Keypair,
}
#[derive(Debug)]
pub enum HotStuffError {
BlockTreeError(BlockTreeError),
}
impl From<BlockTreeError> for HotStuffError {
fn from(value: BlockTreeError) -> Self {
HotStuffError::BlockTreeError(value)
}
}
pub enum ProposalStatus {
WaitingForProposal,
OneLeaderProposed { leader: VerifyingKey },
AllLeadersProposed,
}
impl ProposalStatus {
fn has_one_leader_proposed(&self, leader: &VerifyingKey) -> bool {
match self {
ProposalStatus::OneLeaderProposed { leader: validator } => validator == leader,
_ => false,
}
}
fn have_all_leaders_proposed(&self) -> bool {
matches!(self, ProposalStatus::AllLeadersProposed)
}
}