diff --git a/utils/steward-cli/src/commands/command_args.rs b/utils/steward-cli/src/commands/command_args.rs index bb291126..530e619a 100644 --- a/utils/steward-cli/src/commands/command_args.rs +++ b/utils/steward-cli/src/commands/command_args.rs @@ -1,8 +1,27 @@ -use clap::{arg, command, Parser, Subcommand}; +use clap::{arg, command, Parser, Subcommand, ValueEnum}; use jito_steward::{UpdateParametersArgs, UpdatePriorityFeeParametersArgs}; -use solana_sdk::pubkey::Pubkey; +use solana_sdk::{commitment_config::CommitmentConfig, pubkey::Pubkey}; use std::path::PathBuf; +/// Commitment level for RPC queries +#[derive(Debug, Clone, Copy, Default, ValueEnum)] +pub enum CommitmentLevel { + Processed, + #[default] + Confirmed, + Finalized, +} + +impl From for CommitmentConfig { + fn from(level: CommitmentLevel) -> Self { + match level { + CommitmentLevel::Processed => CommitmentConfig::processed(), + CommitmentLevel::Confirmed => CommitmentConfig::confirmed(), + CommitmentLevel::Finalized => CommitmentConfig::finalized(), + } + } +} + use crate::commands::{ actions::{ add_to_directed_stake_whitelist::AddToDirectedStakeWhitelist, @@ -57,6 +76,10 @@ pub struct Args { #[arg(long, global = true, env)] pub signer: Option, + /// Commitment level for RPC queries + #[arg(long, global = true, env, default_value = "confirmed")] + pub commitment: CommitmentLevel, + #[command(subcommand)] pub commands: Commands, } diff --git a/utils/steward-cli/src/main.rs b/utils/steward-cli/src/main.rs index bd316565..262b001b 100644 --- a/utils/steward-cli/src/main.rs +++ b/utils/steward-cli/src/main.rs @@ -81,9 +81,11 @@ pub mod utils; async fn main() -> Result<()> { dotenv().ok(); // Loads in .env file let args = Args::parse(); - let client = Arc::new(RpcClient::new_with_timeout( + let commitment_config = args.commitment.into(); + let client = Arc::new(RpcClient::new_with_timeout_and_commitment( args.json_rpc_url.clone(), Duration::from_secs(60), + commitment_config, )); let steward_program_id = args.steward_program_id; diff --git a/utils/validator-history-cli/src/commands/cranks/copy_vote_account.rs b/utils/validator-history-cli/src/commands/cranks/copy_vote_account.rs index bad28997..fb3c0768 100644 --- a/utils/validator-history-cli/src/commands/cranks/copy_vote_account.rs +++ b/utils/validator-history-cli/src/commands/cranks/copy_vote_account.rs @@ -8,16 +8,14 @@ use std::{ use anyhow::anyhow; use clap::Parser; use solana_client::nonblocking::rpc_client::RpcClient; -use solana_sdk::{account::Account, pubkey::Pubkey, signature::read_keypair_file, signer::Signer}; +use solana_sdk::{pubkey::Pubkey, signature::read_keypair_file, signer::Signer}; use stakenet_keeper::entries::copy_vote_account_entry::CopyVoteAccountEntry; use stakenet_sdk::{ models::entries::UpdateInstruction, utils::{ - accounts::get_all_validator_history_accounts, + accounts::{get_all_validator_history_accounts, get_validator_list_account}, helpers::vote_account_uploaded_recently, - transactions::{ - get_multiple_accounts_batched, get_vote_accounts_with_retry, submit_instructions, - }, + transactions::submit_instructions, }, }; @@ -28,20 +26,22 @@ pub struct CrankCopyVoteAccount { #[arg(short, long, env, default_value = "~/.config/solana/id.json")] keypair_path: PathBuf, - /// Only process validators returned by get_vote_accounts RPC (active validators) - #[arg(long, env, default_value_t = false)] - active_only: bool, + /// Validator list account pubkey (for filtering to pool validators), e.g. 3R3nGZpQs2aZo5FDQvd2MUQ6R7KhAPainds6uT6uE2mn + /// If not provided, processes all validator history accounts. + #[arg(long, env)] + validator_list_pubkey: Option, } pub async fn run(args: CrankCopyVoteAccount, rpc_url: String) -> anyhow::Result<()> { let keypair = read_keypair_file(args.keypair_path) .map_err(|e| anyhow!("Failed reading keypair file: {e}"))?; let keypair = Arc::new(keypair); - let client = RpcClient::new(rpc_url); + let client = RpcClient::new(rpc_url.clone()); let client = Arc::new(client); let epoch_info = client.get_epoch_info().await?; + // Fetch validator history accounts let validator_histories = get_all_validator_history_accounts(&client, validator_history::id()).await?; @@ -51,61 +51,49 @@ pub async fn run(args: CrankCopyVoteAccount, rpc_url: String) -> anyhow::Result< .map(|vote_history| (vote_history.vote_account, *vote_history)), ); - // If active_only is set, fetch active vote accounts from get_vote_accounts RPC - let active_vote_accounts: Option> = if args.active_only { - let vote_accounts = get_vote_accounts_with_retry(&client, 0, None).await?; - let active_set: HashSet = vote_accounts - .iter() - .filter_map(|va| Pubkey::from_str(&va.vote_pubkey).ok()) - .collect(); - println!( - "Filtering to {} active vote accounts from get_vote_accounts RPC", - active_set.len() - ); - Some(active_set) - } else { - None - }; - - let all_history_vote_account_pubkeys: Vec = - validator_history_map.keys().cloned().collect(); - - let all_history_vote_accounts = - get_multiple_accounts_batched(all_history_vote_account_pubkeys.as_slice(), &client).await?; - - let all_history_vote_account_map = all_history_vote_account_pubkeys - .into_iter() - .zip(all_history_vote_accounts) - .collect::>>(); - - let mut vote_accounts_to_update: HashSet<&Pubkey> = all_history_vote_account_map + // Optionally filter to pool validators if validator list is provided + let pool_vote_accounts: Option> = + if let Some(validator_list_pubkey_str) = &args.validator_list_pubkey { + let validator_list_pubkey = Pubkey::from_str(validator_list_pubkey_str) + .map_err(|e| anyhow!("Failed to parse validator list pubkey: {e}"))?; + + let validator_list = get_validator_list_account(&client, &validator_list_pubkey) + .await + .map_err(|e| anyhow!("Failed to fetch validator list: {e}"))?; + + let pool_vote_accounts: HashSet = validator_list + .validators + .iter() + .map(|v| v.vote_account_address) + .collect(); + + Some(pool_vote_accounts) + } else { + None + }; + + // Filter to accounts that haven't been updated recently, optionally restricted to pool validators + let vote_accounts_to_update: Vec<&Pubkey> = validator_histories .iter() - .filter_map(|(vote_address, vote_account)| match vote_account { - Some(account) => { - if account.owner == solana_sdk::vote::program::id() { - Some(vote_address) - } else { - None + .filter(|vote_history| { + // If pool filtering is enabled, must be in the pool + if let Some(ref pool) = pool_vote_accounts { + if !pool.contains(&vote_history.vote_account) { + return false; } } - _ => None, + + // Must not have been uploaded recently + !vote_account_uploaded_recently( + &validator_history_map, + &vote_history.vote_account, + epoch_info.epoch, + epoch_info.absolute_slot, + ) }) + .map(|vote_history| &vote_history.vote_account) .collect(); - // Filter to only active vote accounts if the flag is set - if let Some(ref active_set) = active_vote_accounts { - vote_accounts_to_update.retain(|vote_account| active_set.contains(vote_account)); - } - - vote_accounts_to_update.retain(|vote_account| { - !vote_account_uploaded_recently( - &validator_history_map, - vote_account, - epoch_info.epoch, - epoch_info.absolute_slot, - ) - }); - println!( "Found {} vote accounts to update", vote_accounts_to_update.len() diff --git a/utils/validator-history-cli/src/main.rs b/utils/validator-history-cli/src/main.rs index 42fb9fe9..6772536e 100644 --- a/utils/validator-history-cli/src/main.rs +++ b/utils/validator-history-cli/src/main.rs @@ -1,5 +1,5 @@ use anchor_lang::{AccountDeserialize, Discriminator, InstructionData, ToAccountMetas}; -use clap::{arg, command, Parser, Subcommand}; +use clap::{arg, command, Parser, Subcommand, ValueEnum}; use dotenvy::dotenv; use ipinfo::{BatchReqOpts, IpInfo, IpInfoConfig}; use rusqlite::Connection; @@ -10,8 +10,28 @@ use solana_client::{ }; use solana_program::instruction::Instruction; use solana_sdk::{ - pubkey::Pubkey, signature::read_keypair_file, signer::Signer, transaction::Transaction, + commitment_config::CommitmentConfig, pubkey::Pubkey, signature::read_keypair_file, + signer::Signer, transaction::Transaction, }; + +/// Commitment level for RPC queries +#[derive(Debug, Clone, Copy, Default, ValueEnum)] +pub enum CommitmentLevel { + Processed, + #[default] + Confirmed, + Finalized, +} + +impl From for CommitmentConfig { + fn from(level: CommitmentLevel) -> Self { + match level { + CommitmentLevel::Processed => CommitmentConfig::processed(), + CommitmentLevel::Confirmed => CommitmentConfig::confirmed(), + CommitmentLevel::Finalized => CommitmentConfig::finalized(), + } + } +} use spl_stake_pool::state::{StakePool, ValidatorList}; use stakenet_keeper::operations::block_metadata::db::DBSlotInfo; use std::{collections::HashMap, path::PathBuf, thread::sleep, time::Duration}; @@ -41,6 +61,10 @@ struct Args { )] json_rpc_url: String, + /// Commitment level for RPC queries + #[arg(long, global = true, env, default_value = "confirmed")] + commitment: CommitmentLevel, + #[command(subcommand)] commands: Commands, } @@ -1265,7 +1289,12 @@ async fn main() -> anyhow::Result<()> { dotenv().ok(); env_logger::init(); let args = Args::parse(); - let client = RpcClient::new_with_timeout(args.json_rpc_url.clone(), Duration::from_secs(60)); + let commitment_config = args.commitment.into(); + let client = RpcClient::new_with_timeout_and_commitment( + args.json_rpc_url.clone(), + Duration::from_secs(60), + commitment_config, + ); match args.commands { Commands::InitConfig(args) => command_init_config(args, client), Commands::ReallocConfig(args) => command_realloc_config(args, client),