hashgraph_like_consensus/types.rs
1//! Core request and event types.
2//!
3//! [`CreateProposalRequest`] is the input for creating new proposals.
4//! [`ConsensusEvent`] represents outcomes emitted via the event bus.
5
6use std::time::Duration;
7
8use crate::{
9 error::ConsensusError,
10 protos::consensus::v1::Proposal,
11 utils::{current_timestamp, generate_id, validate_expected_voters_count, validate_timeout},
12};
13
14/// Events emitted by the consensus service when a proposal reaches a terminal state.
15#[derive(Debug, Clone, PartialEq, Eq)]
16pub enum ConsensusEvent {
17 /// Consensus was reached! The proposal has a final result (yes or no).
18 ConsensusReached {
19 proposal_id: u32,
20 result: bool,
21 timestamp: u64,
22 },
23 /// Consensus failed - not enough votes were collected before the timeout.
24 ConsensusFailed { proposal_id: u32, timestamp: u64 },
25}
26
27/// Internal transition result returned after adding a vote to a session.
28#[derive(Debug, Clone, Copy, PartialEq, Eq)]
29pub enum SessionTransition {
30 /// Session remains active with no outcome yet.
31 StillActive,
32 /// Session converged to a boolean result.
33 ConsensusReached(bool),
34}
35
36/// Parameters for creating a new proposal.
37///
38/// All fields are validated on construction via [`CreateProposalRequest::new`].
39/// The `expiration_timestamp` is a relative duration in seconds that gets converted
40/// to an absolute timestamp when the proposal is created.
41#[derive(Debug, Clone)]
42pub struct CreateProposalRequest {
43 /// A short name for the proposal (e.g., "Upgrade to v2").
44 pub name: String,
45 /// Additional details about what's being voted on.
46 pub payload: Vec<u8>,
47 /// The address (public key bytes) of whoever created this proposal.
48 pub proposal_owner: Vec<u8>,
49 /// How many people are expected to vote (used to calculate consensus threshold).
50 pub expected_voters_count: u32,
51 /// The timestamp at which the proposal becomes outdated.
52 pub expiration_timestamp: u64,
53 /// What happens if votes are tied: `true` means YES wins, `false` means NO wins.
54 pub liveness_criteria_yes: bool,
55}
56
57impl CreateProposalRequest {
58 /// Create a new proposal request with validation.
59 pub fn new(
60 name: String,
61 payload: Vec<u8>,
62 proposal_owner: Vec<u8>,
63 expected_voters_count: u32,
64 expiration_timestamp: u64,
65 liveness_criteria_yes: bool,
66 ) -> Result<Self, ConsensusError> {
67 validate_expected_voters_count(expected_voters_count)?;
68 validate_timeout(Duration::from_secs(expiration_timestamp))?;
69 let request = Self {
70 name,
71 payload,
72 proposal_owner,
73 expected_voters_count,
74 expiration_timestamp,
75 liveness_criteria_yes,
76 };
77 Ok(request)
78 }
79
80 /// Convert this request into an actual proposal.
81 ///
82 /// Generates a unique proposal ID and sets the creation timestamp. The proposal
83 /// starts with round 1 and no votes.
84 pub fn into_proposal(self) -> Result<Proposal, ConsensusError> {
85 let proposal_id = generate_id();
86 let now = current_timestamp()?;
87
88 Ok(Proposal {
89 name: self.name,
90 payload: self.payload,
91 proposal_id,
92 proposal_owner: self.proposal_owner,
93 votes: vec![],
94 expected_voters_count: self.expected_voters_count,
95 round: 1,
96 timestamp: now,
97 expiration_timestamp: now + self.expiration_timestamp,
98 liveness_criteria_yes: self.liveness_criteria_yes,
99 })
100 }
101}