2023-08-12 01:58:41 -07:00
|
|
|
use anyhow::bail;
|
|
|
|
use solana_client::rpc_client::RpcClient;
|
2023-08-11 00:13:31 -07:00
|
|
|
use solana_ledger::leader_schedule::LeaderSchedule;
|
|
|
|
use solana_sdk::clock::NUM_CONSECUTIVE_LEADER_SLOTS;
|
2023-08-12 01:58:41 -07:00
|
|
|
use solana_sdk::commitment_config::CommitmentConfig;
|
2023-08-11 00:13:31 -07:00
|
|
|
use solana_sdk::epoch_info::EpochInfo;
|
|
|
|
use solana_sdk::pubkey::Pubkey;
|
|
|
|
use std::collections::HashMap;
|
2023-08-12 01:58:41 -07:00
|
|
|
use std::str::FromStr;
|
2023-08-11 00:13:31 -07:00
|
|
|
|
2023-08-17 08:01:49 -07:00
|
|
|
const MAX_EPOCH_VALUE: u64 = 18446744073709551615;
|
|
|
|
|
2023-08-11 00:13:31 -07:00
|
|
|
pub fn calculate_leader_schedule_from_stake_map(
|
|
|
|
stake_map: &crate::stakestore::StakeMap,
|
|
|
|
current_epoch_info: &EpochInfo,
|
2023-08-12 01:58:41 -07:00
|
|
|
) -> anyhow::Result<LeaderSchedule> {
|
2023-08-11 00:13:31 -07:00
|
|
|
let mut stakes = HashMap::<Pubkey, u64>::new();
|
2023-08-18 07:14:04 -07:00
|
|
|
//log::trace!("calculate_leader_schedule_from_stake_map stake_map:{stake_map:?} current_epoch_info:{current_epoch_info:?}");
|
2023-08-11 00:13:31 -07:00
|
|
|
for storestake in stake_map.values() {
|
2023-08-17 08:01:49 -07:00
|
|
|
//log::info!("Program_accounts stake:{stake:#?}");
|
|
|
|
//On test validator all stakes are attributes to an account with stake.delegation.activation_epoch == MAX_EPOCH_VALUE.
|
|
|
|
//It's considered as activated stake.
|
|
|
|
if storestake.stake.activation_epoch == MAX_EPOCH_VALUE {
|
|
|
|
log::info!("Found account with stake.delegation.activation_epoch == MAX_EPOCH_VALUE use it: {}", storestake.pubkey.to_string());
|
|
|
|
} else {
|
|
|
|
// Ignore stake accounts activated in this epoch (or later, to include activation_epoch of
|
|
|
|
// u64::MAX which indicates no activation ever happened)
|
|
|
|
if storestake.stake.activation_epoch >= current_epoch_info.epoch {
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
// Ignore stake accounts deactivated before this epoch
|
|
|
|
if storestake.stake.deactivation_epoch < current_epoch_info.epoch {
|
|
|
|
continue;
|
|
|
|
}
|
2023-08-11 00:13:31 -07:00
|
|
|
}
|
2023-08-17 08:01:49 -07:00
|
|
|
|
2023-08-11 00:13:31 -07:00
|
|
|
// Add the stake in this stake account to the total for the delegated-to vote account
|
|
|
|
*(stakes.entry(storestake.stake.voter_pubkey).or_insert(0)) += storestake.stake.stake;
|
|
|
|
}
|
|
|
|
calculate_leader_schedule(stakes, current_epoch_info)
|
|
|
|
}
|
|
|
|
|
|
|
|
//Copied from leader_schedule_utils.rs
|
|
|
|
// Mostly cribbed from leader_schedule_utils
|
|
|
|
fn calculate_leader_schedule(
|
|
|
|
stakes: HashMap<Pubkey, u64>,
|
|
|
|
current_epoch_info: &EpochInfo,
|
2023-08-12 01:58:41 -07:00
|
|
|
) -> anyhow::Result<LeaderSchedule> {
|
|
|
|
if stakes.is_empty() {
|
|
|
|
bail!("calculate_leader_schedule stakes list is empty. no schedule can be calculated.");
|
|
|
|
}
|
2023-08-11 00:13:31 -07:00
|
|
|
let mut seed = [0u8; 32];
|
|
|
|
seed[0..8].copy_from_slice(¤t_epoch_info.epoch.to_le_bytes());
|
|
|
|
let mut stakes: Vec<_> = stakes
|
|
|
|
.iter()
|
|
|
|
.map(|(pubkey, stake)| (*pubkey, *stake))
|
|
|
|
.collect();
|
|
|
|
sort_stakes(&mut stakes);
|
2023-08-12 01:58:41 -07:00
|
|
|
log::trace!("calculate_leader_schedule stakes:{stakes:?}");
|
|
|
|
Ok(LeaderSchedule::new(
|
2023-08-11 00:13:31 -07:00
|
|
|
&stakes,
|
|
|
|
seed,
|
|
|
|
current_epoch_info.slots_in_epoch,
|
|
|
|
NUM_CONSECUTIVE_LEADER_SLOTS,
|
2023-08-12 01:58:41 -07:00
|
|
|
))
|
2023-08-11 00:13:31 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
// Cribbed from leader_schedule_utils
|
|
|
|
fn sort_stakes(stakes: &mut Vec<(Pubkey, u64)>) {
|
|
|
|
// Sort first by stake. If stakes are the same, sort by pubkey to ensure a
|
|
|
|
// deterministic result.
|
|
|
|
// Note: Use unstable sort, because we dedup right after to remove the equal elements.
|
|
|
|
stakes.sort_unstable_by(|(l_pubkey, l_stake), (r_pubkey, r_stake)| {
|
|
|
|
if r_stake == l_stake {
|
|
|
|
r_pubkey.cmp(l_pubkey)
|
|
|
|
} else {
|
|
|
|
r_stake.cmp(l_stake)
|
|
|
|
}
|
|
|
|
});
|
|
|
|
|
|
|
|
// Now that it's sorted, we can do an O(n) dedup.
|
|
|
|
stakes.dedup();
|
|
|
|
}
|
2023-08-12 01:58:41 -07:00
|
|
|
|
|
|
|
pub fn verify_schedule(schedule: LeaderSchedule, rpc_url: String) -> anyhow::Result<()> {
|
|
|
|
let rpc_client = RpcClient::new_with_commitment(rpc_url, CommitmentConfig::confirmed());
|
2023-08-18 07:14:04 -07:00
|
|
|
let Some(rpc_leader_schedule) = rpc_client.get_leader_schedule(None)? else {
|
2023-08-12 01:58:41 -07:00
|
|
|
log::info!("verify_schedule RPC return no schedule. Try later.");
|
|
|
|
return Ok(());
|
|
|
|
};
|
|
|
|
|
2023-08-17 08:01:49 -07:00
|
|
|
log::info!("");
|
|
|
|
|
|
|
|
let vote_account = rpc_client.get_vote_accounts()?;
|
|
|
|
let note_vote_table = vote_account
|
|
|
|
.current
|
|
|
|
.into_iter()
|
|
|
|
.chain(vote_account.delinquent.into_iter())
|
|
|
|
.map(|va| (va.node_pubkey, va.vote_pubkey))
|
|
|
|
.collect::<HashMap<String, String>>();
|
|
|
|
|
2023-08-18 07:14:04 -07:00
|
|
|
//log::info!("note_vote_table:{note_vote_table:?}");
|
2023-08-17 08:01:49 -07:00
|
|
|
|
2023-08-12 01:58:41 -07:00
|
|
|
//map leaderscheudle to HashMap<PubKey, Vec<slot>>
|
|
|
|
let mut input_leader_schedule: HashMap<Pubkey, Vec<usize>> = HashMap::new();
|
2023-08-12 02:00:30 -07:00
|
|
|
for (slot, pubkey) in schedule.get_slot_leaders().iter().copied().enumerate() {
|
2023-08-12 01:58:41 -07:00
|
|
|
input_leader_schedule
|
|
|
|
.entry(pubkey)
|
|
|
|
.or_insert(vec![])
|
|
|
|
.push(slot);
|
|
|
|
}
|
|
|
|
|
2023-08-17 08:01:49 -07:00
|
|
|
//map rpc leader schedule node pubkey to vote account
|
2023-08-18 07:14:04 -07:00
|
|
|
let mut rpc_leader_schedule: HashMap<&String, Vec<usize>> = rpc_leader_schedule.into_iter().filter_map(|(pk, slots)| match note_vote_table.get(&pk) {
|
2023-08-17 08:01:49 -07:00
|
|
|
Some(vote_account) => Some((vote_account,slots)),
|
|
|
|
None => {
|
|
|
|
log::warn!("verify_schedule RPC get_leader_schedule return some Node account:{pk} that are not mapped by rpc get_vote_accounts");
|
|
|
|
None
|
|
|
|
},
|
|
|
|
}).collect();
|
|
|
|
|
2023-08-18 07:14:04 -07:00
|
|
|
//log::trace!("verify_schedule calculated_leader_schedule:{input_leader_schedule:?} RPC leader schedule:{rpc_leader_schedule:?}");
|
2023-08-17 08:01:49 -07:00
|
|
|
|
2023-08-12 01:58:41 -07:00
|
|
|
let mut vote_account_in_error: Vec<Pubkey> = input_leader_schedule.into_iter().filter_map(|(input_vote_key, mut input_slot_list)| {
|
2023-08-18 07:14:04 -07:00
|
|
|
let Some(mut rpc_strake_list) = rpc_leader_schedule.remove(&input_vote_key.to_string()) else {
|
2023-08-12 01:58:41 -07:00
|
|
|
log::warn!("verify_schedule vote account not found in RPC:{input_vote_key}");
|
|
|
|
return Some(input_vote_key);
|
|
|
|
};
|
|
|
|
input_slot_list.sort();
|
|
|
|
rpc_strake_list.sort();
|
2023-08-12 02:00:30 -07:00
|
|
|
if input_slot_list.into_iter().zip(rpc_strake_list.into_iter()).any(|(in_v, rpc)| in_v != rpc) {
|
2023-08-12 01:58:41 -07:00
|
|
|
log::warn!("verify_schedule bad slots for {input_vote_key}"); // Caluclated:{input_slot_list:?} rpc:{rpc_strake_list:?}
|
|
|
|
Some(input_vote_key)
|
|
|
|
} else {
|
|
|
|
None
|
|
|
|
}
|
|
|
|
}).collect();
|
|
|
|
|
2023-08-18 07:14:04 -07:00
|
|
|
if !rpc_leader_schedule.is_empty() {
|
2023-08-12 01:58:41 -07:00
|
|
|
log::warn!(
|
|
|
|
"verify_schedule RPC vote account not present in calculated schedule:{:?}",
|
2023-08-18 07:14:04 -07:00
|
|
|
rpc_leader_schedule.keys()
|
2023-08-12 01:58:41 -07:00
|
|
|
);
|
|
|
|
vote_account_in_error.append(
|
2023-08-18 07:14:04 -07:00
|
|
|
&mut rpc_leader_schedule
|
2023-08-12 01:58:41 -07:00
|
|
|
.keys()
|
2023-08-12 02:00:30 -07:00
|
|
|
.map(|sk| Pubkey::from_str(sk).unwrap())
|
2023-08-12 01:58:41 -07:00
|
|
|
.collect::<Vec<Pubkey>>(),
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
log::info!("verify_schedule these account are wrong:{vote_account_in_error:?}");
|
|
|
|
Ok(())
|
|
|
|
}
|