Skip to content

Commit

Permalink
Add historical_commission_threshold and fixes instant unstaking bug (#42
Browse files Browse the repository at this point in the history
)

* Adds `historical_commission_threshold` as described in the Steward
README. Requires a compiler feature when building the main image to
specify VALIDATOR_HISTORY_FIRST_RELIABLE_EPOCH for the given network
this is running on.
* Fixes a bug in which validators with stake that are not in the
delegation set would not be checked for instant unstake status, which
was wrong because we still need to be able to instant unstake these
validators if they commission rug or go delinquent.
* Re-enables testing of Steward tests in CI.
  • Loading branch information
ebatsell authored Jun 4, 2024
1 parent 1f9a272 commit 3bc3ae7
Show file tree
Hide file tree
Showing 9 changed files with 218 additions and 13 deletions.
20 changes: 18 additions & 2 deletions .github/workflows/build.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -81,7 +81,7 @@ jobs:
run: echo "/home/runner/.local/share/solana/install/active_release/bin" >> $GITHUB_PATH

# build the program and IDL; exit if error
- run: anchor build --no-idl --program-name validator_history
- run: anchor build --no-idl
# - name: Check for diff on IDL
# run: git diff --exit-code

Expand All @@ -94,6 +94,12 @@ jobs:
with:
name: validator_history.so
path: target/deploy/validator_history.so

- name: Upload jito_steward.so
uses: actions/upload-artifact@v4
with:
name: jito_steward.so
path: target/deploy/jito_steward.so
# - name: Upload IDL
# uses: actions/upload-artifact@v4
# with:
Expand All @@ -115,13 +121,23 @@ jobs:
with:
name: validator_history.so
path: target/deploy/
- uses: actions/download-artifact@v4
with:
name: jito_steward.so
path: target/deploy/
- name: cargo test
run: cargo test --package tests --test mod validator_history --all-features --color auto
run: cargo test --package tests --all-features --color auto -- --skip steward::test_state_methods
shell: bash
env:
RUST_LOG: trace
SBF_OUT_DIR: ${{ github.workspace }}/target/deploy
RUST_MIN_STACK: 5000000
- name: cargo test steward::test_state_methods
run: cargo test --package tests --test mod steward::test_state_methods
shell: bash
env:
RUST_LOG: trace
RUST_MIN_STACK: 5000000

