Skip to content

Commit

Permalink
[snapshot-parser] obtaining jito mev comission (#79)
Browse files Browse the repository at this point in the history
* [snapshot-parser] obtaining jito mev comission
  • Loading branch information
ochaloup authored Jul 26, 2024
1 parent 894f48e commit a3855e8
Show file tree
Hide file tree
Showing 4 changed files with 143 additions and 29 deletions.
2 changes: 1 addition & 1 deletion settlement-engine/src/protected_events.rs
Original file line number Diff line number Diff line change
Expand Up @@ -102,7 +102,7 @@ pub fn collect_low_credits_events(
.iter()
.filter(|v| v.stake > 0)
.cloned()
.filter_map(|ValidatorMeta {vote_account, commission, credits, stake}| {
.filter_map(|ValidatorMeta {vote_account, commission, credits, stake, mev_commission: _}| {
if credits < expected_credits && commission < 100 {
debug!("Validator {vote_account} has low credits: {credits}, expected: {expected_credits}");
Some(
Expand Down
14 changes: 7 additions & 7 deletions snapshot-parser/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -9,17 +9,17 @@ edition = "2021"

[dependencies]
anyhow = { workspace = true }
bincode = { workspace = true }
clap = { workspace = true }
env_logger = { workspace = true }
log = { workspace = true }
merkle-tree = { workspace = true }
serde = { workspace = true }
serde_json = { workspace = true }
solana-client = { workspace = true }
solana-ledger = { workspace = true }
solana-program = { workspace = true }
solana-runtime = { workspace = true }
solana-sdk = { workspace = true }
solana-accounts-db = { workspace = true }
log = { workspace = true }
clap = { workspace = true }
env_logger = { workspace = true }
tokio = { workspace = true }
serde = { workspace = true }
serde_json = { workspace = true }
bincode = { workspace = true }
merkle-tree = { workspace = true }
3 changes: 3 additions & 0 deletions snapshot-parser/src/bin/cli.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,12 +12,15 @@ use {
#[derive(Parser, Debug)]
#[command(author, version, about, long_about = None)]
struct Args {
/// Path to the directory where the snapshot is unpacked (e.g., from .tar.zst)
#[arg(long, env, value_parser = Args::path_parser)]
ledger_path: PathBuf,

/// Path to write JSON file to for the validator metas (e.g., validators.json)
#[arg(long, env)]
output_validator_meta_collection: String,

/// Path to write JSON file to for the stake metas (e.g., stakes.json)
#[arg(long, env)]
output_stake_meta_collection: String,
}
Expand Down
153 changes: 132 additions & 21 deletions snapshot-parser/src/validator_meta.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
use solana_accounts_db::accounts_index::ScanConfig;
use solana_program::pubkey::Pubkey;
use solana_sdk::account::{Account, AccountSharedData};
use {
log::{error, info},
log::{error, info, warn},
merkle_tree::serde_serialize::pubkey_string_conversion,
serde::{Deserialize, Serialize},
solana_program::stake_history::Epoch,
Expand All @@ -14,6 +16,8 @@ pub struct ValidatorMeta {
#[serde(with = "pubkey_string_conversion")]
pub vote_account: Pubkey,
pub commission: u8,
/// jito-tip-distribution // TipDistributionAccount // validator_commission_bps
pub mev_commission: Option<u16>,
pub stake: u64,
pub credits: u64,
}
Expand Down Expand Up @@ -64,25 +68,15 @@ impl ValidatorMetaCollection {
}
}

pub fn generate_validator_collection(bank: &Arc<Bank>) -> anyhow::Result<ValidatorMetaCollection> {
assert!(bank.is_frozen());

let EpochInfo {
epoch,
absolute_slot,
..
} = bank.get_epoch_info();

let validator_rate = bank
.inflation()
.validator(bank.slot_in_year_for_inflation());
let capitalization = bank.capitalization();
let epoch_duration_in_years = bank.epoch_duration_in_years(epoch);
let validator_rewards =
(validator_rate * capitalization as f64 * epoch_duration_in_years) as u64;
struct VoteAccountMeta {
vote_account: Pubkey,
commission: u8,
stake: u64,
credits: u64,
}

let mut validator_metas: Vec<ValidatorMeta> = bank
.vote_accounts()
fn fetch_vote_account_metas(bank: &Arc<Bank>, epoch: Epoch) -> Vec<VoteAccountMeta> {
bank.vote_accounts()
.iter()
.filter_map(
|(pubkey, (stake, vote_account))| match vote_account.vote_state() {
Expand All @@ -99,7 +93,7 @@ pub fn generate_validator_collection(bank: &Arc<Bank>) -> anyhow::Result<Validat
})
.unwrap_or(0);

Some(ValidatorMeta {
Some(VoteAccountMeta {
vote_account: *pubkey,
commission: vote_state.commission,
stake: *stake,
Expand All @@ -112,7 +106,124 @@ pub fn generate_validator_collection(bank: &Arc<Bank>) -> anyhow::Result<Validat
}
},
)
.collect();
.collect()
}

struct JitoMevMeta {
vote_account: Pubkey,
mev_commission: u16,
}

// https://github.com/jito-foundation/jito-programs/blob/v0.1.5/mev-programs/programs/tip-distribution/src/state.rs#L32
const JITO_PROGRAM: &str = "4R3gSG8BpU4t19KYj8CfnbtRpnT8gtk4dvTHxVRwc2r7";
const TIP_DISTRIBUTION_ACCOUNT_DISCRIMINATOR: [u8; 8] = [85, 64, 113, 198, 234, 94, 120, 123];
const VALIDATOR_VOTE_ACCOUNT_BYTE_INDEX: usize = 8; // anchor header
const EPOCH_CREATED_AT_BYTE_INDEX: usize = 8 + // anchor header
// TipDistributionAccount "prefix" data
65 +
// MerkleRoot
64;
const VALIDATOR_COMMISSION_BPS_BYTE_INDEX: usize = EPOCH_CREATED_AT_BYTE_INDEX + 8;

fn fetch_jito_mev_metas(bank: &Arc<Bank>, epoch: Epoch) -> anyhow::Result<Vec<JitoMevMeta>> {
let jito_program: Pubkey = JITO_PROGRAM.try_into()?;

let jito_accounts_raw = bank.get_program_accounts(
&jito_program,
&ScanConfig {
collect_all_unsorted: true,
..ScanConfig::default()
},
)?;
info!(
"Jito {} accounts loaded: {}",
JITO_PROGRAM,
jito_accounts_raw.len()
);

let mut jito_mev_metas: Vec<JitoMevMeta> = Default::default();

for (pubkey, shared_account) in jito_accounts_raw {
let account = <AccountSharedData as Into<Account>>::into(shared_account);
if account.data[0..8] == TIP_DISTRIBUTION_ACCOUNT_DISCRIMINATOR {
let epoch_created_at = u64::from_le_bytes(
account.data[EPOCH_CREATED_AT_BYTE_INDEX..EPOCH_CREATED_AT_BYTE_INDEX + 8]
.try_into()
.map_err(|e| {
anyhow::anyhow!(
"Failed to parse epoch_created_at for account {}: {:?}",
pubkey,
e
)
})?,
);
if epoch_created_at == epoch {
let mev_commission = u16::from_le_bytes(
account.data[VALIDATOR_COMMISSION_BPS_BYTE_INDEX..VALIDATOR_COMMISSION_BPS_BYTE_INDEX + 2]
.try_into()
.map_err(|e| anyhow::anyhow!("Failed to parse validator_commission_bps (mev commission) for account {}: {:?}", pubkey, e))?,
);
let vote_account: Pubkey = account.data
[VALIDATOR_VOTE_ACCOUNT_BYTE_INDEX..VALIDATOR_VOTE_ACCOUNT_BYTE_INDEX + 32]
.try_into()
.map_err(|e| {
anyhow::anyhow!(
"Failed to parse vote account for account {}: {:?}",
pubkey,
e
)
})?;
jito_mev_metas.push(JitoMevMeta {
vote_account,
mev_commission,
});
}
}
}

Ok(jito_mev_metas)
}

pub fn generate_validator_collection(bank: &Arc<Bank>) -> anyhow::Result<ValidatorMetaCollection> {
assert!(bank.is_frozen());

let EpochInfo {
epoch,
absolute_slot,
..
} = bank.get_epoch_info();

let validator_rate = bank
.inflation()
.validator(bank.slot_in_year_for_inflation());
let capitalization = bank.capitalization();
let epoch_duration_in_years = bank.epoch_duration_in_years(epoch);
let validator_rewards =
(validator_rate * capitalization as f64 * epoch_duration_in_years) as u64;

let vote_account_metas = fetch_vote_account_metas(bank, epoch);
let jito_mev_metas = fetch_jito_mev_metas(bank, epoch)?;

let mut validator_metas = vote_account_metas
.into_iter()
.map(|vote_account_meta| ValidatorMeta {
vote_account: vote_account_meta.vote_account,
commission: vote_account_meta.commission,
mev_commission: jito_mev_metas
.iter()
.find(|jito_mev_meta| jito_mev_meta.vote_account == vote_account_meta.vote_account)
.map(|jito_mev_meta| Some(jito_mev_meta.mev_commission))
.unwrap_or_else(|| {
warn!(
"No Jito MEV commision found for vote account: {}",
vote_account_meta.vote_account
);
None
}),
stake: vote_account_meta.stake,
credits: vote_account_meta.credits,
})
.collect::<Vec<_>>();

info!(
"Collected all vote account metas: {}",
Expand Down

0 comments on commit a3855e8

Please sign in to comment.