@ -2,7 +2,7 @@ use clap::{crate_description, crate_name, crate_version, value_t, value_t_or_exi
|
|||||||
use log::*;
|
use log::*;
|
||||||
use solana_clap_utils::{
|
use solana_clap_utils::{
|
||||||
input_parsers::{keypair_of, pubkey_of},
|
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::{
|
use solana_client::{
|
||||||
client_error, rpc_client::RpcClient, rpc_request::MAX_GET_SIGNATURE_STATUSES_QUERY_ITEMS,
|
client_error, rpc_client::RpcClient, rpc_request::MAX_GET_SIGNATURE_STATUSES_QUERY_ITEMS,
|
||||||
@ -34,7 +34,7 @@ use std::{
|
|||||||
time::{Duration, Instant},
|
time::{Duration, Instant},
|
||||||
};
|
};
|
||||||
|
|
||||||
mod whitelist;
|
mod validator_list;
|
||||||
|
|
||||||
struct Config {
|
struct Config {
|
||||||
json_rpc_url: String,
|
json_rpc_url: String,
|
||||||
@ -42,18 +42,19 @@ struct Config {
|
|||||||
source_stake_address: Pubkey,
|
source_stake_address: Pubkey,
|
||||||
authorized_staker: Keypair,
|
authorized_staker: Keypair,
|
||||||
|
|
||||||
/// Only validators with an identity pubkey in this whitelist will be staked
|
/// Only validators with an identity pubkey in this validator_list will be staked
|
||||||
whitelist: HashSet<Pubkey>,
|
validator_list: HashSet<Pubkey>,
|
||||||
|
|
||||||
dry_run: bool,
|
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,
|
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,
|
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,
|
quality_block_producer_percentage: usize,
|
||||||
|
|
||||||
/// A delinquent validator gets this number of slots of grace (from the current slot) before it
|
/// 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")
|
.help("Name of the cluster to operate on")
|
||||||
)
|
)
|
||||||
.arg(
|
.arg(
|
||||||
Arg::with_name("whitelist_file")
|
Arg::with_name("validator_list_file")
|
||||||
.long("whitelist")
|
.long("validator-list")
|
||||||
.value_name("FILE")
|
.value_name("FILE")
|
||||||
.required(true)
|
.required(true)
|
||||||
.takes_value(true)
|
.takes_value(true)
|
||||||
@ -128,7 +129,17 @@ fn get_config() -> Config {
|
|||||||
.validator(is_keypair)
|
.validator(is_keypair)
|
||||||
.required(true)
|
.required(true)
|
||||||
.takes_value(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") {
|
let config = if let Some(config_file) = matches.value_of("config_file") {
|
||||||
solana_cli_config::Config::load(config_file).unwrap_or_default()
|
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 authorized_staker = keypair_of(&matches, "authorized_staker").unwrap();
|
||||||
let dry_run = !matches.is_present("confirm");
|
let dry_run = !matches.is_present("confirm");
|
||||||
let cluster = value_t!(matches, "cluster", String).unwrap_or_else(|_| "unknown".into());
|
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" => (
|
"mainnet-beta" => (
|
||||||
"http://api.mainnet-beta.solana.com".into(),
|
"http://api.mainnet-beta.solana.com".into(),
|
||||||
whitelist::mainnet_beta_validators(),
|
validator_list::mainnet_beta_validators(),
|
||||||
),
|
),
|
||||||
"testnet" => (
|
"testnet" => (
|
||||||
"http://testnet.solana.com".into(),
|
"http://testnet.solana.com".into(),
|
||||||
whitelist::testnet_validators(),
|
validator_list::testnet_validators(),
|
||||||
),
|
),
|
||||||
"unknown" => {
|
"unknown" => {
|
||||||
let whitelist_file = File::open(value_t_or_exit!(matches, "whitelist_file", PathBuf))
|
let validator_list_file =
|
||||||
.unwrap_or_else(|err| {
|
File::open(value_t_or_exit!(matches, "validator_list_file", PathBuf))
|
||||||
error!("Unable to open whitelist: {}", err);
|
.unwrap_or_else(|err| {
|
||||||
process::exit(1);
|
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| {
|
.unwrap_or_else(|err| {
|
||||||
error!("Unable to read whitelist: {}", err);
|
error!("Unable to read validator_list: {}", err);
|
||||||
process::exit(1);
|
process::exit(1);
|
||||||
})
|
})
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.map(|p| {
|
.map(|p| {
|
||||||
Pubkey::from_str(&p).unwrap_or_else(|err| {
|
Pubkey::from_str(&p).unwrap_or_else(|err| {
|
||||||
error!("Invalid whitelist pubkey '{}': {}", p, err);
|
error!("Invalid validator_list pubkey '{}': {}", p, err);
|
||||||
process::exit(1);
|
process::exit(1);
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
.collect();
|
.collect();
|
||||||
(
|
(
|
||||||
value_t!(matches, "json_rpc_url", String).unwrap_or_else(|_| config.json_rpc_url),
|
value_t!(matches, "json_rpc_url", String).unwrap_or_else(|_| config.json_rpc_url),
|
||||||
whitelist,
|
validator_list,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
_ => unreachable!(),
|
_ => unreachable!(),
|
||||||
};
|
};
|
||||||
let whitelist = whitelist.into_iter().collect::<HashSet<_>>();
|
let validator_list = validator_list.into_iter().collect::<HashSet<_>>();
|
||||||
|
|
||||||
let config = Config {
|
let config = Config {
|
||||||
json_rpc_url,
|
json_rpc_url,
|
||||||
cluster,
|
cluster,
|
||||||
source_stake_address,
|
source_stake_address,
|
||||||
authorized_staker,
|
authorized_staker,
|
||||||
whitelist,
|
validator_list,
|
||||||
dry_run,
|
dry_run,
|
||||||
baseline_stake_amount: sol_to_lamports(5000.),
|
baseline_stake_amount: sol_to_lamports(5000.),
|
||||||
bonus_stake_amount: sol_to_lamports(50_000.),
|
bonus_stake_amount: sol_to_lamports(50_000.),
|
||||||
delinquent_grace_slot_distance: 21600, // ~24 hours worth of slots at 2.5 slots per second
|
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);
|
info!("RPC URL: {}", config.json_rpc_url);
|
||||||
@ -279,7 +293,8 @@ fn classify_block_producers(
|
|||||||
);
|
);
|
||||||
if validator_slots > 0 {
|
if validator_slots > 0 {
|
||||||
let validator_identity = Pubkey::from_str(&validator_identity)?;
|
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);
|
quality_block_producers.insert(validator_identity);
|
||||||
} else {
|
} else {
|
||||||
poor_block_producers.insert(validator_identity);
|
poor_block_producers.insert(validator_identity);
|
||||||
@ -563,7 +578,7 @@ fn main() -> Result<(), Box<dyn error::Error>> {
|
|||||||
let (quality_block_producers, _poor_block_producers) =
|
let (quality_block_producers, _poor_block_producers) =
|
||||||
classify_block_producers(&rpc_client, &config, last_epoch)?;
|
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_status = rpc_client.get_vote_accounts()?;
|
||||||
let vote_account_info = vote_account_status
|
let vote_account_info = vote_account_status
|
||||||
.current
|
.current
|
||||||
@ -571,7 +586,7 @@ fn main() -> Result<(), Box<dyn error::Error>> {
|
|||||||
.chain(vote_account_status.delinquent.into_iter())
|
.chain(vote_account_status.delinquent.into_iter())
|
||||||
.filter_map(|vai| {
|
.filter_map(|vai| {
|
||||||
let node_pubkey = Pubkey::from_str(&vai.node_pubkey).ok()?;
|
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)
|
Some(vai)
|
||||||
} else {
|
} else {
|
||||||
None
|
None
|
||||||
|
Reference in New Issue
Block a user