diff --git a/.github/workflows/build.yaml b/.github/workflows/build.yaml index 14e8e6e0..8f24371f 100644 --- a/.github/workflows/build.yaml +++ b/.github/workflows/build.yaml @@ -129,6 +129,8 @@ jobs: with: submodules: recursive - uses: actions-rust-lang/setup-rust-toolchain@v1 + with: + toolchain: nightly-2024-02-04 - uses: actions/download-artifact@v4 with: name: validator_history.so @@ -138,18 +140,12 @@ jobs: name: jito_steward.so path: target/deploy/ - name: cargo test - run: cargo test --package tests --all-features --color auto -- --skip steward::test_state_methods + run: cargo test --package tests --all-features --color auto 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 diff --git a/Cargo.lock b/Cargo.lock index affd1b7b..5af13eb2 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -6112,6 +6112,7 @@ dependencies = [ "solana-metrics", "solana-program", "solana-sdk", + "solana-transaction-status", "spl-pod", "spl-stake-pool", "thiserror", diff --git a/docker-compose.yaml b/docker-compose.yaml index d6ae3273..9ff0e144 100644 --- a/docker-compose.yaml +++ b/docker-compose.yaml @@ -35,4 +35,34 @@ services: - GOSSIP_ENTRYPOINT=${GOSSIP_ENTRYPOINT} volumes: - ./credentials:/credentials - restart: on-failure:5 \ No newline at end of file + restart: on-failure:5 + + metrics-only: + build: + context: . + target: validator-history + container_name: metrics-only + environment: + - RUST_LOG=${RUST_LOG:-info} + - SOLANA_METRICS_CONFIG=${SOLANA_METRICS_CONFIG} + - JSON_RPC_URL=${JSON_RPC_URL} + - CLUSTER=${CLUSTER} + - KEYPAIR=${KEYPAIR} + - VALIDATOR_HISTORY_PROGRAM_ID=${VALIDATOR_HISTORY_PROGRAM_ID} + - TIP_DISTRIBUTION_PROGRAM_ID=${TIP_DISTRIBUTION_PROGRAM_ID} + - STEWARD_PROGRAM_ID=${STEWARD_PROGRAM_ID} + - STEWARD_CONFIG=${STEWARD_CONFIG} + - METRICS_INTERVAL=${METRICS_INTERVAL} + - STEWARD_INTERVAL=1000000000000 + - VALIDATOR_HISTORY_INTERVAL=1000000000000 + - RUN_CLUSTER_HISTORY=false + - RUN_COPY_VOTE_ACCOUNTS=false + - RUN_MEV_COMMISSION=false + - RUN_MEV_EARNED=false + - RUN_STEWARD=false + - RUN_STAKE_UPLOAD=false + - RUN_GOSSIP_UPLOAD=false + - RUN_EMIT_METRICS=true + volumes: + - ./credentials:/credentials + restart: on-failure:5 diff --git a/keepers/validator-keeper/src/entries/crank_steward.rs b/keepers/validator-keeper/src/entries/crank_steward.rs index 9c37cc18..ef0d8194 100644 --- a/keepers/validator-keeper/src/entries/crank_steward.rs +++ b/keepers/validator-keeper/src/entries/crank_steward.rs @@ -122,31 +122,33 @@ pub fn _get_update_stake_pool_ixs( .get(&validator_info.vote_account_address) .expect("Stake account not found"); - let should_deactivate = if raw_vote_account.is_none() || raw_stake_account.is_none() { - true - } else { - let stake_account = - StakeStateV2::deserialize(&mut raw_stake_account.clone().unwrap().data.as_slice()) - .expect("Could not deserialize stake account"); - - let vote_account = VoteState::deserialize(&raw_vote_account.clone().unwrap().data) - .expect("Could not deserialize vote account"); - - let latest_epoch = vote_account.epoch_credits.iter().last().unwrap().0; - - match stake_account { - StakeStateV2::Stake(_meta, stake, _stake_flags) => { - if stake.delegation.deactivation_epoch != std::u64::MAX { + let should_deactivate = match (raw_vote_account, raw_stake_account) { + (None, Some(_)) => true, + (Some(raw_vote_account), Some(raw_stake_account)) => { + let stake_account = + StakeStateV2::deserialize(&mut raw_stake_account.data.as_slice()) + .expect("Could not deserialize stake account"); + + let vote_account = VoteState::deserialize(&raw_vote_account.data) + .expect("Could not deserialize vote account"); + + let latest_epoch = vote_account.epoch_credits.iter().last().unwrap().0; + + match stake_account { + StakeStateV2::Stake(_meta, stake, _stake_flags) => { + if stake.delegation.deactivation_epoch != std::u64::MAX { + false + } else { + latest_epoch <= epoch - 5 + } + } + _ => { + println!("🔶 Error: Stake account is not StakeStateV2::Stake"); false - } else { - latest_epoch <= epoch - 5 } } - _ => { - println!("🔶 Error: Stake account is not StakeStateV2::Stake"); - false - } } + (_, None) => false, }; if should_deactivate { @@ -217,6 +219,13 @@ async fn _update_pool( // TODO fix println!("Deactivating Delinquent"); + // for ix in deactivate_delinquent_ixs { + // let tx = Transaction::new_signed_with_payer(&[ix], Some(&payer.pubkey()), &[&payer]); + // let tx = client + // .send_and_confirm_transaction_with_spinner_and_config(&tx) + // .await?; + // stats.add_tx(&tx); + // } let deactivate_txs_to_run = package_instructions( &deactivate_delinquent_ixs, 1, @@ -493,11 +502,7 @@ async fn _handle_delinquent_validators( let bad_vote_accounts = checks .iter() .filter_map(|(vote_account, check)| { - if !check.has_history - || !check.has_stake_account - || check.is_deactivated - || !check.has_vote_account - { + if !check.has_history || check.is_deactivated || !check.has_vote_account { Some(*vote_account) } else { None diff --git a/keepers/validator-keeper/src/main.rs b/keepers/validator-keeper/src/main.rs index 84b2db11..5bb4a2b8 100644 --- a/keepers/validator-keeper/src/main.rs +++ b/keepers/validator-keeper/src/main.rs @@ -249,7 +249,6 @@ async fn run_keeper(keeper_config: KeeperConfig) { // ---------------------- EMIT --------------------------------- if should_fire(tick, metrics_interval) { - info!("Emitting metrics..."); keeper_state.set_runs_errors_and_txs_for_epoch(operations::metrics_emit::fire( &keeper_config, &keeper_state, @@ -257,6 +256,7 @@ async fn run_keeper(keeper_config: KeeperConfig) { } if should_emit(tick, &intervals) { + info!("Emitting metrics..."); keeper_state.emit(); KeeperOperations::emit( @@ -332,6 +332,8 @@ async fn main() { no_pack: args.no_pack, pay_for_new_accounts: args.pay_for_new_accounts, cool_down_range: args.cool_down_range, + tx_retry_count: args.tx_retry_count, + tx_confirmation_seconds: args.tx_confirmation_seconds, }; run_keeper(config).await; diff --git a/keepers/validator-keeper/src/operations/cluster_history.rs b/keepers/validator-keeper/src/operations/cluster_history.rs index dc4dfe21..e7dde1b6 100644 --- a/keepers/validator-keeper/src/operations/cluster_history.rs +++ b/keepers/validator-keeper/src/operations/cluster_history.rs @@ -40,8 +40,18 @@ async fn _process( keypair: &Arc, program_id: &Pubkey, priority_fee_in_microlamports: u64, + retry_count: u16, + confirmation_time: u64, ) -> Result { - update_cluster_info(client, keypair, program_id, priority_fee_in_microlamports).await + update_cluster_info( + client, + keypair, + program_id, + priority_fee_in_microlamports, + retry_count, + confirmation_time, + ) + .await } pub async fn fire( @@ -52,6 +62,8 @@ pub async fn fire( let keypair = &keeper_config.keypair; let program_id = &keeper_config.validator_history_program_id; let priority_fee_in_microlamports = keeper_config.priority_fee_in_microlamports; + let retry_count = keeper_config.tx_retry_count; + let confirmation_time = keeper_config.tx_confirmation_seconds; let operation = _get_operation(); let epoch_info = &keeper_state.epoch_info; @@ -63,7 +75,16 @@ pub async fn fire( _should_run(epoch_info, runs_for_epoch) && check_flag(keeper_config.run_flags, operation); if should_run { - match _process(client, keypair, program_id, priority_fee_in_microlamports).await { + match _process( + client, + keypair, + program_id, + priority_fee_in_microlamports, + retry_count, + confirmation_time, + ) + .await + { Ok(stats) => { for message in stats.results.iter() { if let Err(e) = message { @@ -126,6 +147,8 @@ pub async fn update_cluster_info( keypair: &Arc, program_id: &Pubkey, priority_fee_in_microlamports: u64, + retry_count: u16, + confirmation_time: u64, ) -> Result { let ixs = get_update_cluster_info_instructions( program_id, @@ -133,5 +156,5 @@ pub async fn update_cluster_info( priority_fee_in_microlamports, ); - submit_transactions(client, vec![ixs], keypair).await + submit_transactions(client, vec![ixs], keypair, retry_count, confirmation_time).await } diff --git a/keepers/validator-keeper/src/operations/gossip_upload.rs b/keepers/validator-keeper/src/operations/gossip_upload.rs index c8466d06..96e56c0d 100644 --- a/keepers/validator-keeper/src/operations/gossip_upload.rs +++ b/keepers/validator-keeper/src/operations/gossip_upload.rs @@ -45,6 +45,7 @@ fn _should_run(epoch_info: &EpochInfo, runs_for_epoch: u64) -> bool { || (epoch_info.slot_index > epoch_info.slots_in_epoch * 9 / 10 && runs_for_epoch < 3) } +#[allow(clippy::too_many_arguments)] async fn _process( client: &Arc, keypair: &Arc, @@ -52,6 +53,8 @@ async fn _process( priority_fee_in_microlamports: u64, entrypoint: &SocketAddr, keeper_state: &KeeperState, + retry_count: u16, + confirmation_time: u64, ) -> Result> { upload_gossip_values( client, @@ -60,6 +63,8 @@ async fn _process( priority_fee_in_microlamports, entrypoint, keeper_state, + retry_count, + confirmation_time, ) .await } @@ -76,6 +81,8 @@ pub async fn fire( .expect("Entry point not set"); let priority_fee_in_microlamports = keeper_config.priority_fee_in_microlamports; + let retry_count = keeper_config.tx_retry_count; + let confirmation_time = keeper_config.tx_confirmation_seconds; let operation = _get_operation(); let (mut runs_for_epoch, mut errors_for_epoch, mut txs_for_epoch) = @@ -92,6 +99,8 @@ pub async fn fire( priority_fee_in_microlamports, entrypoint, keeper_state, + retry_count, + confirmation_time, ) .await { @@ -248,6 +257,7 @@ fn build_gossip_entry( } } +#[allow(clippy::too_many_arguments)] pub async fn upload_gossip_values( client: &Arc, keypair: &Arc, @@ -255,6 +265,8 @@ pub async fn upload_gossip_values( priority_fee_in_microlamports: u64, entrypoint: &SocketAddr, keeper_state: &KeeperState, + retry_count: u16, + confirmation_time: u64, ) -> Result> { let vote_accounts = keeper_state.vote_account_map.values().collect::>(); let validator_history_map = &keeper_state.validator_history_map; @@ -314,7 +326,14 @@ pub async fn upload_gossip_values( .map(|entry| entry.build_update_tx(priority_fee_in_microlamports)) .collect::>(); - let submit_result = submit_transactions(client, update_transactions, keypair).await; + let submit_result = submit_transactions( + client, + update_transactions, + keypair, + retry_count, + confirmation_time, + ) + .await; submit_result.map_err(|e| e.into()) } diff --git a/keepers/validator-keeper/src/operations/metrics_emit.rs b/keepers/validator-keeper/src/operations/metrics_emit.rs index da09fbc1..ef162e4d 100644 --- a/keepers/validator-keeper/src/operations/metrics_emit.rs +++ b/keepers/validator-keeper/src/operations/metrics_emit.rs @@ -115,8 +115,8 @@ pub fn emit_validator_history_metrics( let mut cluster_history_blocks: i64 = 0; let cluster_history_entry = cluster_history.history.last(); if let Some(cluster_history) = cluster_history_entry { - // Looking for previous epoch to be updated - if cluster_history.epoch as u64 == epoch_info.epoch - 1 { + // Looking for current epoch to be updated, implies previous is complete as well + if cluster_history.epoch as u64 == epoch_info.epoch { cluster_history_blocks = 1; } } diff --git a/keepers/validator-keeper/src/operations/mev_commission.rs b/keepers/validator-keeper/src/operations/mev_commission.rs index 6af16494..f9f3bf03 100644 --- a/keepers/validator-keeper/src/operations/mev_commission.rs +++ b/keepers/validator-keeper/src/operations/mev_commission.rs @@ -32,12 +32,15 @@ fn _should_run() -> bool { true } +#[allow(clippy::too_many_arguments)] async fn _process( client: &Arc, keypair: &Arc, program_id: &Pubkey, tip_distribution_program_id: &Pubkey, keeper_state: &KeeperState, + retry_count: u16, + confirmation_time: u64, priority_fee_in_microlamports: u64, no_pack: bool, ) -> Result { @@ -47,6 +50,8 @@ async fn _process( program_id, tip_distribution_program_id, keeper_state, + retry_count, + confirmation_time, priority_fee_in_microlamports, no_pack, ) @@ -76,6 +81,8 @@ pub async fn fire( program_id, tip_distribution_program_id, keeper_state, + keeper_config.tx_retry_count, + keeper_config.tx_confirmation_seconds, priority_fee_in_microlamports, keeper_config.no_pack, ) @@ -106,12 +113,15 @@ pub async fn fire( // ----------------- OPERATION SPECIFIC FUNCTIONS ----------------- +#[allow(clippy::too_many_arguments)] pub async fn update_mev_commission( client: &Arc, keypair: &Arc, program_id: &Pubkey, tip_distribution_program_id: &Pubkey, keeper_state: &KeeperState, + retry_count: u16, + confirmation_time: u64, priority_fee_in_microlamports: u64, no_pack: bool, ) -> Result { @@ -148,6 +158,8 @@ pub async fn update_mev_commission( update_instructions, keypair, priority_fee_in_microlamports, + retry_count, + confirmation_time, None, no_pack, ) diff --git a/keepers/validator-keeper/src/operations/mev_earned.rs b/keepers/validator-keeper/src/operations/mev_earned.rs index 0773c23e..59cf6bba 100644 --- a/keepers/validator-keeper/src/operations/mev_earned.rs +++ b/keepers/validator-keeper/src/operations/mev_earned.rs @@ -34,12 +34,15 @@ fn _should_run() -> bool { true } +#[allow(clippy::too_many_arguments)] async fn _process( client: &Arc, keypair: &Arc, program_id: &Pubkey, tip_distribution_program_id: &Pubkey, priority_fee_in_microlamports: u64, + retry_count: u16, + confirmation_time: u64, keeper_state: &KeeperState, no_pack: bool, ) -> Result { @@ -48,6 +51,8 @@ async fn _process( keypair, program_id, priority_fee_in_microlamports, + retry_count, + confirmation_time, tip_distribution_program_id, keeper_state, no_pack, @@ -78,6 +83,8 @@ pub async fn fire( program_id, tip_distribution_program_id, priority_fee_in_microlamports, + keeper_config.tx_retry_count, + keeper_config.tx_confirmation_seconds, keeper_state, keeper_config.no_pack, ) @@ -108,11 +115,14 @@ pub async fn fire( // ----------------- OPERATION SPECIFIC FUNCTIONS ----------------- +#[allow(clippy::too_many_arguments)] pub async fn update_mev_earned( client: &Arc, keypair: &Arc, program_id: &Pubkey, priority_fee_in_microlamports: u64, + retry_count: u16, + confirmation_time: u64, tip_distribution_program_id: &Pubkey, keeper_state: &KeeperState, no_pack: bool, @@ -165,6 +175,8 @@ pub async fn update_mev_earned( update_instructions, keypair, priority_fee_in_microlamports, + retry_count, + confirmation_time, None, no_pack, ) diff --git a/keepers/validator-keeper/src/operations/stake_upload.rs b/keepers/validator-keeper/src/operations/stake_upload.rs index a4a2bf40..7410129a 100644 --- a/keepers/validator-keeper/src/operations/stake_upload.rs +++ b/keepers/validator-keeper/src/operations/stake_upload.rs @@ -34,11 +34,14 @@ fn _should_run(epoch_info: &EpochInfo, runs_for_epoch: u64) -> bool { || (epoch_info.slot_index > epoch_info.slots_in_epoch * 9 / 10 && runs_for_epoch < 3) } +#[allow(clippy::too_many_arguments)] async fn _process( client: &Arc, keypair: &Arc, program_id: &Pubkey, priority_fee_in_microlamports: u64, + retry_count: u16, + confirmation_time: u64, keeper_state: &KeeperState, no_pack: bool, ) -> Result { @@ -47,6 +50,8 @@ async fn _process( keypair, program_id, priority_fee_in_microlamports, + retry_count, + confirmation_time, keeper_state, no_pack, ) @@ -61,6 +66,8 @@ pub async fn fire( let keypair = &keeper_config.keypair; let program_id = &keeper_config.validator_history_program_id; let priority_fee_in_microlamports = keeper_config.priority_fee_in_microlamports; + let retry_count = keeper_config.tx_retry_count; + let confirmation_time = keeper_config.tx_confirmation_seconds; let operation = _get_operation(); let (mut runs_for_epoch, mut errors_for_epoch, mut txs_for_epoch) = @@ -75,6 +82,8 @@ pub async fn fire( keypair, program_id, priority_fee_in_microlamports, + retry_count, + confirmation_time, keeper_state, keeper_config.no_pack, ) @@ -105,11 +114,14 @@ pub async fn fire( // ----------------- OPERATION SPECIFIC FUNCTIONS ----------------- +#[allow(clippy::too_many_arguments)] pub async fn update_stake_history( client: &Arc, keypair: &Arc, program_id: &Pubkey, priority_fee_in_microlamports: u64, + retry_count: u16, + confirmation_time: u64, keeper_state: &KeeperState, no_pack: bool, ) -> Result { @@ -164,6 +176,8 @@ pub async fn update_stake_history( update_instructions, keypair, priority_fee_in_microlamports, + retry_count, + confirmation_time, None, no_pack, ) diff --git a/keepers/validator-keeper/src/operations/vote_account.rs b/keepers/validator-keeper/src/operations/vote_account.rs index 2ea4fe75..2a665d34 100644 --- a/keepers/validator-keeper/src/operations/vote_account.rs +++ b/keepers/validator-keeper/src/operations/vote_account.rs @@ -36,11 +36,14 @@ fn _should_run(epoch_info: &EpochInfo, runs_for_epoch: u64) -> bool { || (epoch_info.slot_index > epoch_info.slots_in_epoch * 9 / 10 && runs_for_epoch < 3) } +#[allow(clippy::too_many_arguments)] async fn _process( client: &Arc, keypair: &Arc, program_id: &Pubkey, priority_fee_in_microlamports: u64, + retry_count: u16, + confirmation_time: u64, keeper_state: &KeeperState, no_pack: bool, ) -> Result { @@ -49,6 +52,8 @@ async fn _process( keypair, program_id, priority_fee_in_microlamports, + retry_count, + confirmation_time, keeper_state, no_pack, ) @@ -63,6 +68,8 @@ pub async fn fire( let keypair = &keeper_config.keypair; let program_id = &keeper_config.validator_history_program_id; let priority_fee_in_microlamports = keeper_config.priority_fee_in_microlamports; + let retry_count = keeper_config.tx_retry_count; + let confirmation_time = keeper_config.tx_confirmation_seconds; let operation = _get_operation(); let epoch_info = &keeper_state.epoch_info; @@ -82,6 +89,8 @@ pub async fn fire( keypair, program_id, priority_fee_in_microlamports, + retry_count, + confirmation_time, keeper_state, keeper_config.no_pack, ) @@ -116,11 +125,14 @@ pub async fn fire( } // SPECIFIC TO THIS OPERATION +#[allow(clippy::too_many_arguments)] pub async fn update_vote_accounts( rpc_client: &Arc, keypair: &Arc, program_id: &Pubkey, priority_fee_in_microlamports: u64, + retry_count: u16, + confirmation_time: u64, keeper_state: &KeeperState, no_pack: bool, ) -> Result { @@ -157,6 +169,8 @@ pub async fn update_vote_accounts( update_instructions, keypair, priority_fee_in_microlamports, + retry_count, + confirmation_time, Some(300_000), no_pack, ) diff --git a/keepers/validator-keeper/src/state/keeper_config.rs b/keepers/validator-keeper/src/state/keeper_config.rs index 8f41f367..022ef25e 100644 --- a/keepers/validator-keeper/src/state/keeper_config.rs +++ b/keepers/validator-keeper/src/state/keeper_config.rs @@ -14,6 +14,8 @@ pub struct KeeperConfig { pub steward_program_id: Pubkey, pub steward_config: Pubkey, pub priority_fee_in_microlamports: u64, + pub tx_retry_count: u16, + pub tx_confirmation_seconds: u64, pub oracle_authority_keypair: Option>, pub gossip_entrypoint: Option, pub validator_history_interval: u64, @@ -94,6 +96,12 @@ pub struct Args { #[arg(long, env, default_value = "20000")] pub priority_fees: u64, + #[arg(long, env, default_value = "50")] + pub tx_retry_count: u16, + + #[arg(long, env, default_value = "30")] + pub tx_confirmation_seconds: u64, + /// Cluster to specify #[arg(long, env, default_value_t = Cluster::Mainnet)] pub cluster: Cluster, @@ -167,6 +175,8 @@ impl fmt::Display for Args { Steward Interval: {} seconds\n\ Metrics Interval: {} seconds\n\ Priority Fees: {} microlamports\n\ + Retry Count: {}\n\ + Confirmation Seconds: {}\n\ Cluster: {:?}\n\ Run Cluster History: {}\n\ Run Copy Vote Accounts: {}\n\ @@ -193,6 +203,8 @@ impl fmt::Display for Args { self.steward_interval, self.metrics_interval, self.priority_fees, + self.tx_retry_count, + self.tx_confirmation_seconds, self.cluster, self.run_cluster_history, self.run_copy_vote_accounts, diff --git a/keepers/validator-keeper/src/state/update_state.rs b/keepers/validator-keeper/src/state/update_state.rs index 1a8fe9d0..bcd22cff 100644 --- a/keepers/validator-keeper/src/state/update_state.rs +++ b/keepers/validator-keeper/src/state/update_state.rs @@ -81,9 +81,15 @@ pub async fn create_missing_accounts( let mut created_accounts_for_epoch = vec![]; // Create Missing Accounts - let new_validator_history_accounts = - create_missing_validator_history_accounts(client, keypair, program_id, keeper_state) - .await?; + let new_validator_history_accounts = create_missing_validator_history_accounts( + client, + keypair, + program_id, + keeper_state, + keeper_config.tx_retry_count, + keeper_config.tx_confirmation_seconds, + ) + .await?; created_accounts_for_epoch.push(( KeeperCreates::CreateValidatorHistory, new_validator_history_accounts, @@ -286,6 +292,8 @@ async fn create_missing_validator_history_accounts( keypair: &Arc, program_id: &Pubkey, keeper_state: &KeeperState, + retry_count: u16, + confirmation_time: u64, ) -> Result> { let vote_accounts = &keeper_state .vote_account_map @@ -319,7 +327,14 @@ async fn create_missing_validator_history_accounts( let accounts_created = create_transactions.len(); - submit_transactions(client, create_transactions, keypair).await?; + submit_transactions( + client, + create_transactions, + keypair, + retry_count, + confirmation_time, + ) + .await?; Ok(accounts_created) } diff --git a/programs/steward/idl/steward.json b/programs/steward/idl/steward.json index 96aa2cc7..e1d711c1 100644 --- a/programs/steward/idl/steward.json +++ b/programs/steward/idl/steward.json @@ -1270,6 +1270,36 @@ ], "args": [] }, + { + "name": "reset_validator_lamport_balances", + "docs": [ + "Reset validator_lamport_balances to default" + ], + "discriminator": [ + 69, + 111, + 124, + 69, + 69, + 29, + 96, + 181 + ], + "accounts": [ + { + "name": "steward_state", + "writable": true + }, + { + "name": "config" + }, + { + "name": "authority", + "signer": true + } + ], + "args": [] + }, { "name": "resume_steward", "docs": [ diff --git a/programs/steward/src/constants.rs b/programs/steward/src/constants.rs index e79bf67b..05b91b74 100644 --- a/programs/steward/src/constants.rs +++ b/programs/steward/src/constants.rs @@ -8,6 +8,7 @@ pub const MAX_VALIDATORS: usize = 5_000; pub const BASIS_POINTS_MAX: u16 = 10_000; pub const COMMISSION_MAX: u8 = 100; pub const SORTED_INDEX_DEFAULT: u16 = u16::MAX; +pub const LAMPORT_BALANCE_DEFAULT: u64 = u64::MAX; // Need at least 1% of slots remaining (4320 slots) to execute steps in state machine pub const EPOCH_PROGRESS_MAX: f64 = 0.99; // Cannot go more than 100 epochs without scoring diff --git a/programs/steward/src/delegation.rs b/programs/steward/src/delegation.rs index e5786d4f..27fa58d4 100644 --- a/programs/steward/src/delegation.rs +++ b/programs/steward/src/delegation.rs @@ -1,6 +1,7 @@ use anchor_lang::prelude::*; use spl_stake_pool::big_vec::BigVec; +use crate::constants::LAMPORT_BALANCE_DEFAULT; use crate::events::DecreaseComponents; use crate::{ errors::StewardError, @@ -22,10 +23,12 @@ pub enum RebalanceType { /// /// Unstaking is calculated this way because stake account balances can change at any time from users' stake withdrawals and deposits, /// and this ensures that unstaking is done fairly at the time of the rebalance. In addition, these instructions can run in any order. +#[allow(clippy::too_many_arguments)] pub fn decrease_stake_calculation( state: &StewardState, target_index: usize, mut unstake_state: UnstakeState, + current_lamports: u64, // active lamports in target stake account adjusted for minimum delegation stake_pool_lamports: u64, validator_list: &BigVec<'_>, minimum_delegation: u64, @@ -56,6 +59,10 @@ pub fn decrease_stake_calculation( // ValidatorList includes base lamports in active_stake_lamports temp_current_lamports = temp_current_lamports.saturating_sub(base_lamport_balance); + if temp_index == target_index { + temp_current_lamports = current_lamports; + } + // For the current `temp` validator, calculate how much we can remove and what category it's coming from let unstake_amounts = if !some_transient_stake && temp_target_lamports < temp_current_lamports { @@ -245,6 +252,7 @@ impl UnstakeState { // either to the target or to the previous balance before the deposit, whichever is lower in terms of total lamports unstaked if current_lamports > state.validator_lamport_balances[index] && self.stake_deposit_unstake_total < self.stake_deposit_unstake_cap + && state.validator_lamport_balances[index] != LAMPORT_BALANCE_DEFAULT { let lamports_above_target = current_lamports .checked_sub(target_lamports) diff --git a/programs/steward/src/instructions/mod.rs b/programs/steward/src/instructions/mod.rs index bf719923..7deb899c 100644 --- a/programs/steward/src/instructions/mod.rs +++ b/programs/steward/src/instructions/mod.rs @@ -16,6 +16,7 @@ pub mod realloc_state; pub mod rebalance; pub mod remove_validator_from_blacklist; pub mod reset_steward_state; +pub mod reset_validator_lamport_balances; pub mod resume_steward; pub mod set_new_authority; pub mod spl_passthrough; @@ -38,6 +39,7 @@ pub use realloc_state::*; pub use rebalance::*; pub use remove_validator_from_blacklist::*; pub use reset_steward_state::*; +pub use reset_validator_lamport_balances::*; pub use resume_steward::*; pub use set_new_authority::*; pub use spl_passthrough::*; diff --git a/programs/steward/src/instructions/realloc_state.rs b/programs/steward/src/instructions/realloc_state.rs index ef9adef3..fd30f0c6 100644 --- a/programs/steward/src/instructions/realloc_state.rs +++ b/programs/steward/src/instructions/realloc_state.rs @@ -1,6 +1,6 @@ use crate::{ bitmask::BitMask, - constants::{MAX_ALLOC_BYTES, MAX_VALIDATORS, SORTED_INDEX_DEFAULT}, + constants::{LAMPORT_BALANCE_DEFAULT, MAX_ALLOC_BYTES, MAX_VALIDATORS, SORTED_INDEX_DEFAULT}, errors::StewardError, state::{Config, StewardStateAccount}, utils::get_validator_list, @@ -74,6 +74,7 @@ pub fn handler(ctx: Context) -> Result<()> { state_account.state.state_tag = StewardStateEnum::ComputeScores; state_account.state.num_pool_validators = validator_list.len() as u64; + state_account.state.validator_lamport_balances = [LAMPORT_BALANCE_DEFAULT; MAX_VALIDATORS]; state_account.state.scores = [0; MAX_VALIDATORS]; state_account.state.sorted_score_indices = [SORTED_INDEX_DEFAULT; MAX_VALIDATORS]; state_account.state.yield_scores = [0; MAX_VALIDATORS]; diff --git a/programs/steward/src/instructions/reset_steward_state.rs b/programs/steward/src/instructions/reset_steward_state.rs index 0588b014..8d318e8b 100644 --- a/programs/steward/src/instructions/reset_steward_state.rs +++ b/programs/steward/src/instructions/reset_steward_state.rs @@ -1,5 +1,5 @@ use crate::{ - constants::{MAX_VALIDATORS, SORTED_INDEX_DEFAULT}, + constants::{LAMPORT_BALANCE_DEFAULT, MAX_VALIDATORS, SORTED_INDEX_DEFAULT}, errors::StewardError, state::{Config, StewardStateAccount}, utils::{deserialize_stake_pool, get_config_admin, get_stake_pool_address}, @@ -47,6 +47,7 @@ pub fn handler(ctx: Context) -> Result<()> { state_account.state.state_tag = StewardStateEnum::ComputeScores; state_account.state.num_pool_validators = validator_list.len() as u64; + state_account.state.validator_lamport_balances = [LAMPORT_BALANCE_DEFAULT; MAX_VALIDATORS]; state_account.state.scores = [0; MAX_VALIDATORS]; state_account.state.sorted_score_indices = [SORTED_INDEX_DEFAULT; MAX_VALIDATORS]; state_account.state.yield_scores = [0; MAX_VALIDATORS]; diff --git a/programs/steward/src/instructions/reset_validator_lamport_balances.rs b/programs/steward/src/instructions/reset_validator_lamport_balances.rs new file mode 100644 index 00000000..ae77b62f --- /dev/null +++ b/programs/steward/src/instructions/reset_validator_lamport_balances.rs @@ -0,0 +1,29 @@ +use crate::{ + constants::{LAMPORT_BALANCE_DEFAULT, MAX_VALIDATORS}, + state::*, + utils::get_config_admin, +}; +use anchor_lang::prelude::*; + +#[derive(Accounts)] +pub struct ResetValidatorLamportBalances<'info> { + #[account( + mut, + seeds = [StewardStateAccount::SEED, config.key().as_ref()], + bump + )] + pub steward_state: AccountLoader<'info, StewardStateAccount>, + + pub config: AccountLoader<'info, Config>, + + #[account(address = get_config_admin(&config)?)] + pub authority: Signer<'info>, +} + +pub fn handler(ctx: Context) -> Result<()> { + let state_account = &mut ctx.accounts.steward_state.load_mut()?; + + state_account.state.validator_lamport_balances = [LAMPORT_BALANCE_DEFAULT; MAX_VALIDATORS]; + + Ok(()) +} diff --git a/programs/steward/src/instructions/spl_passthrough.rs b/programs/steward/src/instructions/spl_passthrough.rs index b9b2bfaa..2f7d315c 100644 --- a/programs/steward/src/instructions/spl_passthrough.rs +++ b/programs/steward/src/instructions/spl_passthrough.rs @@ -4,7 +4,7 @@ // is that the config, stake pool address, staker, signer, and sometimes state account match up. // Otherwise these instructions are intented to be minimally restrictive. -use crate::constants::MAX_VALIDATORS; +use crate::constants::{LAMPORT_BALANCE_DEFAULT, MAX_VALIDATORS}; use crate::errors::StewardError; use crate::state::Config; use crate::utils::{ @@ -400,9 +400,11 @@ pub fn increase_validator_stake_handler( .ok_or(StewardError::ValidatorIndexOutOfBounds)?; // Set the balance - *balance = balance - .checked_add(lamports) - .ok_or(StewardError::ArithmeticError)?; + if *balance != LAMPORT_BALANCE_DEFAULT { + *balance = balance + .checked_add(lamports) + .ok_or(StewardError::ArithmeticError)?; + } } invoke_signed( @@ -521,9 +523,11 @@ pub fn decrease_validator_stake_handler( .ok_or(StewardError::ValidatorIndexOutOfBounds)?; // Set the balance - *balance = balance - .checked_sub(lamports) - .ok_or(StewardError::ArithmeticError)?; + if *balance != LAMPORT_BALANCE_DEFAULT { + *balance = balance + .checked_sub(lamports) + .ok_or(StewardError::ArithmeticError)?; + } } invoke_signed( @@ -641,9 +645,11 @@ pub fn increase_additional_validator_stake_handler( .ok_or(StewardError::ValidatorIndexOutOfBounds)?; // Set the balance - *balance = balance - .checked_add(lamports) - .ok_or(StewardError::ArithmeticError)?; + if *balance != LAMPORT_BALANCE_DEFAULT { + *balance = balance + .checked_add(lamports) + .ok_or(StewardError::ArithmeticError)?; + } } invoke_signed( @@ -765,9 +771,11 @@ pub fn decrease_additional_validator_stake_handler( .ok_or(StewardError::ValidatorIndexOutOfBounds)?; // Set the balance - *balance = balance - .checked_sub(lamports) - .ok_or(StewardError::ArithmeticError)?; + if *balance != LAMPORT_BALANCE_DEFAULT { + *balance = balance + .checked_sub(lamports) + .ok_or(StewardError::ArithmeticError)?; + } } invoke_signed( diff --git a/programs/steward/src/lib.rs b/programs/steward/src/lib.rs index 9eda916a..5217ba22 100644 --- a/programs/steward/src/lib.rs +++ b/programs/steward/src/lib.rs @@ -207,6 +207,13 @@ pub mod steward { ) } + /// Reset validator_lamport_balances to default + pub fn reset_validator_lamport_balances( + ctx: Context, + ) -> Result<()> { + instructions::reset_validator_lamport_balances::handler(ctx) + } + /// Closes Steward PDA accounts associated with a given Config (StewardStateAccount, and Staker). /// Config is not closed as it is a Keypair, so lamports can simply be withdrawn. /// Reclaims lamports to authority diff --git a/programs/steward/src/state/steward_state.rs b/programs/steward/src/state/steward_state.rs index a13a3c35..62b9e59d 100644 --- a/programs/steward/src/state/steward_state.rs +++ b/programs/steward/src/state/steward_state.rs @@ -3,7 +3,7 @@ use std::fmt::Display; use crate::{ bitmask::BitMask, - constants::{MAX_VALIDATORS, SORTED_INDEX_DEFAULT}, + constants::{LAMPORT_BALANCE_DEFAULT, MAX_VALIDATORS, SORTED_INDEX_DEFAULT}, delegation::{ decrease_stake_calculation, increase_stake_calculation, RebalanceType, UnstakeState, }, @@ -859,12 +859,6 @@ impl StewardState { .checked_add(stake_rent) .ok_or(StewardError::ArithmeticError)?; - msg!("Reserve lamports before adjustment: {}", reserve_lamports); - msg!( - "Stake pool lamports before adjustment: {}", - stake_pool_lamports - ); - // Maximum increase amount is the total lamports in the reserve stake account minus (num_validators + 1) * stake_rent, which covers rent for all validators plus the transient rent let all_accounts_needed_reserve_for_rent = validator_list .len() @@ -907,6 +901,11 @@ impl StewardState { In all cases where the current_lamports is now below the target or internal balance, we update the internal balance. Otherwise, keep the internal balance the same to ensure we still see the stake deposit delta, until it can be unstaked. */ + + if self.validator_lamport_balances[index] == LAMPORT_BALANCE_DEFAULT { + self.validator_lamport_balances[index] = current_lamports; + } + self.validator_lamport_balances[index] = match ( current_lamports < self.validator_lamport_balances[index], current_lamports < target_lamports, @@ -947,6 +946,7 @@ impl StewardState { self, index, unstake_state, + current_lamports, stake_pool_lamports, validator_list, minimum_delegation, @@ -967,15 +967,6 @@ impl StewardState { RebalanceType::None }; - msg!("Reserve lamports after adjustment: {}", reserve_lamports); - msg!( - "Stake pool lamports after adjustment: {}", - stake_pool_lamports - ); - msg!("Rebalance Type: {:?}", rebalance); - msg!("Current Lamports: {}", current_lamports); - msg!("Target Lamports: {}", target_lamports); - // Update internal state based on rebalance match rebalance { RebalanceType::Decrease(DecreaseComponents { @@ -984,8 +975,11 @@ impl StewardState { stake_deposit_unstake_lamports, total_unstake_lamports, }) => { - self.validator_lamport_balances[index] = self.validator_lamport_balances[index] - .saturating_sub(total_unstake_lamports); + if self.validator_lamport_balances[index] != LAMPORT_BALANCE_DEFAULT { + self.validator_lamport_balances[index] = self.validator_lamport_balances + [index] + .saturating_sub(total_unstake_lamports); + } self.scoring_unstake_total = self .scoring_unstake_total @@ -1026,9 +1020,12 @@ impl StewardState { } } RebalanceType::Increase(amount) => { - self.validator_lamport_balances[index] = self.validator_lamport_balances[index] - .checked_add(amount) - .ok_or(StewardError::ArithmeticError)?; + if self.validator_lamport_balances[index] != LAMPORT_BALANCE_DEFAULT { + self.validator_lamport_balances[index] = self.validator_lamport_balances + [index] + .checked_add(amount) + .ok_or(StewardError::ArithmeticError)?; + } } RebalanceType::None => {} } diff --git a/sdk/Cargo.toml b/sdk/Cargo.toml index ca19901f..975197fd 100644 --- a/sdk/Cargo.toml +++ b/sdk/Cargo.toml @@ -23,6 +23,7 @@ solana-client = "1.18" solana-metrics = "1.18" solana-program = "1.18" solana-sdk = "1.18" +solana-transaction-status = "1.18" spl-pod = "0.1.0" spl-stake-pool = { features = ["no-entrypoint"], version = "1.0.0" } thiserror = "1.0.37" diff --git a/sdk/src/utils/transactions.rs b/sdk/src/utils/transactions.rs index fccd3a8c..236bdd29 100644 --- a/sdk/src/utils/transactions.rs +++ b/sdk/src/utils/transactions.rs @@ -4,7 +4,9 @@ use std::vec; use std::{collections::HashMap, sync::Arc, time::Duration}; use log::*; -use solana_client::rpc_response::{Response, RpcSimulateTransactionResult, RpcVoteAccountInfo}; +use solana_client::rpc_response::{ + Response, RpcResult, RpcSimulateTransactionResult, RpcVoteAccountInfo, +}; use solana_client::{client_error::ClientError, nonblocking::rpc_client::RpcClient}; use solana_metrics::datapoint_error; use solana_program::hash::Hash; @@ -17,6 +19,7 @@ use solana_sdk::{ instruction::Instruction, packet::Packet, pubkey::Pubkey, signature::Keypair, signature::Signature, signer::Signer, transaction::Transaction, }; +use solana_transaction_status::TransactionStatus; use tokio::task; use tokio::time::sleep; @@ -197,6 +200,19 @@ pub async fn get_vote_accounts_with_retry( } } +pub async fn get_signature_statuses_with_retry( + client: &RpcClient, + signatures: &[Signature], +) -> RpcResult>> { + for _ in 1..4 { + if let Ok(result) = client.get_signature_statuses(signatures).await { + return Ok(result); + } + tokio::time::sleep(tokio::time::Duration::from_secs(1)).await; + } + client.get_signature_statuses(signatures).await +} + async fn find_ix_per_tx( client: &Arc, instruction: &Instruction, @@ -264,14 +280,17 @@ async fn parallel_confirm_transactions( let confirmation_futures: Vec<_> = signatures_to_confirm .chunks(SIG_STATUS_BATCH_SIZE) .map(|sig_batch| async move { - match client.get_signature_statuses(sig_batch).await { + match get_signature_statuses_with_retry(client, sig_batch).await { Ok(sig_batch_response) => sig_batch_response .value .iter() .enumerate() .map(|(i, sig_status)| (sig_batch[i], sig_status.clone())) .collect::>(), - Err(_) => vec![], + Err(e) => { + info!("Failed getting signature statuses: {}", e); + vec![] + } } }) .collect(); @@ -643,6 +662,8 @@ pub async fn submit_transactions( client: &Arc, transactions: Vec>, keypair: &Arc, + retry_count: u16, + confirmation_time: u64, ) -> Result { let mut stats = SubmitStats::default(); let tx_slice = transactions @@ -650,7 +671,9 @@ pub async fn submit_transactions( .map(|t| t.as_slice()) .collect::>(); - match parallel_execute_transactions(client, &tx_slice, keypair, 100, 20).await { + match parallel_execute_transactions(client, &tx_slice, keypair, retry_count, confirmation_time) + .await + { Ok(results) => { stats.successes = results.iter().filter(|&tx| tx.is_ok()).count() as u64; stats.errors = results.len() as u64 - stats.successes; @@ -661,11 +684,14 @@ pub async fn submit_transactions( } } +#[allow(clippy::too_many_arguments)] pub async fn submit_instructions( client: &Arc, instructions: Vec, keypair: &Arc, priority_fee_in_microlamports: u64, + retry_count: u16, + confirmation_time: u64, max_cu_per_tx: Option, no_pack: bool, ) -> Result { @@ -674,8 +700,8 @@ pub async fn submit_instructions( client, &instructions, keypair, - 100, - 20, + retry_count, + confirmation_time, priority_fee_in_microlamports, max_cu_per_tx, no_pack, diff --git a/tests/tests/steward/test_algorithms.rs b/tests/tests/steward/test_algorithms.rs index 94a1545e..08a0eb34 100644 --- a/tests/tests/steward/test_algorithms.rs +++ b/tests/tests/steward/test_algorithms.rs @@ -557,7 +557,6 @@ fn test_instant_unstake() { start_slot, current_epoch, ); - println!("NEED Error: {:?}", res); assert!(res.is_ok()); assert!( @@ -997,6 +996,7 @@ fn test_decrease_stake_calculation() { &state, 2, unstake_state, + u64::from(validator_list[2].active_stake_lamports), 3000 * LAMPORTS_PER_SOL, &validator_list_bigvec, 0, @@ -1017,6 +1017,7 @@ fn test_decrease_stake_calculation() { &state, 2, unstake_state, + u64::from(validator_list[2].active_stake_lamports), 3000 * LAMPORTS_PER_SOL, &validator_list_bigvec, 0, @@ -1048,6 +1049,7 @@ fn test_decrease_stake_calculation() { &state, 2, unstake_state, + u64::from(validator_list[2].active_stake_lamports), 3000 * LAMPORTS_PER_SOL, &validator_list_bigvec, 0, @@ -1082,6 +1084,7 @@ fn test_decrease_stake_calculation() { &state, 2, unstake_state, + u64::from(validator_list[2].active_stake_lamports), 3000 * LAMPORTS_PER_SOL, &validator_list_bigvec, 0, @@ -1114,6 +1117,7 @@ fn test_decrease_stake_calculation() { &state, 2, unstake_state, + u64::from(validator_list[2].active_stake_lamports), 3000 * LAMPORTS_PER_SOL, &validator_list_bigvec, 0, @@ -1143,6 +1147,7 @@ fn test_decrease_stake_calculation() { &state, 2, unstake_state, + u64::from(validator_list[2].active_stake_lamports), 3000 * LAMPORTS_PER_SOL, &validator_list_bigvec, 0, @@ -1184,6 +1189,7 @@ fn test_decrease_stake_calculation() { &state, 2, unstake_state, + u64::from(validator_list[2].active_stake_lamports), 5000 * LAMPORTS_PER_SOL, &validator_list_bigvec, 0, @@ -1206,6 +1212,7 @@ fn test_decrease_stake_calculation() { &state, 2, unstake_state, + u64::from(validator_list[2].active_stake_lamports), 5000 * LAMPORTS_PER_SOL, &validator_list_bigvec, 0, @@ -1235,6 +1242,7 @@ fn test_decrease_stake_calculation() { &state, 2, unstake_state, + u64::from(validator_list[2].active_stake_lamports), 5000 * LAMPORTS_PER_SOL, &validator_list_bigvec, 0, @@ -1253,16 +1261,20 @@ fn test_decrease_stake_calculation() { }); // Test: minimum delegation and stake rent exempt from total lamports unstaked - // Same scenario as above, just leaves minimum_delegation + stake_rent on the validator + // Same scenario as above, ignoring minimum delegation and stake rent on target validator, + // as current_lamports already adjusted for minimum delegation let unstake_state = UnstakeState { stake_deposit_unstake_cap: 2500 * LAMPORTS_PER_SOL, ..Default::default() }; + let current_lamports = u64::from(validator_list[2].active_stake_lamports); + let result = decrease_stake_calculation( &state, 2, unstake_state, + current_lamports, 5000 * LAMPORTS_PER_SOL, &validator_list_bigvec, 100 * LAMPORTS_PER_SOL, @@ -1274,8 +1286,8 @@ fn test_decrease_stake_calculation() { == DecreaseComponents { scoring_unstake_lamports: 0, instant_unstake_lamports: 0, - stake_deposit_unstake_lamports: 850 * LAMPORTS_PER_SOL, - total_unstake_lamports: 850 * LAMPORTS_PER_SOL, + stake_deposit_unstake_lamports: 1000 * LAMPORTS_PER_SOL, + total_unstake_lamports: 1000 * LAMPORTS_PER_SOL, }, _ => false, }); @@ -1300,6 +1312,7 @@ fn test_decrease_stake_calculation() { &state, 2, unstake_state, + u64::from(validator_list[2].active_stake_lamports), 5000 * LAMPORTS_PER_SOL, &validator_list_bigvec, 0, @@ -1333,6 +1346,7 @@ fn test_decrease_stake_calculation() { &state, 0, unstake_state, + u64::from(validator_list[0].active_stake_lamports), 5000 * LAMPORTS_PER_SOL, &validator_list_bigvec, 100 * LAMPORTS_PER_SOL, @@ -1346,6 +1360,7 @@ fn test_decrease_stake_calculation() { &state, 3, UnstakeState::default(), + u64::from(validator_list[2].active_stake_lamports), // validator doesn't exist but include reasonable value 5000 * LAMPORTS_PER_SOL, &validator_list_bigvec, 0, diff --git a/tests/tests/steward/test_spl_passthrough.rs b/tests/tests/steward/test_spl_passthrough.rs index 141bb451..69ab21ee 100644 --- a/tests/tests/steward/test_spl_passthrough.rs +++ b/tests/tests/steward/test_spl_passthrough.rs @@ -5,7 +5,7 @@ use anchor_lang::{ AccountDeserialize, AnchorDeserialize, InstructionData, ToAccountMetas, }; use jito_steward::{ - constants::MAX_VALIDATORS, + constants::{LAMPORT_BALANCE_DEFAULT, MAX_VALIDATORS}, derive_steward_state_address, utils::{StakePool, ValidatorList}, Config, Delegation, StewardStateAccount, StewardStateEnum, @@ -300,7 +300,7 @@ async fn _increase_and_check_stake( let state_account_before: StewardStateAccount = fixture.load_and_deserialize(&fixture.steward_state).await; - let lamports_before_increase = *state_account_before + let _lamports_before_increase = *state_account_before .state .validator_lamport_balances .get(validator_list_index) @@ -354,10 +354,7 @@ async fn _increase_and_check_stake( .get(validator_list_index) .expect("Lamport balance out of bounds"); - assert_eq!( - lamports_after_increase, - lamports_before_increase + lamports_to_stake - ); + assert_eq!(lamports_after_increase, LAMPORT_BALANCE_DEFAULT); } async fn _increase_and_check_additional_stake( @@ -386,7 +383,7 @@ async fn _increase_and_check_additional_stake( let state_account_before: StewardStateAccount = fixture.load_and_deserialize(&fixture.steward_state).await; - let lamports_before_increase = *state_account_before + let _lamports_before_increase = *state_account_before .state .validator_lamport_balances .get(validator_list_index) @@ -447,10 +444,7 @@ async fn _increase_and_check_additional_stake( .get(validator_list_index) .expect("Lamport balance out of bounds"); - assert_eq!( - lamports_after_increase, - lamports_before_increase + lamports_to_stake - ); + assert_eq!(lamports_after_increase, LAMPORT_BALANCE_DEFAULT); } pub async fn _set_staker(fixture: &TestFixture, new_staker: Pubkey) { diff --git a/utils/steward-cli/src/commands/actions/auto_remove_validator_from_pool.rs b/utils/steward-cli/src/commands/actions/auto_remove_validator_from_pool.rs index 6a30bd9b..a0900d42 100644 --- a/utils/steward-cli/src/commands/actions/auto_remove_validator_from_pool.rs +++ b/utils/steward-cli/src/commands/actions/auto_remove_validator_from_pool.rs @@ -3,7 +3,7 @@ use std::sync::Arc; use anchor_lang::{InstructionData, ToAccountMetas}; use anyhow::Result; -use solana_client::nonblocking::rpc_client::RpcClient; +use solana_client::{nonblocking::rpc_client::RpcClient, rpc_config::RpcSendTransactionConfig}; use solana_program::instruction::Instruction; use spl_stake_pool::{find_stake_program_address, find_transient_stake_program_address}; use stakenet_sdk::utils::{ @@ -13,8 +13,8 @@ use stakenet_sdk::utils::{ use validator_history::id as validator_history_id; use solana_sdk::{ - pubkey::Pubkey, signature::read_keypair_file, signer::Signer, stake, system_program, - transaction::Transaction, + commitment_config::CommitmentConfig, pubkey::Pubkey, signature::read_keypair_file, + signer::Signer, stake, system_program, transaction::Transaction, }; use crate::commands::command_args::AutoRemoveValidatorFromPool; @@ -109,7 +109,14 @@ pub async fn command_auto_remove_validator_from_pool( print_base58_tx(&configured_ix) } else { let signature = client - .send_and_confirm_transaction_with_spinner(&transaction) + .send_and_confirm_transaction_with_spinner_and_config( + &transaction, + CommitmentConfig::default(), + RpcSendTransactionConfig { + skip_preflight: true, + ..Default::default() + }, + ) .await?; println!("Signature: {}", signature); diff --git a/utils/steward-cli/src/commands/actions/instant_remove_validator.rs b/utils/steward-cli/src/commands/actions/instant_remove_validator.rs new file mode 100644 index 00000000..8c82e53b --- /dev/null +++ b/utils/steward-cli/src/commands/actions/instant_remove_validator.rs @@ -0,0 +1,87 @@ +use std::sync::Arc; + +use anchor_lang::{InstructionData, ToAccountMetas}; +use anyhow::Result; +use solana_client::{nonblocking::rpc_client::RpcClient, rpc_config::RpcSendTransactionConfig}; +use solana_program::instruction::Instruction; + +use solana_sdk::{ + commitment_config::CommitmentConfig, pubkey::Pubkey, signature::read_keypair_file, + signer::Signer, transaction::Transaction, +}; + +use crate::commands::command_args::InstantRemoveValidator; +use stakenet_sdk::utils::{ + accounts::get_all_steward_accounts, + transactions::{configure_instruction, print_base58_tx}, +}; + +pub async fn command_instant_remove_validator( + args: InstantRemoveValidator, + client: &Arc, + program_id: Pubkey, +) -> Result<()> { + let payer = read_keypair_file(args.permissionless_parameters.payer_keypair_path) + .expect("Failed reading keypair file (Payer)"); + + let steward_config = args.permissionless_parameters.steward_config; + + let steward_accounts = get_all_steward_accounts(client, &program_id, &steward_config).await?; + + let ix = Instruction { + program_id, + accounts: jito_steward::accounts::InstantRemoveValidator { + config: args.permissionless_parameters.steward_config, + state_account: steward_accounts.state_address, + validator_list: steward_accounts.validator_list_address, + stake_pool: steward_accounts.stake_pool_address, + } + .to_account_metas(None), + data: jito_steward::instruction::InstantRemoveValidator { + validator_index_to_remove: args.validator_index_to_remove, + } + .data(), + }; + + let blockhash = client.get_latest_blockhash().await?; + + let configured_ix = configure_instruction( + &[ix], + args.permissionless_parameters + .transaction_parameters + .priority_fee, + args.permissionless_parameters + .transaction_parameters + .compute_limit, + args.permissionless_parameters + .transaction_parameters + .heap_size, + ); + + let transaction = Transaction::new_signed_with_payer( + &configured_ix, + Some(&payer.pubkey()), + &[&payer], + blockhash, + ); + + if args + .permissionless_parameters + .transaction_parameters + .print_tx + { + print_base58_tx(&configured_ix) + } else { + let signature = client + .send_and_confirm_transaction_with_spinner_and_config( + &transaction, + CommitmentConfig::default(), + RpcSendTransactionConfig::default(), + ) + .await?; + + println!("Signature: {}", signature); + } + + Ok(()) +} diff --git a/utils/steward-cli/src/commands/actions/mod.rs b/utils/steward-cli/src/commands/actions/mod.rs index bbb19ece..eebaeee1 100644 --- a/utils/steward-cli/src/commands/actions/mod.rs +++ b/utils/steward-cli/src/commands/actions/mod.rs @@ -2,6 +2,7 @@ pub mod add_to_blacklist; pub mod auto_add_validator_from_pool; pub mod auto_remove_validator_from_pool; pub mod close_steward; +pub mod instant_remove_validator; pub mod manually_copy_all_vote_accounts; pub mod manually_copy_vote_accounts; pub mod manually_remove_validator; @@ -9,8 +10,10 @@ pub mod pause; pub mod remove_bad_validators; pub mod remove_from_blacklist; pub mod reset_state; +pub mod reset_validator_lamport_balances; pub mod resume; pub mod revert_staker; pub mod set_staker; pub mod update_authority; pub mod update_config; +pub mod update_validator_list_balance; diff --git a/utils/steward-cli/src/commands/actions/remove_bad_validators.rs b/utils/steward-cli/src/commands/actions/remove_bad_validators.rs index fb60aec1..6a8fc88c 100644 --- a/utils/steward-cli/src/commands/actions/remove_bad_validators.rs +++ b/utils/steward-cli/src/commands/actions/remove_bad_validators.rs @@ -153,7 +153,7 @@ pub async fn command_remove_bad_validators( } else { println!("Submitting {} instructions", ixs_to_run.len()); - let submit_stats = submit_transactions(client, txs_to_run, &arc_payer).await?; + let submit_stats = submit_transactions(client, txs_to_run, &arc_payer, 20, 30).await?; println!("Submit stats: {:?}", submit_stats); } diff --git a/utils/steward-cli/src/commands/actions/reset_validator_lamport_balances.rs b/utils/steward-cli/src/commands/actions/reset_validator_lamport_balances.rs new file mode 100644 index 00000000..b0e3ad5f --- /dev/null +++ b/utils/steward-cli/src/commands/actions/reset_validator_lamport_balances.rs @@ -0,0 +1,77 @@ +use std::sync::Arc; + +use anchor_lang::{InstructionData, ToAccountMetas}; +use anyhow::Result; +use solana_client::nonblocking::rpc_client::RpcClient; +use solana_program::instruction::Instruction; + +use solana_sdk::{ + pubkey::Pubkey, signature::read_keypair_file, signer::Signer, transaction::Transaction, +}; + +use crate::commands::command_args::ResetValidatorLamportBalances; +use stakenet_sdk::utils::{ + accounts::get_all_steward_accounts, + transactions::{configure_instruction, print_base58_tx}, +}; + +pub async fn command_reset_validator_lamport_balances( + args: ResetValidatorLamportBalances, + client: &Arc, + program_id: Pubkey, +) -> Result<()> { + let authority = read_keypair_file(args.permissioned_parameters.authority_keypair_path) + .expect("Failed reading keypair file ( Authority )"); + + let all_steward_accounts = get_all_steward_accounts( + client, + &program_id, + &args.permissioned_parameters.steward_config, + ) + .await?; + + let ix = Instruction { + program_id, + accounts: jito_steward::accounts::ResetValidatorLamportBalances { + steward_state: all_steward_accounts.state_address, + config: args.permissioned_parameters.steward_config, + authority: authority.pubkey(), + } + .to_account_metas(None), + data: jito_steward::instruction::ResetValidatorLamportBalances {}.data(), + }; + + let blockhash = client.get_latest_blockhash().await?; + + let configured_ix = configure_instruction( + &[ix], + args.permissioned_parameters + .transaction_parameters + .priority_fee, + args.permissioned_parameters + .transaction_parameters + .compute_limit, + args.permissioned_parameters + .transaction_parameters + .heap_size, + ); + + let transaction = Transaction::new_signed_with_payer( + &configured_ix, + Some(&authority.pubkey()), + &[&authority], + blockhash, + ); + + if args.permissioned_parameters.transaction_parameters.print_tx { + print_base58_tx(&configured_ix) + } else { + let signature = client + .send_and_confirm_transaction_with_spinner(&transaction) + .await?; + + println!("Signature: {}", signature); + } + + Ok(()) +} diff --git a/utils/steward-cli/src/commands/actions/update_validator_list_balance.rs b/utils/steward-cli/src/commands/actions/update_validator_list_balance.rs new file mode 100644 index 00000000..cde6f77a --- /dev/null +++ b/utils/steward-cli/src/commands/actions/update_validator_list_balance.rs @@ -0,0 +1,65 @@ +use std::sync::Arc; + +use solana_client::{nonblocking::rpc_client::RpcClient, rpc_config::RpcSendTransactionConfig}; +use solana_sdk::{ + commitment_config::CommitmentConfig, pubkey::Pubkey, signature::read_keypair_file, + signer::Signer, transaction::Transaction, +}; +use spl_stake_pool::instruction::update_validator_list_balance; +use stakenet_sdk::utils::accounts::get_all_steward_accounts; + +use crate::commands::command_args::UpdateValidatorListBalance; + +pub async fn command_update_validator_list_balance( + client: &Arc, + args: UpdateValidatorListBalance, + program_id: Pubkey, +) -> Result<(), anyhow::Error> { + let steward_config = args.permissionless_parameters.steward_config; + let payer = Arc::new( + read_keypair_file(args.permissionless_parameters.payer_keypair_path) + .expect("Failed reading keypair file ( Payer )"), + ); + + let all_steward_accounts = + get_all_steward_accounts(client, &program_id, &steward_config).await?; + + let stake_pool = all_steward_accounts.stake_pool_address; + let validator_list = all_steward_accounts.validator_list_address; + + let target_vote_account = all_steward_accounts.validator_list_account.validators + [args.validator_list_index as usize] + .vote_account_address; + + let instruction = update_validator_list_balance( + &spl_stake_pool::id(), + &stake_pool, + &all_steward_accounts.stake_pool_withdraw_authority, + &validator_list, + &all_steward_accounts.reserve_stake_address, + &all_steward_accounts.validator_list_account, + &[target_vote_account], + args.validator_list_index, + false, + ); + + let recent_blockhash = client.get_latest_blockhash().await?; + let transaction = Transaction::new_signed_with_payer( + &[instruction], + Some(&payer.pubkey()), + &[&*payer], + recent_blockhash, + ); + + let signature = client + .send_and_confirm_transaction_with_spinner_and_config( + &transaction, + CommitmentConfig::confirmed(), + RpcSendTransactionConfig::default(), + ) + .await?; + + println!("Transaction signature: {}", signature); + + Ok(()) +} diff --git a/utils/steward-cli/src/commands/command_args.rs b/utils/steward-cli/src/commands/command_args.rs index e4d731e6..88d3ef48 100644 --- a/utils/steward-cli/src/commands/command_args.rs +++ b/utils/steward-cli/src/commands/command_args.rs @@ -205,6 +205,7 @@ pub enum Commands { UpdateAuthority(UpdateAuthority), UpdateConfig(UpdateConfig), ResetState(ResetState), + ResetValidatorLamportBalances(ResetValidatorLamportBalances), Pause(Pause), Resume(Resume), @@ -219,6 +220,8 @@ pub enum Commands { ManuallyRemoveValidator(ManuallyRemoveValidator), AutoRemoveValidatorFromPool(AutoRemoveValidatorFromPool), AutoAddValidatorFromPool(AutoAddValidatorFromPool), + InstantRemoveValidator(InstantRemoveValidator), + UpdateValidatorListBalance(UpdateValidatorListBalance), // Cranks CrankSteward(CrankSteward), @@ -339,6 +342,13 @@ pub struct ResetState { pub permissioned_parameters: PermissionedParameters, } +#[derive(Parser)] +#[command(about = "Reset steward state")] +pub struct ResetValidatorLamportBalances { + #[command(flatten)] + pub permissioned_parameters: PermissionedParameters, +} + #[derive(Parser)] #[command(about = "Add to the blacklist")] pub struct AddToBlacklist { @@ -427,6 +437,17 @@ pub struct ManuallyRemoveValidator { pub validator_index_to_remove: u64, } +#[derive(Parser)] +#[command(about = "Instantly removes validator from pool")] +pub struct InstantRemoveValidator { + #[command(flatten)] + pub permissionless_parameters: PermissionlessParameters, + + /// Validator index of validator list to remove + #[arg(long, env)] + pub validator_index_to_remove: u64, +} + #[derive(Parser)] #[command(about = "Removes bad validators from the pool")] pub struct RemoveBadValidators { @@ -456,6 +477,20 @@ pub struct AutoAddValidatorFromPool { pub vote_account: Pubkey, } +#[derive(Parser)] +#[command(about = "Updates validator list balance (spl_stake_pool command) for a single validator")] +pub struct UpdateValidatorListBalance { + #[command(flatten)] + pub permissionless_parameters: PermissionlessParameters, + + /// Validator index in the validator list + #[arg(long, env)] + pub validator_list_index: u32, + + #[arg(long, env, default_value_t = false)] + pub no_merge: bool, +} + // ---------- CRANKS ------------ #[derive(Parser)] diff --git a/utils/steward-cli/src/commands/cranks/compute_score.rs b/utils/steward-cli/src/commands/cranks/compute_score.rs index 947343e7..1b577ec9 100644 --- a/utils/steward-cli/src/commands/cranks/compute_score.rs +++ b/utils/steward-cli/src/commands/cranks/compute_score.rs @@ -2,7 +2,6 @@ use std::sync::Arc; use anchor_lang::{InstructionData, ToAccountMetas}; use anyhow::Result; -use jito_steward::StewardStateEnum; use solana_client::nonblocking::rpc_client::RpcClient; use solana_program::instruction::Instruction; use validator_history::id as validator_history_id; @@ -32,17 +31,6 @@ pub async fn command_crank_compute_score( let steward_accounts = get_all_steward_accounts(client, &program_id, &steward_config).await?; - match steward_accounts.state_account.state.state_tag { - StewardStateEnum::ComputeScores => { /* Continue */ } - _ => { - println!( - "State account is not in ComputeScores state: {}", - steward_accounts.state_account.state.state_tag - ); - return Ok(()); - } - } - let validators_to_run = (0..steward_accounts.state_account.state.num_pool_validators) .filter_map(|validator_index| { let has_been_scored = steward_accounts diff --git a/utils/steward-cli/src/commands/info/view_state.rs b/utils/steward-cli/src/commands/info/view_state.rs index 41c9ccb6..4f9a4c38 100644 --- a/utils/steward-cli/src/commands/info/view_state.rs +++ b/utils/steward-cli/src/commands/info/view_state.rs @@ -1,6 +1,8 @@ use anchor_lang::AccountDeserialize; use anyhow::Result; -use jito_steward::{utils::ValidatorList, Config, StewardStateAccount}; +use jito_steward::{ + constants::LAMPORT_BALANCE_DEFAULT, utils::ValidatorList, Config, StewardStateAccount, +}; use solana_client::nonblocking::rpc_client::RpcClient; use solana_sdk::{account::Account, pubkey::Pubkey}; use spl_stake_pool::{ @@ -298,44 +300,65 @@ fn _print_verbose_state( formatted_string += &format!("Stake Account: {:?}\n", stake_address); formatted_string += &format!("Transient Stake Account: {:?}\n", transient_stake_address); formatted_string += &format!( - "Validator Lamports: {:?}\n", - u64::from(validator.active_stake_lamports) + "Internal Validator Lamports: {}\n", + match steward_state_account + .state + .validator_lamport_balances + .get(index) + { + Some(&LAMPORT_BALANCE_DEFAULT) | None => "Unset".to_string(), + Some(&lamports) => lamports.to_string(), + } ); - formatted_string += &format!("Index: {:?}\n", index); + formatted_string += &format!("Index: {}\n", index); formatted_string += &format!( - "Marked for removal: {:?}\n", - steward_state_account.state.validators_to_remove.get(index) + "Marked for removal: {}\n", + steward_state_account + .state + .validators_to_remove + .get(index) + .unwrap_or_default() + ); + formatted_string += &format!( + "Marked for immediate removal: {}\n", + steward_state_account + .state + .validators_for_immediate_removal + .get(index) + .unwrap_or_default() ); formatted_string += &format!( - "Is Instant Unstake: {:?}\n", - steward_state_account.state.instant_unstake.get(index) + "Is Instant Unstake: {}\n", + steward_state_account + .state + .instant_unstake + .get(index) + .unwrap_or_default() ); formatted_string += &format!( - "Score: {:?}\n", - steward_state_account.state.scores.get(index) + "Score: {}\n", + steward_state_account.state.scores.get(index).unwrap_or(&0) ); formatted_string += &format!( - "Yield Score: {:?}\n", - steward_state_account.state.yield_scores.get(index) + "Yield Score: {}\n", + steward_state_account + .state + .yield_scores + .get(index) + .unwrap_or(&0) ); formatted_string += &format!("Score Index: {:?}\n", score_index); formatted_string += &format!("Yield Score Index: {:?}\n", yield_score_index); if let Some(history_info) = history_info { - formatted_string += &format!( - "\nValidator History Index: {:?}\n", - format!("{:?}", history_info.index) - ); + formatted_string += &format!("\nValidator History Index: {}\n", history_info.index); formatted_string += &format!( - "Is blacklisted: {:?}\n", - format!( - "{:?}", - config_account - .validator_history_blacklist - .get_unsafe(history_info.index as usize) - ) + "Is blacklisted: {}\n", + config_account + .validator_history_blacklist + .get_unsafe(history_info.index as usize) ); } diff --git a/utils/steward-cli/src/main.rs b/utils/steward-cli/src/main.rs index 2b12314d..ebae522f 100644 --- a/utils/steward-cli/src/main.rs +++ b/utils/steward-cli/src/main.rs @@ -6,14 +6,17 @@ use commands::{ auto_add_validator_from_pool::command_auto_add_validator_from_pool, auto_remove_validator_from_pool::command_auto_remove_validator_from_pool, close_steward::command_close_steward, + instant_remove_validator::command_instant_remove_validator, manually_copy_all_vote_accounts::command_manually_copy_all_vote_accounts, manually_copy_vote_accounts::command_manually_copy_vote_account, manually_remove_validator::command_manually_remove_validator, pause::command_pause, remove_bad_validators::command_remove_bad_validators, remove_from_blacklist::command_remove_from_blacklist, reset_state::command_reset_state, + reset_validator_lamport_balances::command_reset_validator_lamport_balances, resume::command_resume, revert_staker::command_revert_staker, set_staker::command_set_staker, update_authority::command_update_authority, update_config::command_update_config, + update_validator_list_balance::command_update_validator_list_balance, }, command_args::{Args, Commands}, cranks::{ @@ -72,12 +75,18 @@ async fn main() -> Result<()> { Commands::Resume(args) => command_resume(args, &client, program_id).await, Commands::ReallocState(args) => command_realloc_state(args, &client, program_id).await, Commands::ResetState(args) => command_reset_state(args, &client, program_id).await, + Commands::ResetValidatorLamportBalances(args) => { + command_reset_validator_lamport_balances(args, &client, program_id).await + } Commands::ManuallyRemoveValidator(args) => { command_manually_remove_validator(args, &client, program_id).await } Commands::ManuallyCopyAllVoteAccounts(args) => { command_manually_copy_all_vote_accounts(args, &client, program_id).await } + Commands::InstantRemoveValidator(args) => { + command_instant_remove_validator(args, &client, program_id).await + } Commands::AutoRemoveValidatorFromPool(args) => { command_auto_remove_validator_from_pool(args, &client, program_id).await } @@ -91,6 +100,9 @@ async fn main() -> Result<()> { Commands::RemoveFromBlacklist(args) => { command_remove_from_blacklist(args, &client, program_id).await } + Commands::UpdateValidatorListBalance(args) => { + command_update_validator_list_balance(&client, args, program_id).await + } // --- Cranks --- Commands::CrankSteward(args) => command_crank_steward(args, &client, program_id).await,