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

feat: add MEV earned per epoch in the ValidatorHistoryAccount #13

Merged
merged 10 commits into from
Jan 26, 2024
Merged
Show file tree
Hide file tree
Changes from 6 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
10 changes: 10 additions & 0 deletions keepers/validator-keeper/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,16 @@ pub fn emit_mev_commission_datapoint(stats: CreateUpdateStats) {
);
}

pub fn emit_mev_earned_datapoint(stats: CreateUpdateStats) {
datapoint_info!(
"mev-earned-stats",
("num_creates_success", stats.creates.successes, i64),
("num_creates_error", stats.creates.errors, i64),
("num_updates_success", stats.updates.successes, i64),
("num_updates_error", stats.updates.errors, i64),
);
}

pub fn emit_validator_commission_datapoint(stats: CreateUpdateStats, runs_for_epoch: i64) {
datapoint_info!(
"vote-account-stats",
Expand Down
48 changes: 46 additions & 2 deletions keepers/validator-keeper/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,10 +18,10 @@ use solana_sdk::{
use tokio::time::sleep;
use validator_keeper::{
cluster_info::update_cluster_info,
emit_cluster_history_datapoint, emit_mev_commission_datapoint,
emit_cluster_history_datapoint, emit_mev_commission_datapoint, emit_mev_earned_datapoint,
emit_validator_commission_datapoint, emit_validator_history_metrics,
gossip::{emit_gossip_datapoint, upload_gossip_values},
mev_commission::update_mev_commission,
mev_commission::{update_mev_commission, update_mev_earned},
stake::{emit_stake_history_datapoint, update_stake_history},
vote_account::update_vote_accounts,
};
Expand Down Expand Up @@ -115,6 +115,43 @@ async fn mev_commission_loop(
}
}

async fn mev_earned_loop(
client: Arc<RpcClient>,
keypair: Arc<Keypair>,
commission_history_program_id: Pubkey,
tip_distribution_program_id: Pubkey,
interval: u64,
) {
let mut curr_epoch = 0;
// {TipDistributionAccount : VoteAccount}
let mut validators_updated: HashMap<Pubkey, Pubkey> = HashMap::new();

loop {
// Continuously runs throughout an epoch, polling for tip distribution accounts from the prev epoch with uploaded merkle roots
// and submitting update_mev_earned (technically update_mev_comission) txs when the uploaded merkle roots are detected
match update_mev_earned(
client.clone(),
Copy link
Collaborator

Choose a reason for hiding this comment

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

use &client and &keypair here to avoid clones

keypair.clone(),
&commission_history_program_id,
&tip_distribution_program_id,
&mut validators_updated,
&mut curr_epoch,
)
.await
{
Ok(stats) => {
emit_mev_earned_datapoint(stats);
sleep(Duration::from_secs(interval)).await;
}
Err((e, stats)) => {
emit_mev_earned_datapoint(stats);
datapoint_error!("mev-earned-error", ("error", e.to_string(), String),);
sleep(Duration::from_secs(5)).await;
}
};
}
}

async fn vote_account_loop(
rpc_client: Arc<RpcClient>,
keypair: Arc<Keypair>,
Expand Down Expand Up @@ -369,5 +406,12 @@ async fn main() {
args.interval,
));

tokio::spawn(mev_earned_loop(
Arc::clone(&client),
Copy link
Collaborator

Choose a reason for hiding this comment

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

this can be client.clone()

Copy link
Contributor Author

Choose a reason for hiding this comment

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

done, modified in the existing code as well

Arc::clone(&keypair),
args.program_id,
args.tip_distribution_program_id,
args.interval,
));
gossip_upload_loop(client, keypair, args.program_id, entrypoint, args.interval).await;
}
107 changes: 104 additions & 3 deletions keepers/validator-keeper/src/mev_commission.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
use std::{collections::HashMap, str::FromStr, sync::Arc};

use anchor_lang::{InstructionData, ToAccountMetas};
use anchor_lang::{AccountDeserialize, InstructionData, ToAccountMetas};
use jito_tip_distribution::sdk::derive_tip_distribution_account_address;
use jito_tip_distribution::state::TipDistributionAccount;
use keeper_core::{
build_create_and_update_instructions, get_multiple_accounts_batched,
get_vote_accounts_with_retry, submit_create_and_update, Address, CreateTransaction,
Expand Down Expand Up @@ -34,6 +35,7 @@ pub struct ValidatorMevCommissionEntry {
pub config: Pubkey,
pub program_id: Pubkey,
pub signer: Pubkey,
pub epoch: u64,
}

impl ValidatorMevCommissionEntry {
Expand Down Expand Up @@ -67,6 +69,7 @@ impl ValidatorMevCommissionEntry {
config,
program_id: *program_id,
signer: *signer,
epoch,
}
}
}
Expand Down Expand Up @@ -114,15 +117,16 @@ impl UpdateInstruction for ValidatorMevCommissionEntry {
fn update_instruction(&self) -> Instruction {
Instruction {
program_id: self.program_id,
accounts: validator_history::accounts::UpdateMevCommission {
accounts: validator_history::accounts::CopyTipDistributionAccount {
validator_history_account: self.validator_history_account,
vote_account: self.vote_account,
tip_distribution_account: self.tip_distribution_account,
config: self.config,
signer: self.signer,
}
.to_account_metas(None),
data: validator_history::instruction::UpdateMevCommission {}.data(),
data: validator_history::instruction::CopyTipDistributionAccount { epoch: self.epoch }
.data(),
}
}
}
Expand Down Expand Up @@ -190,6 +194,73 @@ pub async fn update_mev_commission(
submit_result.map_err(|(e, stats)| (e.into(), stats))
}

pub async fn update_mev_earned(
client: Arc<RpcClient>,
Copy link
Collaborator

Choose a reason for hiding this comment

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

can use & here and on keypair

Copy link
Contributor Author

Choose a reason for hiding this comment

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

makes sense, i def should borrow more than move into when i can

keypair: Arc<Keypair>,
validator_history_program_id: &Pubkey,
tip_distribution_program_id: &Pubkey,
validators_updated: &mut HashMap<Pubkey, Pubkey>,
curr_epoch: &mut u64,
) -> Result<CreateUpdateStats, (MevCommissionError, CreateUpdateStats)> {
let epoch = client
.get_epoch_info()
.await
.map_err(|e| (e.into(), CreateUpdateStats::default()))?
.epoch;

if epoch > *curr_epoch {
// new epoch started, we assume here that all the validators with TDAs from curr_epoch-1 have had their merkle roots uploaded/processed by this point
// clear our map of TDAs derived from curr_epoch -1 and start fresh for epoch-1 (or curr_epoch)
validators_updated.clear();
}
*curr_epoch = epoch;

let vote_accounts = get_vote_accounts_with_retry(&client, MIN_VOTE_EPOCHS, None)
.await
.map_err(|e| (e.into(), CreateUpdateStats::default()))?;

let entries = vote_accounts
.iter()
.map(|vote_account| {
ValidatorMevCommissionEntry::new(
vote_account,
epoch - 1, // TDA derived from the prev epoch since the merkle roots are uploaded shortly after rollover
Copy link
Collaborator

Choose a reason for hiding this comment

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

ultra nit: saturating_sub for epoch 0

Copy link
Contributor Author

Choose a reason for hiding this comment

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

makes sense

validator_history_program_id,
tip_distribution_program_id,
&keypair.pubkey(),
)
})
.collect::<Vec<ValidatorMevCommissionEntry>>();

let uploaded_merkleroot_entries =
get_entries_with_uploaded_merkleroot(client.clone(), &entries)
Copy link
Collaborator

Choose a reason for hiding this comment

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

use &client

.await
.map_err(|e| (e.into(), CreateUpdateStats::default()))?;

let entries_to_update = uploaded_merkleroot_entries
.into_iter()
.filter(|entry| !validators_updated.contains_key(&entry.tip_distribution_account))
.collect::<Vec<ValidatorMevCommissionEntry>>();
let (create_transactions, update_instructions) =
build_create_and_update_instructions(&client, &entries_to_update)
.await
.map_err(|e| (e.into(), CreateUpdateStats::default()))?;

let submit_result =
submit_create_and_update(&client, create_transactions, update_instructions, &keypair).await;
if submit_result.is_ok() {
for ValidatorMevCommissionEntry {
vote_account,
tip_distribution_account,
..
} in entries_to_update
{
validators_updated.insert(tip_distribution_account, vote_account);
}
}
submit_result.map_err(|(e, stats)| (e.into(), stats))
}

async fn get_existing_entries(
client: Arc<RpcClient>,
entries: &[ValidatorMevCommissionEntry],
Expand All @@ -215,3 +286,33 @@ async fn get_existing_entries(
// Fetch existing tip distribution accounts for this epoch
Ok(result)
}

async fn get_entries_with_uploaded_merkleroot(
client: Arc<RpcClient>,
Copy link
Collaborator

Choose a reason for hiding this comment

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

use &Arc

entries: &[ValidatorMevCommissionEntry],
) -> Result<Vec<ValidatorMevCommissionEntry>, MultipleAccountsError> {
/* Filters tip distribution tuples to the addresses, then fetches accounts to see which ones have an uploaded merkle root */
let tip_distribution_addresses = entries
.iter()
.map(|entry| entry.tip_distribution_account)
.collect::<Vec<Pubkey>>();

let accounts = get_multiple_accounts_batched(&tip_distribution_addresses, &client).await?;
let result = accounts
.iter()
.enumerate()
.filter_map(|(i, account_data)| {
if let Some(account_data) = account_data {
let mut data: &[u8] = &account_data.data;
buffalu marked this conversation as resolved.
Show resolved Hide resolved
if let Ok(tda) = TipDistributionAccount::try_deserialize(&mut data) {
if tda.merkle_root.is_some() {
return Some(entries[i].clone());
}
}
}
ebatsell marked this conversation as resolved.
Show resolved Hide resolved
None
})
.collect::<Vec<ValidatorMevCommissionEntry>>();
// Fetch tip distribution accounts with uploaded merkle roots for this epoch
Ok(result)
}
2 changes: 1 addition & 1 deletion programs/validator-history/idl/validator_history.json
Original file line number Diff line number Diff line change
Expand Up @@ -126,7 +126,7 @@
"args": []
},
{
"name": "updateMevCommission",
"name": "copyTipDistributionAccount",
"accounts": [
{
"name": "validatorHistoryAccount",
Expand Down
Original file line number Diff line number Diff line change
@@ -1,16 +1,17 @@
use anchor_lang::{
prelude::*,
solana_program::{clock::Clock, vote},
};
use anchor_lang::{prelude::*, solana_program::vote};

use crate::{
errors::ValidatorHistoryError,
state::{Config, ValidatorHistory},
utils::cast_epoch,
utils::fixed_point_sol,
};

use jito_tip_distribution::state::TipDistributionAccount;

#[derive(Accounts)]
pub struct UpdateMevCommission<'info> {
#[instruction(epoch: u64)]
pub struct CopyTipDistributionAccount<'info> {
#[account(
mut,
seeds = [ValidatorHistory::SEED, vote_account.key().as_ref()],
Expand All @@ -36,7 +37,7 @@ pub struct UpdateMevCommission<'info> {
seeds = [
TipDistributionAccount::SEED,
vote_account.key().as_ref(),
Clock::get().unwrap().epoch.to_le_bytes().as_ref(),
epoch.to_le_bytes().as_ref(),
],
bump,
seeds::program = config.tip_distribution_program.key(),
Expand All @@ -48,16 +49,25 @@ pub struct UpdateMevCommission<'info> {
pub signer: Signer<'info>,
}

pub fn handler(ctx: Context<UpdateMevCommission>) -> Result<()> {
pub fn handler(ctx: Context<CopyTipDistributionAccount>, epoch: u64) -> Result<()> {
// Cannot set stake for future epochs
if epoch > Clock::get()?.epoch {
Copy link
Collaborator

Choose a reason for hiding this comment

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

comment?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

good catch

return Err(ValidatorHistoryError::EpochOutOfRange.into());
}
let epoch = cast_epoch(epoch);
let mut validator_history_account = ctx.accounts.validator_history_account.load_mut()?;

let mut tda_data: &[u8] = &ctx.accounts.tip_distribution_account.try_borrow_data()?;

let tip_distribution_account = TipDistributionAccount::try_deserialize(&mut tda_data)?;
let mev_commission_bps = tip_distribution_account.validator_commission_bps;
let epoch = cast_epoch(Clock::get()?.epoch);
let mut mev_earned: u32 = 0;
// if the merkle_root has been uploaded pull the mev_earned for the epoch
if let Some(merkle_root) = tip_distribution_account.merkle_root {
mev_earned = fixed_point_sol(merkle_root.max_total_claim);
}

validator_history_account.set_mev_commission(epoch, mev_commission_bps)?;
validator_history_account.set_mev_commission(epoch, mev_commission_bps, mev_earned)?;

Ok(())
}
4 changes: 2 additions & 2 deletions programs/validator-history/src/instructions/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
pub mod backfill_total_blocks;
pub mod copy_cluster_info;
pub mod copy_gossip_contact_info;
pub mod copy_tip_distribution_account;
pub mod copy_vote_account;
pub mod initialize_cluster_history_account;
pub mod initialize_config;
Expand All @@ -11,12 +12,12 @@ pub mod realloc_validator_history_account;
pub mod set_new_admin;
pub mod set_new_oracle_authority;
pub mod set_new_tip_distribution_program;
pub mod update_mev_commission;
pub mod update_stake_history;

pub use backfill_total_blocks::*;
pub use copy_cluster_info::*;
pub use copy_gossip_contact_info::*;
pub use copy_tip_distribution_account::*;
pub use copy_vote_account::*;
pub use initialize_cluster_history_account::*;
pub use initialize_config::*;
Expand All @@ -26,5 +27,4 @@ pub use realloc_validator_history_account::*;
pub use set_new_admin::*;
pub use set_new_oracle_authority::*;
pub use set_new_tip_distribution_program::*;
pub use update_mev_commission::*;
pub use update_stake_history::*;
7 changes: 5 additions & 2 deletions programs/validator-history/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -69,8 +69,11 @@ pub mod validator_history {
instructions::copy_vote_account::handler(ctx)
}

pub fn update_mev_commission(ctx: Context<UpdateMevCommission>) -> Result<()> {
instructions::update_mev_commission::handler(ctx)
pub fn copy_tip_distribution_account(
ctx: Context<CopyTipDistributionAccount>,
epoch: u64,
) -> Result<()> {
instructions::copy_tip_distribution_account::handler(ctx, epoch)
}

pub fn initialize_config(ctx: Context<InitializeConfig>, authority: Pubkey) -> Result<()> {
Expand Down
Loading
Loading