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 4 commits
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
7 changes: 7 additions & 0 deletions programs/steward/src/constants.rs
Original file line number Diff line number Diff line change
Expand Up @@ -20,3 +20,10 @@ 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";
#[cfg(feature = "mainnet-beta")]
pub const TVC_ACTIVATION_EPOCH: u64 = 704;
#[cfg(all(not(feature = "mainnet-beta"), feature = "testnet"))]
pub const TVC_ACTIVATION_EPOCH: u64 = 705;
#[cfg(all(not(feature = "mainnet-beta"), not(feature = "testnet")))]
pub const TVC_ACTIVATION_EPOCH: u64 = 0;
28 changes: 19 additions & 9 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 @@ -271,7 +274,7 @@ pub fn calculate_epoch_credits(
// If vote credits are None, then validator was not active because we retroactively fill credits for last 64 epochs.
// If total blocks are None, then keepers missed an upload and validator should not be punished.
let credits = maybe_credits.unwrap_or(0);
let ratio = credits as f64 / *blocks as f64;
let ratio = credits as f64 / (blocks * TVC_MULTIPLIER) as f64;
if ratio < scoring_delinquency_threshold_ratio {
delinquency_score = 0.0;
delinquency_ratio = ratio;
Expand All @@ -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
13 changes: 11 additions & 2 deletions programs/steward/src/state/steward_state.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,9 @@ use std::fmt::Display;

use crate::{
bitmask::BitMask,
constants::{LAMPORT_BALANCE_DEFAULT, MAX_VALIDATORS, SORTED_INDEX_DEFAULT},
constants::{
LAMPORT_BALANCE_DEFAULT, MAX_VALIDATORS, SORTED_INDEX_DEFAULT, TVC_ACTIVATION_EPOCH,
},
delegation::{
decrease_stake_calculation, increase_stake_calculation, RebalanceType, UnstakeState,
},
Expand Down Expand Up @@ -681,7 +683,13 @@ impl StewardState {
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 @@ -809,6 +817,7 @@ impl StewardState {
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
18 changes: 13 additions & 5 deletions tests/src/steward_fixtures.rs
Original file line number Diff line number Diff line change
Expand Up @@ -30,8 +30,10 @@ use spl_stake_pool::{
state::{Fee, StakeStatus, ValidatorList as SPLValidatorList, ValidatorStakeInfo},
};
use validator_history::{
self, constants::MAX_ALLOC_BYTES, CircBuf, CircBufCluster, ClusterHistory, ClusterHistoryEntry,
ValidatorHistory, ValidatorHistoryEntry,
self,
constants::{MAX_ALLOC_BYTES, TVC_MULTIPLIER},
CircBuf, CircBufCluster, ClusterHistory, ClusterHistoryEntry, ValidatorHistory,
ValidatorHistoryEntry,
};

pub struct StakePoolMetadata {
Expand Down Expand Up @@ -881,12 +883,18 @@ impl Default for StateMachineFixtures {
let vote_account_2 = Pubkey::new_unique();
let vote_account_3 = Pubkey::new_unique();

////////
////////
//////// TODO: update the vote credits to 8x higher here
////////
////////

// First one: Good validator
let mut validator_history_1 = validator_history_default(vote_account_1, 0);
for i in 0..=20 {
validator_history_1.history.push(ValidatorHistoryEntry {
epoch: i,
epoch_credits: 1000,
epoch_credits: 1000 * TVC_MULTIPLIER,
commission: 0,
mev_commission: 0,
is_superminority: 0,
Expand All @@ -900,7 +908,7 @@ impl Default for StateMachineFixtures {
for i in 0..=20 {
validator_history_2.history.push(ValidatorHistoryEntry {
epoch: i,
epoch_credits: 200,
epoch_credits: 200 * TVC_MULTIPLIER,
commission: 99,
mev_commission: 10000,
is_superminority: 1,
Expand All @@ -914,7 +922,7 @@ impl Default for StateMachineFixtures {
for i in 0..=20 {
validator_history_3.history.push(ValidatorHistoryEntry {
epoch: i,
epoch_credits: 1000,
epoch_credits: 1000 * TVC_MULTIPLIER,
commission: 5,
mev_commission: 500,
is_superminority: 0,
Expand Down
Loading
Loading