use std::{
collections::HashMap,
net::IpAddr,
str::FromStr,
sync::{
atomic::{AtomicBool, Ordering},
Arc, Mutex, RwLock,
},
thread::{self, sleep, Builder, JoinHandle},
time::{Duration, Instant},
};
use jito_rpc::load_balancer::LoadBalancer;
use solana_client::{client_error, rpc_response::RpcContactInfo};
use solana_sdk::pubkey::Pubkey;
use solana_streamer::streamer::StakedNodes;
const IP_TO_STAKE_REFRESH_DURATION: Duration = Duration::from_secs(5);
pub struct StakedNodesUpdaterService {
thread_hdl: JoinHandle<()>,
}
impl StakedNodesUpdaterService {
pub fn new(
exit: Arc<AtomicBool>,
rpc_load_balancer: Arc<Mutex<LoadBalancer>>,
shared_staked_nodes: Arc<RwLock<StakedNodes>>,
) -> Self {
let thread_hdl = Builder::new()
.name("sol-sn-updater".to_string())
.spawn(move || {
let mut last_stakes = Instant::now();
while !exit.load(Ordering::Relaxed) {
let mut new_ip_to_stake = HashMap::new();
let mut total_stake = 0;
let mut new_pubkey_stake_map: HashMap<Pubkey, u64> = HashMap::new();
if let Ok(true) = Self::try_refresh_ip_to_stake(
&mut last_stakes,
&mut new_ip_to_stake,
&mut new_pubkey_stake_map,
&mut total_stake,
&rpc_load_balancer,
) {
let mut shared = shared_staked_nodes.write().unwrap();
shared.total_stake = total_stake;
shared.ip_stake_map = new_ip_to_stake;
shared.pubkey_stake_map = new_pubkey_stake_map;
}
}
})
.unwrap();
Self { thread_hdl }
}
fn try_refresh_ip_to_stake(
last_stakes: &mut Instant,
ip_to_stake: &mut HashMap<IpAddr, u64>,
pubkey_stake_map: &mut HashMap<Pubkey, u64>,
total_stake: &mut u64,
rpc_load_balancer: &Arc<Mutex<LoadBalancer>>,
) -> client_error::Result<bool> {
if last_stakes.elapsed() > IP_TO_STAKE_REFRESH_DURATION {
let client = rpc_load_balancer.lock().unwrap().rpc_client();
let vote_accounts = client.get_vote_accounts()?;
let cluster_info: HashMap<String, RpcContactInfo> = client
.get_cluster_nodes()?
.into_iter()
.map(|contact_info| (contact_info.pubkey.to_string(), contact_info))
.collect();
*total_stake = vote_accounts
.current
.iter()
.chain(vote_accounts.delinquent.iter())
.map(|acc| acc.activated_stake)
.sum();
*ip_to_stake = vote_accounts
.current
.iter()
.chain(vote_accounts.delinquent.iter())
.filter_map(
|vote_account| match cluster_info.get(&vote_account.node_pubkey) {
None => None,
Some(node_info) => {
Some((node_info.gossip?.ip(), vote_account.activated_stake))
}
},
)
.collect();
*pubkey_stake_map = vote_accounts
.current
.iter()
.chain(vote_accounts.delinquent.iter())
.filter_map(|vote_account| {
Some((
Pubkey::from_str(&vote_account.node_pubkey).ok()?,
vote_account.activated_stake,
))
})
.collect();
*last_stakes = Instant::now();
Ok(true)
} else {
sleep(Duration::from_millis(1));
Ok(false)
}
}
pub fn join(self) -> thread::Result<()> {
self.thread_hdl.join()
}
}