Skip to main content

de_mls/core/
group_update_handle.rs

1//! Proposal lifecycle management for group membership changes.
2//!
3//! This module tracks proposals through their lifecycle:
4//!
5//! ```text
6//! ┌─────────────┐    vote     ┌─────────────┐   commit   ┌─────────────┐
7//! │   Voting    │ ──────────► │  Approved   │ ─────────► │  Archived   │
8//! │  Proposals  │  (consensus)│  Proposals  │  (steward) │  (history)  │
9//! └─────────────┘             └─────────────┘            └─────────────┘
10//!        │
11//!        │ (rejected)
12//!        ▼
13//!   ┌─────────┐
14//!   │ Removed │
15//!   └─────────┘
16//! ```
17//!
18//! # Proposal Flow
19//!
20//! 1. **Voting**: Proposal created via `add_voting_proposal()`, waiting for votes
21//! 2. **Approved**: Consensus reached, moved via `move_proposal_to_approved()`
22//! 3. **Committed**: Steward batches proposals, clears via `clear_approved_proposals()`
23//! 4. **Archived**: Past batches stored in `epoch_history` for UI display
24
25use std::collections::{HashMap, VecDeque};
26
27use crate::protos::de_mls::messages::v1::GroupUpdateRequest;
28
29/// Consensus proposal identifier (assigned by the consensus service).
30pub type ProposalId = u32;
31
32/// Maximum number of past epoch batches to retain for UI display.
33const MAX_EPOCH_HISTORY: usize = 10;
34
35/// Tracks proposals through voting, approval, and commit lifecycle.
36///
37/// This is the internal state container for proposal management.
38/// Use [`GroupHandle`](crate::core::GroupHandle) methods for access.
39#[derive(Clone, Debug, Default)]
40pub struct CurrentEpochProposals {
41    /// Proposals waiting for consensus voting.
42    /// Key: proposal_id from consensus service
43    approved_proposals: HashMap<ProposalId, GroupUpdateRequest>,
44
45    /// Proposals that passed consensus, waiting for steward to commit.
46    voting_proposals: HashMap<ProposalId, GroupUpdateRequest>,
47
48    /// History of committed proposal batches (most recent last).
49    /// Limited to `MAX_EPOCH_HISTORY` entries for memory efficiency.
50    epoch_history: VecDeque<HashMap<ProposalId, GroupUpdateRequest>>,
51}
52
53impl CurrentEpochProposals {
54    /// Create a new steward with empty proposal queues.
55    pub fn new() -> Self {
56        Self {
57            approved_proposals: HashMap::new(),
58            voting_proposals: HashMap::new(),
59            epoch_history: VecDeque::new(),
60        }
61    }
62
63    /// Add a proposal to the approved proposals queue.
64    pub fn add_proposal(&mut self, proposal_id: ProposalId, proposal: GroupUpdateRequest) {
65        self.approved_proposals.insert(proposal_id, proposal);
66    }
67
68    /// Get the count of approved proposals waiting for voting.
69    pub fn approved_proposals_count(&self) -> usize {
70        self.approved_proposals.len()
71    }
72
73    /// Get a copy of the approved proposals.
74    pub fn approved_proposals(&self) -> HashMap<ProposalId, GroupUpdateRequest> {
75        self.approved_proposals.clone()
76    }
77    /// Add a proposal to the voting proposals queue.
78    ///
79    /// # Arguments
80    /// * `proposal_id` - The proposal ID
81    /// * `proposal` - The group update request to add
82    pub fn add_voting_proposal(&mut self, proposal_id: ProposalId, proposal: GroupUpdateRequest) {
83        self.voting_proposals.insert(proposal_id, proposal);
84    }
85
86    pub fn remove_voting_proposal(&mut self, proposal_id: ProposalId) {
87        self.voting_proposals.remove(&proposal_id);
88    }
89
90    /// Clear the approved proposals, archiving them to epoch history.
91    pub fn clear_approved_proposals(&mut self) {
92        if !self.approved_proposals.is_empty() {
93            let snapshot = std::mem::take(&mut self.approved_proposals);
94            if self.epoch_history.len() >= MAX_EPOCH_HISTORY {
95                self.epoch_history.pop_front();
96            }
97            self.epoch_history.push_back(snapshot);
98        }
99    }
100
101    /// Get the epoch history (past batches of approved proposals, most recent last).
102    pub fn epoch_history(&self) -> &VecDeque<HashMap<ProposalId, GroupUpdateRequest>> {
103        &self.epoch_history
104    }
105
106    pub fn move_proposal_to_approved(&mut self, proposal_id: ProposalId) {
107        if let Some(proposal) = self.voting_proposals.remove(&proposal_id) {
108            self.approved_proposals.insert(proposal_id, proposal);
109        }
110    }
111
112    pub fn is_owner_of_proposal(&self, proposal_id: ProposalId) -> bool {
113        self.voting_proposals.contains_key(&proposal_id)
114    }
115}