Instant Unstaking

Automated validator removal based on performance and fee criteria

All validators are checked for a set of Instant Unstaking criteria, like commission thresholds, delinquency, etc. If they hit the criteria, they are marked for the rest of the cycle.

Overview

The system automatically identifies validators that should be unstaked immediately based on 4 criteria:

  1. Delinquency: Poor block production performance (based on Timely Vote Credits)
  2. High Commission: Commission rates above threshold
  3. High MEV Commission: MEV commission above threshold
  4. Blacklisted: Validator is on a blacklist

Example Scenario

Let's say we're in epoch 500, and we want to evaluate validator at index 42.

Initial Setup

// Current state
let current_epoch = 500;
let epoch_start_slot = 216_000_000; // First slot of epoch 500
let current_slot = 216_100_000; // We're partway through the epoch

// Validator we're evaluating
let validator_index = 42;
let validator_vote_account = "Vote111111111111111111111111111111111111111";

Step 1: Instruction Call

// Keeper calls the compute_instant_unstake instruction
compute_instant_unstake(
    ctx: Context<ComputeInstantUnstake>,
    validator_list_index: 42 // Index of validator to evaluate
)

Step 2: State and Input Validation

// Check we're in the right state
assert_eq!(state_account.state.state_tag, StewardStateEnum::ComputeInstantUnstake);

// Check we're past the minimum epoch progress (90% through epoch)
let epoch_progress = (current_slot - epoch_start_slot) as f64 / slots_in_epoch as f64;
assert!(epoch_progress >= 0.90); // instant_unstake_epoch_progress = 90%

// Check validator hasn't been processed yet
assert!(!state_account.state.progress.get(42)?); // false = not processed

// Check validator data is recent enough (50% through epoch minimum)
let min_acceptable_slot = epoch_start_slot + (slots_in_epoch as f64 * 0.50).round() as u64;
assert!(validator_history.vote_account_last_update_slot >= min_acceptable_slot);
assert!(cluster_history.cluster_history_last_update_slot >= min_acceptable_slot);

Step 3: Instant Unstake Calculation

// Calculate shared values
let cluster_history_slot_index = cluster_history_last_update_slot - epoch_start_slot;
// = 216_090_000 - 216_000_000 = 90_000

let validator_history_slot_index = vote_account_last_update_slot - epoch_start_slot;
// = 216_085_000 - 216_000_000 = 85_000

let total_blocks_latest = 85_000; // Total blocks produced by cluster
let epoch_credits_latest = 450;   // Credits earned by this validator

Step 4: Compute Checks

Delinquency Check

// Calculate rates for cluster and validator
let blocks_produced_rate = total_blocks_latest as f64 / cluster_history_slot_index as f64;
// = 85_000 / 90_000 = 0.944 blocks per slot

let vote_credits_rate = epoch_credits_latest as f64 / validator_history_slot_index as f64;
// = 450 / 85_000 = 0.00529 credits per slot

// Calculate delinquency ratio using TVC multiplier (16)
const TVC_MULTIPLIER: u32 = 16;
let delinquency_ratio = vote_credits_rate / (blocks_produced_rate * (TVC_MULTIPLIER as f64));
// = 0.00529 / (0.944 * 16.0) = 0.00529 / 15.104 = 0.00035

let delinquency_check = delinquency_ratio < 0.85; // Vote Credits Delinquency Threshold = 85%
// 0.00035 < 0.85 = true (FAIL - validator is severely delinquent)
What this means:
  • Expected: This validator should earn ~15.1 credits/slot if performing optimally
  • Actual: They're only earning ~0.005 credits/slot
  • Performance: Only 0.035% of expected perfomance (far below 85% threshold)
  • Issues: Likey severe downtime, very slow voting, or consistently wrong fork voting

Commission Check

let commission = validator.history.commission_latest().unwrap_or(100); // COMMISSION_MAX = 100%
// = 8 (8% - stored as integer, not basis points)

let commission_check = commission > 5; // Commission Threshold = 5%
// 8 > 5 = true (FAIL - commission too high)

MEV Commission Check

let previous_epoch = current_epoch.saturating_sub(1); // = 499
let example_current_epoch = 500;