# release only runs on tagged commits
# it should wait for all the other steps to finish, to ensure releases are the highest quality
Expand Down
3 changes: 3 additions & 0 deletions programs/steward/src/constants.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,4 +11,7 @@ pub const EPOCH_PROGRESS_MAX: f64 = 0.99;
pub const NUM_EPOCHS_BETWEEN_SCORING_MAX: u64 = 100;
// Cannot score validators in under 100 slots, to submit 1 instruction per validator
pub const COMPUTE_SCORE_SLOT_RANGE_MIN: usize = 100;
#[cfg(feature = "mainnet-beta")]
pub const VALIDATOR_HISTORY_FIRST_RELIABLE_EPOCH: usize = 520;
#[cfg(not(feature = "mainnet-beta"))]
pub const VALIDATOR_HISTORY_FIRST_RELIABLE_EPOCH: usize = 0;
26 changes: 24 additions & 2 deletions programs/steward/src/score.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ use anchor_lang::{
use validator_history::{ClusterHistory, ValidatorHistory};

use crate::{
constants::{BASIS_POINTS_MAX, COMMISSION_MAX},
constants::{BASIS_POINTS_MAX, COMMISSION_MAX, VALIDATOR_HISTORY_FIRST_RELIABLE_EPOCH},
errors::StewardError::{self, ArithmeticError},
Config,
};
Expand Down Expand Up @@ -33,9 +33,12 @@ pub struct ScoreComponents {
/// If validator has a mev commission in the last 10 epochs, score is 1.0, else 0.0
pub running_jito_score: f64,

/// If max commission in commission_range epochs is less than threshold, score is 1.0, else 0.0
/// If max commission in commission_range epochs is less than commission_threshold, score is 1.0, else 0.0
pub commission_score: f64,

/// If max commission in all validator history epochs is less than historical_commission_threshold, score is 1.0, else 0.0
pub historical_commission_score: f64,

/// Average vote credits in last epoch_credits_range epochs / average blocks in last epoch_credits_range epochs
/// Excluding current epoch
pub vote_credits_ratio: f64,
Expand Down Expand Up @@ -140,6 +143,23 @@ pub fn validator_score(
};
let commission = commission_u8 as f64 / COMMISSION_MAX as f64;

/////// Historical Commission ///////

let historical_commission_max = validator
.history
.commission_range(VALIDATOR_HISTORY_FIRST_RELIABLE_EPOCH as u16, current_epoch)
.iter()
.filter_map(|&i| i)
.max()
.unwrap_or(0);

let historical_commission_score: f64 =
if historical_commission_max <= params.historical_commission_threshold {
1.0
} else {
0.0
};

/////// Superminority ///////
/*
If epoch credits exist, we expect the validator to have a superminority flag set. If not, scoring fails and we wait for
Expand Down Expand Up @@ -186,6 +206,7 @@ pub fn validator_score(

let score = mev_commission_score
* commission_score
* historical_commission_score
* blacklisted_score
* superminority_score
* delinquency_score
Expand All @@ -201,6 +222,7 @@ pub fn validator_score(
delinquency_score,
running_jito_score,
commission_score,
historical_commission_score,
vote_credits_ratio: average_vote_credits / average_blocks,
vote_account: validator.vote_account,
epoch: current_epoch,
Expand Down
17 changes: 15 additions & 2 deletions programs/steward/src/state/parameters.rs
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ pub struct UpdateParametersArgs {
pub instant_unstake_delinquency_threshold_ratio: Option<f64>,
pub mev_commission_bps_threshold: Option<u16>,
pub commission_threshold: Option<u8>,
pub historical_commission_threshold: Option<u8>,
// Delegation parameters
pub num_delegation_validators: Option<u32>,
pub scoring_unstake_cap_bps: Option<u32>,
Expand Down Expand Up @@ -56,12 +57,15 @@ pub struct Parameters {
/// Proportion of delinquent slots to total slots to trigger instant unstake
pub instant_unstake_delinquency_threshold_ratio: f64,

/// Highest commission rate allowed in percent
/// Highest commission rate allowed in commission_range epochs, in percent
pub commission_threshold: u8,

/// Highest commission rate allowed in tracked history
pub historical_commission_threshold: u8,

/// Required so that the struct is 8-byte aligned
/// https://doc.rust-lang.org/reference/type-layout.html#reprc-structs
pub padding0: [u8; 7],
pub padding0: [u8; 6],

/////// Delegation parameters ///////
/// Number of validators to delegate to
Expand Down Expand Up @@ -113,6 +117,7 @@ impl Parameters {
instant_unstake_delinquency_threshold_ratio,
mev_commission_bps_threshold,
commission_threshold,
historical_commission_threshold,
num_delegation_validators,
scoring_unstake_cap_bps,
instant_unstake_cap_bps,
Expand Down Expand Up @@ -159,6 +164,10 @@ impl Parameters {
new_parameters.commission_threshold = commission_threshold;
}

if let Some(historical_commission_threshold) = historical_commission_threshold {
new_parameters.historical_commission_threshold = historical_commission_threshold;
}

if let Some(num_delegation_validators) = num_delegation_validators {
new_parameters.num_delegation_validators = num_delegation_validators;
}
Expand Down Expand Up @@ -244,6 +253,10 @@ impl Parameters {
return Err(StewardError::InvalidParameterValue.into());
}

if self.historical_commission_threshold > COMMISSION_MAX {
return Err(StewardError::InvalidParameterValue.into());
}

if self.num_delegation_validators == 0
|| self.num_delegation_validators > MAX_VALIDATORS as u32
{
Expand Down
5 changes: 2 additions & 3 deletions programs/steward/src/state/steward_state.rs
Original file line number Diff line number Diff line change
Expand Up @@ -636,9 +636,8 @@ impl StewardState {
clock.epoch as u16,
)?;
emit!(instant_unstake_result);
if self.delegations[index].numerator > 0 && instant_unstake_result.instant_unstake {
self.instant_unstake.set(index, true)?;
}
self.instant_unstake
.set(index, instant_unstake_result.instant_unstake)?;
self.progress.set(index, true)?;
return Ok(());
}
Expand Down
3 changes: 2 additions & 1 deletion tests/src/steward_fixtures.rs
Original file line number Diff line number Diff line change
Expand Up @@ -762,7 +762,8 @@ impl Default for StateMachineFixtures {
scoring_delinquency_threshold_ratio: 0.875,
instant_unstake_delinquency_threshold_ratio: 0.1,
commission_threshold: 10,
padding0: [0; 7],
historical_commission_threshold: 10,
padding0: [0; 6],
num_delegation_validators: 3,
scoring_unstake_cap_bps: 1000,
instant_unstake_cap_bps: 1000,
Expand Down
77 changes: 77 additions & 0 deletions tests/tests/steward/test_algorithms.rs
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@ fn test_compute_score() {
running_jito_score: 1.0,
vote_credits_ratio: 1.0,
commission_score: 1.0,
historical_commission_score: 1.0,
vote_account: good_validator.vote_account,
epoch: current_epoch as u16
}
Expand Down Expand Up @@ -83,6 +84,7 @@ fn test_compute_score() {
running_jito_score: 1.0,
vote_credits_ratio: 1.0,
commission_score: 1.0,
historical_commission_score: 1.0,
vote_account: validator.vote_account,
epoch: current_epoch as u16
}
Expand Down Expand Up @@ -110,6 +112,7 @@ fn test_compute_score() {
running_jito_score: 1.0,
vote_credits_ratio: 1.0,
commission_score: 1.0,
historical_commission_score: 1.0,
vote_account: validator.vote_account,
epoch: current_epoch as u16
}
Expand All @@ -136,6 +139,7 @@ fn test_compute_score() {
running_jito_score: 1.0,
vote_credits_ratio: 1.0,
commission_score: 1.0,
historical_commission_score: 1.0,
vote_account: validator.vote_account,
epoch: current_epoch as u16
}
Expand Down Expand Up @@ -167,6 +171,7 @@ fn test_compute_score() {
running_jito_score: 1.0,
vote_credits_ratio: 1.0,
commission_score: 1.0,
historical_commission_score: 1.0,
vote_account: validator.vote_account,
epoch: current_epoch as u16
}
Expand Down Expand Up @@ -196,6 +201,7 @@ fn test_compute_score() {
running_jito_score: 1.0,
vote_credits_ratio: 1.0,
commission_score: 1.0,
historical_commission_score: 1.0,
vote_account: validator.vote_account,
epoch: current_epoch as u16
}
Expand Down Expand Up @@ -226,6 +232,7 @@ fn test_compute_score() {
running_jito_score: 1.0,
vote_credits_ratio: 1.0,
commission_score: 1.0,
historical_commission_score: 1.0,
vote_account: validator.vote_account,
epoch: current_epoch as u16
}
Expand Down Expand Up @@ -257,6 +264,7 @@ fn test_compute_score() {
running_jito_score: 0.0,
vote_credits_ratio: 1.0,
commission_score: 1.0,
historical_commission_score: 1.0,
vote_account: validator.vote_account,
epoch: current_epoch as u16
}
Expand Down Expand Up @@ -285,6 +293,71 @@ fn test_compute_score() {
running_jito_score: 1.0,
vote_credits_ratio: 1.0,
commission_score: 0.0,
historical_commission_score: 0.0,
vote_account: validator.vote_account,
epoch: current_epoch as u16
}
);

//// historical commission ////
let mut validator = good_validator;
let mut config = default_fixture.config;

config.parameters.historical_commission_threshold = 15;
config.parameters.commission_threshold = 10;
config.parameters.commission_range = 10;

// commission above regular threshold, below historical threshold, outside of regular threshold window
validator.history.arr[0].commission = 14;

let components = validator_score(
&validator,
validator.index as usize,
&cluster_history,
&config,
current_epoch as u16,
)
.unwrap();
assert_eq!(
components,
ScoreComponents {
score: 1.0,
yield_score: 1.0,
mev_commission_score: 1.0,
blacklisted_score: 1.0,
superminority_score: 1.0,
delinquency_score: 1.0,
running_jito_score: 1.0,
vote_credits_ratio: 1.0,
commission_score: 1.0,
historical_commission_score: 1.0,
vote_account: validator.vote_account,
epoch: current_epoch as u16
}
);

validator.history.arr[0].commission = 16;
let components = validator_score(
&validator,
validator.index as usize,
&cluster_history,
&config,
current_epoch as u16,
)
.unwrap();
assert_eq!(
components,
ScoreComponents {
score: 0.0,
yield_score: 1.0,
mev_commission_score: 1.0,
blacklisted_score: 1.0,
superminority_score: 1.0,
delinquency_score: 1.0,
running_jito_score: 1.0,
vote_credits_ratio: 1.0,
commission_score: 1.0,
historical_commission_score: 0.0,
vote_account: validator.vote_account,
epoch: current_epoch as u16
}
Expand Down Expand Up @@ -318,6 +391,7 @@ fn test_compute_score() {
running_jito_score: 1.0,
vote_credits_ratio: 0.88,
commission_score: 1.0,
historical_commission_score: 1.0,
vote_account: validator.vote_account,
epoch: current_epoch as u16
}
Expand Down Expand Up @@ -346,6 +420,7 @@ fn test_compute_score() {
running_jito_score: 1.0,
vote_credits_ratio: 0.95,
commission_score: 1.0,
historical_commission_score: 1.0,
vote_account: validator.vote_account,
epoch: current_epoch as u16
}
Expand Down Expand Up @@ -379,6 +454,7 @@ fn test_compute_score() {
running_jito_score: 1.0,
vote_credits_ratio: 0.9,
commission_score: 1.0,
historical_commission_score: 1.0,
vote_account: validator.vote_account,
epoch: current_epoch as u16
}
Expand Down Expand Up @@ -410,6 +486,7 @@ fn test_compute_score() {
running_jito_score: 1.0,
vote_credits_ratio: 1.0,
commission_score: 1.0,
historical_commission_score: 1.0,
vote_account: validator.vote_account,
epoch: current_epoch as u16
}
Expand Down
9 changes: 6 additions & 3 deletions tests/tests/steward/test_integration.rs
Original file line number Diff line number Diff line change
Expand Up @@ -304,7 +304,8 @@ async fn test_compute_scores() {
let tx = Transaction::new_signed_with_payer(
&[
// Only high because we are averaging 512 epochs
ComputeBudgetInstruction::set_compute_unit_limit(1_400_000),
ComputeBudgetInstruction::set_compute_unit_limit(600_000),
ComputeBudgetInstruction::request_heap_frame(128 * 1024),
compute_scores_ix.clone(),
],
Some(&fixture.keypair.pubkey()),
Expand Down Expand Up @@ -349,7 +350,8 @@ async fn test_compute_scores() {

let tx = Transaction::new_signed_with_payer(
&[
ComputeBudgetInstruction::set_compute_unit_limit(1_400_000),
ComputeBudgetInstruction::set_compute_unit_limit(600_000),
ComputeBudgetInstruction::request_heap_frame(128 * 1024),
compute_scores_ix.clone(),
],
Some(&fixture.keypair.pubkey()),
Expand Down Expand Up @@ -377,7 +379,8 @@ async fn test_compute_scores() {
let blockhash = fixture.get_latest_blockhash().await;
let tx = Transaction::new_signed_with_payer(
&[
ComputeBudgetInstruction::set_compute_unit_limit(1_400_000),
ComputeBudgetInstruction::set_compute_unit_limit(600_000),
ComputeBudgetInstruction::request_heap_frame(128 * 1024),
compute_scores_ix.clone(),
],
Some(&fixture.keypair.pubkey()),
Expand Down
Loading

0 comments on commit 3bc3ae7

Please sign in to comment.