Add support for monitoring system account balances (#9345)

automerge
This commit is contained in:
Michael Vines
2020-04-06 21:41:53 -07:00
committed by GitHub
parent 33a68ec9c3
commit 03978ac5a5
2 changed files with 289 additions and 173 deletions

View File

@@ -9,48 +9,46 @@ use solana_clap_utils::{
};
use solana_client::rpc_client::RpcClient;
use solana_metrics::datapoint_error;
use solana_sdk::{clock::Slot, native_token::lamports_to_sol, pubkey::Pubkey};
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_stake_accounts_info(data_file: &str) -> StakeAccountsInfo {
fn load_accounts_info(data_file: &str) -> AccountsInfo {
let data_file_new = data_file.to_owned() + "new";
let stake_accounts_info = solana_cli_config::load_config_file(&data_file_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_stake_accounts_info(data_file, &stake_accounts_info).expect("save_stake_accounts_info");
save_accounts_info(data_file, &accounts_info).expect("save_accounts_info");
stake_accounts_info
accounts_info
}
fn save_stake_accounts_info(
data_file: &str,
stake_accounts_info: &StakeAccountsInfo,
) -> io::Result<()> {
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(&stake_accounts_info, &data_file_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: String, json_rpc_url: String, first_slot: Slot, batch_size: u64) {
let mut stake_accounts_info = load_stake_accounts_info(&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 stake_accounts_info.slot < first_slot {
stake_accounts_info.slot = first_slot;
if accounts_info.slot < first_slot {
accounts_info.slot = first_slot;
}
loop {
process_slots(&rpc_client, &mut stake_accounts_info, batch_size);
save_stake_accounts_info(&data_file, &stake_accounts_info).unwrap_or_else(|err| {
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 stake_accounts_info: {}", err),
format!("failed to save accounts_info: {}", err),
String
)
);
@@ -58,26 +56,54 @@ fn command_record(data_file: String, json_rpc_url: String, first_slot: Slot, bat
}
}
fn command_check(data_file: String, stake_account_pubkey: Pubkey) {
let stake_accounts_info = load_stake_accounts_info(&data_file);
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");
if let Some(stake_account_info) = stake_accounts_info
.account_info
.get(&stake_account_pubkey.to_string())
{
if let Some(slot) = stake_account_info.compliant_since {
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!(
"{}Stake account compliant since slot {} with a balance of {} SOL",
"{}Account compliant since slot {} with a balance of {} SOL",
Emoji("", ""),
slot,
lamports_to_sol(stake_account_info.lamports)
lamports_to_sol(account_info.lamports)
);
process::exit(0);
} else {
eprintln!(
"{}Stake account not compliant due to: {:?}",
"{}Account not compliant due to: {:?}",
Emoji("", ""),
stake_account_info.transactions.last().unwrap()
account_info.transactions.last().unwrap()
);
process::exit(1);
}
@@ -107,30 +133,30 @@ fn main() {
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({
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
}
})
.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(
Arg::with_name("first_slot")
.long("--first-slot")
@@ -151,37 +177,53 @@ fn main() {
)
.subcommand(
SubCommand::with_name("check")
.about("Check if a state account is in compliance")
.about("Check if an account is in compliance")
.arg(
Arg::with_name("stake_account_pubkey")
Arg::with_name("account_address")
.index(1)
.value_name("ADDRESS")
.validator(is_pubkey)
.required(true)
.help("Stake account address"),
.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);
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
});
command_record(data_file, json_rpc_url, first_slot, batch_size);
command_record(&data_file, json_rpc_url, first_slot, batch_size);
}
("check", Some(matches)) => {
let stake_account_pubkey = pubkey_of(&matches, "stake_account_pubkey").unwrap();
command_check(data_file, stake_account_pubkey);
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!(),
}