Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Normalize Timely Vote Credits in Steward score #92

Merged
merged 12 commits into from
Dec 7, 2024
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions programs/steward/src/constants.rs
Original file line number Diff line number Diff line change
Expand Up @@ -20,3 +20,6 @@ pub const COMPUTE_SCORE_SLOT_RANGE_MIN: u64 = 100;
pub const VALIDATOR_HISTORY_FIRST_RELIABLE_EPOCH: u64 = 520;
#[cfg(not(feature = "mainnet-beta"))]
pub const VALIDATOR_HISTORY_FIRST_RELIABLE_EPOCH: u64 = 0;
pub const TVC_FEATURE_PUBKEY: &str = "tvcF6b1TRz353zKuhBjinZkKzjmihXmBAHJdjNYw1sQ";
pub const TVC_MAINNET_ACTIVATION_EPOCH: u64 = 704;
pub const TVC_TESTNET_ACTIVATION_EPOCH: u64 = 705;
18 changes: 18 additions & 0 deletions programs/steward/src/instructions/compute_instant_unstake.rs
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
use crate::{
constants::TVC_MAINNET_ACTIVATION_EPOCH,
errors::StewardError,
maybe_transition,
utils::{get_validator_list, get_validator_stake_info_at_index, state_checks},
Config, StewardStateAccount, StewardStateEnum,
};
use anchor_lang::prelude::*;
use spl_pod::solana_program::feature::Feature;
use validator_history::{ClusterHistory, ValidatorHistory};

#[derive(Accounts)]
Expand All @@ -31,6 +33,8 @@ pub struct ComputeInstantUnstake<'info> {
bump
)]
pub cluster_history: AccountLoader<'info, ClusterHistory>,

pub maybe_tvc_feature_account: Option<AccountInfo<'info>>,
}

pub fn handler(ctx: Context<ComputeInstantUnstake>, validator_list_index: usize) -> Result<()> {
Expand All @@ -42,6 +46,19 @@ pub fn handler(ctx: Context<ComputeInstantUnstake>, validator_list_index: usize)
let clock = Clock::get()?;
let epoch_schedule = EpochSchedule::get()?;

let tvc_activation_epoch = {
if let Some(tvc_feature_account) = ctx.accounts.maybe_tvc_feature_account.as_ref() {
let activation_slot = Feature::from_account_info(tvc_feature_account)?.activated_at;
if let Some(activation_slot) = activation_slot {
epoch_schedule.get_epoch(activation_slot)
} else {
TVC_MAINNET_ACTIVATION_EPOCH
}
} else {
TVC_MAINNET_ACTIVATION_EPOCH
}
};

// Transitions to Idle before doing compute_instant_unstake if RESET_TO_IDLE is set
if let Some(event) = maybe_transition(
&mut state_account.state,
Expand Down Expand Up @@ -75,6 +92,7 @@ pub fn handler(ctx: Context<ComputeInstantUnstake>, validator_list_index: usize)
validator_list_index,
&cluster,
&config,
tvc_activation_epoch,
)? {
emit!(instant_unstake);
}
Expand Down
23 changes: 23 additions & 0 deletions programs/steward/src/instructions/compute_score.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,10 @@
use std::str::FromStr;

use anchor_lang::prelude::*;
use spl_pod::solana_program::{clock::Epoch, feature::Feature};

Check failure on line 4 in programs/steward/src/instructions/compute_score.rs

View workflow job for this annotation

GitHub Actions / lint

unused import: `clock::Epoch`

Check failure on line 4 in programs/steward/src/instructions/compute_score.rs

View workflow job for this annotation

GitHub Actions / udeps

unused import: `clock::Epoch`

use crate::{
constants::{TVC_FEATURE_PUBKEY, TVC_MAINNET_ACTIVATION_EPOCH},
errors::StewardError,
maybe_transition,
utils::{
Expand Down Expand Up @@ -34,6 +38,11 @@
bump
)]
pub cluster_history: AccountLoader<'info, ClusterHistory>,

#[account(
address = Pubkey::from_str(TVC_FEATURE_PUBKEY).unwrap()
)]
pub maybe_tvc_feature_account: Option<AccountInfo<'info>>,
}

