NAC_Blockchain/protocol/nac-cbpp-l1/src/penalty.rs

762 lines
22 KiB
Rust
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

//! 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, Hash, Serialize, Deserialize)]
pub enum ViolationType {
/// 双签
DoubleSign,
/// 连续错过区块
ConsecutiveMissedBlocks,
/// 恶意行为
MaliciousBehavior,
/// 硬件不达标
HardwareFailure,
/// KYC过期
KycExpired,
/// 质押不足
InsufficientStake,
/// 宪法违规
ConstitutionViolation,
}
/// 处罚类型
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
pub enum PenaltyType {
/// 警告
Warning,
/// 罚款
Fine,
/// 暂停
Suspension,
/// 强制退出
ForceExit,
/// 削减质押
Slashing,
}
/// 违规检测结果
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ViolationDetection {
/// CBP地址
pub address: Address,
/// 违规类型
pub violation_type: ViolationType,
/// 检测时间
pub detected_at: u64,
/// 证据
pub evidence: String,
/// 严重程度1-10
pub severity: u8,
}
/// 处罚记录
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct PenaltyRecord {
/// 记录ID
pub id: u64,
/// CBP地址
pub address: Address,
/// 违规类型
pub violation_type: ViolationType,
/// 处罚类型
pub penalty_type: PenaltyType,
/// 罚款金额
pub fine_amount: u64,
/// 暂停时长(秒)
pub suspension_duration: Option<u64>,
/// 削减金额
pub slashed_amount: u64,
/// 处罚时间
pub penalized_at: u64,
/// 处罚原因
pub reason: String,
/// 证据
pub evidence: String,
/// 是否已申诉
pub appealed: bool,
}
/// 申诉状态
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
pub enum AppealStatus {
/// 待审核
Pending,
/// 审核通过
Approved,
/// 审核拒绝
Rejected,
}
/// 申诉请求
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct AppealRequest {
/// 申诉ID
pub id: u64,
/// 处罚记录ID
pub penalty_id: u64,
/// CBP地址
pub address: Address,
/// 申诉理由
pub reason: String,
/// 申诉证据
pub evidence: String,
/// 申诉时间
pub appealed_at: u64,
/// 申诉状态
pub status: AppealStatus,
/// 审核时间
pub reviewed_at: Option<u64>,
/// 审核人
pub reviewer: Option<Address>,
/// 审核意见
pub review_comment: Option<String>,
}
/// 处罚配置
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct PenaltyConfig {
/// 违规处罚映射
pub violation_penalties: HashMap<ViolationType, (PenaltyType, u64)>,
/// 警告阈值
pub warning_threshold: u8,
/// 暂停阈值
pub suspension_threshold: u8,
/// 强制退出阈值
pub force_exit_threshold: u8,
/// 申诉期限(秒)
pub appeal_period: u64,
}
impl Default for PenaltyConfig {
fn default() -> Self {
let mut violation_penalties = HashMap::new();
violation_penalties.insert(ViolationType::DoubleSign, (PenaltyType::Slashing, 50_000_000_000));
violation_penalties.insert(ViolationType::ConsecutiveMissedBlocks, (PenaltyType::Fine, 1_000_000_000));
violation_penalties.insert(ViolationType::MaliciousBehavior, (PenaltyType::ForceExit, 100_000_000_000));
violation_penalties.insert(ViolationType::HardwareFailure, (PenaltyType::Warning, 0));
violation_penalties.insert(ViolationType::KycExpired, (PenaltyType::Suspension, 0));
violation_penalties.insert(ViolationType::InsufficientStake, (PenaltyType::Suspension, 0));
violation_penalties.insert(ViolationType::ConstitutionViolation, (PenaltyType::Fine, 5_000_000_000));
Self {
violation_penalties,
warning_threshold: 3,
suspension_threshold: 5,
force_exit_threshold: 10,
appeal_period: 7 * 24 * 3600, // 7天
}
}
}
/// 处罚管理器
pub struct PenaltyManager {
/// 处罚配置
config: PenaltyConfig,
/// 处罚记录
records: Vec<PenaltyRecord>,
/// 申诉请求
appeals: Vec<AppealRequest>,
/// 下一个记录ID
next_record_id: u64,
/// 下一个申诉ID
next_appeal_id: u64,
/// 节点警告计数
warning_counts: HashMap<Address, u8>,
}
impl PenaltyManager {
pub fn new() -> Self {
Self {
config: PenaltyConfig::default(),
records: Vec::new(),
appeals: Vec::new(),
next_record_id: 1,
next_appeal_id: 1,
warning_counts: HashMap::new(),
}
}
pub fn with_config(config: PenaltyConfig) -> Self {
Self {
config,
records: Vec::new(),
appeals: Vec::new(),
next_record_id: 1,
next_appeal_id: 1,
warning_counts: HashMap::new(),
}
}
/// 检测违规
pub fn detect_violation(
&self,
node: &CbpNode,
violation_type: ViolationType,
evidence: String,
severity: u8,
timestamp: u64,
) -> ViolationDetection {
ViolationDetection {
address: node.address,
violation_type,
detected_at: timestamp,
evidence,
severity,
}
}
/// 执行处罚
pub fn execute_penalty(
&mut self,
node: &mut CbpNode,
detection: ViolationDetection,
timestamp: u64,
) -> Result<u64, CbppL1Error> {
// 获取处罚配置
let (penalty_type, base_amount) = self.config.violation_penalties
.get(&detection.violation_type)
.copied()
.ok_or(CbppL1Error::UnknownViolationType)?;
// 根据严重程度调整金额
let adjusted_amount = (base_amount as f64 * detection.severity as f64 / 5.0) as u64;
let mut fine_amount = 0u64;
let mut suspension_duration = None;
let mut slashed_amount = 0u64;
// 执行处罚
match penalty_type {
PenaltyType::Warning => {
let count = self.warning_counts.entry(node.address).or_insert(0);
*count += 1;
// 警告次数过多自动升级为暂停
if *count >= self.config.warning_threshold {
node.status = CbpStatus::Suspended;
suspension_duration = Some(7 * 24 * 3600); // 7天
}
}
PenaltyType::Fine => {
fine_amount = adjusted_amount;
// 从质押中扣除
if node.stake_amount >= fine_amount {
node.stake_amount -= fine_amount;
} else {
return Err(CbppL1Error::InsufficientStakeForPenalty);
}
}
PenaltyType::Suspension => {
node.status = CbpStatus::Suspended;
suspension_duration = Some(30 * 24 * 3600); // 30天
}
PenaltyType::ForceExit => {
node.status = CbpStatus::Exited;
slashed_amount = adjusted_amount.min(node.stake_amount);
node.stake_amount -= slashed_amount;
}
PenaltyType::Slashing => {
slashed_amount = adjusted_amount.min(node.stake_amount);
node.stake_amount -= slashed_amount;
// 严重违规同时暂停
if detection.severity >= 8 {
node.status = CbpStatus::Suspended;
suspension_duration = Some(90 * 24 * 3600); // 90天
}
}
}
// 创建处罚记录
let record = PenaltyRecord {
id: self.next_record_id,
address: node.address,
violation_type: detection.violation_type,
penalty_type,
fine_amount,
suspension_duration,
slashed_amount,
penalized_at: timestamp,
reason: format!("Violation detected: {:?}", detection.violation_type),
evidence: detection.evidence,
appealed: false,
};
let record_id = record.id;
self.records.push(record);
self.next_record_id += 1;
Ok(record_id)
}
/// 提交申诉
pub fn submit_appeal(
&mut self,
penalty_id: u64,
address: Address,
reason: String,
evidence: String,
timestamp: u64,
) -> Result<u64, CbppL1Error> {
// 查找处罚记录
let record = self.records.iter_mut()
.find(|r| r.id == penalty_id)
.ok_or(CbppL1Error::PenaltyRecordNotFound)?;
// 检查地址
if record.address != address {
return Err(CbppL1Error::InvalidAddress);
}
// 检查是否已申诉
if record.appealed {
return Err(CbppL1Error::AlreadyAppealed);
}
// 检查申诉期限
if timestamp > record.penalized_at + self.config.appeal_period {
return Err(CbppL1Error::AppealPeriodExpired);
}
// 标记已申诉
record.appealed = true;
// 创建申诉请求
let appeal = AppealRequest {
id: self.next_appeal_id,
penalty_id,
address,
reason,
evidence,
appealed_at: timestamp,
status: AppealStatus::Pending,
reviewed_at: None,
reviewer: None,
review_comment: None,
};
let appeal_id = appeal.id;
self.appeals.push(appeal);
self.next_appeal_id += 1;
Ok(appeal_id)
}
/// 审核申诉
pub fn review_appeal(
&mut self,
appeal_id: u64,
approved: bool,
reviewer: Address,
comment: Option<String>,
timestamp: u64,
) -> Result<(), CbppL1Error> {
let appeal = self.appeals.iter_mut()
.find(|a| a.id == appeal_id)
.ok_or(CbppL1Error::AppealNotFound)?;
// 检查申诉状态
if appeal.status != AppealStatus::Pending {
return Err(CbppL1Error::InvalidStatus(
format!("Appeal status is {:?}, expected Pending", appeal.status)
));
}
// 更新申诉状态
appeal.status = if approved {
AppealStatus::Approved
} else {
AppealStatus::Rejected
};
appeal.reviewed_at = Some(timestamp);
appeal.reviewer = Some(reviewer);
appeal.review_comment = comment;
Ok(())
}
/// 撤销处罚(申诉成功后)
pub fn revoke_penalty(
&mut self,
penalty_id: u64,
node: &mut CbpNode,
) -> Result<(), CbppL1Error> {
let record = self.records.iter()
.find(|r| r.id == penalty_id)
.ok_or(CbppL1Error::PenaltyRecordNotFound)?;
// 检查地址
if record.address != node.address {
return Err(CbppL1Error::InvalidAddress);
}
// 恢复质押
node.stake_amount += record.fine_amount + record.slashed_amount;
// 恢复状态(如果是暂停或退出)
if node.status == CbpStatus::Suspended || node.status == CbpStatus::Exited {
node.status = CbpStatus::Active;
}
// 清除警告计数
if record.penalty_type == PenaltyType::Warning {
if let Some(count) = self.warning_counts.get_mut(&node.address) {
*count = count.saturating_sub(1);
}
}
Ok(())
}
/// 获取处罚记录
pub fn get_penalty_records(&self, address: Option<&Address>) -> Vec<&PenaltyRecord> {
if let Some(addr) = address {
self.records.iter()
.filter(|r| &r.address == addr)
.collect()
} else {
self.records.iter().collect()
}
}
/// 获取申诉请求
pub fn get_appeals(&self, status: Option<AppealStatus>) -> Vec<&AppealRequest> {
if let Some(s) = status {
self.appeals.iter()
.filter(|a| a.status == s)
.collect()
} else {
self.appeals.iter().collect()
}
}
/// 获取处罚统计
pub fn get_penalty_statistics(&self) -> PenaltyStatistics {
let total_penalties = self.records.len();
let warnings = self.records.iter().filter(|r| r.penalty_type == PenaltyType::Warning).count();
let fines = self.records.iter().filter(|r| r.penalty_type == PenaltyType::Fine).count();
let suspensions = self.records.iter().filter(|r| r.penalty_type == PenaltyType::Suspension).count();
let force_exits = self.records.iter().filter(|r| r.penalty_type == PenaltyType::ForceExit).count();
let slashings = self.records.iter().filter(|r| r.penalty_type == PenaltyType::Slashing).count();
let total_fines = self.records.iter().map(|r| r.fine_amount).sum();
let total_slashed = self.records.iter().map(|r| r.slashed_amount).sum();
let total_appeals = self.appeals.len();
let approved_appeals = self.appeals.iter().filter(|a| a.status == AppealStatus::Approved).count();
PenaltyStatistics {
total_penalties,
warnings,
fines,
suspensions,
force_exits,
slashings,
total_fines,
total_slashed,
total_appeals,
approved_appeals,
}
}
/// 更新配置
pub fn update_config(&mut self, config: PenaltyConfig) {
self.config = config;
}
/// 获取配置
pub fn get_config(&self) -> &PenaltyConfig {
&self.config
}
}
impl Default for PenaltyManager {
fn default() -> Self {
Self::new()
}
}
/// 处罚统计
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct PenaltyStatistics {
/// 总处罚数
pub total_penalties: usize,
/// 警告数
pub warnings: usize,
/// 罚款数
pub fines: usize,
/// 暂停数
pub suspensions: usize,
/// 强制退出数
pub force_exits: usize,
/// 削减数
pub slashings: usize,
/// 总罚款金额
pub total_fines: u64,
/// 总削减金额
pub total_slashed: u64,
/// 总申诉数
pub total_appeals: usize,
/// 通过申诉数
pub approved_appeals: usize,
}
#[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_detect_violation() {
let manager = PenaltyManager::new();
let node = create_test_node();
let detection = manager.detect_violation(
&node,
ViolationType::DoubleSign,
"Evidence of double signing".to_string(),
8,
3000,
);
assert_eq!(detection.violation_type, ViolationType::DoubleSign);
assert_eq!(detection.severity, 8);
}
#[test]
fn test_execute_penalty_warning() {
let mut manager = PenaltyManager::new();
let mut node = create_test_node();
let detection = manager.detect_violation(
&node,
ViolationType::HardwareFailure,
"Hardware benchmark failed".to_string(),
3,
3000,
);
let result = manager.execute_penalty(&mut node, detection, 3000);
assert!(result.is_ok());
let warning_count = manager.warning_counts.get(&node.address).copied().unwrap_or(0);
assert_eq!(warning_count, 1);
}
#[test]
fn test_execute_penalty_fine() {
let mut manager = PenaltyManager::new();
let mut node = create_test_node();
let original_stake = node.stake_amount;
let detection = manager.detect_violation(
&node,
ViolationType::ConsecutiveMissedBlocks,
"Missed 10 consecutive blocks".to_string(),
5,
3000,
);
let result = manager.execute_penalty(&mut node, detection, 3000);
assert!(result.is_ok());
assert!(node.stake_amount < original_stake);
}
#[test]
fn test_execute_penalty_suspension() {
let mut manager = PenaltyManager::new();
let mut node = create_test_node();
let detection = manager.detect_violation(
&node,
ViolationType::KycExpired,
"KYC expired".to_string(),
5,
3000,
);
let result = manager.execute_penalty(&mut node, detection, 3000);
assert!(result.is_ok());
assert_eq!(node.status, CbpStatus::Suspended);
}
#[test]
fn test_execute_penalty_slashing() {
let mut manager = PenaltyManager::new();
let mut node = create_test_node();
let original_stake = node.stake_amount;
let detection = manager.detect_violation(
&node,
ViolationType::DoubleSign,
"Double signing detected".to_string(),
10,
3000,
);
let result = manager.execute_penalty(&mut node, detection, 3000);
assert!(result.is_ok());
assert!(node.stake_amount < original_stake);
}
#[test]
fn test_submit_appeal() {
let mut manager = PenaltyManager::new();
let mut node = create_test_node();
let detection = manager.detect_violation(
&node,
ViolationType::ConsecutiveMissedBlocks,
"Evidence".to_string(),
5,
1000,
);
let penalty_id = manager.execute_penalty(&mut node, detection, 1000).expect("FIX-006: unexpected None/Err");
let result = manager.submit_appeal(
penalty_id,
node.address,
"False positive".to_string(),
"Counter evidence".to_string(),
1000 + 3600,
);
assert!(result.is_ok());
}
#[test]
fn test_appeal_period_expired() {
let mut manager = PenaltyManager::new();
let mut node = create_test_node();
let detection = manager.detect_violation(
&node,
ViolationType::ConsecutiveMissedBlocks,
"Evidence".to_string(),
5,
1000,
);
let penalty_id = manager.execute_penalty(&mut node, detection, 1000).expect("FIX-006: unexpected None/Err");
// 超过申诉期限
let result = manager.submit_appeal(
penalty_id,
node.address,
"False positive".to_string(),
"Counter evidence".to_string(),
1000 + 8 * 24 * 3600,
);
assert!(result.is_err());
}
#[test]
fn test_review_appeal() {
let mut manager = PenaltyManager::new();
let mut node = create_test_node();
let reviewer = Address::new([2u8; 32]);
let detection = manager.detect_violation(
&node,
ViolationType::ConsecutiveMissedBlocks,
"Evidence".to_string(),
5,
1000,
);
let penalty_id = manager.execute_penalty(&mut node, detection, 1000).expect("FIX-006: unexpected None/Err");
let appeal_id = manager.submit_appeal(
penalty_id,
node.address,
"False positive".to_string(),
"Counter evidence".to_string(),
1000 + 3600,
).expect("FIX-006: unexpected None/Err");
let result = manager.review_appeal(
appeal_id,
true,
reviewer,
Some("Approved".to_string()),
1000 + 7200,
);
assert!(result.is_ok());
let appeals = manager.get_appeals(Some(AppealStatus::Approved));
assert_eq!(appeals.len(), 1);
}
#[test]
fn test_revoke_penalty() {
let mut manager = PenaltyManager::new();
let mut node = create_test_node();
let original_stake = node.stake_amount;
let detection = manager.detect_violation(
&node,
ViolationType::ConsecutiveMissedBlocks,
"Evidence".to_string(),
5,
1000,
);
let penalty_id = manager.execute_penalty(&mut node, detection, 1000).expect("FIX-006: unexpected None/Err");
let stake_after_penalty = node.stake_amount;
let result = manager.revoke_penalty(penalty_id, &mut node);
assert!(result.is_ok());
assert!(node.stake_amount > stake_after_penalty);
}
#[test]
fn test_penalty_statistics() {
let mut manager = PenaltyManager::new();
let mut node = create_test_node();
let detection1 = manager.detect_violation(
&node,
ViolationType::HardwareFailure,
"Evidence".to_string(),
3,
1000,
);
manager.execute_penalty(&mut node, detection1, 1000).expect("FIX-006: unexpected None/Err");
let detection2 = manager.detect_violation(
&node,
ViolationType::ConsecutiveMissedBlocks,
"Evidence".to_string(),
5,
2000,
);
manager.execute_penalty(&mut node, detection2, 2000).expect("FIX-006: unexpected None/Err");
let stats = manager.get_penalty_statistics();
assert_eq!(stats.total_penalties, 2);
assert_eq!(stats.warnings, 1);
assert_eq!(stats.fines, 1);
}
}