//! CBP节点退出机制 //! //! 实现CBP节点的退出申请、审核、确认和记录功能 use crate::{CbpNode, CbpStatus, CbppL1Error}; use nac_udm::primitives::Address; use serde::{Deserialize, Serialize}; use std::collections::HashMap; /// 退出状态 #[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)] pub enum ExitStatus { /// 待审核 Pending, /// 审核通过 Approved, /// 审核拒绝 Rejected, /// 已确认 Confirmed, /// 已取消 Cancelled, } /// 退出原因 #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] pub enum ExitReason { /// 主动退出 Voluntary, /// 硬件故障 HardwareFailure, /// 违规被迫退出 Violation, /// 质押不足 InsufficientStake, /// 其他原因 Other(String), } /// 退出申请 #[derive(Debug, Clone, Serialize, Deserialize)] pub struct ExitRequest { /// CBP地址 pub address: Address, /// 退出原因 pub reason: ExitReason, /// 申请时间 pub requested_at: u64, /// 退出状态 pub status: ExitStatus, /// 审核时间 pub reviewed_at: Option, /// 审核人 pub reviewer: Option
, /// 审核意见 pub review_comment: Option, /// 确认时间 pub confirmed_at: Option, } impl ExitRequest { pub fn new(address: Address, reason: ExitReason, timestamp: u64) -> Self { Self { address, reason, requested_at: timestamp, status: ExitStatus::Pending, reviewed_at: None, reviewer: None, review_comment: None, confirmed_at: None, } } } /// 退出记录 #[derive(Debug, Clone, Serialize, Deserialize)] pub struct ExitRecord { /// CBP地址 pub address: Address, /// 退出原因 pub reason: ExitReason, /// 申请时间 pub requested_at: u64, /// 确认时间 pub confirmed_at: u64, /// 质押金额 pub stake_amount: u64, /// 赎回金额 pub redemption_amount: u64, /// 处罚金额 pub penalty_amount: u64, /// 声誉分数 pub final_reputation: f64, /// 生产区块数 pub blocks_produced: u64, } /// 退出管理器 pub struct ExitManager { /// 退出申请 requests: HashMap, /// 退出记录 records: Vec, /// 审核等待期(秒) review_period: u64, /// 确认等待期(秒) confirmation_period: u64, } impl ExitManager { pub fn new() -> Self { Self { requests: HashMap::new(), records: Vec::new(), review_period: 7 * 24 * 3600, // 7天 confirmation_period: 14 * 24 * 3600, // 14天 } } /// 提交退出申请 pub fn submit_exit_request( &mut self, node: &CbpNode, reason: ExitReason, timestamp: u64, ) -> Result<(), CbppL1Error> { // 检查节点状态 if node.status == CbpStatus::Exited { return Err(CbppL1Error::InvalidStatus("Node already exited".to_string())); } // 检查是否已有待处理的申请 if let Some(existing) = self.requests.get(&node.address) { if existing.status == ExitStatus::Pending || existing.status == ExitStatus::Approved { return Err(CbppL1Error::ExitRequestExists); } } // 创建退出申请 let request = ExitRequest::new(node.address, reason, timestamp); self.requests.insert(node.address, request); Ok(()) } /// 审核退出申请 pub fn review_exit_request( &mut self, address: &Address, approved: bool, reviewer: Address, comment: Option, timestamp: u64, ) -> Result<(), CbppL1Error> { let request = self.requests.get_mut(address) .ok_or(CbppL1Error::ExitRequestNotFound)?; // 检查申请状态 if request.status != ExitStatus::Pending { return Err(CbppL1Error::InvalidStatus( format!("Request status is {:?}, expected Pending", request.status) )); } // 检查审核等待期 if timestamp < request.requested_at + self.review_period { return Err(CbppL1Error::ReviewPeriodNotMet); } // 更新申请状态 request.status = if approved { ExitStatus::Approved } else { ExitStatus::Rejected }; request.reviewed_at = Some(timestamp); request.reviewer = Some(reviewer); request.review_comment = comment; Ok(()) } /// 确认退出 pub fn confirm_exit( &mut self, address: &Address, node: &CbpNode, redemption_amount: u64, penalty_amount: u64, timestamp: u64, ) -> Result<(), CbppL1Error> { let request = self.requests.get_mut(address) .ok_or(CbppL1Error::ExitRequestNotFound)?; // 检查申请状态 if request.status != ExitStatus::Approved { return Err(CbppL1Error::InvalidStatus( format!("Request status is {:?}, expected Approved", request.status) )); } // 检查确认等待期 let reviewed_at = request.reviewed_at.ok_or(CbppL1Error::InvalidStatus( "Request not reviewed".to_string() ))?; if timestamp < reviewed_at + self.confirmation_period { return Err(CbppL1Error::ConfirmationPeriodNotMet); } // 更新申请状态 request.status = ExitStatus::Confirmed; request.confirmed_at = Some(timestamp); // 创建退出记录 let record = ExitRecord { address: *address, reason: request.reason.clone(), requested_at: request.requested_at, confirmed_at: timestamp, stake_amount: node.stake_amount, redemption_amount, penalty_amount, final_reputation: node.reputation, blocks_produced: node.blocks_produced, }; self.records.push(record); Ok(()) } /// 取消退出申请 pub fn cancel_exit_request(&mut self, address: &Address) -> Result<(), CbppL1Error> { let request = self.requests.get_mut(address) .ok_or(CbppL1Error::ExitRequestNotFound)?; // 只能取消待审核的申请 if request.status != ExitStatus::Pending { return Err(CbppL1Error::InvalidStatus( format!("Cannot cancel request with status {:?}", request.status) )); } request.status = ExitStatus::Cancelled; Ok(()) } /// 获取退出申请 pub fn get_exit_request(&self, address: &Address) -> Option<&ExitRequest> { self.requests.get(address) } /// 获取所有待审核的申请 pub fn get_pending_requests(&self) -> Vec<&ExitRequest> { self.requests.values() .filter(|r| r.status == ExitStatus::Pending) .collect() } /// 获取退出记录 pub fn get_exit_records(&self, address: Option<&Address>) -> Vec<&ExitRecord> { if let Some(addr) = address { self.records.iter() .filter(|r| &r.address == addr) .collect() } else { self.records.iter().collect() } } /// 获取退出统计 pub fn get_exit_statistics(&self) -> ExitStatistics { let total_exits = self.records.len(); let voluntary_exits = self.records.iter() .filter(|r| r.reason == ExitReason::Voluntary) .count(); let violation_exits = self.records.iter() .filter(|r| r.reason == ExitReason::Violation) .count(); let total_penalties = self.records.iter() .map(|r| r.penalty_amount) .sum(); ExitStatistics { total_exits, voluntary_exits, violation_exits, total_penalties, } } } impl Default for ExitManager { fn default() -> Self { Self::new() } } /// 退出统计 #[derive(Debug, Clone, Serialize, Deserialize)] pub struct ExitStatistics { /// 总退出数 pub total_exits: usize, /// 主动退出数 pub voluntary_exits: usize, /// 违规退出数 pub violation_exits: usize, /// 总处罚金额 pub total_penalties: u64, } #[cfg(test)] mod tests { use super::*; fn create_test_node() -> CbpNode { CbpNode { address: Address::new([1u8; 32]), did: "did:nac:test".to_string(), stake_amount: 100_000_000_000, kyc_level: 2, hardware_score: 8000, constitution_score: 80, status: CbpStatus::Active, registered_at: 1000, last_active_at: 2000, blocks_produced: 100, reputation: 0.8, } } #[test] fn test_submit_exit_request() { let mut manager = ExitManager::new(); let node = create_test_node(); let result = manager.submit_exit_request( &node, ExitReason::Voluntary, 3000, ); assert!(result.is_ok()); let request = manager.get_exit_request(&node.address).expect("FIX-006: unexpected None/Err"); assert_eq!(request.status, ExitStatus::Pending); assert_eq!(request.reason, ExitReason::Voluntary); } #[test] fn test_duplicate_exit_request() { let mut manager = ExitManager::new(); let node = create_test_node(); manager.submit_exit_request(&node, ExitReason::Voluntary, 3000).expect("FIX-006: unexpected None/Err"); let result = manager.submit_exit_request(&node, ExitReason::Voluntary, 3000); assert!(result.is_err()); } #[test] fn test_review_exit_request() { let mut manager = ExitManager::new(); let node = create_test_node(); let reviewer = Address::new([2u8; 32]); manager.submit_exit_request(&node, ExitReason::Voluntary, 1000).expect("FIX-006: unexpected None/Err"); // 审核(等待期满足) let result = manager.review_exit_request( &node.address, true, reviewer, Some("Approved".to_string()), 1000 + 7 * 24 * 3600, ); assert!(result.is_ok()); let request = manager.get_exit_request(&node.address).expect("FIX-006: unexpected None/Err"); assert_eq!(request.status, ExitStatus::Approved); } #[test] fn test_review_period_not_met() { let mut manager = ExitManager::new(); let node = create_test_node(); let reviewer = Address::new([2u8; 32]); manager.submit_exit_request(&node, ExitReason::Voluntary, 1000).expect("FIX-006: unexpected None/Err"); // 审核(等待期不满足) let result = manager.review_exit_request( &node.address, true, reviewer, None, 1000 + 3600, // 只过了1小时 ); assert!(result.is_err()); } #[test] fn test_confirm_exit() { let mut manager = ExitManager::new(); let node = create_test_node(); let reviewer = Address::new([2u8; 32]); manager.submit_exit_request(&node, ExitReason::Voluntary, 1000).expect("FIX-006: unexpected None/Err"); manager.review_exit_request( &node.address, true, reviewer, None, 1000 + 7 * 24 * 3600, ).expect("FIX-006: unexpected None/Err"); // 确认退出 let result = manager.confirm_exit( &node.address, &node, 95_000_000_000, 5_000_000_000, 1000 + 21 * 24 * 3600, ); assert!(result.is_ok()); let records = manager.get_exit_records(Some(&node.address)); assert_eq!(records.len(), 1); assert_eq!(records[0].redemption_amount, 95_000_000_000); } #[test] fn test_cancel_exit_request() { let mut manager = ExitManager::new(); let node = create_test_node(); manager.submit_exit_request(&node, ExitReason::Voluntary, 1000).expect("FIX-006: unexpected None/Err"); let result = manager.cancel_exit_request(&node.address); assert!(result.is_ok()); let request = manager.get_exit_request(&node.address).expect("FIX-006: unexpected None/Err"); assert_eq!(request.status, ExitStatus::Cancelled); } #[test] fn test_get_pending_requests() { let mut manager = ExitManager::new(); let node1 = create_test_node(); let mut node2 = create_test_node(); node2.address = Address::new([2u8; 32]); manager.submit_exit_request(&node1, ExitReason::Voluntary, 1000).expect("FIX-006: unexpected None/Err"); manager.submit_exit_request(&node2, ExitReason::HardwareFailure, 1000).expect("FIX-006: unexpected None/Err"); let pending = manager.get_pending_requests(); assert_eq!(pending.len(), 2); } #[test] fn test_exit_statistics() { let mut manager = ExitManager::new(); let node = create_test_node(); let reviewer = Address::new([2u8; 32]); manager.submit_exit_request(&node, ExitReason::Voluntary, 1000).expect("FIX-006: unexpected None/Err"); manager.review_exit_request(&node.address, true, reviewer, None, 1000 + 7 * 24 * 3600).expect("FIX-006: unexpected None/Err"); manager.confirm_exit(&node.address, &node, 95_000_000_000, 5_000_000_000, 1000 + 21 * 24 * 3600).expect("FIX-006: unexpected None/Err"); let stats = manager.get_exit_statistics(); assert_eq!(stats.total_exits, 1); assert_eq!(stats.voluntary_exits, 1); assert_eq!(stats.total_penalties, 5_000_000_000); } }