pub fn handler(ctx: Context<ComputeScore>, validator_list_index: usize) -> Result<()> {
Expand All @@ -45,6 +54,19 @@
let clock: Clock = Clock::get()?;
let epoch_schedule = EpochSchedule::get()?;

let tvc_activation_epoch = {
if let Some(tvc_feature_account) = ctx.accounts.maybe_tvc_feature_account.as_ref() {
let activation_slot = Feature::from_account_info(tvc_feature_account)?.activated_at;
if let Some(activation_slot) = activation_slot {
epoch_schedule.get_epoch(activation_slot)
} else {
TVC_MAINNET_ACTIVATION_EPOCH
}
} else {
TVC_MAINNET_ACTIVATION_EPOCH
}
};

// We don't check the state here because we force it below
state_checks(&clock, &config, &state_account, validator_list, None)?;

Expand Down Expand Up @@ -89,6 +111,7 @@
&cluster_history,
&config,
num_pool_validators as u64,
tvc_activation_epoch,
)? {
emit!(score);
}
Expand Down
26 changes: 18 additions & 8 deletions programs/steward/src/score.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ use anchor_lang::IdlBuild;
use anchor_lang::{
prelude::event, solana_program::pubkey::Pubkey, AnchorDeserialize, AnchorSerialize, Result,
};
use validator_history::{ClusterHistory, ValidatorHistory};
use validator_history::{constants::TVC_MULTIPLIER, ClusterHistory, ValidatorHistory};

