@ -2,7 +2,7 @@ use clap::{crate_description, crate_name, crate_version, value_t, value_t_or_exi
|
||||
use log::*;
|
||||
use solana_clap_utils::{
|
||||
input_parsers::{keypair_of, pubkey_of},
|
||||
input_validators::{is_keypair, is_pubkey_or_keypair, is_url},
|
||||
input_validators::{is_keypair, is_pubkey_or_keypair, is_url, is_valid_percentage},
|
||||
};
|
||||
use solana_client::{
|
||||
client_error, rpc_client::RpcClient, rpc_request::MAX_GET_SIGNATURE_STATUSES_QUERY_ITEMS,
|
||||
@ -34,7 +34,7 @@ use std::{
|
||||
time::{Duration, Instant},
|
||||
};
|
||||
|
||||
mod whitelist;
|
||||
mod validator_list;
|
||||
|
||||
struct Config {
|
||||
json_rpc_url: String,
|
||||
@ -42,18 +42,19 @@ struct Config {
|
||||
source_stake_address: Pubkey,
|
||||
authorized_staker: Keypair,
|
||||
|
||||
/// Only validators with an identity pubkey in this whitelist will be staked
|
||||
whitelist: HashSet<Pubkey>,
|
||||
/// Only validators with an identity pubkey in this validator_list will be staked
|
||||
validator_list: HashSet<Pubkey>,
|
||||
|
||||
dry_run: bool,
|
||||
|
||||
/// Amount of lamports to stake any validator in the whitelist that is not delinquent
|
||||
/// Amount of lamports to stake any validator in the validator_list that is not delinquent
|
||||
baseline_stake_amount: u64,
|
||||
|
||||
/// Amount of additional lamports to stake quality block producers in the whitelist
|
||||
/// Amount of additional lamports to stake quality block producers in the validator_list
|
||||
bonus_stake_amount: u64,
|
||||
|
||||
/// Quality validators produce a block in more than this percentage of their leader slots
|
||||
/// Quality validators produce a block at least this percentage of their leader slots over the
|
||||
/// previous epoch
|
||||
quality_block_producer_percentage: usize,
|
||||
|
||||
/// A delinquent validator gets this number of slots of grace (from the current slot) before it
|
||||
@ -98,8 +99,8 @@ fn get_config() -> Config {
|
||||
.help("Name of the cluster to operate on")
|
||||
)
|
||||
.arg(
|
||||
Arg::with_name("whitelist_file")
|
||||
.long("whitelist")
|
||||
Arg::with_name("validator_list_file")
|
||||
.long("validator-list")
|
||||
.value_name("FILE")
|
||||
.required(true)
|
||||
.takes_value(true)
|
||||
@ -128,7 +129,17 @@ fn get_config() -> Config {
|
||||
.validator(is_keypair)
|
||||
.required(true)
|
||||
.takes_value(true)
|
||||
) .get_matches();
|
||||
)
|
||||
.arg(
|
||||
Arg::with_name("quality_block_producer_percentage")
|
||||
.long("quality-block-producer-percentage")
|
||||
.value_name("PERCENTAGE")
|
||||
.takes_value(true)
|
||||
.default_value("75")
|
||||
.validator(is_valid_percentage)
|
||||
.help("Quality validators produce a block in at least this percentage of their leader slots over the previous epoch")
|
||||
)
|
||||
.get_matches();
|
||||
|
||||
let config = if let Some(config_file) = matches.value_of("config_file") {
|
||||
solana_cli_config::Config::load(config_file).unwrap_or_default()
|
||||
@ -140,56 +151,59 @@ fn get_config() -> Config {
|
||||
let authorized_staker = keypair_of(&matches, "authorized_staker").unwrap();
|
||||
let dry_run = !matches.is_present("confirm");
|
||||
let cluster = value_t!(matches, "cluster", String).unwrap_or_else(|_| "unknown".into());
|
||||
let quality_block_producer_percentage =
|
||||
value_t_or_exit!(matches, "quality_block_producer_percentage", usize);
|
||||
|
||||
let (json_rpc_url, whitelist) = match cluster.as_str() {
|
||||
let (json_rpc_url, validator_list) = match cluster.as_str() {
|
||||
"mainnet-beta" => (
|
||||
"http://api.mainnet-beta.solana.com".into(),
|
||||
whitelist::mainnet_beta_validators(),
|
||||
validator_list::mainnet_beta_validators(),
|
||||
),
|
||||
"testnet" => (
|
||||
"http://testnet.solana.com".into(),
|
||||
whitelist::testnet_validators(),
|
||||
validator_list::testnet_validators(),
|
||||
),
|
||||
"unknown" => {
|
||||
let whitelist_file = File::open(value_t_or_exit!(matches, "whitelist_file", PathBuf))
|
||||
.unwrap_or_else(|err| {
|
||||
error!("Unable to open whitelist: {}", err);
|
||||
process::exit(1);
|
||||
});
|
||||
let validator_list_file =
|
||||
File::open(value_t_or_exit!(matches, "validator_list_file", PathBuf))
|
||||
.unwrap_or_else(|err| {
|
||||
error!("Unable to open validator_list: {}", err);
|
||||
process::exit(1);
|
||||
});
|
||||
|
||||
let whitelist = serde_yaml::from_reader::<_, Vec<String>>(whitelist_file)
|
||||
let validator_list = serde_yaml::from_reader::<_, Vec<String>>(validator_list_file)
|
||||
.unwrap_or_else(|err| {
|
||||
error!("Unable to read whitelist: {}", err);
|
||||
error!("Unable to read validator_list: {}", err);
|
||||
process::exit(1);
|
||||
})
|
||||
.into_iter()
|
||||
.map(|p| {
|
||||
Pubkey::from_str(&p).unwrap_or_else(|err| {
|
||||
error!("Invalid whitelist pubkey '{}': {}", p, err);
|
||||
error!("Invalid validator_list pubkey '{}': {}", p, err);
|
||||
process::exit(1);
|
||||
})
|
||||
})
|
||||
.collect();
|
||||
(
|
||||
value_t!(matches, "json_rpc_url", String).unwrap_or_else(|_| config.json_rpc_url),
|
||||
whitelist,
|
||||
validator_list,
|
||||
)
|
||||
}
|
||||
_ => unreachable!(),
|
||||
};
|
||||
let whitelist = whitelist.into_iter().collect::<HashSet<_>>();
|
||||
let validator_list = validator_list.into_iter().collect::<HashSet<_>>();
|
||||
|
||||
let config = Config {
|
||||
json_rpc_url,
|
||||
cluster,
|
||||
source_stake_address,
|
||||
authorized_staker,
|
||||
whitelist,
|
||||
validator_list,
|
||||
dry_run,
|
||||
baseline_stake_amount: sol_to_lamports(5000.),
|
||||
bonus_stake_amount: sol_to_lamports(50_000.),
|
||||
delinquent_grace_slot_distance: 21600, // ~24 hours worth of slots at 2.5 slots per second
|
||||
quality_block_producer_percentage: 75,
|
||||
quality_block_producer_percentage,
|
||||
};
|
||||
|
||||
info!("RPC URL: {}", config.json_rpc_url);
|
||||
@ -279,7 +293,8 @@ fn classify_block_producers(
|
||||
);
|
||||
if validator_slots > 0 {
|
||||
let validator_identity = Pubkey::from_str(&validator_identity)?;
|
||||
if validator_blocks * 100 / validator_slots > config.quality_block_producer_percentage {
|
||||
if validator_blocks * 100 / validator_slots >= config.quality_block_producer_percentage
|
||||
{
|
||||
quality_block_producers.insert(validator_identity);
|
||||
} else {
|
||||
poor_block_producers.insert(validator_identity);
|
||||
@ -563,7 +578,7 @@ fn main() -> Result<(), Box<dyn error::Error>> {
|
||||
let (quality_block_producers, _poor_block_producers) =
|
||||
classify_block_producers(&rpc_client, &config, last_epoch)?;
|
||||
|
||||
// Fetch vote account status for all the whitelisted validators
|
||||
// Fetch vote account status for all the validator_listed validators
|
||||
let vote_account_status = rpc_client.get_vote_accounts()?;
|
||||
let vote_account_info = vote_account_status
|
||||
.current
|
||||
@ -571,7 +586,7 @@ fn main() -> Result<(), Box<dyn error::Error>> {
|
||||
.chain(vote_account_status.delinquent.into_iter())
|
||||
.filter_map(|vai| {
|
||||
let node_pubkey = Pubkey::from_str(&vai.node_pubkey).ok()?;
|
||||
if config.whitelist.contains(&node_pubkey) {
|
||||
if config.validator_list.contains(&node_pubkey) {
|
||||
Some(vai)
|
||||
} else {
|
||||
None
|
||||
|
Reference in New Issue
Block a user