/ ledger / validator / src / validator.rs
validator.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  //! # Validator Staking Model (F-V00 to F-V06)
  17  //!
  18  //! Implements the dual-staking validator model for ALPHA/DELTA networks.
  19  //!
  20  //! ## Staking Requirements
  21  //!
  22  //! - ALPHA stake: 100,000 ALPHA minimum
  23  //! - DELTA stake: 10,000 DELTA minimum
  24  //! - Both required for validator pool inclusion
  25  //!
  26  //! ## Key Features
  27  //!
  28  //! - Earn-in period: Validators can earn ALPHA stake through validation rewards
  29  //! - 5-epoch warmup before selection eligibility
  30  //! - 40 active validators per epoch from 300-validator pool
  31  //! - Hot backup support for automatic failover
  32  //! - Slashing: ALPHA → validator pool, DELTA → reward pool
  33  
  34  use crate::console::{prelude::*, types::Field};
  35  use alphavm_ledger_governor::bls::BlsPublicKey;
  36  
  37  // ============================================================================
  38  // Constants
  39  // ============================================================================
  40  
  41  /// Minimum ALPHA stake required for validators (100,000 ALPHA)
  42  pub const VALIDATOR_ALPHA_STAKE_MIN: u64 = 100_000;
  43  
  44  /// Minimum DELTA stake required for validators (10,000 DELTA)
  45  pub const VALIDATOR_DELTA_STAKE_MIN: u64 = 10_000;
  46  
  47  /// Number of active validators per rotation (40)
  48  pub const ACTIVE_VALIDATOR_COUNT: usize = 40;
  49  
  50  /// Validator warm-up epochs required before selection eligibility
  51  pub const VALIDATOR_WARMUP_EPOCHS: u64 = 5;
  52  
  53  /// Maximum validators in the reward pool
  54  pub const VALIDATOR_POOL_MAX: usize = 300;
  55  
  56  /// Cooldown epochs after deregistration before stake return
  57  pub const DEREGISTRATION_COOLDOWN_EPOCHS: u64 = 7;
  58  
  59  /// Maximum cooldown epochs before validator can be re-selected (at full 300 pool)
  60  /// After completing a 7-epoch (1 week) active rotation, must wait 35 epochs (5 weeks)
  61  /// This ensures fair distribution: with 40 active from 300 pool, ~95% of validators
  62  /// will have served at least once within 7 rotations
  63  ///
  64  /// Note: Actual cooldown is dynamically calculated based on pool size.
  65  /// See `ValidatorPool::effective_cooldown_epochs()` for the scaling logic.
  66  pub const MAX_SELECTION_COOLDOWN_EPOCHS: u64 = 35;
  67  
  68  /// Minimum validators needed before rotation begins (below this, all are active)
  69  pub const MIN_VALIDATORS_FOR_ROTATION: usize = 40;
  70  
  71  // ============================================================================
  72  // Validator Status
  73  // ============================================================================
  74  
  75  /// Status of a validator in the network
  76  #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
  77  pub enum ValidatorState {
  78      /// Registered but in warmup period (not yet eligible for selection)
  79      Warmup,
  80      /// In the eligible pool, can be selected for active set
  81      Eligible,
  82      /// Currently in the active set for this epoch
  83      Active,
  84      /// Temporarily suspended (e.g., missed blocks, under investigation)
  85      Suspended,
  86      /// Deregistering, in cooldown period
  87      Deregistering,
  88      /// Fully deregistered, stake returned
  89      Deregistered,
  90      /// Slashed and ejected from network
  91      Slashed,
  92  }
  93  
  94  impl Default for ValidatorState {
  95      fn default() -> Self {
  96          ValidatorState::Warmup
  97      }
  98  }
  99  
 100  impl std::fmt::Display for ValidatorState {
 101      fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
 102          match self {
 103              ValidatorState::Warmup => write!(f, "warmup"),
 104              ValidatorState::Eligible => write!(f, "eligible"),
 105              ValidatorState::Active => write!(f, "active"),
 106              ValidatorState::Suspended => write!(f, "suspended"),
 107              ValidatorState::Deregistering => write!(f, "deregistering"),
 108              ValidatorState::Deregistered => write!(f, "deregistered"),
 109              ValidatorState::Slashed => write!(f, "slashed"),
 110          }
 111      }
 112  }
 113  
 114  // ============================================================================
 115  // Stake Info
 116  // ============================================================================
 117  
 118  /// Stake information for a validator
 119  #[derive(Clone, Debug)]
 120  pub struct StakeInfo {
 121      /// ALPHA staked amount
 122      alpha_staked: u64,
 123      /// ALPHA earned through validation (earn-in mechanism)
 124      alpha_earned: u64,
 125      /// DELTA staked amount
 126      delta_staked: u64,
 127      /// Epoch when stake was last modified
 128      last_modified_epoch: u64,
 129  }
 130  
 131  impl StakeInfo {
 132      /// Create new stake info
 133      pub fn new(alpha_staked: u64, delta_staked: u64, current_epoch: u64) -> Self {
 134          Self { alpha_staked, alpha_earned: 0, delta_staked, last_modified_epoch: current_epoch }
 135      }
 136  
 137      /// Create stake info for earn-in validator (starts with 0 ALPHA)
 138      pub fn new_earn_in(delta_staked: u64, current_epoch: u64) -> Self {
 139          Self { alpha_staked: 0, alpha_earned: 0, delta_staked, last_modified_epoch: current_epoch }
 140      }
 141  
 142      /// Total effective ALPHA stake (staked + earned)
 143      pub fn effective_alpha_stake(&self) -> u64 {
 144          self.alpha_staked.saturating_add(self.alpha_earned)
 145      }
 146  
 147      /// Get ALPHA staked
 148      pub fn alpha_staked(&self) -> u64 {
 149          self.alpha_staked
 150      }
 151  
 152      /// Get ALPHA earned
 153      pub fn alpha_earned(&self) -> u64 {
 154          self.alpha_earned
 155      }
 156  
 157      /// Get DELTA staked
 158      pub fn delta_staked(&self) -> u64 {
 159          self.delta_staked
 160      }
 161  
 162      /// Check if stake meets minimum requirements
 163      pub fn meets_requirements(&self) -> bool {
 164          self.effective_alpha_stake() >= VALIDATOR_ALPHA_STAKE_MIN && self.delta_staked >= VALIDATOR_DELTA_STAKE_MIN
 165      }
 166  
 167      /// Check if ALPHA requirement is met
 168      pub fn meets_alpha_requirement(&self) -> bool {
 169          self.effective_alpha_stake() >= VALIDATOR_ALPHA_STAKE_MIN
 170      }
 171  
 172      /// Check if DELTA requirement is met
 173      pub fn meets_delta_requirement(&self) -> bool {
 174          self.delta_staked >= VALIDATOR_DELTA_STAKE_MIN
 175      }
 176  
 177      /// Add to ALPHA stake
 178      pub fn add_alpha_stake(&mut self, amount: u64, current_epoch: u64) {
 179          self.alpha_staked = self.alpha_staked.saturating_add(amount);
 180          self.last_modified_epoch = current_epoch;
 181      }
 182  
 183      /// Add earned ALPHA (from validation rewards)
 184      pub fn add_alpha_earned(&mut self, amount: u64, current_epoch: u64) {
 185          self.alpha_earned = self.alpha_earned.saturating_add(amount);
 186          self.last_modified_epoch = current_epoch;
 187      }
 188  
 189      /// Add to DELTA stake
 190      pub fn add_delta_stake(&mut self, amount: u64, current_epoch: u64) {
 191          self.delta_staked = self.delta_staked.saturating_add(amount);
 192          self.last_modified_epoch = current_epoch;
 193      }
 194  
 195      /// Slash ALPHA stake (returns slashed amount)
 196      pub fn slash_alpha(&mut self, percentage_bps: u16, current_epoch: u64) -> u64 {
 197          let total = self.effective_alpha_stake();
 198          let slash_amount = (total as u128 * percentage_bps as u128 / 10000) as u64;
 199  
 200          // Slash from earned first, then staked
 201          if slash_amount <= self.alpha_earned {
 202              self.alpha_earned = self.alpha_earned.saturating_sub(slash_amount);
 203          } else {
 204              let remaining = slash_amount - self.alpha_earned;
 205              self.alpha_earned = 0;
 206              self.alpha_staked = self.alpha_staked.saturating_sub(remaining);
 207          }
 208  
 209          self.last_modified_epoch = current_epoch;
 210          slash_amount
 211      }
 212  
 213      /// Slash DELTA stake (returns slashed amount)
 214      pub fn slash_delta(&mut self, percentage_bps: u16, current_epoch: u64) -> u64 {
 215          let slash_amount = (self.delta_staked as u128 * percentage_bps as u128 / 10000) as u64;
 216          self.delta_staked = self.delta_staked.saturating_sub(slash_amount);
 217          self.last_modified_epoch = current_epoch;
 218          slash_amount
 219      }
 220  }
 221  
 222  // ============================================================================
 223  // Validator Record
 224  // ============================================================================
 225  
 226  /// A validator in the network
 227  #[derive(Clone, Debug)]
 228  pub struct Validator<N: Network> {
 229      /// Unique validator ID
 230      id: Field<N>,
 231      /// BLS public key for consensus
 232      consensus_key: BlsPublicKey<N>,
 233      /// Operator address (receives rewards, can deregister)
 234      operator: Field<N>,
 235      /// Current state
 236      state: ValidatorState,
 237      /// Stake information
 238      stake: StakeInfo,
 239      /// Epoch when registered
 240      registered_epoch: u64,
 241      /// Epoch when warmup completes (registered + WARMUP_EPOCHS)
 242      eligible_epoch: u64,
 243      /// Last epoch when selected for active set
 244      last_active_epoch: Option<u64>,
 245      /// Consecutive epochs active (for performance tracking)
 246      consecutive_active: u64,
 247      /// Hot backup validator ID (if configured)
 248      backup_validator: Option<Field<N>>,
 249      /// Epoch when deregistration was initiated (if deregistering)
 250      deregistration_epoch: Option<u64>,
 251      /// Performance score (0-100, affects selection probability)
 252      performance_score: u8,
 253  }
 254  
 255  impl<N: Network> Validator<N> {
 256      /// Create a new validator (starts in Warmup state)
 257      pub fn new(
 258          id: Field<N>,
 259          consensus_key: BlsPublicKey<N>,
 260          operator: Field<N>,
 261          alpha_staked: u64,
 262          delta_staked: u64,
 263          current_epoch: u64,
 264      ) -> Result<Self> {
 265          // Must have DELTA stake from day 1
 266          ensure!(
 267              delta_staked >= VALIDATOR_DELTA_STAKE_MIN,
 268              "DELTA stake {} below minimum {}",
 269              delta_staked,
 270              VALIDATOR_DELTA_STAKE_MIN
 271          );
 272  
 273          Ok(Self {
 274              id,
 275              consensus_key,
 276              operator,
 277              state: ValidatorState::Warmup,
 278              stake: StakeInfo::new(alpha_staked, delta_staked, current_epoch),
 279              registered_epoch: current_epoch,
 280              eligible_epoch: current_epoch + VALIDATOR_WARMUP_EPOCHS,
 281              last_active_epoch: None,
 282              consecutive_active: 0,
 283              backup_validator: None,
 284              deregistration_epoch: None,
 285              performance_score: 100, // Start with perfect score
 286          })
 287      }
 288  
 289      /// Create a new earn-in validator (no initial ALPHA stake)
 290      pub fn new_earn_in(
 291          id: Field<N>,
 292          consensus_key: BlsPublicKey<N>,
 293          operator: Field<N>,
 294          delta_staked: u64,
 295          current_epoch: u64,
 296      ) -> Result<Self> {
 297          ensure!(
 298              delta_staked >= VALIDATOR_DELTA_STAKE_MIN,
 299              "DELTA stake {} below minimum {}",
 300              delta_staked,
 301              VALIDATOR_DELTA_STAKE_MIN
 302          );
 303  
 304          Ok(Self {
 305              id,
 306              consensus_key,
 307              operator,
 308              state: ValidatorState::Warmup,
 309              stake: StakeInfo::new_earn_in(delta_staked, current_epoch),
 310              registered_epoch: current_epoch,
 311              eligible_epoch: current_epoch + VALIDATOR_WARMUP_EPOCHS,
 312              last_active_epoch: None,
 313              consecutive_active: 0,
 314              backup_validator: None,
 315              deregistration_epoch: None,
 316              performance_score: 100,
 317          })
 318      }
 319  
 320      // Getters
 321      pub fn id(&self) -> &Field<N> {
 322          &self.id
 323      }
 324  
 325      pub fn consensus_key(&self) -> &BlsPublicKey<N> {
 326          &self.consensus_key
 327      }
 328  
 329      pub fn operator(&self) -> &Field<N> {
 330          &self.operator
 331      }
 332  
 333      pub fn state(&self) -> ValidatorState {
 334          self.state
 335      }
 336  
 337      pub fn stake(&self) -> &StakeInfo {
 338          &self.stake
 339      }
 340  
 341      pub fn registered_epoch(&self) -> u64 {
 342          self.registered_epoch
 343      }
 344  
 345      pub fn eligible_epoch(&self) -> u64 {
 346          self.eligible_epoch
 347      }
 348  
 349      pub fn last_active_epoch(&self) -> Option<u64> {
 350          self.last_active_epoch
 351      }
 352  
 353      pub fn backup_validator(&self) -> Option<&Field<N>> {
 354          self.backup_validator.as_ref()
 355      }
 356  
 357      pub fn performance_score(&self) -> u8 {
 358          self.performance_score
 359      }
 360  
 361      /// Check if validator is in warmup period
 362      pub fn is_warming_up(&self, current_epoch: u64) -> bool {
 363          self.state == ValidatorState::Warmup && current_epoch < self.eligible_epoch
 364      }
 365  
 366      /// Check if validator is eligible for selection
 367      pub fn is_eligible(&self) -> bool {
 368          matches!(self.state, ValidatorState::Eligible | ValidatorState::Active)
 369      }
 370  
 371      /// Check if validator is currently active
 372      pub fn is_active(&self) -> bool {
 373          self.state == ValidatorState::Active
 374      }
 375  
 376      /// Check if validator can be selected (eligible + meets requirements + not in cooldown)
 377      /// The cooldown_epochs parameter allows dynamic cooldown based on pool size
 378      pub fn can_be_selected(&self, current_epoch: u64, cooldown_epochs: u64) -> bool {
 379          if !self.is_eligible() || !self.stake.meets_requirements() {
 380              return false;
 381          }
 382  
 383          // Validators must wait cooldown_epochs after their last active period ended
 384          match self.last_active_epoch {
 385              Some(last) => current_epoch >= last + cooldown_epochs,
 386              None => true, // Never been active, can be selected
 387          }
 388      }
 389  
 390      /// Check if validator is in cooldown period
 391      pub fn is_in_cooldown(&self, current_epoch: u64, cooldown_epochs: u64) -> bool {
 392          match self.last_active_epoch {
 393              Some(last) => current_epoch < last + cooldown_epochs,
 394              None => false,
 395          }
 396      }
 397  
 398      /// Get epochs remaining in cooldown (0 if not in cooldown)
 399      pub fn cooldown_remaining(&self, current_epoch: u64, cooldown_epochs: u64) -> u64 {
 400          match self.last_active_epoch {
 401              Some(last) => {
 402                  let cooldown_end = last + cooldown_epochs;
 403                  if current_epoch < cooldown_end { cooldown_end - current_epoch } else { 0 }
 404              }
 405              None => 0,
 406          }
 407      }
 408  
 409      /// Update state based on current epoch
 410      pub fn update_state(&mut self, current_epoch: u64) {
 411          match self.state {
 412              ValidatorState::Warmup => {
 413                  if current_epoch >= self.eligible_epoch && self.stake.meets_requirements() {
 414                      self.state = ValidatorState::Eligible;
 415                  }
 416              }
 417              ValidatorState::Deregistering => {
 418                  if let Some(dereg_epoch) = self.deregistration_epoch {
 419                      if current_epoch >= dereg_epoch + DEREGISTRATION_COOLDOWN_EPOCHS {
 420                          self.state = ValidatorState::Deregistered;
 421                      }
 422                  }
 423              }
 424              _ => {}
 425          }
 426      }
 427  
 428      /// Mark as selected for active set
 429      pub fn select_for_active(&mut self, current_epoch: u64) -> Result<()> {
 430          ensure!(self.is_eligible(), "Validator not eligible for selection");
 431          ensure!(self.stake.meets_requirements(), "Validator does not meet stake requirements");
 432  
 433          self.state = ValidatorState::Active;
 434  
 435          if self.last_active_epoch == Some(current_epoch.saturating_sub(1)) {
 436              self.consecutive_active += 1;
 437          } else {
 438              self.consecutive_active = 1;
 439          }
 440          self.last_active_epoch = Some(current_epoch);
 441  
 442          Ok(())
 443      }
 444  
 445      /// Mark as no longer in active set (back to eligible)
 446      /// The current_epoch is recorded as last_active_epoch for cooldown tracking
 447      pub fn deselect(&mut self, current_epoch: u64) {
 448          if self.state == ValidatorState::Active {
 449              self.state = ValidatorState::Eligible;
 450              // Record when active period ended (for cooldown calculation)
 451              self.last_active_epoch = Some(current_epoch);
 452          }
 453      }
 454  
 455      /// Initiate deregistration
 456      pub fn initiate_deregistration(&mut self, current_epoch: u64) -> Result<()> {
 457          ensure!(
 458              matches!(self.state, ValidatorState::Eligible | ValidatorState::Active | ValidatorState::Warmup),
 459              "Cannot deregister from state {:?}",
 460              self.state
 461          );
 462  
 463          self.state = ValidatorState::Deregistering;
 464          self.deregistration_epoch = Some(current_epoch);
 465          Ok(())
 466      }
 467  
 468      /// Complete deregistration and return stake
 469      pub fn complete_deregistration(&mut self, current_epoch: u64) -> Result<(u64, u64)> {
 470          ensure!(self.state == ValidatorState::Deregistering, "Not in deregistering state");
 471  
 472          if let Some(dereg_epoch) = self.deregistration_epoch {
 473              ensure!(current_epoch >= dereg_epoch + DEREGISTRATION_COOLDOWN_EPOCHS, "Cooldown period not complete");
 474          }
 475  
 476          self.state = ValidatorState::Deregistered;
 477  
 478          // Return all stake
 479          let alpha = self.stake.effective_alpha_stake();
 480          let delta = self.stake.delta_staked();
 481  
 482          Ok((alpha, delta))
 483      }
 484  
 485      /// Suspend validator
 486      pub fn suspend(&mut self) -> Result<()> {
 487          ensure!(self.is_eligible() || self.is_active(), "Cannot suspend validator in state {:?}", self.state);
 488          self.state = ValidatorState::Suspended;
 489          Ok(())
 490      }
 491  
 492      /// Reinstate suspended validator
 493      pub fn reinstate(&mut self) -> Result<()> {
 494          ensure!(self.state == ValidatorState::Suspended, "Validator not suspended");
 495          self.state = ValidatorState::Eligible;
 496          Ok(())
 497      }
 498  
 499      /// Set hot backup validator
 500      pub fn set_backup(&mut self, backup_id: Field<N>) -> Result<()> {
 501          ensure!(self.is_eligible() || self.is_active(), "Cannot set backup for validator in state {:?}", self.state);
 502          self.backup_validator = Some(backup_id);
 503          Ok(())
 504      }
 505  
 506      /// Clear hot backup
 507      pub fn clear_backup(&mut self) {
 508          self.backup_validator = None;
 509      }
 510  
 511      /// Add ALPHA stake
 512      pub fn add_alpha_stake(&mut self, amount: u64, current_epoch: u64) {
 513          self.stake.add_alpha_stake(amount, current_epoch);
 514          self.update_state(current_epoch);
 515      }
 516  
 517      /// Add earned ALPHA (validation rewards)
 518      pub fn add_earned_alpha(&mut self, amount: u64, current_epoch: u64) {
 519          self.stake.add_alpha_earned(amount, current_epoch);
 520          self.update_state(current_epoch);
 521      }
 522  
 523      /// Add DELTA stake
 524      pub fn add_delta_stake(&mut self, amount: u64, current_epoch: u64) {
 525          self.stake.add_delta_stake(amount, current_epoch);
 526      }
 527  
 528      /// Slash validator (returns slashed amounts: ALPHA, DELTA)
 529      pub fn slash(&mut self, alpha_slash_bps: u16, delta_slash_bps: u16, current_epoch: u64) -> (u64, u64) {
 530          let alpha_slashed = self.stake.slash_alpha(alpha_slash_bps, current_epoch);
 531          let delta_slashed = self.stake.slash_delta(delta_slash_bps, current_epoch);
 532  
 533          // Check if stake still meets requirements
 534          if !self.stake.meets_requirements() {
 535              self.state = ValidatorState::Slashed;
 536          }
 537  
 538          (alpha_slashed, delta_slashed)
 539      }
 540  
 541      /// Update performance score
 542      pub fn update_performance(&mut self, new_score: u8) {
 543          self.performance_score = new_score.min(100);
 544      }
 545  
 546      /// Rotate consensus key
 547      pub fn rotate_key(&mut self, new_key: BlsPublicKey<N>) {
 548          self.consensus_key = new_key;
 549      }
 550  }
 551  
 552  // ============================================================================
 553  // Validator Pool
 554  // ============================================================================
 555  
 556  /// The validator pool managing all validators
 557  #[derive(Clone, Debug)]
 558  pub struct ValidatorPool<N: Network> {
 559      /// All registered validators (by ID)
 560      validators: std::collections::HashMap<Field<N>, Validator<N>>,
 561      /// Current active set (validator IDs)
 562      active_set: Vec<Field<N>>,
 563      /// Current epoch
 564      current_epoch: u64,
 565      /// Epoch when the current active set was selected (rotation happens every 7 epochs)
 566      rotation_epoch: u64,
 567      /// Random seed for validator selection (derived from block hash in production)
 568      selection_seed: u64,
 569  }
 570  
 571  impl<N: Network> ValidatorPool<N> {
 572      /// Create a new empty validator pool
 573      pub fn new(current_epoch: u64) -> Self {
 574          Self {
 575              validators: std::collections::HashMap::new(),
 576              active_set: Vec::new(),
 577              current_epoch,
 578              rotation_epoch: 0,
 579              selection_seed: 0,
 580          }
 581      }
 582  
 583      /// Create validator pool with initial seed
 584      pub fn new_with_seed(current_epoch: u64, seed: u64) -> Self {
 585          Self {
 586              validators: std::collections::HashMap::new(),
 587              active_set: Vec::new(),
 588              current_epoch,
 589              rotation_epoch: 0,
 590              selection_seed: seed,
 591          }
 592      }
 593  
 594      /// Get current epoch
 595      pub fn current_epoch(&self) -> u64 {
 596          self.current_epoch
 597      }
 598  
 599      /// Get validator count
 600      pub fn validator_count(&self) -> usize {
 601          self.validators.len()
 602      }
 603  
 604      /// Get eligible validator count
 605      pub fn eligible_count(&self) -> usize {
 606          self.validators.values().filter(|v| v.is_eligible()).count()
 607      }
 608  
 609      /// Get active set size
 610      pub fn active_count(&self) -> usize {
 611          self.active_set.len()
 612      }
 613  
 614      /// Check if pool is full
 615      pub fn is_pool_full(&self) -> bool {
 616          self.eligible_count() >= VALIDATOR_POOL_MAX
 617      }
 618  
 619      /// Register a new validator
 620      pub fn register(&mut self, validator: Validator<N>) -> Result<()> {
 621          ensure!(!self.is_pool_full(), "Validator pool is full ({} max)", VALIDATOR_POOL_MAX);
 622          ensure!(!self.validators.contains_key(validator.id()), "Validator already registered");
 623  
 624          self.validators.insert(validator.id().clone(), validator);
 625          Ok(())
 626      }
 627  
 628      /// Get validator by ID
 629      pub fn get(&self, id: &Field<N>) -> Option<&Validator<N>> {
 630          self.validators.get(id)
 631      }
 632  
 633      /// Get mutable validator by ID
 634      pub fn get_mut(&mut self, id: &Field<N>) -> Option<&mut Validator<N>> {
 635          self.validators.get_mut(id)
 636      }
 637  
 638      /// Get all active validators
 639      pub fn active_validators(&self) -> Vec<&Validator<N>> {
 640          self.active_set.iter().filter_map(|id| self.validators.get(id)).collect()
 641      }
 642  
 643      /// Get all eligible validators
 644      pub fn eligible_validators(&self) -> Vec<&Validator<N>> {
 645          self.validators.values().filter(|v| v.is_eligible()).collect()
 646      }
 647  
 648      /// Get standby validators (eligible but not in active set)
 649      pub fn standby_validators(&self) -> Vec<&Validator<N>> {
 650          self.validators.values().filter(|v| v.is_eligible() && !self.active_set.contains(v.id())).collect()
 651      }
 652  
 653      /// Get standby count
 654      pub fn standby_count(&self) -> usize {
 655          self.eligible_count().saturating_sub(self.active_count())
 656      }
 657  
 658      /// Get rotation epoch (when current active set was selected)
 659      pub fn rotation_epoch(&self) -> u64 {
 660          self.rotation_epoch
 661      }
 662  
 663      /// Check if rotation is due this epoch
 664      pub fn is_rotation_due(&self) -> bool {
 665          // No rotation if below minimum validators
 666          if self.eligible_count() < MIN_VALIDATORS_FOR_ROTATION {
 667              return false;
 668          }
 669          self.current_epoch >= self.rotation_epoch + crate::VALIDATOR_ROTATION_EPOCHS
 670      }
 671  
 672      /// Set the random seed for selection (in production, derived from block hash)
 673      pub fn set_selection_seed(&mut self, seed: u64) {
 674          self.selection_seed = seed;
 675      }
 676  
 677      /// Calculate effective cooldown based on current pool size
 678      ///
 679      /// The cooldown scales with pool size to ensure fair rotation:
 680      /// - < 40 validators: No cooldown (all active)
 681      /// - 40-79 validators: No cooldown (can't sustain rotation with cooldown)
 682      /// - 80-119 validators: 1 week cooldown
 683      /// - 120-159 validators: 2 week cooldown
 684      /// - 160-199 validators: 3 week cooldown
 685      /// - 200-239 validators: 4 week cooldown
 686      /// - 240+ validators: 5 week cooldown (full)
 687      ///
 688      /// Formula: cooldown_weeks = min(5, (eligible_count / 40) - 1)
 689      /// This ensures there are always enough non-cooldown validators to fill the active set.
 690      pub fn effective_cooldown_epochs(&self) -> u64 {
 691          let eligible = self.eligible_count();
 692  
 693          if eligible < MIN_VALIDATORS_FOR_ROTATION {
 694              return 0; // All active, no rotation
 695          }
 696  
 697          // Calculate how many "sets" of 40 we have
 698          // We need at least 2 sets (80 validators) to have any cooldown
 699          let sets = eligible / ACTIVE_VALIDATOR_COUNT;
 700  
 701          if sets < 2 {
 702              return 0; // Not enough for cooldown
 703          }
 704  
 705          // cooldown_weeks = sets - 1, capped at 5
 706          let cooldown_weeks = (sets - 1).min(5) as u64;
 707          cooldown_weeks * crate::VALIDATOR_ROTATION_EPOCHS
 708      }
 709  
 710      /// Get the effective active set size based on pool size
 711      ///
 712      /// - < 40 validators: All are active
 713      /// - >= 40 validators: Standard 40 active
 714      pub fn effective_active_count(&self) -> usize {
 715          let eligible = self.eligible_count();
 716          if eligible < MIN_VALIDATORS_FOR_ROTATION {
 717              eligible // All eligible validators are active
 718          } else {
 719              ACTIVE_VALIDATOR_COUNT
 720          }
 721      }
 722  
 723      /// Check if we're in "all active" mode (sub-40 validators)
 724      pub fn is_all_active_mode(&self) -> bool {
 725          self.eligible_count() < MIN_VALIDATORS_FOR_ROTATION
 726      }
 727  
 728      /// Advance to next epoch, rotate validators if 7 epochs have passed
 729      pub fn advance_epoch(&mut self, new_epoch: u64) -> Result<Vec<Field<N>>> {
 730          self.advance_epoch_with_seed(new_epoch, None)
 731      }
 732  
 733      /// Advance to next epoch with explicit random seed
 734      pub fn advance_epoch_with_seed(&mut self, new_epoch: u64, seed: Option<u64>) -> Result<Vec<Field<N>>> {
 735          ensure!(new_epoch > self.current_epoch, "New epoch must be greater than current");
 736  
 737          // Update random seed if provided
 738          if let Some(s) = seed {
 739              self.selection_seed = s;
 740          }
 741  
 742          // Update all validator states
 743          for validator in self.validators.values_mut() {
 744              validator.update_state(new_epoch);
 745          }
 746  
 747          self.current_epoch = new_epoch;
 748  
 749          // Handle "all active" mode for small pools (< 40 validators)
 750          if self.is_all_active_mode() {
 751              return self.select_all_eligible();
 752          }
 753  
 754          // Check if rotation is due (every 7 epochs)
 755          if self.is_rotation_due() || self.active_set.is_empty() {
 756              // Deselect all current active validators (record epoch for cooldown)
 757              for validator in self.validators.values_mut() {
 758                  if validator.is_active() {
 759                      validator.deselect(new_epoch);
 760                  }
 761              }
 762  
 763              // Select new active set with random rotation
 764              self.rotation_epoch = new_epoch;
 765              self.select_active_set_random()
 766          } else {
 767              // No rotation - keep current active set
 768              Ok(self.active_set.clone())
 769          }
 770      }
 771  
 772      /// Select all eligible validators as active (for small pools < 40)
 773      fn select_all_eligible(&mut self) -> Result<Vec<Field<N>>> {
 774          let mut selected = Vec::new();
 775  
 776          for validator in self.validators.values_mut() {
 777              if validator.is_eligible() && validator.stake().meets_requirements() {
 778                  if !validator.is_active() {
 779                      validator.select_for_active(self.current_epoch)?;
 780                  }
 781                  selected.push(validator.id().clone());
 782              }
 783          }
 784  
 785          self.active_set = selected.clone();
 786          self.rotation_epoch = self.current_epoch;
 787          Ok(selected)
 788      }
 789  
 790      /// Select active set using random selection weighted by performance
 791      fn select_active_set_random(&mut self) -> Result<Vec<Field<N>>> {
 792          let cooldown = self.effective_cooldown_epochs();
 793          let target_count = self.effective_active_count();
 794  
 795          // Get all eligible validators that can be selected (respecting cooldown)
 796          let mut candidates: Vec<_> = self
 797              .validators
 798              .values()
 799              .filter(|v| v.can_be_selected(self.current_epoch, cooldown))
 800              .map(|v| (v.id().clone(), v.performance_score()))
 801              .collect();
 802  
 803          // Sort by ID first to ensure deterministic ordering before shuffle
 804          // (HashMap iteration order is not deterministic)
 805          candidates.sort_by(|a, b| format!("{}", a.0).cmp(&format!("{}", b.0)));
 806  
 807          // Fisher-Yates shuffle using the selection seed for deterministic randomness
 808          // This ensures all nodes arrive at the same selection given the same seed
 809          let mut rng_state = self.selection_seed;
 810          for i in (1..candidates.len()).rev() {
 811              // Simple LCG for deterministic pseudo-randomness
 812              rng_state = rng_state.wrapping_mul(6364136223846793005).wrapping_add(1442695040888963407);
 813              let j = (rng_state as usize) % (i + 1);
 814              candidates.swap(i, j);
 815          }
 816  
 817          // After shuffle, sort by performance score (descending)
 818          // This means higher performance validators are more likely to be selected
 819          // but with randomized ordering among validators with equal scores
 820          candidates.sort_by(|a, b| b.1.cmp(&a.1));
 821  
 822          // Select up to target_count (normally 40, or all if < 40 eligible)
 823          let selected: Vec<Field<N>> = candidates.into_iter().take(target_count).map(|(id, _)| id).collect();
 824  
 825          // Mark selected validators as active
 826          for id in &selected {
 827              if let Some(validator) = self.validators.get_mut(id) {
 828                  validator.select_for_active(self.current_epoch)?;
 829              }
 830          }
 831  
 832          self.active_set = selected.clone();
 833          Ok(selected)
 834      }
 835  
 836      /// Deregister a validator
 837      pub fn initiate_deregistration(&mut self, id: &Field<N>) -> Result<()> {
 838          let validator = self.validators.get_mut(id).ok_or_else(|| anyhow::anyhow!("Validator not found"))?;
 839  
 840          validator.initiate_deregistration(self.current_epoch)?;
 841  
 842          // Remove from active set if present
 843          self.active_set.retain(|v| v != id);
 844  
 845          Ok(())
 846      }
 847  
 848      /// Complete deregistration and return stake
 849      pub fn complete_deregistration(&mut self, id: &Field<N>) -> Result<(u64, u64)> {
 850          let validator = self.validators.get_mut(id).ok_or_else(|| anyhow::anyhow!("Validator not found"))?;
 851  
 852          validator.complete_deregistration(self.current_epoch)
 853      }
 854  
 855      /// Slash a validator
 856      pub fn slash_validator(&mut self, id: &Field<N>, alpha_slash_bps: u16, delta_slash_bps: u16) -> Result<(u64, u64)> {
 857          let validator = self.validators.get_mut(id).ok_or_else(|| anyhow::anyhow!("Validator not found"))?;
 858  
 859          let slashed = validator.slash(alpha_slash_bps, delta_slash_bps, self.current_epoch);
 860  
 861          // Remove from active set if slashed
 862          if validator.state() == ValidatorState::Slashed {
 863              self.active_set.retain(|v| v != id);
 864          }
 865  
 866          Ok(slashed)
 867      }
 868  
 869      /// Activate backup for a failed validator
 870      pub fn activate_backup(&mut self, failed_id: &Field<N>) -> Result<Option<Field<N>>> {
 871          let backup_id = {
 872              let validator = self.validators.get(failed_id).ok_or_else(|| anyhow::anyhow!("Validator not found"))?;
 873              validator.backup_validator().cloned()
 874          };
 875  
 876          if let Some(backup_id) = backup_id {
 877              // Suspend failed validator
 878              if let Some(validator) = self.validators.get_mut(failed_id) {
 879                  validator.suspend()?;
 880              }
 881  
 882              // Remove failed from active set
 883              self.active_set.retain(|v| v != failed_id);
 884  
 885              // Activate backup if eligible
 886              if let Some(backup) = self.validators.get_mut(&backup_id) {
 887                  if backup.is_eligible() && backup.stake().meets_requirements() {
 888                      backup.select_for_active(self.current_epoch)?;
 889                      self.active_set.push(backup_id.clone());
 890                      return Ok(Some(backup_id));
 891                  }
 892              }
 893          }
 894  
 895          Ok(None)
 896      }
 897  }
 898  
 899  #[cfg(test)]
 900  mod tests {
 901      use super::*;
 902      use crate::console::{network::MainnetV0, types::Group};
 903      use core::ops::Add;
 904  
 905      type CurrentNetwork = MainnetV0;
 906  
 907      fn create_test_key(id: u64) -> BlsPublicKey<CurrentNetwork> {
 908          let generator = Group::<CurrentNetwork>::generator();
 909          let mut point = generator.clone();
 910          for _ in 0..id {
 911              point = point.add(&generator);
 912          }
 913          BlsPublicKey::new(point)
 914      }
 915  
 916      #[test]
 917      fn test_validator_creation() {
 918          let validator = Validator::<CurrentNetwork>::new(
 919              Field::from_u64(1),
 920              create_test_key(1),
 921              Field::from_u64(100),
 922              100_000, // ALPHA
 923              10_000,  // DELTA
 924              0,       // epoch
 925          )
 926          .unwrap();
 927  
 928          assert_eq!(validator.state(), ValidatorState::Warmup);
 929          assert!(validator.stake().meets_requirements());
 930          assert_eq!(validator.eligible_epoch(), VALIDATOR_WARMUP_EPOCHS);
 931      }
 932  
 933      #[test]
 934      fn test_validator_earn_in() {
 935          let validator = Validator::<CurrentNetwork>::new_earn_in(
 936              Field::from_u64(1),
 937              create_test_key(1),
 938              Field::from_u64(100),
 939              10_000, // DELTA only
 940              0,
 941          )
 942          .unwrap();
 943  
 944          assert!(!validator.stake().meets_alpha_requirement());
 945          assert!(validator.stake().meets_delta_requirement());
 946          assert!(!validator.stake().meets_requirements());
 947      }
 948  
 949      #[test]
 950      fn test_validator_warmup_to_eligible() {
 951          let mut validator = Validator::<CurrentNetwork>::new(
 952              Field::from_u64(1),
 953              create_test_key(1),
 954              Field::from_u64(100),
 955              100_000,
 956              10_000,
 957              0,
 958          )
 959          .unwrap();
 960  
 961          assert_eq!(validator.state(), ValidatorState::Warmup);
 962  
 963          // Still in warmup
 964          validator.update_state(3);
 965          assert_eq!(validator.state(), ValidatorState::Warmup);
 966  
 967          // After warmup period
 968          validator.update_state(VALIDATOR_WARMUP_EPOCHS);
 969          assert_eq!(validator.state(), ValidatorState::Eligible);
 970      }
 971  
 972      #[test]
 973      fn test_validator_selection() {
 974          let mut validator = Validator::<CurrentNetwork>::new(
 975              Field::from_u64(1),
 976              create_test_key(1),
 977              Field::from_u64(100),
 978              100_000,
 979              10_000,
 980              0,
 981          )
 982          .unwrap();
 983  
 984          validator.update_state(VALIDATOR_WARMUP_EPOCHS);
 985          assert!(validator.is_eligible());
 986  
 987          validator.select_for_active(VALIDATOR_WARMUP_EPOCHS).unwrap();
 988          assert!(validator.is_active());
 989          assert_eq!(validator.last_active_epoch(), Some(VALIDATOR_WARMUP_EPOCHS));
 990      }
 991  
 992      #[test]
 993      fn test_validator_deregistration() {
 994          let mut validator = Validator::<CurrentNetwork>::new(
 995              Field::from_u64(1),
 996              create_test_key(1),
 997              Field::from_u64(100),
 998              100_000,
 999              10_000,
1000              0,
1001          )
1002          .unwrap();
1003  
1004          validator.update_state(VALIDATOR_WARMUP_EPOCHS);
1005          validator.initiate_deregistration(10).unwrap();
1006  
1007          assert_eq!(validator.state(), ValidatorState::Deregistering);
1008  
1009          // Before cooldown
1010          validator.update_state(15);
1011          assert_eq!(validator.state(), ValidatorState::Deregistering);
1012  
1013          // After cooldown
1014          validator.update_state(10 + DEREGISTRATION_COOLDOWN_EPOCHS);
1015          assert_eq!(validator.state(), ValidatorState::Deregistered);
1016      }
1017  
1018      #[test]
1019      fn test_stake_slashing() {
1020          let mut stake = StakeInfo::new(100_000, 10_000, 0);
1021  
1022          // Slash 10% ALPHA
1023          let slashed = stake.slash_alpha(1000, 1); // 1000 bps = 10%
1024          assert_eq!(slashed, 10_000);
1025          assert_eq!(stake.effective_alpha_stake(), 90_000);
1026  
1027          // Slash 5% DELTA
1028          let slashed = stake.slash_delta(500, 2); // 500 bps = 5%
1029          assert_eq!(slashed, 500);
1030          assert_eq!(stake.delta_staked(), 9_500);
1031      }
1032  
1033      #[test]
1034      fn test_earned_alpha_slashed_first() {
1035          let mut stake = StakeInfo::new(50_000, 10_000, 0);
1036          stake.add_alpha_earned(50_000, 1); // 50k staked + 50k earned = 100k total
1037  
1038          assert_eq!(stake.effective_alpha_stake(), 100_000);
1039  
1040          // Slash 60% - should take all earned (50k) and some staked (10k)
1041          let slashed = stake.slash_alpha(6000, 2);
1042          assert_eq!(slashed, 60_000);
1043          assert_eq!(stake.alpha_earned(), 0);
1044          assert_eq!(stake.alpha_staked(), 40_000);
1045      }
1046  
1047      #[test]
1048      fn test_validator_pool_registration() {
1049          let mut pool = ValidatorPool::<CurrentNetwork>::new(0);
1050  
1051          let validator =
1052              Validator::new(Field::from_u64(1), create_test_key(1), Field::from_u64(100), 100_000, 10_000, 0).unwrap();
1053  
1054          pool.register(validator).unwrap();
1055          assert_eq!(pool.validator_count(), 1);
1056          assert!(pool.get(&Field::from_u64(1)).is_some());
1057      }
1058  
1059      #[test]
1060      fn test_validator_pool_advance_epoch() {
1061          let mut pool = ValidatorPool::<CurrentNetwork>::new_with_seed(0, 12345);
1062  
1063          // Register multiple validators
1064          for i in 1..=50 {
1065              let validator =
1066                  Validator::new(Field::from_u64(i), create_test_key(i), Field::from_u64(100 + i), 100_000, 10_000, 0)
1067                      .unwrap();
1068              pool.register(validator).unwrap();
1069          }
1070  
1071          // Advance past warmup - first rotation
1072          let active = pool.advance_epoch(VALIDATOR_WARMUP_EPOCHS).unwrap();
1073  
1074          assert_eq!(active.len(), ACTIVE_VALIDATOR_COUNT);
1075          assert_eq!(pool.active_count(), ACTIVE_VALIDATOR_COUNT);
1076          assert_eq!(pool.rotation_epoch(), VALIDATOR_WARMUP_EPOCHS);
1077      }
1078  
1079      #[test]
1080      fn test_validator_weekly_rotation() {
1081          use crate::VALIDATOR_ROTATION_EPOCHS;
1082  
1083          let mut pool = ValidatorPool::<CurrentNetwork>::new_with_seed(0, 12345);
1084  
1085          // Register 300 validators (full pool) to ensure enough for rotations
1086          // With 5-week cooldown: after 5 rotations, 200 validators in cooldown
1087          // Still leaves 100 available for 6th rotation
1088          for i in 1..=300 {
1089              let validator =
1090                  Validator::new(Field::from_u64(i), create_test_key(i), Field::from_u64(100 + i), 100_000, 10_000, 0)
1091                      .unwrap();
1092              pool.register(validator).unwrap();
1093          }
1094  
1095          // Initial rotation at warmup completion
1096          let first_active = pool.advance_epoch(VALIDATOR_WARMUP_EPOCHS).unwrap();
1097          let first_rotation_epoch = pool.rotation_epoch();
1098          assert_eq!(first_rotation_epoch, VALIDATOR_WARMUP_EPOCHS);
1099          assert_eq!(first_active.len(), ACTIVE_VALIDATOR_COUNT);
1100  
1101          // Advance 3 epochs - should NOT rotate (same active set)
1102          let same_active = pool.advance_epoch(VALIDATOR_WARMUP_EPOCHS + 3).unwrap();
1103          assert_eq!(pool.rotation_epoch(), first_rotation_epoch); // Still same rotation epoch
1104          assert_eq!(first_active, same_active); // Same validators
1105  
1106          // Advance to 7 epochs later - SHOULD rotate
1107          let new_epoch = first_rotation_epoch + VALIDATOR_ROTATION_EPOCHS;
1108          let new_active = pool.advance_epoch_with_seed(new_epoch, Some(99999)).unwrap();
1109          assert_eq!(pool.rotation_epoch(), new_epoch);
1110          assert_eq!(new_active.len(), ACTIVE_VALIDATOR_COUNT);
1111  
1112          // Verify first active set is NOT in second active set (5-week cooldown enforced)
1113          let first_set: std::collections::HashSet<_> = first_active.iter().cloned().collect();
1114          for v in &new_active {
1115              assert!(!first_set.contains(v), "Validator should be in 5-week cooldown");
1116          }
1117      }
1118  
1119      #[test]
1120      fn test_random_selection_deterministic() {
1121          // Same seed should produce same selection
1122          let mut pool1 = ValidatorPool::<CurrentNetwork>::new_with_seed(0, 42);
1123          let mut pool2 = ValidatorPool::<CurrentNetwork>::new_with_seed(0, 42);
1124  
1125          for i in 1..=50 {
1126              let v1 =
1127                  Validator::new(Field::from_u64(i), create_test_key(i), Field::from_u64(100 + i), 100_000, 10_000, 0)
1128                      .unwrap();
1129              let v2 =
1130                  Validator::new(Field::from_u64(i), create_test_key(i), Field::from_u64(100 + i), 100_000, 10_000, 0)
1131                      .unwrap();
1132              pool1.register(v1).unwrap();
1133              pool2.register(v2).unwrap();
1134          }
1135  
1136          let active1 = pool1.advance_epoch(VALIDATOR_WARMUP_EPOCHS).unwrap();
1137          let active2 = pool2.advance_epoch(VALIDATOR_WARMUP_EPOCHS).unwrap();
1138  
1139          // Same seed = same selection
1140          assert_eq!(active1, active2);
1141      }
1142  
1143      #[test]
1144      fn test_five_week_cooldown() {
1145          use crate::VALIDATOR_ROTATION_EPOCHS;
1146  
1147          // With 5-week (35 epoch) cooldown and 40 active per week:
1148          // Week 1: 40 active, 260 available
1149          // Week 2: 40 active, 40 in cooldown, 220 available
1150          // ...continuing this pattern ensures fair distribution
1151          let mut pool = ValidatorPool::<CurrentNetwork>::new_with_seed(0, 12345);
1152  
1153          // Register 300 validators (full pool)
1154          for i in 1..=300 {
1155              let validator =
1156                  Validator::new(Field::from_u64(i), create_test_key(i), Field::from_u64(100 + i), 100_000, 10_000, 0)
1157                      .unwrap();
1158              pool.register(validator).unwrap();
1159          }
1160  
1161          // First rotation (Week 1) - selects 40 validators
1162          let first_active = pool.advance_epoch(VALIDATOR_WARMUP_EPOCHS).unwrap();
1163          assert_eq!(first_active.len(), ACTIVE_VALIDATOR_COUNT);
1164  
1165          // Verify full 5-week cooldown at 300 validators
1166          assert_eq!(pool.effective_cooldown_epochs(), MAX_SELECTION_COOLDOWN_EPOCHS);
1167  
1168          // Track which validators were in the first rotation
1169          let first_active_set: std::collections::HashSet<_> = first_active.iter().cloned().collect();
1170  
1171          // Second rotation (Week 2, 7 epochs later) - first group should be in cooldown
1172          let rotation_2_epoch = VALIDATOR_WARMUP_EPOCHS + VALIDATOR_ROTATION_EPOCHS;
1173          let second_active = pool.advance_epoch_with_seed(rotation_2_epoch, Some(99999)).unwrap();
1174          let cooldown = pool.effective_cooldown_epochs();
1175  
1176          // None of the first active validators should be in the second rotation
1177          for validator_id in &second_active {
1178              assert!(
1179                  !first_active_set.contains(validator_id),
1180                  "Validator {:?} was in first rotation and should be in cooldown",
1181                  validator_id
1182              );
1183          }
1184  
1185          // Verify first group validators are in cooldown
1186          for validator_id in &first_active {
1187              let validator = pool.get(validator_id).unwrap();
1188              assert!(
1189                  validator.is_in_cooldown(rotation_2_epoch, cooldown),
1190                  "Validator {:?} should be in cooldown at epoch {}",
1191                  validator_id,
1192                  rotation_2_epoch
1193              );
1194          }
1195  
1196          // At deselection time, validators have full 35-epoch cooldown
1197          for validator_id in &first_active {
1198              let validator = pool.get(validator_id).unwrap();
1199              assert_eq!(
1200                  validator.cooldown_remaining(rotation_2_epoch, cooldown),
1201                  MAX_SELECTION_COOLDOWN_EPOCHS,
1202                  "Validator should have full {} epochs cooldown at deselection",
1203                  MAX_SELECTION_COOLDOWN_EPOCHS
1204              );
1205          }
1206  
1207          // One week later (7 epochs), cooldown remaining should be 35 - 7 = 28
1208          let one_week_later = rotation_2_epoch + VALIDATOR_ROTATION_EPOCHS;
1209          let expected_remaining = MAX_SELECTION_COOLDOWN_EPOCHS - VALIDATOR_ROTATION_EPOCHS;
1210          for validator_id in &first_active {
1211              let validator = pool.get(validator_id).unwrap();
1212              assert_eq!(
1213                  validator.cooldown_remaining(one_week_later, cooldown),
1214                  expected_remaining,
1215                  "Validator should have {} epochs remaining after 1 week",
1216                  expected_remaining
1217              );
1218          }
1219  
1220          // After 5 weeks (35 epochs) from deselection, first group can be selected again
1221          let cooldown_end_epoch = rotation_2_epoch + MAX_SELECTION_COOLDOWN_EPOCHS;
1222          for validator_id in &first_active {
1223              let validator = pool.get(validator_id).unwrap();
1224              assert!(
1225                  !validator.is_in_cooldown(cooldown_end_epoch, cooldown),
1226                  "Validator {:?} should NOT be in cooldown at epoch {}",
1227                  validator_id,
1228                  cooldown_end_epoch
1229              );
1230          }
1231      }
1232  
1233      #[test]
1234      fn test_small_pool_all_active() {
1235          // With < 40 validators, all should be active (no rotation)
1236          let mut pool = ValidatorPool::<CurrentNetwork>::new_with_seed(0, 12345);
1237  
1238          // Register 20 validators (below rotation threshold)
1239          for i in 1..=20 {
1240              let validator =
1241                  Validator::new(Field::from_u64(i), create_test_key(i), Field::from_u64(100 + i), 100_000, 10_000, 0)
1242                      .unwrap();
1243              pool.register(validator).unwrap();
1244          }
1245  
1246          // Advance past warmup
1247          let active = pool.advance_epoch(VALIDATOR_WARMUP_EPOCHS).unwrap();
1248  
1249          // All 20 should be active
1250          assert_eq!(active.len(), 20);
1251          assert!(pool.is_all_active_mode());
1252          assert_eq!(pool.effective_cooldown_epochs(), 0);
1253  
1254          // No rotation should happen even after 7 epochs
1255          let active_after_week = pool.advance_epoch(VALIDATOR_WARMUP_EPOCHS + 7).unwrap();
1256          assert_eq!(active_after_week.len(), 20);
1257      }
1258  
1259      #[test]
1260      fn test_scaling_cooldown() {
1261          // Test that cooldown scales with pool size
1262          let mut pool = ValidatorPool::<CurrentNetwork>::new_with_seed(0, 12345);
1263  
1264          // Start with 80 validators (2 sets) -> 1 week cooldown
1265          for i in 1..=80 {
1266              let validator =
1267                  Validator::new(Field::from_u64(i), create_test_key(i), Field::from_u64(100 + i), 100_000, 10_000, 0)
1268                      .unwrap();
1269              pool.register(validator).unwrap();
1270          }
1271  
1272          pool.advance_epoch(VALIDATOR_WARMUP_EPOCHS).unwrap();
1273          assert_eq!(pool.effective_cooldown_epochs(), 7); // 1 week
1274  
1275          // Add more validators to reach 120 (3 sets) -> 2 week cooldown
1276          for i in 81..=120 {
1277              let validator = Validator::new(
1278                  Field::from_u64(i),
1279                  create_test_key(i),
1280                  Field::from_u64(100 + i),
1281                  100_000,
1282                  10_000,
1283                  VALIDATOR_WARMUP_EPOCHS, // Register at warmup epoch
1284              )
1285              .unwrap();
1286              pool.register(validator).unwrap();
1287          }
1288  
1289          // Advance to make new validators eligible
1290          pool.advance_epoch(VALIDATOR_WARMUP_EPOCHS + VALIDATOR_WARMUP_EPOCHS).unwrap();
1291          assert_eq!(pool.effective_cooldown_epochs(), 14); // 2 weeks
1292  
1293          // Add more to reach 240 (6 sets) -> 5 week cooldown (capped)
1294          for i in 121..=240 {
1295              let validator = Validator::new(
1296                  Field::from_u64(i),
1297                  create_test_key(i),
1298                  Field::from_u64(100 + i),
1299                  100_000,
1300                  10_000,
1301                  VALIDATOR_WARMUP_EPOCHS * 2,
1302              )
1303              .unwrap();
1304              pool.register(validator).unwrap();
1305          }
1306  
1307          pool.advance_epoch(VALIDATOR_WARMUP_EPOCHS * 3).unwrap();
1308          assert_eq!(pool.effective_cooldown_epochs(), 35); // 5 weeks (max)
1309      }
1310  
1311      #[test]
1312      fn test_backup_activation() {
1313          let mut pool = ValidatorPool::<CurrentNetwork>::new(0);
1314  
1315          // Primary validator
1316          let primary =
1317              Validator::new(Field::from_u64(1), create_test_key(1), Field::from_u64(100), 100_000, 10_000, 0).unwrap();
1318  
1319          // Backup validator
1320          let backup =
1321              Validator::new(Field::from_u64(2), create_test_key(2), Field::from_u64(101), 100_000, 10_000, 0).unwrap();
1322  
1323          pool.register(primary).unwrap();
1324          pool.register(backup).unwrap();
1325  
1326          // Advance to make eligible first
1327          pool.advance_epoch(VALIDATOR_WARMUP_EPOCHS).unwrap();
1328  
1329          // Now set backup on eligible primary
1330          pool.get_mut(&Field::from_u64(1)).unwrap().set_backup(Field::from_u64(2)).unwrap();
1331  
1332          // Activate backup for failed primary
1333          let activated = pool.activate_backup(&Field::from_u64(1)).unwrap();
1334          assert_eq!(activated, Some(Field::from_u64(2)));
1335  
1336          // Primary should be suspended
1337          assert_eq!(pool.get(&Field::from_u64(1)).unwrap().state(), ValidatorState::Suspended);
1338  
1339          // Backup should be active
1340          assert!(pool.get(&Field::from_u64(2)).unwrap().is_active());
1341      }
1342  }