// Get MEV commission for previous and current epoch, take the maximum
let mev_commission_range = validator.history.mev_commission_range(previous_epoch, example_current_epoch);
// Returns [Some(1000), Some(1200)] for epochs 499 and 500

let mev_commission_bps = mev_commission_range
    .iter()
    .filter_map(|&i| i)
    .max()
    .unwrap_or(0);
// = max(1000, 1200) = 1200 basis points (12.00%)

let mev_commission_check = mev_commission_bps > 1000; // MEV Commission Threshold = 10%
// 1200 > 1000 = true (FAIL - MEV commission too high)

Blacklist Check

let is_blacklisted = config.validator_history_blacklist.get(validator_index)?;
// = config.validator_history_blacklist.get(42) = false

Step 5: Final Result

let instant_unstake = delinquency_check || commission_check || mev_commission_check || is_blacklisted;
// = true || true || true || false = true

// This validator SHOULD be unstaked because:
// - Severely delinquent (performance ratio 0.00035 vs 0.85 threshold)
// - Commission too high (8% vs 5% threshold)  
// - High MEV commission (12% vs 10% threshold)

Step 6: State Update and Event Emission

// Update the state
state_account.instant_unstake.set(42, true)?;  // Mark for unstaking
state_account.progress.set(42, true)?;         // Mark as processed

// Emit the result event
emit!(InstantUnstakeComponentsV2 {
    instant_unstake: true,
    delinquency_check: true,   // FAIL - 0.00035 << 0.85 threshold
    commission_check: true,    // FAIL - 8% > 5% threshold
    mev_commission_check: true, // FAIL - 12% > 10% threshold
    is_blacklisted: false,
    vote_account: validator_vote_account,
    epoch: 500,
    details: InstantUnstakeDetails {
        epoch_credits_latest: 450,
        vote_account_last_update_slot: 216_085_000,
        total_blocks_latest: 85_000,
        cluster_history_slot_index: 90_000,
        commission: 8,        // 8% (stored as integer)
        mev_commission: 1200, // 12.00% (in basis points)
    },
});

Key Calculation Details

Delinquency Formula

The delinquency check uses a formula that accounts for the TVC (Timely Vote Credits) multiplier:

delinquency_ratio = vote_credits_rate / (blocks_produced_rate * TVC_MULTIPLIER)

Where:

  • vote_credits_rate: validator's credits earned per slot
  • blocks_produced_rate: cluster's blocks produced per slot
  • TVC_MULTIPLIER: 16 (maximum credits per vote under Timely Vote Credits)

MEV Commission Logic

MEV commission checks the maximum commission between the previous and current epoch, ensuring validators can't game the system by temporarily lowering rates.

Commission Storage

Regular commission is stored as an integer percentage (8 = 8%), while MEV commission uses basis points (1200 = 12.00%).

Example Results for Different Validators

Using the actual Steward configuration thresholds:

  • Delinquency Threshold: 85% (Vote Credits Delinquency Threshold)
  • Commission Threshold: 5% (Commission Threshold)
  • MEV Commission Threshold: 10% (MEV Commission Threshold)
ValidatorDelinquencyCommissionMEV CommissionBlacklistedResult
A✅ 98%✅ 3%✅ 8%Keep
B❌ 80%✅ 4%✅ 9%Unstake
C✅ 90%❌ 7%✅ 6%Unstake
D✅ 95%✅ 4%❌ 12%Unstake
E✅ 88%✅ 2%✅ 5%Unstake

Key Points

  1. Timing: Only runs after 90% progress through the epoch (Instant Unstake Epoch Progress)
  2. Data Freshness: Requires validator/cluster data updated after 50% epoch progress (Instant Unstake Inputs Epoch Progress)
  3. One Shot: Each validator is processed only once per cycle
  4. Multiple Criteria: Any single failure triggers instant unstake
  5. Strict Thresholds: Commission >5%, MEV Commission >10%, Delinquency <85%
  6. State Management: Tracks progress and results in persistent state
  7. Event Driven: Emits detailed events for monitoring and debugging

This system helps maintain stake pool health by automatically removing underperforming validators without manual intervention.