1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
use crate::utils;
use log::*;
use solana_client::rpc_client::RpcClient;
use solana_sdk::{
    account::from_account,
    clock::Epoch,
    epoch_info::EpochInfo,
    genesis_config::GenesisConfig,
    stake_history::StakeHistoryEntry,
    sysvar::stake_history::{self, StakeHistory},
};
use solana_stake_program::config::Config as StakeConfig;
use std::{thread::sleep, time::Duration};

fn calculate_stake_warmup(mut stake_entry: StakeHistoryEntry, stake_config: &StakeConfig) -> u64 {
    let mut epochs = 0;
    loop {
        let percent_warming_up =
            stake_entry.activating as f64 / stake_entry.effective.max(1) as f64;
        let percent_cooling_down =
            stake_entry.deactivating as f64 / stake_entry.effective.max(1) as f64;
        debug!(
            "epoch +{}: stake warming up {:.1}%, cooling down {:.1}% ",
            epochs,
            percent_warming_up * 100.,
            percent_cooling_down * 100.
        );

        if (percent_warming_up < 0.05) && (percent_cooling_down < 0.05) {
            break;
        }
        let warmup_cooldown_rate = stake_config.warmup_cooldown_rate;
        let max_warmup_stake = (stake_entry.effective as f64 * warmup_cooldown_rate) as u64;
        let warmup_stake = stake_entry.activating.min(max_warmup_stake);
        stake_entry.effective += warmup_stake;
        stake_entry.activating -= warmup_stake;

        let max_cooldown_stake = (stake_entry.effective as f64 * warmup_cooldown_rate) as u64;
        let cooldown_stake = stake_entry.deactivating.min(max_cooldown_stake);
        stake_entry.effective -= cooldown_stake;
        stake_entry.deactivating -= cooldown_stake;
        debug!(
            "epoch +{}: stake warming up {}, cooling down {}",
            epochs, warmup_stake, cooldown_stake
        );

        epochs += 1;
    }
    info!("95% stake warmup will take {} epochs", epochs);
    epochs
}

fn stake_history_entry(epoch: Epoch, rpc_client: &RpcClient) -> Option<StakeHistoryEntry> {
    let stake_history_account = rpc_client.get_account(&stake_history::id()).ok()?;
    let stake_history = from_account::<StakeHistory>(&stake_history_account)?;
    stake_history.get(&epoch).cloned()
}

/// Wait until stake warms up and return the current epoch
pub fn wait_for_warm_up(
    activation_epoch: Epoch,
    mut epoch_info: EpochInfo,
    rpc_client: &RpcClient,
    stake_config: &StakeConfig,
    genesis_config: &GenesisConfig,
    notifier: &solana_notifier::Notifier,
) {
    // Sleep until activation_epoch has finished
    if epoch_info.epoch <= activation_epoch {
        notifier.send(&format!(
            "Waiting until epoch {} is finished...",
            activation_epoch
        ));

        utils::sleep_until_epoch(
            rpc_client,
            notifier,
            genesis_config,
            epoch_info.absolute_slot,
            activation_epoch + 1,
        );
    }

    loop {
        epoch_info = rpc_client.get_epoch_info().unwrap_or_else(|err| {
            utils::bail(
                notifier,
                &format!("Error: get_epoch_info RPC call failed: {}", err),
            );
        });

        let current_slot = epoch_info.absolute_slot;
        info!("Current slot is {}", current_slot);

        let current_epoch = epoch_info.epoch;
        let latest_epoch = current_epoch - 1;
        debug!(
            "Fetching stake history entry for epoch: {}...",
            latest_epoch
        );

        if let Some(stake_entry) = stake_history_entry(latest_epoch, &rpc_client) {
            debug!("Stake history entry: {:?}", &stake_entry);
            let warm_up_epochs = calculate_stake_warmup(stake_entry, stake_config);
            let stake_warmed_up_epoch = latest_epoch + warm_up_epochs;
            if stake_warmed_up_epoch > current_epoch {
                notifier.send(&format!(
                    "Waiting until epoch {} for stake to warmup (current epoch is {})...",
                    stake_warmed_up_epoch, current_epoch
                ));
                utils::sleep_until_epoch(
                    rpc_client,
                    notifier,
                    genesis_config,
                    current_slot,
                    stake_warmed_up_epoch,
                );
            } else {
                break;
            }
        } else {
            warn!(
                "Failed to fetch stake history entry for epoch: {}",
                latest_epoch
            );
            sleep(Duration::from_secs(5));
        }

        let latest_slot = rpc_client.get_slot().unwrap_or_else(|err| {
            utils::bail(
                notifier,
                &format!("Error: get_slot RPC call 3 failed: {}", err),
            );
        });
        if current_slot == latest_slot {
            utils::bail(
                notifier,
                &format!("Error: Slot did not advance from {}", current_slot),
            );
        }
    }
}