clp.rs
1 // Copyright (c) 2025 ADnet Contributors 2 // This file is part of the AlphaVM library. 3 4 // Licensed under the Apache License, Version 2.0 (the "License"); 5 // you may not use this file except in compliance with the License. 6 // You may obtain a copy of the License at: 7 8 // http://www.apache.org/licenses/LICENSE-2.0 9 10 // Unless required by applicable law or agreed to in writing, software 11 // distributed under the License is distributed on an "AS IS" BASIS, 12 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 // See the License for the specific language governing permissions and 14 // limitations under the License. 15 16 //! # Continuous Liveness Proof (CLP) System (F-V10 to F-V13) 17 //! 18 //! Implements the CLP attestation system for validator liveness verification. 19 //! 20 //! ## Attestation Tiers 21 //! 22 //! 1. Intel SGX (hardware TEE) 23 //! 2. AMD SEV-SNP (hardware TEE) 24 //! 3. TPM 2.0 Measured Boot 25 //! 4. Software-only attestation 26 //! 27 //! All tiers receive equal rewards - tier choice is validator discretion. 28 //! 29 //! ## Requirements 30 //! 31 //! - Validators must submit CLP attestation every 24 hours 32 //! - 24-hour grace period after expiry before penalties 33 //! - Failure penalties: ejection, 5-epoch disqualification, 3% stake loss 34 35 use crate::console::{prelude::*, types::Field}; 36 37 use std::collections::HashMap; 38 39 // ============================================================================ 40 // Constants 41 // ============================================================================ 42 43 /// CLP attestation validity period in seconds (24 hours) 44 pub const CLP_VALIDITY_PERIOD_SECS: u64 = 24 * 60 * 60; 45 46 /// Grace period after CLP expiry before penalties (24 hours) 47 pub const CLP_GRACE_PERIOD_SECS: u64 = 24 * 60 * 60; 48 49 /// Disqualification period after CLP failure (5 epochs) 50 pub const CLP_DISQUALIFICATION_EPOCHS: u64 = 5; 51 52 /// Stake slashing percentage for CLP failure (3% = 300 basis points) 53 pub const CLP_FAILURE_SLASH_BPS: u16 = 300; 54 55 // ============================================================================ 56 // Attestation Tier 57 // ============================================================================ 58 59 /// CLP attestation tier (security level) 60 #[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Hash)] 61 pub enum AttestationTier { 62 /// Intel SGX hardware TEE - highest hardware security 63 IntelSgx, 64 /// AMD SEV-SNP hardware TEE - high hardware security 65 AmdSevSnp, 66 /// TPM 2.0 Measured Boot - hardware-backed measurement 67 Tpm2, 68 /// Software-only attestation - cryptographic proof without hardware TEE 69 #[default] 70 SoftwareOnly, 71 } 72 73 impl AttestationTier { 74 /// Get human-readable name for the tier 75 pub fn name(&self) -> &'static str { 76 match self { 77 AttestationTier::IntelSgx => "Intel SGX", 78 AttestationTier::AmdSevSnp => "AMD SEV-SNP", 79 AttestationTier::Tpm2 => "TPM 2.0", 80 AttestationTier::SoftwareOnly => "Software", 81 } 82 } 83 84 /// Get tier number (1-4) 85 pub fn tier_number(&self) -> u8 { 86 match self { 87 AttestationTier::IntelSgx => 1, 88 AttestationTier::AmdSevSnp => 2, 89 AttestationTier::Tpm2 => 3, 90 AttestationTier::SoftwareOnly => 4, 91 } 92 } 93 94 /// Check if this is a hardware TEE tier 95 pub fn is_hardware_tee(&self) -> bool { 96 matches!(self, AttestationTier::IntelSgx | AttestationTier::AmdSevSnp) 97 } 98 } 99 100 impl std::fmt::Display for AttestationTier { 101 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 102 write!(f, "{}", self.name()) 103 } 104 } 105 106 // ============================================================================ 107 // Attestation Status 108 // ============================================================================ 109 110 /// Status of a CLP attestation 111 #[derive(Clone, Copy, Debug, Default, PartialEq, Eq)] 112 pub enum AttestationStatus { 113 /// Attestation is valid and current 114 Valid, 115 /// Attestation has expired but within grace period 116 Expired, 117 /// Attestation failed verification 118 Invalid, 119 /// No attestation submitted (new validator or after failure) 120 #[default] 121 Missing, 122 /// Validator is disqualified due to CLP failure 123 Disqualified, 124 } 125 126 impl std::fmt::Display for AttestationStatus { 127 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 128 match self { 129 AttestationStatus::Valid => write!(f, "valid"), 130 AttestationStatus::Expired => write!(f, "expired"), 131 AttestationStatus::Invalid => write!(f, "invalid"), 132 AttestationStatus::Missing => write!(f, "missing"), 133 AttestationStatus::Disqualified => write!(f, "disqualified"), 134 } 135 } 136 } 137 138 // ============================================================================ 139 // CLP Attestation 140 // ============================================================================ 141 142 /// A CLP attestation proof 143 #[derive(Clone, Debug)] 144 pub struct ClpAttestation<N: Network> { 145 /// Validator ID this attestation is for 146 validator_id: Field<N>, 147 /// Attestation tier used 148 tier: AttestationTier, 149 /// Software version hash being attested 150 software_hash: Field<N>, 151 /// Attestation quote/proof data (tier-specific) 152 quote_data: Vec<u8>, 153 /// Signature from TEE or signing key 154 signature: Vec<u8>, 155 /// Timestamp when attestation was created 156 created_at: u64, 157 /// Timestamp when attestation expires 158 expires_at: u64, 159 /// Block height when submitted on-chain 160 submitted_block: Option<u32>, 161 } 162 163 impl<N: Network> ClpAttestation<N> { 164 /// Create a new CLP attestation 165 pub fn new( 166 validator_id: Field<N>, 167 tier: AttestationTier, 168 software_hash: Field<N>, 169 quote_data: Vec<u8>, 170 signature: Vec<u8>, 171 current_time: u64, 172 ) -> Self { 173 Self { 174 validator_id, 175 tier, 176 software_hash, 177 quote_data, 178 signature, 179 created_at: current_time, 180 expires_at: current_time + CLP_VALIDITY_PERIOD_SECS, 181 submitted_block: None, 182 } 183 } 184 185 // Getters 186 pub fn validator_id(&self) -> &Field<N> { 187 &self.validator_id 188 } 189 190 pub fn tier(&self) -> AttestationTier { 191 self.tier 192 } 193 194 pub fn software_hash(&self) -> &Field<N> { 195 &self.software_hash 196 } 197 198 pub fn quote_data(&self) -> &[u8] { 199 &self.quote_data 200 } 201 202 pub fn signature(&self) -> &[u8] { 203 &self.signature 204 } 205 206 pub fn created_at(&self) -> u64 { 207 self.created_at 208 } 209 210 pub fn expires_at(&self) -> u64 { 211 self.expires_at 212 } 213 214 pub fn submitted_block(&self) -> Option<u32> { 215 self.submitted_block 216 } 217 218 /// Set the block height when submitted 219 pub fn set_submitted_block(&mut self, block: u32) { 220 self.submitted_block = Some(block); 221 } 222 223 /// Check if attestation is expired 224 pub fn is_expired(&self, current_time: u64) -> bool { 225 current_time >= self.expires_at 226 } 227 228 /// Check if attestation is within grace period 229 pub fn is_in_grace_period(&self, current_time: u64) -> bool { 230 current_time >= self.expires_at && current_time < self.expires_at + CLP_GRACE_PERIOD_SECS 231 } 232 233 /// Check if attestation has completely failed (past grace period) 234 pub fn is_failed(&self, current_time: u64) -> bool { 235 current_time >= self.expires_at + CLP_GRACE_PERIOD_SECS 236 } 237 238 /// Get time remaining until expiry (0 if already expired) 239 pub fn time_remaining(&self, current_time: u64) -> u64 { 240 self.expires_at.saturating_sub(current_time) 241 } 242 243 /// Get time remaining in grace period (0 if not in grace or past it) 244 pub fn grace_time_remaining(&self, current_time: u64) -> u64 { 245 if !self.is_in_grace_period(current_time) { 246 return 0; 247 } 248 let grace_end = self.expires_at + CLP_GRACE_PERIOD_SECS; 249 grace_end - current_time 250 } 251 } 252 253 // ============================================================================ 254 // Trusted Root Keys 255 // ============================================================================ 256 257 /// Trusted root key for attestation verification 258 #[derive(Clone, Debug)] 259 pub struct TrustedRoot<N: Network> { 260 /// Root key identifier 261 id: Field<N>, 262 /// Tier this root is valid for 263 tier: AttestationTier, 264 /// Public key or certificate hash 265 public_key: Vec<u8>, 266 /// Issuer name (e.g., "Intel", "AMD", manufacturer) 267 issuer: String, 268 /// Whether this root is currently active 269 active: bool, 270 /// Activation timestamp 271 activated_at: u64, 272 /// Revocation timestamp (if revoked) 273 revoked_at: Option<u64>, 274 } 275 276 impl<N: Network> TrustedRoot<N> { 277 /// Create a new trusted root 278 pub fn new(id: Field<N>, tier: AttestationTier, public_key: Vec<u8>, issuer: String, current_time: u64) -> Self { 279 Self { id, tier, public_key, issuer, active: true, activated_at: current_time, revoked_at: None } 280 } 281 282 // Getters 283 pub fn id(&self) -> &Field<N> { 284 &self.id 285 } 286 287 pub fn tier(&self) -> AttestationTier { 288 self.tier 289 } 290 291 pub fn public_key(&self) -> &[u8] { 292 &self.public_key 293 } 294 295 pub fn issuer(&self) -> &str { 296 &self.issuer 297 } 298 299 pub fn is_active(&self) -> bool { 300 self.active 301 } 302 303 /// Revoke this root 304 pub fn revoke(&mut self, current_time: u64) { 305 self.active = false; 306 self.revoked_at = Some(current_time); 307 } 308 } 309 310 // ============================================================================ 311 // Validator CLP Record 312 // ============================================================================ 313 314 /// CLP record for a validator 315 #[derive(Clone, Debug)] 316 pub struct ValidatorClpRecord<N: Network> { 317 /// Validator ID 318 validator_id: Field<N>, 319 /// Current attestation (if any) 320 current_attestation: Option<ClpAttestation<N>>, 321 /// Current status 322 status: AttestationStatus, 323 /// Preferred tier (may differ from current if hardware unavailable) 324 preferred_tier: AttestationTier, 325 /// Historical attestation count 326 attestation_count: u64, 327 /// Consecutive successful attestations 328 consecutive_success: u64, 329 /// Last failure timestamp (if any) 330 last_failure: Option<u64>, 331 /// Disqualification end epoch (if disqualified) 332 disqualified_until_epoch: Option<u64>, 333 /// Total times disqualified 334 disqualification_count: u32, 335 } 336 337 impl<N: Network> ValidatorClpRecord<N> { 338 /// Create a new CLP record for a validator 339 pub fn new(validator_id: Field<N>) -> Self { 340 Self { 341 validator_id, 342 current_attestation: None, 343 status: AttestationStatus::Missing, 344 preferred_tier: AttestationTier::SoftwareOnly, 345 attestation_count: 0, 346 consecutive_success: 0, 347 last_failure: None, 348 disqualified_until_epoch: None, 349 disqualification_count: 0, 350 } 351 } 352 353 // Getters 354 pub fn validator_id(&self) -> &Field<N> { 355 &self.validator_id 356 } 357 358 pub fn current_attestation(&self) -> Option<&ClpAttestation<N>> { 359 self.current_attestation.as_ref() 360 } 361 362 pub fn status(&self) -> AttestationStatus { 363 self.status 364 } 365 366 pub fn preferred_tier(&self) -> AttestationTier { 367 self.preferred_tier 368 } 369 370 pub fn current_tier(&self) -> Option<AttestationTier> { 371 self.current_attestation.as_ref().map(|a| a.tier()) 372 } 373 374 pub fn attestation_count(&self) -> u64 { 375 self.attestation_count 376 } 377 378 pub fn consecutive_success(&self) -> u64 { 379 self.consecutive_success 380 } 381 382 pub fn disqualified_until_epoch(&self) -> Option<u64> { 383 self.disqualified_until_epoch 384 } 385 386 pub fn disqualification_count(&self) -> u32 { 387 self.disqualification_count 388 } 389 390 /// Set preferred tier 391 pub fn set_preferred_tier(&mut self, tier: AttestationTier) { 392 self.preferred_tier = tier; 393 } 394 395 /// Check if validator is disqualified 396 pub fn is_disqualified(&self, current_epoch: u64) -> bool { 397 match self.disqualified_until_epoch { 398 Some(until) => current_epoch < until, 399 None => false, 400 } 401 } 402 403 /// Submit a new attestation 404 pub fn submit_attestation(&mut self, attestation: ClpAttestation<N>) { 405 self.current_attestation = Some(attestation); 406 self.status = AttestationStatus::Valid; 407 self.attestation_count += 1; 408 self.consecutive_success += 1; 409 } 410 411 /// Update status based on current time 412 pub fn update_status(&mut self, current_time: u64, current_epoch: u64) { 413 // Check disqualification first 414 if self.is_disqualified(current_epoch) { 415 self.status = AttestationStatus::Disqualified; 416 return; 417 } 418 419 // Clear disqualification if expired 420 if self.disqualified_until_epoch.is_some() && !self.is_disqualified(current_epoch) { 421 self.disqualified_until_epoch = None; 422 } 423 424 // Check attestation status 425 match &self.current_attestation { 426 None => { 427 self.status = AttestationStatus::Missing; 428 } 429 Some(attestation) => { 430 if attestation.is_failed(current_time) { 431 self.status = AttestationStatus::Invalid; 432 } else if attestation.is_expired(current_time) { 433 // Covers both in-grace-period and just-expired states 434 self.status = AttestationStatus::Expired; 435 } else { 436 self.status = AttestationStatus::Valid; 437 } 438 } 439 } 440 } 441 442 /// Record a CLP failure and apply disqualification 443 pub fn record_failure(&mut self, current_time: u64, current_epoch: u64) { 444 self.last_failure = Some(current_time); 445 self.consecutive_success = 0; 446 self.disqualified_until_epoch = Some(current_epoch + CLP_DISQUALIFICATION_EPOCHS); 447 self.disqualification_count += 1; 448 self.status = AttestationStatus::Disqualified; 449 self.current_attestation = None; 450 } 451 452 /// Check if attestation needs renewal (within 4 hours of expiry) 453 pub fn needs_renewal(&self, current_time: u64) -> bool { 454 match &self.current_attestation { 455 None => true, 456 Some(attestation) => { 457 let renewal_threshold = 4 * 60 * 60; // 4 hours before expiry 458 attestation.time_remaining(current_time) <= renewal_threshold 459 } 460 } 461 } 462 } 463 464 // ============================================================================ 465 // CLP Verifier 466 // ============================================================================ 467 468 /// Result of CLP verification 469 #[derive(Clone, Debug)] 470 pub struct VerificationResult { 471 /// Whether verification passed 472 pub valid: bool, 473 /// Error message if invalid 474 pub error: Option<String>, 475 /// Verified tier (if valid) 476 pub verified_tier: Option<AttestationTier>, 477 } 478 479 impl VerificationResult { 480 /// Create a successful result 481 pub fn success(tier: AttestationTier) -> Self { 482 Self { valid: true, error: None, verified_tier: Some(tier) } 483 } 484 485 /// Create a failure result 486 pub fn failure(error: impl Into<String>) -> Self { 487 Self { valid: false, error: Some(error.into()), verified_tier: None } 488 } 489 } 490 491 /// CLP attestation verifier 492 #[derive(Clone, Debug)] 493 pub struct ClpVerifier<N: Network> { 494 /// Trusted roots by tier 495 trusted_roots: HashMap<AttestationTier, Vec<TrustedRoot<N>>>, 496 /// Expected software hash (current valid version) 497 expected_software_hash: Field<N>, 498 /// Whether to allow software-only attestations 499 allow_software_only: bool, 500 } 501 502 impl<N: Network> ClpVerifier<N> { 503 /// Create a new CLP verifier 504 pub fn new(expected_software_hash: Field<N>) -> Self { 505 Self { trusted_roots: HashMap::new(), expected_software_hash, allow_software_only: true } 506 } 507 508 /// Add a trusted root 509 pub fn add_trusted_root(&mut self, root: TrustedRoot<N>) { 510 self.trusted_roots.entry(root.tier()).or_default().push(root); 511 } 512 513 /// Remove/revoke a trusted root 514 pub fn revoke_root(&mut self, tier: AttestationTier, root_id: &Field<N>, current_time: u64) { 515 if let Some(roots) = self.trusted_roots.get_mut(&tier) { 516 for root in roots.iter_mut() { 517 if root.id() == root_id { 518 root.revoke(current_time); 519 } 520 } 521 } 522 } 523 524 /// Update expected software hash (for upgrades) 525 pub fn set_expected_software_hash(&mut self, hash: Field<N>) { 526 self.expected_software_hash = hash; 527 } 528 529 /// Set whether software-only attestations are allowed 530 pub fn set_allow_software_only(&mut self, allow: bool) { 531 self.allow_software_only = allow; 532 } 533 534 /// Verify an attestation 535 pub fn verify(&self, attestation: &ClpAttestation<N>, current_time: u64) -> VerificationResult { 536 // Check if attestation is expired 537 if attestation.is_expired(current_time) { 538 return VerificationResult::failure("Attestation expired"); 539 } 540 541 // Check software hash matches expected 542 if attestation.software_hash() != &self.expected_software_hash { 543 return VerificationResult::failure("Software hash mismatch"); 544 } 545 546 // Tier-specific verification 547 match attestation.tier() { 548 AttestationTier::SoftwareOnly => { 549 if !self.allow_software_only { 550 return VerificationResult::failure("Software-only attestations disabled"); 551 } 552 // For software-only, verify the signature is valid 553 // In production, this would verify against the validator's registered key 554 if attestation.signature().is_empty() { 555 return VerificationResult::failure("Missing signature"); 556 } 557 VerificationResult::success(AttestationTier::SoftwareOnly) 558 } 559 tier => { 560 // Hardware attestation - verify against trusted roots 561 let roots = match self.trusted_roots.get(&tier) { 562 Some(r) => r, 563 None => return VerificationResult::failure("No trusted roots for tier"), 564 }; 565 566 // Check if any active root can verify this attestation 567 let has_valid_root = roots.iter().any(|r| r.is_active()); 568 if !has_valid_root { 569 return VerificationResult::failure("No active trusted roots"); 570 } 571 572 // In production, this would: 573 // 1. Parse the quote_data for the specific TEE format 574 // 2. Verify the quote signature against the trusted root 575 // 3. Check enclave measurements match expected values 576 // For now, we do basic validation 577 if attestation.quote_data().is_empty() { 578 return VerificationResult::failure("Missing quote data"); 579 } 580 if attestation.signature().is_empty() { 581 return VerificationResult::failure("Missing signature"); 582 } 583 584 VerificationResult::success(tier) 585 } 586 } 587 } 588 } 589 590 // ============================================================================ 591 // CLP Registry 592 // ============================================================================ 593 594 /// Central CLP registry managing all validator attestations 595 #[derive(Clone, Debug)] 596 pub struct ClpRegistry<N: Network> { 597 /// Validator CLP records 598 records: HashMap<Field<N>, ValidatorClpRecord<N>>, 599 /// CLP verifier 600 verifier: ClpVerifier<N>, 601 /// Current timestamp 602 current_time: u64, 603 /// Current epoch 604 current_epoch: u64, 605 } 606 607 impl<N: Network> ClpRegistry<N> { 608 /// Create a new CLP registry 609 pub fn new(expected_software_hash: Field<N>, current_time: u64, current_epoch: u64) -> Self { 610 Self { 611 records: HashMap::new(), 612 verifier: ClpVerifier::new(expected_software_hash), 613 current_time, 614 current_epoch, 615 } 616 } 617 618 /// Get the verifier for configuration 619 pub fn verifier_mut(&mut self) -> &mut ClpVerifier<N> { 620 &mut self.verifier 621 } 622 623 /// Register a validator for CLP tracking 624 pub fn register_validator(&mut self, validator_id: Field<N>) { 625 if !self.records.contains_key(&validator_id) { 626 self.records.insert(validator_id, ValidatorClpRecord::new(validator_id)); 627 } 628 } 629 630 /// Get validator CLP record 631 pub fn get_record(&self, validator_id: &Field<N>) -> Option<&ValidatorClpRecord<N>> { 632 self.records.get(validator_id) 633 } 634 635 /// Get mutable validator CLP record 636 pub fn get_record_mut(&mut self, validator_id: &Field<N>) -> Option<&mut ValidatorClpRecord<N>> { 637 self.records.get_mut(validator_id) 638 } 639 640 /// Submit and verify an attestation 641 pub fn submit_attestation(&mut self, attestation: ClpAttestation<N>) -> Result<VerificationResult> { 642 let validator_id = *attestation.validator_id(); 643 644 // Ensure validator is registered 645 if !self.records.contains_key(&validator_id) { 646 bail!("Validator not registered for CLP"); 647 } 648 649 // Verify the attestation 650 let result = self.verifier.verify(&attestation, self.current_time); 651 652 if result.valid { 653 // Update record with new attestation 654 if let Some(record) = self.records.get_mut(&validator_id) { 655 record.submit_attestation(attestation); 656 } 657 } 658 659 Ok(result) 660 } 661 662 /// Update time and check for expired attestations 663 pub fn update_time(&mut self, new_time: u64, new_epoch: u64) { 664 self.current_time = new_time; 665 self.current_epoch = new_epoch; 666 667 // Update all record statuses 668 for record in self.records.values_mut() { 669 record.update_status(self.current_time, self.current_epoch); 670 } 671 } 672 673 /// Get validators with expired attestations (in grace period) 674 pub fn get_expired_validators(&self) -> Vec<&Field<N>> { 675 self.records.iter().filter(|(_, r)| r.status() == AttestationStatus::Expired).map(|(id, _)| id).collect() 676 } 677 678 /// Get validators with failed attestations (past grace period) 679 pub fn get_failed_validators(&self) -> Vec<&Field<N>> { 680 self.records 681 .iter() 682 .filter(|(_, r)| matches!(r.status(), AttestationStatus::Invalid | AttestationStatus::Missing)) 683 .filter(|(_, r)| { 684 // Check if past grace period 685 match r.current_attestation() { 686 Some(att) => att.is_failed(self.current_time), 687 None => true, // Missing attestation counts as failed 688 } 689 }) 690 .map(|(id, _)| id) 691 .collect() 692 } 693 694 /// Get disqualified validators 695 pub fn get_disqualified_validators(&self) -> Vec<&Field<N>> { 696 self.records.iter().filter(|(_, r)| r.is_disqualified(self.current_epoch)).map(|(id, _)| id).collect() 697 } 698 699 /// Process CLP failures and return validators to penalize 700 /// Returns: Vec of (validator_id, should_slash) 701 pub fn process_failures(&mut self) -> Vec<(Field<N>, bool)> { 702 let mut to_penalize = Vec::new(); 703 704 let failed = self.get_failed_validators().into_iter().cloned().collect::<Vec<_>>(); 705 706 for validator_id in failed { 707 if let Some(record) = self.records.get_mut(&validator_id) { 708 // Only penalize if not already disqualified 709 if !record.is_disqualified(self.current_epoch) { 710 record.record_failure(self.current_time, self.current_epoch); 711 to_penalize.push((validator_id, true)); 712 } 713 } 714 } 715 716 to_penalize 717 } 718 719 /// Get CLP statistics 720 pub fn get_statistics(&self) -> ClpStatistics { 721 let mut stats = ClpStatistics::default(); 722 723 for record in self.records.values() { 724 stats.total_validators += 1; 725 726 match record.status() { 727 AttestationStatus::Valid => stats.valid_attestations += 1, 728 AttestationStatus::Expired => stats.expired_attestations += 1, 729 AttestationStatus::Invalid => stats.invalid_attestations += 1, 730 AttestationStatus::Missing => stats.missing_attestations += 1, 731 AttestationStatus::Disqualified => stats.disqualified_validators += 1, 732 } 733 734 if let Some(tier) = record.current_tier() { 735 match tier { 736 AttestationTier::IntelSgx => stats.intel_sgx_count += 1, 737 AttestationTier::AmdSevSnp => stats.amd_sev_count += 1, 738 AttestationTier::Tpm2 => stats.tpm2_count += 1, 739 AttestationTier::SoftwareOnly => stats.software_only_count += 1, 740 } 741 } 742 } 743 744 stats 745 } 746 747 /// Get validator count 748 pub fn validator_count(&self) -> usize { 749 self.records.len() 750 } 751 } 752 753 // ============================================================================ 754 // Statistics 755 // ============================================================================ 756 757 /// CLP statistics for monitoring 758 #[derive(Clone, Debug, Default)] 759 pub struct ClpStatistics { 760 /// Total registered validators 761 pub total_validators: u64, 762 /// Validators with valid attestations 763 pub valid_attestations: u64, 764 /// Validators with expired attestations (in grace) 765 pub expired_attestations: u64, 766 /// Validators with invalid attestations 767 pub invalid_attestations: u64, 768 /// Validators with missing attestations 769 pub missing_attestations: u64, 770 /// Disqualified validators 771 pub disqualified_validators: u64, 772 /// Count using Intel SGX 773 pub intel_sgx_count: u64, 774 /// Count using AMD SEV-SNP 775 pub amd_sev_count: u64, 776 /// Count using TPM 2.0 777 pub tpm2_count: u64, 778 /// Count using software-only 779 pub software_only_count: u64, 780 } 781 782 impl ClpStatistics { 783 /// Get percentage of valid attestations 784 pub fn valid_percentage(&self) -> f64 { 785 if self.total_validators == 0 { 786 0.0 787 } else { 788 (self.valid_attestations as f64 / self.total_validators as f64) * 100.0 789 } 790 } 791 792 /// Get percentage using hardware TEE 793 pub fn hardware_tee_percentage(&self) -> f64 { 794 let hardware = self.intel_sgx_count + self.amd_sev_count; 795 let total_with_attestation = hardware + self.tpm2_count + self.software_only_count; 796 if total_with_attestation == 0 { 0.0 } else { (hardware as f64 / total_with_attestation as f64) * 100.0 } 797 } 798 } 799 800 // ============================================================================ 801 // Tests 802 // ============================================================================ 803 804 #[cfg(test)] 805 mod tests { 806 use super::*; 807 use crate::console::network::MainnetV0; 808 809 type CurrentNetwork = MainnetV0; 810 811 #[test] 812 fn test_attestation_creation() { 813 let attestation = ClpAttestation::<CurrentNetwork>::new( 814 Field::from_u64(1), 815 AttestationTier::IntelSgx, 816 Field::from_u64(12345), 817 vec![1, 2, 3, 4], 818 vec![5, 6, 7, 8], 819 1000, 820 ); 821 822 assert_eq!(attestation.tier(), AttestationTier::IntelSgx); 823 assert!(!attestation.is_expired(1000)); 824 assert!(attestation.is_expired(1000 + CLP_VALIDITY_PERIOD_SECS)); 825 } 826 827 #[test] 828 fn test_attestation_expiry_and_grace() { 829 let attestation = ClpAttestation::<CurrentNetwork>::new( 830 Field::from_u64(1), 831 AttestationTier::SoftwareOnly, 832 Field::from_u64(12345), 833 vec![], 834 vec![1, 2, 3], 835 1000, 836 ); 837 838 let expiry = 1000 + CLP_VALIDITY_PERIOD_SECS; 839 840 // Not expired yet 841 assert!(!attestation.is_expired(expiry - 1)); 842 assert!(!attestation.is_in_grace_period(expiry - 1)); 843 844 // Just expired, in grace period 845 assert!(attestation.is_expired(expiry)); 846 assert!(attestation.is_in_grace_period(expiry)); 847 assert!(!attestation.is_failed(expiry)); 848 849 // Still in grace period 850 let mid_grace = expiry + CLP_GRACE_PERIOD_SECS / 2; 851 assert!(attestation.is_in_grace_period(mid_grace)); 852 assert!(!attestation.is_failed(mid_grace)); 853 854 // Past grace period - failed 855 let past_grace = expiry + CLP_GRACE_PERIOD_SECS; 856 assert!(!attestation.is_in_grace_period(past_grace)); 857 assert!(attestation.is_failed(past_grace)); 858 } 859 860 #[test] 861 fn test_attestation_tiers() { 862 assert!(AttestationTier::IntelSgx.is_hardware_tee()); 863 assert!(AttestationTier::AmdSevSnp.is_hardware_tee()); 864 assert!(!AttestationTier::Tpm2.is_hardware_tee()); 865 assert!(!AttestationTier::SoftwareOnly.is_hardware_tee()); 866 867 assert_eq!(AttestationTier::IntelSgx.tier_number(), 1); 868 assert_eq!(AttestationTier::AmdSevSnp.tier_number(), 2); 869 assert_eq!(AttestationTier::Tpm2.tier_number(), 3); 870 assert_eq!(AttestationTier::SoftwareOnly.tier_number(), 4); 871 } 872 873 #[test] 874 fn test_validator_clp_record() { 875 let mut record = ValidatorClpRecord::<CurrentNetwork>::new(Field::from_u64(1)); 876 877 assert_eq!(record.status(), AttestationStatus::Missing); 878 assert_eq!(record.attestation_count(), 0); 879 880 // Submit attestation 881 let attestation = ClpAttestation::new( 882 Field::from_u64(1), 883 AttestationTier::Tpm2, 884 Field::from_u64(12345), 885 vec![1, 2, 3], 886 vec![4, 5, 6], 887 1000, 888 ); 889 890 record.submit_attestation(attestation); 891 assert_eq!(record.status(), AttestationStatus::Valid); 892 assert_eq!(record.attestation_count(), 1); 893 assert_eq!(record.consecutive_success(), 1); 894 assert_eq!(record.current_tier(), Some(AttestationTier::Tpm2)); 895 } 896 897 #[test] 898 fn test_clp_failure_disqualification() { 899 let mut record = ValidatorClpRecord::<CurrentNetwork>::new(Field::from_u64(1)); 900 901 // Record failure at epoch 10 902 record.record_failure(1000, 10); 903 904 assert_eq!(record.status(), AttestationStatus::Disqualified); 905 assert_eq!(record.disqualified_until_epoch(), Some(10 + CLP_DISQUALIFICATION_EPOCHS)); 906 assert!(record.is_disqualified(10)); 907 assert!(record.is_disqualified(14)); // Still disqualified 908 assert!(!record.is_disqualified(15)); // Disqualification ended 909 } 910 911 #[test] 912 fn test_verifier_software_only() { 913 let software_hash = Field::<CurrentNetwork>::from_u64(12345); 914 let verifier = ClpVerifier::new(software_hash); 915 916 let attestation = ClpAttestation::new( 917 Field::from_u64(1), 918 AttestationTier::SoftwareOnly, 919 software_hash, 920 vec![], 921 vec![1, 2, 3], // Non-empty signature 922 1000, 923 ); 924 925 let result = verifier.verify(&attestation, 1000); 926 assert!(result.valid); 927 assert_eq!(result.verified_tier, Some(AttestationTier::SoftwareOnly)); 928 } 929 930 #[test] 931 fn test_verifier_wrong_software_hash() { 932 let software_hash = Field::<CurrentNetwork>::from_u64(12345); 933 let verifier = ClpVerifier::new(software_hash); 934 935 let attestation = ClpAttestation::new( 936 Field::from_u64(1), 937 AttestationTier::SoftwareOnly, 938 Field::from_u64(99999), // Wrong hash 939 vec![], 940 vec![1, 2, 3], 941 1000, 942 ); 943 944 let result = verifier.verify(&attestation, 1000); 945 assert!(!result.valid); 946 assert!(result.error.unwrap().contains("hash mismatch")); 947 } 948 949 #[test] 950 fn test_verifier_expired_attestation() { 951 let software_hash = Field::<CurrentNetwork>::from_u64(12345); 952 let verifier = ClpVerifier::new(software_hash); 953 954 let attestation = ClpAttestation::new( 955 Field::from_u64(1), 956 AttestationTier::SoftwareOnly, 957 software_hash, 958 vec![], 959 vec![1, 2, 3], 960 1000, 961 ); 962 963 // Verify after expiry 964 let result = verifier.verify(&attestation, 1000 + CLP_VALIDITY_PERIOD_SECS + 1); 965 assert!(!result.valid); 966 assert!(result.error.unwrap().contains("expired")); 967 } 968 969 #[test] 970 fn test_registry_basic_operations() { 971 let software_hash = Field::<CurrentNetwork>::from_u64(12345); 972 let mut registry = ClpRegistry::<CurrentNetwork>::new(software_hash, 1000, 0); 973 974 // Register validators 975 registry.register_validator(Field::from_u64(1)); 976 registry.register_validator(Field::from_u64(2)); 977 978 assert_eq!(registry.validator_count(), 2); 979 980 // Submit attestation 981 let attestation = ClpAttestation::new( 982 Field::from_u64(1), 983 AttestationTier::SoftwareOnly, 984 software_hash, 985 vec![], 986 vec![1, 2, 3], 987 1000, 988 ); 989 990 let result = registry.submit_attestation(attestation).unwrap(); 991 assert!(result.valid); 992 993 // Check record updated 994 let record = registry.get_record(&Field::from_u64(1)).unwrap(); 995 assert_eq!(record.status(), AttestationStatus::Valid); 996 } 997 998 #[test] 999 fn test_registry_failure_processing() { 1000 let software_hash = Field::<CurrentNetwork>::from_u64(12345); 1001 let mut registry = ClpRegistry::<CurrentNetwork>::new(software_hash, 1000, 0); 1002 1003 // Register validator but don't submit attestation 1004 registry.register_validator(Field::from_u64(1)); 1005 1006 // Advance time past grace period 1007 let past_grace = 1000 + CLP_VALIDITY_PERIOD_SECS + CLP_GRACE_PERIOD_SECS + 1; 1008 registry.update_time(past_grace, 5); 1009 1010 // Process failures 1011 let penalties = registry.process_failures(); 1012 assert_eq!(penalties.len(), 1); 1013 assert_eq!(penalties[0].0, Field::from_u64(1)); 1014 assert!(penalties[0].1); // Should slash 1015 1016 // Validator should now be disqualified 1017 let record = registry.get_record(&Field::from_u64(1)).unwrap(); 1018 assert!(record.is_disqualified(5)); 1019 assert_eq!(record.disqualified_until_epoch(), Some(5 + CLP_DISQUALIFICATION_EPOCHS)); 1020 } 1021 1022 #[test] 1023 fn test_registry_statistics() { 1024 let software_hash = Field::<CurrentNetwork>::from_u64(12345); 1025 let mut registry = ClpRegistry::<CurrentNetwork>::new(software_hash, 1000, 0); 1026 1027 // Add trusted roots for hardware tiers 1028 registry.verifier_mut().add_trusted_root(TrustedRoot::new( 1029 Field::from_u64(100), 1030 AttestationTier::IntelSgx, 1031 vec![1, 2, 3], 1032 "Intel".to_string(), 1033 1000, 1034 )); 1035 registry.verifier_mut().add_trusted_root(TrustedRoot::new( 1036 Field::from_u64(101), 1037 AttestationTier::AmdSevSnp, 1038 vec![4, 5, 6], 1039 "AMD".to_string(), 1040 1000, 1041 )); 1042 registry.verifier_mut().add_trusted_root(TrustedRoot::new( 1043 Field::from_u64(102), 1044 AttestationTier::Tpm2, 1045 vec![7, 8, 9], 1046 "TPM Manufacturer".to_string(), 1047 1000, 1048 )); 1049 1050 // Register validators 1051 for i in 1..=10 { 1052 registry.register_validator(Field::from_u64(i)); 1053 } 1054 1055 // Submit attestations for some (with different tiers) 1056 for i in 1..=5 { 1057 let tier = match i { 1058 1 | 2 => AttestationTier::IntelSgx, 1059 3 => AttestationTier::AmdSevSnp, 1060 4 => AttestationTier::Tpm2, 1061 _ => AttestationTier::SoftwareOnly, 1062 }; 1063 1064 let attestation = ClpAttestation::new( 1065 Field::from_u64(i), 1066 tier, 1067 software_hash, 1068 vec![1, 2, 3], // Quote data 1069 vec![4, 5, 6], // Signature 1070 1000, 1071 ); 1072 let result = registry.submit_attestation(attestation).unwrap(); 1073 assert!(result.valid, "Attestation for validator {} should be valid", i); 1074 } 1075 1076 let stats = registry.get_statistics(); 1077 assert_eq!(stats.total_validators, 10); 1078 assert_eq!(stats.valid_attestations, 5); 1079 assert_eq!(stats.missing_attestations, 5); 1080 assert_eq!(stats.intel_sgx_count, 2); 1081 assert_eq!(stats.amd_sev_count, 1); 1082 assert_eq!(stats.tpm2_count, 1); 1083 assert_eq!(stats.software_only_count, 1); 1084 } 1085 1086 #[test] 1087 fn test_trusted_root_management() { 1088 let software_hash = Field::<CurrentNetwork>::from_u64(12345); 1089 let mut verifier = ClpVerifier::<CurrentNetwork>::new(software_hash); 1090 1091 // Add trusted root for Intel SGX 1092 let root = TrustedRoot::new( 1093 Field::from_u64(1), 1094 AttestationTier::IntelSgx, 1095 vec![1, 2, 3, 4], 1096 "Intel".to_string(), 1097 1000, 1098 ); 1099 verifier.add_trusted_root(root); 1100 1101 // Now SGX attestations can be verified (basic check) 1102 let attestation = ClpAttestation::new( 1103 Field::from_u64(1), 1104 AttestationTier::IntelSgx, 1105 software_hash, 1106 vec![1, 2, 3], // Quote data 1107 vec![4, 5, 6], // Signature 1108 1000, 1109 ); 1110 1111 let result = verifier.verify(&attestation, 1000); 1112 assert!(result.valid); 1113 assert_eq!(result.verified_tier, Some(AttestationTier::IntelSgx)); 1114 } 1115 1116 #[test] 1117 fn test_renewal_detection() { 1118 let mut record = ValidatorClpRecord::<CurrentNetwork>::new(Field::from_u64(1)); 1119 1120 // No attestation - needs renewal 1121 assert!(record.needs_renewal(1000)); 1122 1123 // Submit attestation 1124 let attestation = ClpAttestation::new( 1125 Field::from_u64(1), 1126 AttestationTier::SoftwareOnly, 1127 Field::from_u64(12345), 1128 vec![], 1129 vec![1, 2, 3], 1130 1000, 1131 ); 1132 record.submit_attestation(attestation); 1133 1134 // Just after submission - doesn't need renewal 1135 assert!(!record.needs_renewal(1000)); 1136 1137 // 20 hours later (4 hours before expiry) - needs renewal 1138 let renewal_time = 1000 + CLP_VALIDITY_PERIOD_SECS - (4 * 60 * 60); 1139 assert!(record.needs_renewal(renewal_time)); 1140 } 1141 }