use crate::{
constants::{
Expand Down Expand Up @@ -90,6 +90,7 @@ pub fn validator_score(
cluster: &ClusterHistory,
config: &Config,
current_epoch: u16,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

current_epoch should be u64

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

legacy issue cause of ValidatorHistoryEntry trying to minimize bytes

tvc_activation_epoch: u64,
) -> Result<ScoreComponentsV2> {
let params = &config.parameters;

Expand All @@ -107,9 +108,11 @@ pub fn validator_score(
// Epoch credits should not include current epoch because it is in progress and data would be incomplete
let epoch_credits_end = current_epoch.checked_sub(1).ok_or(ArithmeticError)?;

let epoch_credits_window = validator
.history
.epoch_credits_range(epoch_credits_start, epoch_credits_end);
let normalized_epoch_credits_window = validator.history.epoch_credits_range_normalized(
epoch_credits_start,
epoch_credits_end,
tvc_activation_epoch,
);

let total_blocks_window = cluster
.history
Expand All @@ -132,7 +135,7 @@ pub fn validator_score(

let (vote_credits_ratio, delinquency_score, delinquency_ratio, delinquency_epoch) =
calculate_epoch_credits(
&epoch_credits_window,
&normalized_epoch_credits_window,
&total_blocks_window,
epoch_credits_start,
params.scoring_delinquency_threshold_ratio,
Expand Down Expand Up @@ -283,8 +286,11 @@ pub fn calculate_epoch_credits(
}
}

let normalized_vote_credits_ratio =
average_vote_credits / (average_blocks * (TVC_MULTIPLIER as f64));

Ok((
average_vote_credits / average_blocks,
normalized_vote_credits_ratio,
delinquency_score,
delinquency_ratio,
delinquency_epoch,
Expand Down Expand Up @@ -471,6 +477,7 @@ pub fn instant_unstake_validator(
config: &Config,
epoch_start_slot: u64,
current_epoch: u16,
tvc_activation_epoch: u64,
) -> Result<InstantUnstakeComponentsV2> {
let params = &config.parameters;

Expand All @@ -494,7 +501,10 @@ pub fn instant_unstake_validator(
.checked_sub(epoch_start_slot)
.ok_or(StewardError::ArithmeticError)?;

let epoch_credits_latest = validator.history.epoch_credits_latest().unwrap_or(0);
let epoch_credits_latest = validator
.history
.epoch_credits_latest_normalized(current_epoch as u64, tvc_activation_epoch)
.unwrap_or(0);

/////// Component calculations ///////
let delinquency_check = calculate_instant_unstake_delinquency(
Expand Down Expand Up @@ -555,7 +565,7 @@ pub fn calculate_instant_unstake_delinquency(

if blocks_produced_rate > 0. {
Ok(
(vote_credits_rate / blocks_produced_rate)
(vote_credits_rate / (blocks_produced_rate * (TVC_MULTIPLIER as f64)))
< instant_unstake_delinquency_threshold_ratio,
)
} else {
Expand Down
11 changes: 10 additions & 1 deletion programs/steward/src/state/steward_state.rs
Original file line number Diff line number Diff line change
Expand Up @@ -600,6 +600,7 @@
cluster: &ClusterHistory,
config: &Config,
num_pool_validators: u64,
tvc_activation_epoch: u64,
) -> Result<Option<ScoreComponentsV2>> {
if matches!(self.state_tag, StewardStateEnum::ComputeScores) {
let current_epoch = clock.epoch;
Expand Down Expand Up @@ -681,7 +682,13 @@
return Err(StewardError::ClusterHistoryNotRecentEnough.into());
}

let score = validator_score(validator, cluster, config, current_epoch as u16)?;
let score = validator_score(
validator,
cluster,
config,
current_epoch as u16,
tvc_activation_epoch,
)?;

self.scores[index] = (score.score * 1_000_000_000.) as u32;
self.yield_scores[index] = (score.yield_score * 1_000_000_000.) as u32;
Expand Down Expand Up @@ -748,7 +755,7 @@
/// If so, set the validator.index bit in `instant_unstake` to true
///
/// Mutates: instant_unstake, progress
pub fn compute_instant_unstake(

Check failure on line 758 in programs/steward/src/state/steward_state.rs

View workflow job for this annotation

GitHub Actions / lint

this function has too many arguments (8/7)
&mut self,
clock: &Clock,
epoch_schedule: &EpochSchedule,
Expand All @@ -756,6 +763,7 @@
index: usize,
cluster: &ClusterHistory,
config: &Config,
tvc_activation_epoch: u64,
) -> Result<Option<InstantUnstakeComponentsV2>> {
if matches!(self.state_tag, StewardStateEnum::ComputeInstantUnstake) {
if clock.epoch >= self.next_cycle_epoch {
Expand Down Expand Up @@ -809,6 +817,7 @@
config,
first_slot,
clock.epoch as u16,
tvc_activation_epoch,
)?;

self.instant_unstake
Expand Down
1 change: 1 addition & 0 deletions programs/validator-history/src/constants.rs
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
pub const MAX_ALLOC_BYTES: usize = 10240;
pub const MIN_VOTE_EPOCHS: usize = 5;
pub const TVC_MULTIPLIER: u32 = 8;
40 changes: 40 additions & 0 deletions programs/validator-history/src/state.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
use {
crate::{
constants::TVC_MULTIPLIER,
crds_value::{ContactInfo, LegacyContactInfo, LegacyVersion, Version2},
errors::ValidatorHistoryError,
utils::{cast_epoch, find_insert_position, get_max_epoch, get_min_epoch},
Expand Down Expand Up @@ -274,10 +275,49 @@ impl CircBuf {
field_latest!(self, epoch_credits)
}

/// Normalized epoch credits, accounting for Timely Vote Credits making the max number of credits 8x higher
/// for every epoch starting at `tvc_activation_epoch`
ebatsell marked this conversation as resolved.
Show resolved Hide resolved
pub fn epoch_credits_latest_normalized(
&self,
current_epoch: u64,
tvc_activation_epoch: u64,
) -> Option<u32> {
self.epoch_credits_latest().map(|credits| {
if current_epoch < tvc_activation_epoch {
credits.saturating_mul(TVC_MULTIPLIER)
} else {
credits
}
})
}

pub fn epoch_credits_range(&self, start_epoch: u16, end_epoch: u16) -> Vec<Option<u32>> {
field_range!(self, start_epoch, end_epoch, epoch_credits, u32)
}

/// Normalized epoch credits, accounting for Timely Vote Credits making the max number of credits 8x higher
/// for every epoch starting at `tvc_activation_epoch`
pub fn epoch_credits_range_normalized(
&self,
start_epoch: u16,
end_epoch: u16,
tvc_activation_epoch: u64,
) -> Vec<Option<u32>> {
field_range!(self, start_epoch, end_epoch, epoch_credits, u32)
.into_iter()
.zip(start_epoch..=end_epoch)
.map(|(maybe_credits, epoch)| {
maybe_credits.map(|credits| {
if (epoch as u64) < tvc_activation_epoch {
credits.saturating_mul(TVC_MULTIPLIER)
} else {
credits
}
})
})
.collect()
}

pub fn superminority_latest(&self) -> Option<u8> {
// Protect against unexpected values
if let Some(value) = field_latest!(self, is_superminority) {
Expand Down
Loading