From f859a39b863942456f7cf4333d6a4c9920619407 Mon Sep 17 00:00:00 2001 From: Michael Vines Date: Wed, 16 Jun 2021 11:39:58 -0700 Subject: [PATCH] Remove obsolete stake-monitor command --- Cargo.lock | 24 -- Cargo.toml | 1 - scripts/cargo-install-all.sh | 1 - stake-monitor/.gitignore | 2 - stake-monitor/Cargo.toml | 39 -- stake-monitor/README.md | 17 - stake-monitor/src/lib.rs | 794 ----------------------------------- stake-monitor/src/main.rs | 230 ---------- 8 files changed, 1108 deletions(-) delete mode 100644 stake-monitor/.gitignore delete mode 100644 stake-monitor/Cargo.toml delete mode 100644 stake-monitor/README.md delete mode 100644 stake-monitor/src/lib.rs delete mode 100644 stake-monitor/src/main.rs diff --git a/Cargo.lock b/Cargo.lock index 3028ff58d51b83..cf91c200925504 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -5540,30 +5540,6 @@ dependencies = [ "solana-stake-program", ] -[[package]] -name = "solana-stake-monitor" -version = "1.8.0" -dependencies = [ - "clap 2.33.3", - "console 0.14.1", - "log 0.4.11", - "serde", - "serde_yaml", - "serial_test 0.5.1", - "solana-clap-utils", - "solana-cli-config", - "solana-client", - "solana-core", - "solana-local-cluster", - "solana-logger 1.8.0", - "solana-metrics", - "solana-rpc", - "solana-sdk", - "solana-transaction-status", - "solana-version", - "tempfile", -] - [[package]] name = "solana-stake-program" version = "1.8.0" diff --git a/Cargo.toml b/Cargo.toml index 29fa7da9e03373..d5c83d8a5e46fe 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -61,7 +61,6 @@ members = [ "sdk/cargo-test-bpf", "scripts", "stake-accounts", - "stake-monitor", "sys-tuner", "tokens", "transaction-status", diff --git a/scripts/cargo-install-all.sh b/scripts/cargo-install-all.sh index 7e6a01c7bf6cab..3fad4c41c98307 100755 --- a/scripts/cargo-install-all.sh +++ b/scripts/cargo-install-all.sh @@ -106,7 +106,6 @@ else solana-dos solana-install-init solana-stake-accounts - solana-stake-monitor solana-test-validator solana-tokens solana-watchtower diff --git a/stake-monitor/.gitignore b/stake-monitor/.gitignore deleted file mode 100644 index 5404b132dba6e1..00000000000000 --- a/stake-monitor/.gitignore +++ /dev/null @@ -1,2 +0,0 @@ -/target/ -/farf/ diff --git a/stake-monitor/Cargo.toml b/stake-monitor/Cargo.toml deleted file mode 100644 index 202373f24386d1..00000000000000 --- a/stake-monitor/Cargo.toml +++ /dev/null @@ -1,39 +0,0 @@ -[package] -authors = ["Solana Maintainers "] -edition = "2018" -name = "solana-stake-monitor" -description = "Blockchain, Rebuilt for Scale" -version = "1.8.0" -repository = "https://github.com/solana-labs/solana" -license = "Apache-2.0" -homepage = "https://solana.com/" -documentation = "https://docs.rs/solana-stake-monitor" - -[dependencies] -clap = "2.33.1" -console = "0.14.1" -log = "0.4.11" -serde = "1.0.126" -serde_yaml = "0.8.13" -solana-clap-utils = { path = "../clap-utils", version = "=1.8.0" } -solana-cli-config = { path = "../cli-config", version = "=1.8.0" } -solana-client = { path = "../client", version = "=1.8.0" } -solana-logger = { path = "../logger", version = "=1.8.0" } -solana-metrics = { path = "../metrics", version = "=1.8.0" } -solana-rpc = { path = "../rpc", version = "=1.8.0" } -solana-sdk = { path = "../sdk", version = "=1.8.0" } -solana-transaction-status = { path = "../transaction-status", version = "=1.8.0" } -solana-version = { path = "../version", version = "=1.8.0" } - -[dev-dependencies] -serial_test = "0.5.1" -solana-local-cluster = { path = "../local-cluster", version = "=1.8.0" } -solana-core = { path = "../core", version = "=1.8.0" } -tempfile = "3.2.0" - -[[bin]] -name = "solana-stake-monitor" -path = "src/main.rs" - -[package.metadata.docs.rs] -targets = ["x86_64-unknown-linux-gnu"] diff --git a/stake-monitor/README.md b/stake-monitor/README.md deleted file mode 100644 index 6e9e719ddb165d..00000000000000 --- a/stake-monitor/README.md +++ /dev/null @@ -1,17 +0,0 @@ -## Overview -`solana-stake-monitor` is a utility that scans all transactions to ensure that stake accounts remain in compliance with the following rules: - -1. The stake account must be created after genesis -1. The "compliant balance" of a stake account is set upon stake account initialization, system transfers of additional funds into a compliant stake account are excluded from the "compliant balance" -1. The stake account cannot have a lockup or custodian -1. Withdrawing funds from the stake account trigger non-compliance -1. Stake accounts split from a compliant stake account remain compliant, and the "compliant balance" is adjusted accordingly for the original stake account - -In terms of `solana` command-line subcommands: -* `create-stake-account`: Creates a compliant stake account provided the `--lockup-date`, `--lockup-epoch`, or `--custodian` options are not specified -* `delegate-stake` / `deactivate-stake` / `stake-authorize` / `split-stake`: These commands do not affect compliance -* `withdraw-stake` / `stake-set-lockup`: These commands will cause non-compliance -* `transfer`: Any additional funds transferred after `create-stake-account` are excluded from the "compliant balance" - -System accounts can also be manually enrolled with the `solana-stake-monitor enroll` subcommand. -An enrolled system account must always maintain a balance greater than the balance it had at enrollment minus 1 SOL. diff --git a/stake-monitor/src/lib.rs b/stake-monitor/src/lib.rs deleted file mode 100644 index a16168a731f92d..00000000000000 --- a/stake-monitor/src/lib.rs +++ /dev/null @@ -1,794 +0,0 @@ -#![allow(clippy::integer_arithmetic)] -use log::*; -use serde::{Deserialize, Serialize}; -use solana_client::{client_error::Result as ClientResult, rpc_client::RpcClient}; -use solana_metrics::{datapoint_error, datapoint_info}; -use solana_sdk::{ - clock::Slot, - native_token::LAMPORTS_PER_SOL, - program_utils::limited_deserialize, - pubkey::Pubkey, - signature::Signature, - stake::{self, instruction::StakeInstruction, state::Lockup}, - transaction::Transaction, -}; -use solana_transaction_status::{ - EncodedConfirmedBlock, UiTransactionEncoding, UiTransactionStatusMeta, -}; -use std::{collections::HashMap, thread::sleep, time::Duration}; - -pub type PubkeyString = String; -pub type SignatureString = String; - -#[derive(Serialize, Deserialize, Debug, PartialEq)] -pub enum AccountOperation { - Initialize, - Withdraw, - SplitSource, - SplitDestination, - SystemAccountEnroll, - FailedToMaintainMinimumBalance, - MergeSource, -} - -#[derive(Serialize, Deserialize, Debug)] -pub struct AccountTransactionInfo { - pub op: AccountOperation, - pub slot: Slot, // Slot the transaction completed in - pub signature: SignatureString, // Transaction signature -} - -#[derive(Serialize, Deserialize, Debug)] -pub struct AccountInfo { - pub compliant_since: Option, // The slot when the account was first in compliance - pub lamports: u64, // Account balance - pub transactions: Vec, // Transactions affecting the account -} - -#[derive(Serialize, Deserialize, Default, Debug)] -pub struct AccountsInfo { - pub slot: Slot, // Latest processed slot - pub account_info: HashMap, -} - -impl AccountsInfo { - // System accounts must be explicitly enrolled - pub fn enroll_system_account(&mut self, account_address: &Pubkey, slot: Slot, lamports: u64) { - self.account_info.insert( - account_address.to_string(), - AccountInfo { - compliant_since: Some(slot), - lamports, - transactions: vec![AccountTransactionInfo { - op: AccountOperation::SystemAccountEnroll, - slot, - signature: Signature::default().to_string(), - }], - }, - ); - } -} - -fn process_transaction( - slot: Slot, - transaction: &Transaction, - meta: &UiTransactionStatusMeta, - accounts: &mut HashMap, -) { - let mut last_instruction = true; - let message = &transaction.message; - let signature = transaction.signatures[0].to_string(); - - // Look for stake operations - for instruction in message.instructions.iter().rev() { - let program_pubkey = message.account_keys[instruction.program_id_index as usize]; - if program_pubkey != stake::program::id() { - continue; - } - - // Only look for stake instructions in the last instruction of a - // transaction. This ensures that the `meta.post_balances` for the - // transaction reflects the account balances after the stake instruction - // executed. At this time the `solana` cli will only create transactions with the stake - // instruction as the last instruction. - if !last_instruction { - datapoint_error!( - "stake-monitor-failure", - ("slot", slot, i64), - ("err", "Stake instruction ignored", String) - ); - continue; - } - last_instruction = false; - - match limited_deserialize::(&instruction.data) { - Err(err) => datapoint_error!( - "stake-monitor-failure", - ("slot", slot, i64), - ( - "err", - format!("Failed to deserialize stake instruction: {}", err), - String - ) - ), - Ok(stake_instruction) => { - match stake_instruction { - StakeInstruction::Initialize(_authorized, lockup) => { - // The initialized stake account is at instruction account 0 - let stake_account_index = instruction.accounts[0] as usize; - - let stake_pubkey = message.account_keys[stake_account_index].to_string(); - - // The amount staked is the stake account's post balance - let lamports = meta.post_balances[stake_account_index]; - - accounts.insert( - stake_pubkey, - AccountInfo { - compliant_since: if lockup != Lockup::default() { - None // Initialize with a lockup or custodian is non-compliant - } else { - Some(slot) - }, - lamports, - transactions: vec![AccountTransactionInfo { - op: AccountOperation::Initialize, - slot, - signature: signature.clone(), - }], - }, - ); - } - StakeInstruction::Authorize(_, _) - | StakeInstruction::AuthorizeWithSeed(_) - | StakeInstruction::DelegateStake - | StakeInstruction::Deactivate => { - // These instructions are always permitted - } - StakeInstruction::Split(lamports) => { - // Split is permitted and propagates compliance - let source_stake_account_index = instruction.accounts[0] as usize; - let split_stake_account_index = instruction.accounts[1] as usize; - - let source_stake_pubkey = - message.account_keys[source_stake_account_index].to_string(); - let split_stake_pubkey = - message.account_keys[split_stake_account_index].to_string(); - - if let Some(mut source_account_info) = - accounts.get_mut(&source_stake_pubkey) - { - if source_account_info.compliant_since.is_some() { - source_account_info - .transactions - .push(AccountTransactionInfo { - op: AccountOperation::SplitSource, - slot, - signature: signature.clone(), - }); - source_account_info.lamports -= lamports; - - let split_account_info = AccountInfo { - compliant_since: source_account_info.compliant_since, - lamports, - transactions: vec![AccountTransactionInfo { - op: AccountOperation::SplitDestination, - slot, - signature: signature.clone(), - }], - }; - accounts.insert(split_stake_pubkey, split_account_info); - } - } - } - StakeInstruction::Merge => { - // Merge invalidates the source account, but does not affect the - // destination account - let source_merge_account_index = instruction.accounts[1] as usize; - - let source_stake_pubkey = - message.account_keys[source_merge_account_index].to_string(); - - if let Some(mut source_account_info) = - accounts.get_mut(&source_stake_pubkey) - { - if source_account_info.compliant_since.is_some() { - source_account_info.compliant_since = None; - source_account_info - .transactions - .push(AccountTransactionInfo { - op: AccountOperation::MergeSource, - slot, - signature: signature.clone(), - }); - } - } - } - StakeInstruction::Withdraw(_) => { - // Withdrawing is not permitted - - let stake_account_index = instruction.accounts[0] as usize; - let stake_pubkey = message.account_keys[stake_account_index].to_string(); - - if let Some(mut account_info) = accounts.get_mut(&stake_pubkey) { - if account_info.compliant_since.is_some() { - account_info.compliant_since = None; - account_info.transactions.push(AccountTransactionInfo { - op: AccountOperation::Withdraw, - slot, - signature: signature.clone(), - }); - } - } - } - StakeInstruction::SetLockup(_lockup_args) => { - // No processing is required because SetLockup requires a custodian key, - // and this is already blocked in the StakeInstruction::Initialize - // processing - } - } - } - } - } - - // Ensure the balances of all monitored accounts remain in compliance - for (index, account_pubkey) in message.account_keys.iter().enumerate() { - if let Some(mut account_info) = accounts.get_mut(&account_pubkey.to_string()) { - let post_balance = meta.post_balances[index]; - if account_info.compliant_since.is_some() - && post_balance <= account_info.lamports.saturating_sub(LAMPORTS_PER_SOL) - { - account_info.compliant_since = None; - account_info.transactions.push(AccountTransactionInfo { - op: AccountOperation::FailedToMaintainMinimumBalance, - slot, - signature: signature.clone(), - }); - } - } - } -} - -fn process_confirmed_block( - slot: Slot, - confirmed_block: EncodedConfirmedBlock, - accounts: &mut HashMap, -) { - for rpc_transaction in confirmed_block.transactions { - match rpc_transaction.meta { - None => { - datapoint_error!( - "stake-monitor-failure", - ("slot", slot, i64), - ("err", "Transaction meta not available", String) - ); - } - Some(meta) => { - if meta.err.is_none() { - if let Some(transaction) = rpc_transaction.transaction.decode() { - if transaction.verify().is_ok() { - process_transaction(slot, &transaction, &meta, accounts); - } else { - datapoint_error!( - "stake-monitor-failure", - ("slot", slot, i64), - ("err", "Transaction signature verification failed", String) - ); - } - } else { - error!("Transaction decode failed"); - } - } - } - } - } -} - -fn load_blocks( - rpc_client: &RpcClient, - start_slot: Slot, - end_slot: Slot, -) -> ClientResult> { - info!( - "Loading confirmed blocks between slots: {} - {}", - start_slot, end_slot - ); - - let slots = rpc_client.get_blocks(start_slot, Some(end_slot))?; - - let mut blocks = vec![]; - for slot in slots.into_iter() { - let block = rpc_client.get_block_with_encoding(slot, UiTransactionEncoding::Base64)?; - blocks.push((slot, block)); - } - Ok(blocks) -} - -pub fn process_slots(rpc_client: &RpcClient, accounts_info: &mut AccountsInfo, batch_size: u64) { - let end_slot = accounts_info.slot + batch_size; - loop { - let start_slot = accounts_info.slot + 1; - info!("start_slot:{} - end_slot:{}", start_slot, end_slot); - if start_slot >= end_slot { - break; - } - let latest_available_slot = rpc_client.get_slot().unwrap_or_else(|err| { - datapoint_error!( - "stake-monitor-failure", - ("err", format!("get_slot() failed: {}", err), String) - ); - 0 - }); - - if accounts_info.slot >= latest_available_slot { - info!("Waiting for a slot greater than {}...", accounts_info.slot); - sleep(Duration::from_secs(5)); - continue; - } - - match load_blocks(&rpc_client, start_slot, end_slot) { - Ok(blocks) => { - info!("Loaded {} blocks", blocks.len()); - - if blocks.is_empty() && end_slot < latest_available_slot { - accounts_info.slot = end_slot; - } else { - for (slot, block) in blocks.into_iter() { - process_confirmed_block(slot, block, &mut accounts_info.account_info); - accounts_info.slot = slot; - } - } - datapoint_info!("stake-monitor-slot", ("slot", accounts_info.slot, i64)); - } - Err(err) => { - datapoint_error!( - "stake-monitor-failure", - ( - "err", - format!( - "failed to get blocks in range ({},{}): {}", - start_slot, end_slot, err - ), - String - ) - ); - sleep(Duration::from_secs(1)); - } - } - } -} - -#[cfg(test)] -mod test { - use super::*; - use serial_test::serial; - use solana_client::rpc_config::RpcSendTransactionConfig; - use solana_core::validator::ValidatorConfig; - use solana_local_cluster::local_cluster::{ClusterConfig, LocalCluster}; - use solana_rpc::rpc::JsonRpcConfig; - use solana_sdk::{ - commitment_config::CommitmentConfig, - genesis_config::ClusterType, - message::Message, - native_token::sol_to_lamports, - signature::{Keypair, Signer}, - stake::{instruction as stake_instruction, state::Authorized}, - system_transaction, - transaction::Transaction, - }; - - #[test] - #[serial] - fn test_record() { - solana_logger::setup(); - let mut accounts_info = AccountsInfo::default(); - - let one_sol = sol_to_lamports(1.0); - let cluster = LocalCluster::new(&mut ClusterConfig { - cluster_type: ClusterType::MainnetBeta, - node_stakes: vec![10; 1], - cluster_lamports: sol_to_lamports(1_000_000_000.0), - validator_configs: vec![ValidatorConfig { - rpc_config: JsonRpcConfig { - enable_rpc_transaction_history: true, - ..JsonRpcConfig::default() - }, - ..ValidatorConfig::default() - }], - ..ClusterConfig::default() - }); - - let payer = &cluster.funding_keypair; - - let rpc_client = RpcClient::new_socket(cluster.entry_point_info.rpc); - - let (blockhash, _fee_calculator) = rpc_client.get_recent_blockhash().unwrap(); - - // Configure stake1 - let stake1_keypair = Keypair::new(); - let instructions = stake_instruction::create_account( - &payer.pubkey(), - &stake1_keypair.pubkey(), - &Authorized::auto(&payer.pubkey()), - &Lockup::default(), - one_sol, - ); - let message = Message::new(&instructions, Some(&payer.pubkey())); - let stake1_signature = rpc_client - .send_transaction(&Transaction::new( - &[&payer, &stake1_keypair], - message, - blockhash, - )) - .unwrap(); - - rpc_client - .poll_for_signature_with_commitment(&stake1_signature, CommitmentConfig::processed()) - .unwrap(); - - // A balance increase by system transfer is ignored - rpc_client - .send_transaction(&system_transaction::transfer( - &payer, - &stake1_keypair.pubkey(), - one_sol, - blockhash, - )) - .unwrap(); - - // Configure stake2 with non-compliant lockup - let stake2_keypair = Keypair::new(); - let instructions = stake_instruction::create_account( - &payer.pubkey(), - &stake2_keypair.pubkey(), - &Authorized::auto(&payer.pubkey()), - &Lockup { - custodian: payer.pubkey(), - ..Lockup::default() - }, - one_sol, - ); - let message = Message::new(&instructions, Some(&payer.pubkey())); - let stake2_signature = rpc_client - .send_transaction(&Transaction::new( - &[&payer, &stake2_keypair], - message, - blockhash, - )) - .unwrap(); - - // Configure stake3 - let stake3_keypair = Keypair::new(); - let instructions = stake_instruction::create_account( - &payer.pubkey(), - &stake3_keypair.pubkey(), - &Authorized::auto(&stake3_keypair.pubkey()), - &Lockup::default(), - one_sol, - ); - let message = Message::new(&instructions, Some(&payer.pubkey())); - let stake3_initialize_signature = rpc_client - .send_transaction(&Transaction::new( - &[&payer, &stake3_keypair], - message, - blockhash, - )) - .unwrap(); - - rpc_client - .poll_for_signature_with_commitment( - &stake3_initialize_signature, - CommitmentConfig::processed(), - ) - .unwrap(); - - // Withdraw instruction causes non-compliance - let stake3_withdraw_signature = rpc_client - .send_transaction_with_config( - &Transaction::new( - &[&payer, &stake3_keypair], - Message::new( - &[stake_instruction::withdraw( - &stake3_keypair.pubkey(), - &stake3_keypair.pubkey(), - &payer.pubkey(), - one_sol, - None, - )], - Some(&payer.pubkey()), - ), - blockhash, - ), - RpcSendTransactionConfig { - skip_preflight: true, - ..RpcSendTransactionConfig::default() - }, - ) - .unwrap(); - - rpc_client - .poll_for_signature_with_commitment( - &stake3_withdraw_signature, - CommitmentConfig::processed(), - ) - .unwrap(); - - // Configure stake4 - let stake4_keypair = Keypair::new(); - let instructions = stake_instruction::create_account( - &payer.pubkey(), - &stake4_keypair.pubkey(), - &Authorized::auto(&payer.pubkey()), - &Lockup::default(), - 2 * one_sol, - ); - let message = Message::new(&instructions, Some(&payer.pubkey())); - let stake4_initialize_signature = rpc_client - .send_transaction(&Transaction::new( - &[&payer, &stake4_keypair], - message, - blockhash, - )) - .unwrap(); - - rpc_client - .poll_for_signature_with_commitment( - &stake4_initialize_signature, - CommitmentConfig::processed(), - ) - .unwrap(); - - // Split stake4 into stake5 - let stake5_keypair = Keypair::new(); - let stake45_split_signature = rpc_client - .send_transaction_with_config( - &Transaction::new( - &[&payer, &stake5_keypair], - Message::new( - &stake_instruction::split( - &stake4_keypair.pubkey(), - &payer.pubkey(), - one_sol, - &stake5_keypair.pubkey(), - ), - Some(&payer.pubkey()), - ), - blockhash, - ), - RpcSendTransactionConfig { - skip_preflight: true, - ..RpcSendTransactionConfig::default() - }, - ) - .unwrap(); - - rpc_client - .poll_for_signature_with_commitment( - &stake45_split_signature, - CommitmentConfig::processed(), - ) - .unwrap(); - - // System transfer 1 - let system1_keypair = Keypair::new(); - - // Fund system1 - let fund_system1_signature = rpc_client - .send_transaction(&system_transaction::transfer( - &payer, - &system1_keypair.pubkey(), - 2 * one_sol, - blockhash, - )) - .unwrap(); - rpc_client - .poll_for_signature_with_commitment( - &fund_system1_signature, - CommitmentConfig::processed(), - ) - .unwrap(); - accounts_info.enroll_system_account( - &system1_keypair.pubkey(), - rpc_client - .get_slot_with_commitment(CommitmentConfig::processed()) - .unwrap(), - 2 * one_sol, - ); - - // Withdraw 1 sol from system 1 to make it non-compliant - rpc_client - .send_transaction_with_config( - &system_transaction::transfer( - &system1_keypair, - &payer.pubkey(), - one_sol, - blockhash, - ), - RpcSendTransactionConfig { - skip_preflight: true, - ..RpcSendTransactionConfig::default() - }, - ) - .unwrap(); - - // System transfer 2 - let system2_keypair = Keypair::new(); - - // Fund system2 - let fund_system2_signature = rpc_client - .send_transaction(&system_transaction::transfer( - &payer, - &system2_keypair.pubkey(), - 2 * one_sol, - blockhash, - )) - .unwrap(); - rpc_client - .poll_for_signature_with_commitment( - &fund_system2_signature, - CommitmentConfig::processed(), - ) - .unwrap(); - accounts_info.enroll_system_account( - &system2_keypair.pubkey(), - rpc_client - .get_slot_with_commitment(CommitmentConfig::processed()) - .unwrap(), - 2 * one_sol, - ); - - // Withdraw 1 sol - 1 lamport from system 2, it's still compliant - rpc_client - .send_transaction_with_config( - &system_transaction::transfer( - &system2_keypair, - &payer.pubkey(), - one_sol - 1, - blockhash, - ), - RpcSendTransactionConfig { - skip_preflight: true, - ..RpcSendTransactionConfig::default() - }, - ) - .unwrap(); - - // Process all the transactions - let current_slot = rpc_client - .get_slot_with_commitment(CommitmentConfig::processed()) - .unwrap(); - process_slots(&rpc_client, &mut accounts_info, current_slot + 1); - - // - // Check that `accounts_info` was populated with the expected results - // - - info!("Check the data recorded for stake1"); - let account_info = accounts_info - .account_info - .get(&stake1_keypair.pubkey().to_string()) - .unwrap(); - assert!(account_info.compliant_since.is_some()); - assert_eq!(account_info.lamports, one_sol); - assert_eq!(account_info.transactions.len(), 1); - assert_eq!( - account_info.transactions[0].op, - AccountOperation::Initialize - ); - assert_eq!( - account_info.transactions[0].signature, - stake1_signature.to_string() - ); - - info!("Check the data recorded for stake2"); - let account_info = accounts_info - .account_info - .get(&stake2_keypair.pubkey().to_string()) - .unwrap(); - assert!(account_info.compliant_since.is_none()); - assert_eq!(account_info.lamports, one_sol); - assert_eq!(account_info.transactions.len(), 1); - assert_eq!( - account_info.transactions[0].op, - AccountOperation::Initialize - ); - assert_eq!( - account_info.transactions[0].signature, - stake2_signature.to_string() - ); - - info!("Check the data recorded for stake3"); - let account_info = accounts_info - .account_info - .get(&stake3_keypair.pubkey().to_string()) - .unwrap(); - assert!(account_info.compliant_since.is_none()); - assert_eq!(account_info.lamports, one_sol); - assert_eq!(account_info.transactions.len(), 2); - assert_eq!( - account_info.transactions[0].op, - AccountOperation::Initialize - ); - assert_eq!( - account_info.transactions[0].signature, - stake3_initialize_signature.to_string() - ); - assert_eq!(account_info.transactions[1].op, AccountOperation::Withdraw,); - assert_eq!( - account_info.transactions[1].signature, - stake3_withdraw_signature.to_string() - ); - - info!("Check the data recorded for stake4"); - let account_info = accounts_info - .account_info - .get(&stake4_keypair.pubkey().to_string()) - .unwrap(); - assert!(account_info.compliant_since.is_some()); - assert_eq!(account_info.lamports, one_sol); - assert_eq!(account_info.transactions.len(), 2); - assert_eq!( - account_info.transactions[0].op, - AccountOperation::Initialize - ); - assert_eq!( - account_info.transactions[0].signature, - stake4_initialize_signature.to_string() - ); - assert_eq!( - account_info.transactions[1].op, - AccountOperation::SplitSource, - ); - assert_eq!( - account_info.transactions[1].signature, - stake45_split_signature.to_string() - ); - - info!("Check the data recorded for stake5"); - let account_info = accounts_info - .account_info - .get(&stake5_keypair.pubkey().to_string()) - .unwrap(); - assert!(account_info.compliant_since.is_some()); - assert_eq!(account_info.lamports, one_sol); - assert_eq!(account_info.transactions.len(), 1); - assert_eq!( - account_info.transactions[0].op, - AccountOperation::SplitDestination, - ); - assert_eq!( - account_info.transactions[0].signature, - stake45_split_signature.to_string() - ); - - info!("Check the data recorded for system1"); - let account_info = accounts_info - .account_info - .get(&system1_keypair.pubkey().to_string()) - .unwrap(); - assert!(account_info.compliant_since.is_none()); - assert_eq!(account_info.lamports, 2 * one_sol); - assert_eq!(account_info.transactions.len(), 2); - assert_eq!( - account_info.transactions[0].op, - AccountOperation::SystemAccountEnroll, - ); - assert_eq!( - account_info.transactions[1].op, - AccountOperation::FailedToMaintainMinimumBalance, - ); - - info!("Check the data recorded for system2"); - let account_info = accounts_info - .account_info - .get(&system2_keypair.pubkey().to_string()) - .unwrap(); - assert!(account_info.compliant_since.is_some()); - assert_eq!(account_info.lamports, 2 * one_sol); - assert_eq!(account_info.transactions.len(), 1); - assert_eq!( - account_info.transactions[0].op, - AccountOperation::SystemAccountEnroll, - ); - } -} diff --git a/stake-monitor/src/main.rs b/stake-monitor/src/main.rs deleted file mode 100644 index c0eaeae6fcc4d6..00000000000000 --- a/stake-monitor/src/main.rs +++ /dev/null @@ -1,230 +0,0 @@ -use clap::{ - crate_description, crate_name, value_t, value_t_or_exit, App, AppSettings, Arg, SubCommand, -}; -use console::Emoji; -use log::*; -use solana_clap_utils::{ - input_parsers::pubkey_of, - input_validators::{is_pubkey, is_slot, is_url}, -}; -use solana_client::rpc_client::RpcClient; -use solana_metrics::datapoint_error; -use solana_sdk::{clock::Slot, native_token::lamports_to_sol, pubkey::Pubkey, system_program}; -use solana_stake_monitor::*; -use std::{fs, io, process}; - -fn load_accounts_info(data_file: &str) -> AccountsInfo { - let data_file_new = data_file.to_owned() + "new"; - let accounts_info = solana_cli_config::load_config_file(&data_file_new) - .or_else(|_| solana_cli_config::load_config_file(data_file)) - .unwrap_or_default(); - - // Ensure `data_file` always exists - save_accounts_info(data_file, &accounts_info).expect("save_accounts_info"); - - accounts_info -} - -fn save_accounts_info(data_file: &str, accounts_info: &AccountsInfo) -> io::Result<()> { - let data_file_new = data_file.to_owned() + "new"; - solana_cli_config::save_config_file(&accounts_info, &data_file_new)?; - let _ = fs::remove_file(data_file); - fs::rename(&data_file_new, data_file) -} - -fn command_record(data_file: &str, json_rpc_url: String, first_slot: Slot, batch_size: u64) { - let mut accounts_info = load_accounts_info(&data_file); - - info!("RPC URL: {}", json_rpc_url); - let rpc_client = RpcClient::new(json_rpc_url); - if accounts_info.slot < first_slot { - accounts_info.slot = first_slot; - } - - loop { - process_slots(&rpc_client, &mut accounts_info, batch_size); - save_accounts_info(data_file, &accounts_info).unwrap_or_else(|err| { - datapoint_error!( - "stake-monitor-failure", - ( - "err", - format!("failed to save accounts_info: {}", err), - String - ) - ); - }); - } -} - -fn command_enroll(data_file: &str, json_rpc_url: String, account_address: &Pubkey) { - info!("RPC URL: {}", json_rpc_url); - let rpc_client = RpcClient::new(json_rpc_url); - let slot = rpc_client.get_slot().expect("get slot"); - - let account = rpc_client - .get_account(account_address) - .unwrap_or_else(|err| { - eprintln!( - "Unable to get account info for {}: {}", - account_address, err - ); - process::exit(1); - }); - - if account.owner != system_program::id() && !account.data.is_empty() { - eprintln!("{} is not a system account", account_address); - process::exit(1); - } - - let mut accounts_info = load_accounts_info(data_file); - accounts_info.enroll_system_account(account_address, slot, account.lamports); - save_accounts_info(data_file, &accounts_info).unwrap(); - println!( - "Enrolled {} at slot {} with a balance of {} SOL", - account_address, - slot, - lamports_to_sol(account.lamports) - ); -} - -fn command_check(data_file: &str, account_address: &Pubkey) { - let accounts_info = load_accounts_info(data_file); - - if let Some(account_info) = accounts_info.account_info.get(&account_address.to_string()) { - if let Some(slot) = account_info.compliant_since { - println!( - "{}Account compliant since slot {} with a balance of {} SOL", - Emoji("✅ ", ""), - slot, - lamports_to_sol(account_info.lamports) - ); - process::exit(0); - } else { - eprintln!( - "{}Account not compliant due to: {:?}", - Emoji("❌ ", ""), - account_info.transactions.last().unwrap() - ); - process::exit(1); - } - } else { - eprintln!("{} Unknown stake account", Emoji("⚠️ ", "")); - process::exit(1); - } -} - -fn main() { - solana_logger::setup_with_default("solana=info"); - solana_metrics::set_panic_hook("stake-monitor"); - - let matches = App::new(crate_name!()) - .about(crate_description!()) - .version(solana_version::version!()) - .setting(AppSettings::SubcommandRequiredElseHelp) - .arg( - Arg::with_name("data_file") - .long("data-file") - .value_name("PATH") - .takes_value(true) - .default_value("stake-info.yml") - .global(true) - .help( - "Output YAML file that receives the information for all stake accounts.\ - This file is updated atomically after each batch of slots is processed.", - ), - ) - .arg( - Arg::with_name("json_rpc_url") - .long("url") - .value_name("URL") - .takes_value(true) - .validator(is_url) - .help("JSON RPC URL for the cluster"), - ) - .arg({ - let arg = Arg::with_name("config_file") - .short("C") - .long("config") - .value_name("PATH") - .takes_value(true) - .help("Configuration file to use"); - if let Some(ref config_file) = *solana_cli_config::CONFIG_FILE { - arg.default_value(&config_file) - } else { - arg - } - }) - .subcommand( - SubCommand::with_name("record") - .about("Monitor all Cluster transactions for state account compliance") - .arg( - Arg::with_name("first_slot") - .long("--first-slot") - .value_name("SLOT") - .validator(is_slot) - .takes_value(true) - .default_value("0") - .help("Don't process slots lower than this value"), - ) - .arg( - Arg::with_name("batch_size") - .long("--batch-size") - .value_name("NUMBER") - .takes_value(true) - .default_value("10") - .help("Process up to this many slots in one batch"), - ), - ) - .subcommand( - SubCommand::with_name("check") - .about("Check if an account is in compliance") - .arg( - Arg::with_name("account_address") - .index(1) - .value_name("ADDRESS") - .validator(is_pubkey) - .required(true) - .help("Account address"), - ), - ) - .subcommand( - SubCommand::with_name("enroll") - .about("Enroll a system account for balance monitoring") - .arg( - Arg::with_name("account_address") - .index(1) - .value_name("ADDRESS") - .validator(is_pubkey) - .required(true) - .help("Account address"), - ), - ) - .get_matches(); - - let data_file = value_t_or_exit!(matches, "data_file", String); - let json_rpc_url = value_t!(matches, "json_rpc_url", String).unwrap_or_else(|_| { - let config = if let Some(config_file) = matches.value_of("config_file") { - solana_cli_config::Config::load(config_file).unwrap_or_default() - } else { - solana_cli_config::Config::default() - }; - config.json_rpc_url - }); - - match matches.subcommand() { - ("record", Some(matches)) => { - let batch_size = value_t_or_exit!(matches, "batch_size", u64); - let first_slot = value_t_or_exit!(matches, "first_slot", Slot); - command_record(&data_file, json_rpc_url, first_slot, batch_size); - } - ("check", Some(matches)) => { - let account_address = pubkey_of(&matches, "account_address").unwrap(); - command_check(&data_file, &account_address); - } - ("enroll", Some(matches)) => { - let account_address = pubkey_of(&matches, "account_address").unwrap(); - command_enroll(&data_file, json_rpc_url, &account_address); - } - _ => unreachable!(), - } -}