Skip to main content

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}