first impl for RPC get_vote_accounts

This commit is contained in:
musitdev 2023-10-25 19:26:59 +02:00
parent 7fba107165
commit b3317667a6
11 changed files with 752 additions and 282 deletions

View File

@ -1,7 +1,37 @@
use crate::stores::block_information_store::BlockInformation;
use crate::stores::data_cache::DataCache;
use solana_rpc_client_api::config::RpcGetVoteAccountsConfig;
use solana_sdk::commitment_config::CommitmentConfig;
use solana_sdk::pubkey::ParsePubkeyError;
use solana_sdk::pubkey::Pubkey;
use std::collections::HashMap;
use std::str::FromStr;
#[derive(Clone)]
pub struct GetVoteAccountsConfig {
pub vote_pubkey: Option<Pubkey>,
pub commitment: Option<CommitmentConfig>,
pub keep_unstaked_delinquents: Option<bool>,
pub delinquent_slot_distance: Option<u64>,
}
impl TryFrom<RpcGetVoteAccountsConfig> for GetVoteAccountsConfig {
type Error = ParsePubkeyError;
fn try_from(config: RpcGetVoteAccountsConfig) -> Result<Self, Self::Error> {
let vote_pubkey = config
.vote_pubkey
.as_ref()
.map(|pk| Pubkey::from_str(pk))
.transpose()?;
Ok(GetVoteAccountsConfig {
vote_pubkey,
commitment: config.commitment,
keep_unstaked_delinquents: config.keep_unstaked_delinquents,
delinquent_slot_distance: config.delinquent_slot_distance,
})
}
}
#[derive(Clone, Default)]
pub struct CalculatedSchedule {
@ -31,7 +61,7 @@ impl CalculatedSchedule {
let get_schedule = |schedule_data: Option<&LeaderScheduleData>| {
schedule_data.and_then(|current| {
(current.epoch == epoch.epoch).then_some(current.schedule.clone())
(current.epoch == epoch.epoch).then_some(current.schedule_by_node.clone())
})
};
get_schedule(self.current.as_ref()).or_else(|| get_schedule(self.next.as_ref()))
@ -40,6 +70,7 @@ impl CalculatedSchedule {
#[derive(Clone)]
pub struct LeaderScheduleData {
pub schedule: HashMap<String, Vec<usize>>,
pub schedule_by_node: HashMap<String, Vec<usize>>,
pub schedule_by_slot: Vec<Pubkey>,
pub epoch: u64,
}

View File

@ -55,6 +55,7 @@ Method calls:
##### Cluster info Domain
- [getclusternodes](https://docs.solana.com/api/http#getclusternodes) not in geyser plugin can be get from gossip. Try to update gyser first.
##### Validator Domain
- [getslot](https://docs.solana.com/api/http#getslot) Need top add 2 new commitment level for first shred seen and half confirm (1/3 of the stake has voted on the block)
- [getBlockHeight](https://docs.solana.com/api/http#getblockheight)

View File

@ -3,6 +3,8 @@ use crate::{
jsonrpsee_subscrption_handler_sink::JsonRpseeSubscriptionHandlerSink,
rpc::LiteRpcServer,
};
use solana_rpc_client_api::config::RpcGetVoteAccountsConfig;
use solana_rpc_client_api::response::RpcVoteAccountStatus;
use solana_sdk::epoch_info::EpochInfo;
use std::collections::HashMap;
@ -485,4 +487,20 @@ impl LiteRpcServer for LiteBridge {
.await;
Ok(schedule)
}
async fn get_slot_leaders(
&self,
start_slot: u64,
limit: u64,
) -> crate::rpc::Result<Vec<Pubkey>> {
todo!()
}
async fn get_vote_accounts(
&self,
config: Option<RpcGetVoteAccountsConfig>,
) -> crate::rpc::Result<RpcVoteAccountStatus> {
let config: GetVoteAccountsConfig =
GetVoteAccountsConfig::try_from(config.unwrap_or_default())?;
todo!();
}
}

View File

@ -1,6 +1,7 @@
use crate::configs::{IsBlockHashValidConfig, SendTransactionConfig};
use jsonrpsee::core::SubscriptionResult;
use jsonrpsee::proc_macros::rpc;
use solana_rpc_client_api::config::RpcGetVoteAccountsConfig;
use solana_rpc_client_api::config::{
RpcBlockConfig, RpcBlockSubscribeConfig, RpcBlockSubscribeFilter, RpcBlocksConfigWrapper,
RpcContextConfig, RpcEncodingConfigWrapper, RpcEpochConfig, RpcGetVoteAccountsConfig,
@ -13,9 +14,9 @@ use solana_rpc_client_api::response::{
RpcContactInfo, RpcLeaderSchedule, RpcPerfSample, RpcPrioritizationFee, RpcVersionInfo,
RpcVoteAccountStatus,
};
use solana_sdk::commitment_config::CommitmentConfig;
use solana_sdk::epoch_info::EpochInfo;
use solana_sdk::pubkey::Pubkey;
use solana_sdk::slot_history::Slot;
use solana_transaction_status::{TransactionStatus, UiConfirmedBlock};
use std::collections::HashMap;
@ -238,4 +239,17 @@ pub trait LiteRpc {
slot: Option<u64>,
config: Option<RpcLeaderScheduleConfig>,
) -> crate::rpc::Result<Option<HashMap<String, Vec<usize>>>>;
#[method(name = "getSlotLeaders")]
async fn get_slot_leaders(
&self,
start_slot: u64,
limit: u64,
) -> crate::rpc::Result<Vec<Pubkey>>;
#[method(name = "getVoteAccounts")]
async fn get_vote_accounts(
&self,
config: Option<RpcGetVoteAccountsConfig>,
) -> crate::rpc::Result<RpcVoteAccountStatus>;
}

View File

@ -1,9 +1,13 @@
use crate::epoch::ScheduleEpochData;
use crate::leader_schedule::LeaderScheduleGeneratedData;
use crate::stake::StakeMap;
use crate::stake::StakeStore;
use crate::utils::{Takable, TakeResult};
use crate::vote::EpochVoteStakes;
use crate::vote::VoteMap;
use crate::vote::VoteStore;
use anyhow::bail;
use futures::future::join_all;
use futures_util::stream::FuturesUnordered;
use solana_client::client_error::ClientError;
use solana_client::rpc_client::RpcClient;
@ -71,17 +75,22 @@ pub fn run_bootstrap_events(
bootstrap_tasks: &mut FuturesUnordered<JoinHandle<BootstrapEvent>>,
stakestore: &mut StakeStore,
votestore: &mut VoteStore,
) -> anyhow::Result<Option<bool>> {
let result = process_bootstrap_event(event, stakestore, votestore);
slots_in_epoch: u64,
) -> anyhow::Result<Option<anyhow::Result<CalculatedSchedule>>> {
let result = process_bootstrap_event(event, stakestore, votestore, slots_in_epoch);
match result {
BootsrapProcessResult::TaskHandle(jh) => {
bootstrap_tasks.push(jh);
Ok(None)
}
BootsrapProcessResult::Event(event) => {
run_bootstrap_events(event, bootstrap_tasks, stakestore, votestore)
}
BootsrapProcessResult::End => Ok(Some(true)),
BootsrapProcessResult::Event(event) => run_bootstrap_events(
event,
bootstrap_tasks,
stakestore,
votestore,
slots_in_epoch,
),
BootsrapProcessResult::End(leader_schedule_result) => Ok(Some(leader_schedule_result)),
BootsrapProcessResult::Error(err) => bail!(err),
}
}
@ -105,7 +114,13 @@ pub enum BootstrapEvent {
Account,
String,
),
AccountsMerged(StakeMap, Option<StakeHistory>, VoteMap, String),
AccountsMerged(
StakeMap,
Option<StakeHistory>,
VoteMap,
String,
anyhow::Result<CalculatedSchedule>,
),
Exit,
}
@ -114,13 +129,14 @@ enum BootsrapProcessResult {
TaskHandle(JoinHandle<BootstrapEvent>),
Event(BootstrapEvent),
Error(String),
End,
End(anyhow::Result<CalculatedSchedule>),
}
fn process_bootstrap_event(
event: BootstrapEvent,
stakestore: &mut StakeStore,
votestore: &mut VoteStore,
slots_in_epoch: u64,
) -> BootsrapProcessResult {
match event {
BootstrapEvent::InitBootstrap {
@ -148,24 +164,30 @@ fn process_bootstrap_event(
}
BootstrapEvent::BootstrapAccountsFetched(stakes, votes, history, rpc_url) => {
log::info!("BootstrapEvent::BootstrapAccountsFetched RECV");
match (
StakeStore::take_stakestore(stakestore),
VoteStore::take_votestore(votestore),
) {
(Ok((stake_map, _)), Ok(vote_map)) => {
match (&mut stakestore.stakes, &mut votestore.votes).take() {
TakeResult::Map(((stake_map, _), vote_map)) => {
BootsrapProcessResult::Event(BootstrapEvent::StoreExtracted(
stake_map, vote_map, stakes, votes, history, rpc_url,
))
}
_ => {
let jh = tokio::spawn(async move {
tokio::time::sleep(Duration::from_secs(1)).await;
BootstrapEvent::BootstrapAccountsFetched(stakes, votes, history, rpc_url)
TakeResult::Taken(stake_notify) => {
let notif_jh = tokio::spawn({
async move {
let notifs = stake_notify
.iter()
.map(|n| n.notified())
.collect::<Vec<tokio::sync::futures::Notified>>();
join_all(notifs).await;
BootstrapEvent::BootstrapAccountsFetched(
stakes, votes, history, rpc_url,
)
}
});
BootsrapProcessResult::TaskHandle(jh)
BootsrapProcessResult::TaskHandle(notif_jh)
}
}
}
BootstrapEvent::StoreExtracted(
mut stake_map,
mut vote_map,
@ -198,18 +220,33 @@ fn process_bootstrap_event(
0, //with RPC no way to know the slot of the account update. Set to 0.
);
BootstrapEvent::AccountsMerged(stake_map, stake_history, vote_map, rpc_url)
let leader_schedule_result = bootstrap_current_leader_schedule(slots_in_epoch);
BootstrapEvent::AccountsMerged(
stake_map,
stake_history,
vote_map,
rpc_url,
leader_schedule_result,
)
}
});
BootsrapProcessResult::TaskHandle(jh)
}
BootstrapEvent::AccountsMerged(stake_map, stake_history, vote_map, rpc_url) => {
BootstrapEvent::AccountsMerged(
stake_map,
stake_history,
vote_map,
rpc_url,
leader_schedule_result,
) => {
log::info!("BootstrapEvent::AccountsMerged RECV");
match (
StakeStore::merge_stakestore(stakestore, stake_map, stake_history),
VoteStore::merge_votestore(votestore, vote_map),
stakestore.stakes.merge((stake_map, stake_history)),
votestore.votes.merge(vote_map),
) {
(Ok(()), Ok(())) => BootsrapProcessResult::End,
(Ok(()), Ok(())) => BootsrapProcessResult::End(leader_schedule_result),
_ => {
//TODO remove this error using type state
log::warn!("BootstrapEvent::AccountsMerged merge stake or vote fail, non extracted stake/vote map err, restart bootstrap");
@ -273,33 +310,41 @@ pub fn get_stakehistory_account(rpc_url: String) -> Result<Account, ClientError>
res_stake
}
// pub struct BootstrapScheduleResult {
// schedule: CalculatedSchedule,
// vote_stakes: Vec<EpochVoteStakes>,
// }
pub fn bootstrap_current_leader_schedule(
slots_in_epoch: u64,
) -> anyhow::Result<CalculatedSchedule> {
let (current_epoch, current_stakes) =
let (current_epoch, current_epoch_stakes) =
crate::utils::read_schedule_vote_stakes(CURRENT_EPOCH_VOTE_STAKES_FILE)?;
let (next_epoch, next_stakes) =
let (next_epoch, next_epoch_stakes) =
crate::utils::read_schedule_vote_stakes(NEXT_EPOCH_VOTE_STAKES_FILE)?;
//calcualte leader schedule for all vote stakes.
let current_schedule = crate::leader_schedule::calculate_leader_schedule(
&current_stakes,
&current_epoch_stakes,
current_epoch,
slots_in_epoch,
);
let next_schedule =
crate::leader_schedule::calculate_leader_schedule(&next_stakes, next_epoch, slots_in_epoch);
let next_schedule = crate::leader_schedule::calculate_leader_schedule(
&next_epoch_stakes,
next_epoch,
slots_in_epoch,
);
Ok(CalculatedSchedule {
current: Some(LeaderScheduleData {
schedule: current_schedule,
//TODO use epoch stake for get_vote_accounts
schedule_by_node: LeaderScheduleGeneratedData::get_schedule_by_nodes(&current_schedule),
schedule_by_slot: current_schedule.get_slot_leaders().to_vec(),
epoch: current_epoch,
}),
next: Some(LeaderScheduleData {
schedule: next_schedule,
//TODO use epoch stake for get_vote_accounts
// vote_stakes: next_stakes.stake_vote_map,
schedule_by_node: LeaderScheduleGeneratedData::get_schedule_by_nodes(&next_schedule),
schedule_by_slot: next_schedule.get_slot_leaders().to_vec(),
epoch: next_epoch,
}),
})

View File

@ -1,6 +1,8 @@
use crate::stake::{StakeMap, StakeStore};
use crate::utils::{Takable, TakeResult};
use crate::vote::StoredVote;
use crate::vote::{VoteMap, VoteStore};
use futures::future::join_all;
use futures::stream::FuturesUnordered;
use itertools::Itertools;
use serde::{Deserialize, Serialize};
@ -15,11 +17,24 @@ use tokio::task::JoinHandle;
#[derive(Debug)]
pub struct LeaderScheduleGeneratedData {
pub schedule: HashMap<String, Vec<usize>>,
pub vote_stakes: HashMap<Pubkey, (u64, Arc<StoredVote>)>,
pub schedule: LeaderSchedule,
pub epoch_vote_stakes: HashMap<Pubkey, (u64, Arc<StoredVote>)>,
pub epoch: u64,
}
impl LeaderScheduleGeneratedData {
pub fn get_schedule_by_nodes(schedule: &LeaderSchedule) -> HashMap<String, Vec<usize>> {
schedule
.get_slot_leaders()
.iter()
.enumerate()
.map(|(i, pk)| (pk.to_string(), i))
.into_group_map()
.into_iter()
.collect()
}
}
#[derive(Debug, Serialize, Deserialize)]
pub struct EpochStake {
epoch: u64,
@ -92,11 +107,8 @@ fn process_leadershedule_event(
) -> LeaderScheduleResult {
match event {
LeaderScheduleEvent::Init(new_epoch, slots_in_epoch, new_rate_activation_epoch) => {
match (
StakeStore::take_stakestore(stakestore),
VoteStore::take_votestore(votestore),
) {
(Ok((stake_map, mut stake_history)), Ok(vote_map)) => {
match (&mut stakestore.stakes, &mut votestore.votes).take() {
TakeResult::Map(((stake_map, mut stake_history), vote_map)) => {
log::info!("LeaderScheduleEvent::CalculateScedule");
//do the calculus in a blocking task.
let jh = tokio::task::spawn_blocking({
@ -142,12 +154,13 @@ fn process_leadershedule_event(
}
log::info!("End calculate leader schedule");
LeaderScheduleEvent::MergeStoreAndSaveSchedule(
stake_map,
vote_map,
LeaderScheduleGeneratedData {
schedule: leader_schedule,
vote_stakes: epoch_vote_stakes,
epoch_vote_stakes,
epoch: next_epoch,
},
(new_epoch, slots_in_epoch, new_rate_activation_epoch),
@ -157,13 +170,22 @@ fn process_leadershedule_event(
});
LeaderScheduleResult::TaskHandle(jh)
}
_ => {
log::error!("Create leadershedule init event error during extract store");
LeaderScheduleResult::Event(LeaderScheduleEvent::Init(
new_epoch,
slots_in_epoch,
new_rate_activation_epoch,
))
TakeResult::Taken(stake_notify) => {
let notif_jh = tokio::spawn({
async move {
let notifs = stake_notify
.iter()
.map(|n| n.notified())
.collect::<Vec<tokio::sync::futures::Notified>>();
join_all(notifs).await;
LeaderScheduleEvent::Init(
new_epoch,
slots_in_epoch,
new_rate_activation_epoch,
)
}
});
LeaderScheduleResult::TaskHandle(notif_jh)
}
}
}
@ -176,8 +198,8 @@ fn process_leadershedule_event(
) => {
log::info!("LeaderScheduleEvent::MergeStoreAndSaveSchedule RECV");
match (
StakeStore::merge_stakestore(stakestore, stake_map, stake_history),
VoteStore::merge_votestore(votestore, vote_map),
stakestore.stakes.merge((stake_map, stake_history)),
votestore.votes.merge(vote_map),
) {
(Ok(()), Ok(())) => LeaderScheduleResult::End(schedule_data),
_ => {
@ -263,7 +285,7 @@ pub fn calculate_leader_schedule(
stake_vote_map: &HashMap<Pubkey, (u64, Arc<StoredVote>)>,
epoch: u64,
slots_in_epoch: u64,
) -> HashMap<String, Vec<usize>> {
) -> LeaderSchedule {
let stakes_map: HashMap<Pubkey, u64> = stake_vote_map
.iter()
.filter_map(|(_, (stake, vote_account))| {
@ -281,16 +303,7 @@ pub fn calculate_leader_schedule(
sort_stakes(&mut stakes);
log::info!("calculate_leader_schedule stakes:{stakes:?} epoch:{epoch}");
let schedule = LeaderSchedule::new(&stakes, seed, slots_in_epoch, NUM_CONSECUTIVE_LEADER_SLOTS);
let slot_schedule = schedule
.get_slot_leaders()
.iter()
.enumerate()
.map(|(i, pk)| (pk.to_string(), i))
.into_group_map()
.into_iter()
.collect();
slot_schedule
schedule
}
// Cribbed from leader_schedule_utils

View File

@ -1,15 +1,19 @@
use crate::account::AccountPretty;
use crate::bootstrap::BootstrapEvent;
use crate::leader_schedule::LeaderScheduleGeneratedData;
use crate::utils::{Takable, TakeResult};
use futures::Stream;
use futures_util::stream::FuturesUnordered;
use futures_util::StreamExt;
use solana_lite_rpc_core::stores::data_cache::DataCache;
use solana_lite_rpc_core::structures::leaderschedule::GetVoteAccountsConfig;
use solana_lite_rpc_core::structures::leaderschedule::LeaderScheduleData;
use solana_lite_rpc_core::types::SlotStream;
use solana_rpc_client::nonblocking::rpc_client::RpcClient;
use solana_rpc_client_api::response::RpcVoteAccountStatus;
use std::collections::HashMap;
use std::sync::Arc;
use tokio::sync::mpsc::Receiver;
use yellowstone_grpc_client::GeyserGrpcClient;
use yellowstone_grpc_proto::geyser::CommitmentLevel;
use yellowstone_grpc_proto::prelude::SubscribeRequestFilterAccounts;
@ -34,6 +38,10 @@ type Slot = u64;
pub async fn start_stakes_and_votes_loop(
mut data_cache: DataCache,
mut slot_notification: SlotStream,
mut vote_account_rpc_request: Receiver<(
GetVoteAccountsConfig,
tokio::sync::oneshot::Sender<RpcVoteAccountStatus>,
)>,
rpc_client: Arc<RpcClient>,
grpc_url: String,
) -> anyhow::Result<tokio::task::JoinHandle<()>> {
@ -51,20 +59,11 @@ pub async fn start_stakes_and_votes_loop(
let mut current_schedule_epoch =
crate::bootstrap::bootstrap_scheduleepoch_data(&data_cache).await;
match crate::bootstrap::bootstrap_current_leader_schedule(
current_schedule_epoch.slots_in_epoch,
) {
Ok(current_schedule_data) => {
data_cache.leader_schedule = Arc::new(current_schedule_data)
}
Err(err) => {
log::warn!("Error during current leader schedule bootstrap from files:{err}")
}
}
//future execution collection.
let mut spawned_leader_schedule_task = FuturesUnordered::new();
let mut spawned_bootstrap_task = FuturesUnordered::new();
let mut rpc_notify_task = FuturesUnordered::new();
let mut rpc_exec_task = FuturesUnordered::new();
let jh = tokio::spawn(async move {
BootstrapEvent::InitBootstrap {
sleep_time: 1,
@ -74,6 +73,7 @@ pub async fn start_stakes_and_votes_loop(
spawned_bootstrap_task.push(jh);
let mut bootstrap_done = false;
let mut pending_rpc_request = vec![];
loop {
tokio::select! {
@ -93,6 +93,84 @@ pub async fn start_stakes_and_votes_loop(
}
}
}
Some((config, return_channel)) = vote_account_rpc_request.recv() => {
pending_rpc_request.push(return_channel);
let current_slot = crate::utils::get_current_confirmed_slot(&data_cache).await;
let vote_accounts = votestore.vote_stakes_for_epoch(0); //TODO define epoch storage.
match votestore.votes.take() {
TakeResult::Map(votes) => {
let jh = tokio::task::spawn_blocking({
move || {
let rpc_vote_accounts = crate::vote::get_rpc_vote_accounts_info(
current_slot,
&votes,
&vote_accounts.as_ref().unwrap().vote_stakes, //TODO put in take.
config,
);
(votes, vote_accounts, rpc_vote_accounts)
}
});
rpc_exec_task.push(jh);
}
TakeResult::Taken(mut stake_notify) => {
let notif_jh = tokio::spawn({
async move {
stake_notify.pop().unwrap().notified().await;
(current_slot, vote_accounts, config)
}
});
rpc_notify_task.push(notif_jh);
}
}
}
//manage rpc waiting request notification.
Some(Ok((votes, vote_accounts, rpc_vote_accounts))) = rpc_exec_task.next() => {
if let Err(err) = votestore.votes.merge(votes) {
log::info!("Error during RPC get vote account merge:{err}");
}
//avoid clone on the first request
//TODO change the logic use take less one.
if pending_rpc_request.len() == 1 {
if let Err(_) = pending_rpc_request.pop().unwrap().send(rpc_vote_accounts.clone()) {
log::error!("Vote accounts RPC channel send closed.");
}
} else {
for return_channel in pending_rpc_request.drain(..) {
if let Err(_) = return_channel.send(rpc_vote_accounts.clone()) {
log::error!("Vote accounts RPC channel send closed.");
}
}
}
}
//manage rpc waiting request notification.
Some(Ok((current_slot, vote_accounts, config))) = rpc_notify_task.next() => {
match votestore.votes.take() {
TakeResult::Map(votes) => {
let jh = tokio::task::spawn_blocking({
move || {
let rpc_vote_accounts = crate::vote::get_rpc_vote_accounts_info(
current_slot,
&votes,
&vote_accounts.as_ref().unwrap().vote_stakes, //TODO put in take.
config,
);
(votes, vote_accounts, rpc_vote_accounts)
}
});
rpc_exec_task.push(jh);
}
TakeResult::Taken(mut stake_notify) => {
let notif_jh = tokio::spawn({
async move {
stake_notify.pop().unwrap().notified().await;
(current_slot, vote_accounts, config)
}
});
rpc_notify_task.push(notif_jh);
}
}
}
//manage geyser account notification
//Geyser delete account notification patch must be installed on the validator.
//see https://github.com/solana-labs/solana/pull/33292
@ -129,7 +207,7 @@ pub async fn start_stakes_and_votes_loop(
//log::info!("Geyser notif VOTE account:{}", account);
let account_pubkey = account.pubkey;
//process vote accout notification
if let Err(err) = votestore.add_vote(account, current_schedule_epoch.last_slot_in_epoch) {
if let Err(err) = votestore.notify_vote_change(account, current_schedule_epoch.last_slot_in_epoch) {
log::warn!("Can't add new stake from account data err:{} account:{}", err, account_pubkey);
continue;
}
@ -163,8 +241,22 @@ pub async fn start_stakes_and_votes_loop(
}
//manage bootstrap event
Some(Ok(event)) = spawned_bootstrap_task.next() => {
match crate::bootstrap::run_bootstrap_events(event, &mut spawned_bootstrap_task, &mut stakestore, &mut votestore) {
Ok(Some(boot_res))=> bootstrap_done = boot_res,
match crate::bootstrap::run_bootstrap_events(event, &mut spawned_bootstrap_task, &mut stakestore, &mut votestore, current_schedule_epoch.slots_in_epoch) {
Ok(Some(boot_res))=> {
match boot_res {
Ok(current_schedule_data) => {
//let data_schedule = Arc::make_mut(&mut data_cache.leader_schedule);
data_cache.leader_schedule = Arc::new(current_schedule_data);
bootstrap_done = true;
}
Err(err) => {
log::warn!("Error during current leader schedule bootstrap from files:{err}")
}
}
},
Ok(None) => (),
Err(err) => log::error!("Stake / Vote Account bootstrap fail because '{err}'"),
}
@ -183,10 +275,11 @@ pub async fn start_stakes_and_votes_loop(
data_schedule.current = data_schedule.next.take();
match new_leader_schedule {
//TODO use vote_stakes for vote accounts RPC call.
Some(LeaderScheduleGeneratedData{schedule, vote_stakes, epoch}) => {
Some(schedule_data) => {
let new_schedule_data = LeaderScheduleData{
schedule,
epoch
schedule_by_node: LeaderScheduleGeneratedData::get_schedule_by_nodes(&schedule_data.schedule),
schedule_by_slot: schedule_data.schedule.get_slot_leaders().to_vec(),
epoch: schedule_data.epoch
};
data_schedule.next = Some(new_schedule_data);
}

View File

@ -1,5 +1,8 @@
use crate::utils::Takable;
use crate::utils::TakableContent;
use crate::utils::TakableMap;
use crate::utils::TakeResult;
use crate::utils::UpdateAction;
use crate::AccountPretty;
use crate::Slot;
use anyhow::bail;
@ -13,30 +16,30 @@ use std::collections::HashMap;
pub type StakeMap = HashMap<Pubkey, StoredStake>;
type StakeContent = (StakeMap, Option<StakeHistory>);
#[derive(Debug, Default)]
pub enum StakeAction {
Notify {
stake: StoredStake,
},
Remove(Pubkey, Slot),
// Merge {
// source_account: Pubkey,
// destination_account: Pubkey,
// update_slot: Slot,
// },
#[default]
None,
}
// #[derive(Debug, Default)]
// pub enum StakeAction {
// Notify {
// stake: StoredStake,
// },
// Remove(Pubkey, Slot),
// // Merge {
// // source_account: Pubkey,
// // destination_account: Pubkey,
// // update_slot: Slot,
// // },
// #[default]
// None,
// }
impl StakeAction {
fn get_update_slot(&self) -> u64 {
match self {
StakeAction::Notify { stake } => stake.last_update_slot,
StakeAction::Remove(_, slot) => *slot,
StakeAction::None => 0,
}
}
}
// impl StakeAction {
// fn get_update_slot(&self) -> u64 {
// match self {
// StakeAction::Notify { stake } => stake.last_update_slot,
// StakeAction::Remove(_, slot) => *slot,
// StakeAction::None => 0,
// }
// }
// }
#[derive(Debug, Default, Clone, Serialize, Deserialize)]
pub struct StoredStake {
@ -47,15 +50,15 @@ pub struct StoredStake {
pub write_version: u64,
}
impl TakableContent<StakeAction> for StakeContent {
fn add_value(&mut self, val: StakeAction) {
impl TakableContent<StoredStake> for StakeContent {
fn add_value(&mut self, val: UpdateAction<StoredStake>) {
StakeStore::process_stake_action(&mut self.0, val);
}
}
#[derive(Debug, Default)]
pub struct StakeStore {
stakes: TakableMap<StakeAction, StakeContent>,
pub stakes: TakableMap<StoredStake, StakeContent>,
}
impl StakeStore {
@ -76,9 +79,9 @@ impl StakeStore {
) -> anyhow::Result<()> {
//if lamport == 0 the account has been removed.
if account.lamports == 0 {
self.notify_stake_action(
StakeAction::Remove(account.pubkey, account.slot),
current_end_epoch_slot,
self.stakes.add_value(
UpdateAction::Remove(account.pubkey, account.slot),
account.slot <= current_end_epoch_slot,
);
} else {
let Ok(delegated_stake_opt) = account.read_stake() else {
@ -94,26 +97,36 @@ impl StakeStore {
write_version: account.write_version,
};
self.notify_stake_action(StakeAction::Notify { stake }, current_end_epoch_slot);
let action_update_slot = stake.last_update_slot;
self.stakes.add_value(
UpdateAction::Notify(action_update_slot, stake),
action_update_slot <= current_end_epoch_slot,
);
}
}
Ok(())
}
pub fn notify_stake_action(&mut self, action: StakeAction, current_end_epoch_slot: Slot) {
let action_update_slot = action.get_update_slot();
self.stakes
.add_value(action, action_update_slot <= current_end_epoch_slot);
}
//helper method to extract and merge stakes.
// pub fn take_stakestore(&mut self) -> TakeResult<StakeContent> {
// self.stakes.take()
// }
fn process_stake_action(stakes: &mut StakeMap, action: StakeAction) {
// pub fn merge_stakestore(
// &mut self,
// stake_map: StakeMap,
// stake_hisotry: Option<StakeHistory>,
// ) -> anyhow::Result<()> {
// self.stakes.merge((stake_map, stake_hisotry))
// }
fn process_stake_action(stakes: &mut StakeMap, action: UpdateAction<StoredStake>) {
match action {
StakeAction::Notify { stake } => {
UpdateAction::Notify(_, stake) => {
Self::notify_stake(stakes, stake);
}
StakeAction::Remove(account_pk, slot) => Self::remove_stake(stakes, &account_pk, slot),
StakeAction::None => (),
UpdateAction::Remove(account_pk, slot) => Self::remove_stake(stakes, &account_pk, slot),
}
}
fn notify_stake(map: &mut StakeMap, stake: StoredStake) {
@ -151,20 +164,20 @@ impl StakeStore {
}
}
//helper method to extract and merge stakes.
pub fn take_stakestore(
stakestore: &mut StakeStore,
) -> anyhow::Result<(StakeMap, Option<StakeHistory>)> {
crate::utils::take(&mut stakestore.stakes)
}
// //helper method to extract and merge stakes.
// pub fn take_stakestore(
// stakestore: &mut StakeStore,
// ) -> anyhow::Result<(StakeMap, Option<StakeHistory>)> {
// crate::utils::take(&mut stakestore.stakes)
// }
pub fn merge_stakestore(
stakestore: &mut StakeStore,
stake_map: StakeMap,
stake_history: Option<StakeHistory>,
) -> anyhow::Result<()> {
crate::utils::merge(&mut stakestore.stakes, (stake_map, stake_history))
}
// pub fn merge_stakestore(
// stakestore: &mut StakeStore,
// stake_map: StakeMap,
// stake_history: Option<StakeHistory>,
// ) -> anyhow::Result<()> {
// crate::utils::merge(&mut stakestore.stakes, (stake_map, stake_history))
// }
}
pub fn merge_program_account_in_strake_map(

View File

@ -1,4 +1,5 @@
use crate::vote::StoredVote;
use crate::Slot;
use anyhow::bail;
use serde::{Deserialize, Serialize};
use solana_lite_rpc_core::stores::block_information_store::BlockInformation;
@ -12,6 +13,7 @@ use std::fs::File;
use std::io::Write;
use std::str::FromStr;
use std::sync::Arc;
use tokio::sync::Notify;
pub async fn get_current_confirmed_slot(data_cache: &DataCache) -> u64 {
let commitment = CommitmentConfig::confirmed();
@ -73,9 +75,120 @@ pub fn save_schedule_vote_stakes(
Ok(())
}
#[derive(Debug)]
pub enum UpdateAction<Account> {
Notify(Slot, Account),
Remove(Pubkey, Slot),
}
// impl<Account> UpdateAction<Account> {
// pub fn get_update_slot(&self) -> u64 {
// match self {
// UpdateAction::Notify(slot, _) | UpdateAction::Remove(_, slot) => *slot,
// }
// }
// }
pub enum TakeResult<C> {
//Vec because can wait on several collection to be merged
Taken(Vec<Arc<Notify>>),
Map(C),
}
impl<C1> TakeResult<C1> {
pub fn and_then<C2>(self, action: TakeResult<C2>) -> TakeResult<(C1, C2)> {
match (self, action) {
(TakeResult::Taken(mut notif1), TakeResult::Taken(mut notif2)) => {
notif1.append(&mut notif2);
TakeResult::Taken(notif1)
}
(TakeResult::Map(content1), TakeResult::Map(content2)) => {
TakeResult::Map((content1, content2))
}
_ => unreachable!("Bad take result association."), //TODO add mix result.
}
}
// pub fn get_content(self) -> Option<C1> {
// match self {
// TakeResult::Taken(_) => None,
// TakeResult::Map(content) => Some(content),
// }
// }
}
//Takable struct code
pub trait TakableContent<T>: Default {
fn add_value(&mut self, val: T);
fn add_value(&mut self, val: UpdateAction<T>);
}
//Takable struct code
pub trait Takable<C> {
fn take(self) -> TakeResult<C>;
fn merge(self, content: C) -> anyhow::Result<()>;
fn is_taken(&self) -> bool;
}
impl<'a, T, C: TakableContent<T>> Takable<C> for &'a mut TakableMap<T, C> {
fn take(self) -> TakeResult<C> {
match self.content.take() {
Some(content) => TakeResult::Map(content),
None => TakeResult::Taken(vec![Arc::clone(&self.notifier)]),
}
}
fn merge(mut self, mut content: C) -> anyhow::Result<()> {
if self.content.is_none() {
//apply stake added during extraction.
for val in self.updates.drain(..) {
content.add_value(val);
}
self.content = Some(content);
self.notifier.notify_waiters();
Ok(())
} else {
bail!("TakableMap with a existing content".to_string())
}
}
fn is_taken(&self) -> bool {
self.content.is_none()
}
}
impl<'a, T1, T2, C1: TakableContent<T1>, C2: TakableContent<T2>> Takable<(C1, C2)>
for (&'a mut TakableMap<T1, C1>, &'a mut TakableMap<T2, C2>)
{
fn take(self) -> TakeResult<(C1, C2)> {
let first = self.0;
let second = self.1;
match (first.is_taken(), second.is_taken()) {
(true, true) | (false, false) => first.take().and_then(second.take()),
(true, false) => {
match first.take() {
TakeResult::Taken(notif) => TakeResult::Taken(notif),
TakeResult::Map(_) => unreachable!(), //tested before.
}
}
(false, true) => {
match second.take() {
TakeResult::Taken(notif) => TakeResult::Taken(notif),
TakeResult::Map(_) => unreachable!(), //tested before.
}
}
}
}
fn merge(self, content: (C1, C2)) -> anyhow::Result<()> {
self.0
.merge(content.0)
.and_then(|_| self.1.merge(content.1))
}
fn is_taken(&self) -> bool {
self.0.is_taken() && self.1.is_taken()
}
}
///A struct that hold a collection call content that can be taken during some time and merged after.
@ -83,84 +196,60 @@ pub trait TakableContent<T>: Default {
///It allow to process struct content while allowing to still update it without lock.
#[derive(Default, Debug)]
pub struct TakableMap<T, C: TakableContent<T>> {
pub content: C,
pub updates: Vec<T>,
taken: bool,
pub content: Option<C>,
pub updates: Vec<UpdateAction<T>>,
notifier: Arc<Notify>,
}
impl<T: Default, C: TakableContent<T> + Default> TakableMap<T, C> {
pub fn new(content: C) -> Self {
TakableMap {
content,
content: Some(content),
updates: vec![],
taken: false,
notifier: Arc::new(Notify::new()),
}
}
//add a value to the content if not taken or put it in the update waiting list.
//Use force_in_update to force the insert in update waiting list.
pub fn add_value(&mut self, val: T, force_in_update: bool) {
pub fn add_value(&mut self, val: UpdateAction<T>, force_in_update: bool) {
//during extract push the new update or
//don't insert now account change that has been done in next epoch.
//put in update pool to be merged next epoch change.
match self.taken || force_in_update {
match self.content.is_none() || force_in_update {
true => self.updates.push(val),
false => self.content.add_value(val),
false => {
let content = self.content.as_mut().unwrap(); //unwrap tested
content.add_value(val);
}
}
}
pub fn take(self) -> (Self, C) {
let takenmap = TakableMap {
content: C::default(),
updates: self.updates,
taken: true,
};
(takenmap, self.content)
}
pub fn merge(self, content: C) -> Self {
let mut mergedstore = TakableMap {
content,
updates: vec![],
taken: false,
};
//apply stake added during extraction.
for val in self.updates {
mergedstore.content.add_value(val);
}
mergedstore
}
pub fn is_taken(&self) -> bool {
self.taken
}
}
pub fn take<T: Default, C: TakableContent<T> + Default>(
map: &mut TakableMap<T, C>,
) -> anyhow::Result<C> {
if map.is_taken() {
bail!("TakableMap already taken. Try later");
}
let new_store = std::mem::take(map);
let (new_store, content) = new_store.take();
*map = new_store;
Ok(content)
}
// pub fn take<T: Default, C: TakableContent<T> + Default>(
// map: &mut TakableMap<T, C>,
// ) -> anyhow::Result<C> {
// if map.is_taken() {
// bail!("TakableMap already taken. Try later");
// }
// let new_store = std::mem::take(map);
// let (new_store, content) = new_store.take();
// *map = new_store;
// Ok(content)
// }
pub fn merge<T: Default, C: TakableContent<T> + Default>(
map: &mut TakableMap<T, C>,
content: C,
) -> anyhow::Result<()> {
if !map.is_taken() {
bail!("TakableMap merge of non taken map. Try later");
}
let new_store = std::mem::take(map);
let new_store = new_store.merge(content);
*map = new_store;
Ok(())
}
// pub fn merge<T: Default, C: TakableContent<T> + Default>(
// map: &mut TakableMap<T, C>,
// content: C,
// ) -> anyhow::Result<()> {
// if !map.is_taken() {
// bail!("TakableMap merge of non taken map. Try later");
// }
// let new_store = std::mem::take(map);
// let new_store = new_store.merge(content);
// *map = new_store;
// Ok(())
// }
#[cfg(test)]
mod tests {
@ -169,37 +258,55 @@ mod tests {
#[test]
fn test_takable_struct() {
impl TakableContent<u64> for Vec<u64> {
fn add_value(&mut self, val: u64) {
self.push(val)
fn add_value(&mut self, val: UpdateAction<u64>) {
match val {
UpdateAction::Notify(account, _) => self.push(account),
UpdateAction::Remove(_, _) => (),
UpdateAction::None => (),
}
}
}
let content: Vec<u64> = vec![];
let mut takable = TakableMap::new(content);
takable.add_value(23, false);
assert_eq!(takable.content.len(), 1);
takable.add_value(UpdateAction::Notify(23, 0), false);
assert_eq!(takable.content.as_ref().unwrap().len(), 1);
takable.add_value(24, true);
assert_eq!(takable.content.len(), 1);
takable.add_value(UpdateAction::Notify(24, 0), true);
assert_eq!(takable.content.as_ref().unwrap().len(), 1);
assert_eq!(takable.updates.len(), 1);
let content = take(&mut takable).unwrap();
assert_eq!(content.len(), 1);
assert_eq!(takable.content.len(), 0);
let take_content = (&mut takable).take();
assert_take_content_map(&take_content, 1);
assert_eq!(takable.updates.len(), 1);
let err_content = take(&mut takable);
assert!(err_content.is_err());
assert_eq!(content.len(), 1);
assert_eq!(takable.content.len(), 0);
let take_content = (&mut takable).take();
assert_take_content_taken(&take_content);
assert!(takable.content.is_none());
assert_eq!(takable.updates.len(), 1);
takable.add_value(25, false);
assert_eq!(takable.content.len(), 0);
takable.add_value(UpdateAction::Notify(25, 0), false);
assert_eq!(takable.updates.len(), 2);
merge(&mut takable, content).unwrap();
assert_eq!(takable.content.len(), 3);
let content = match take_content {
TakeResult::Taken(_) => panic!("not a content"),
TakeResult::Map(content) => content,
};
takable.merge(content);
assert_eq!(takable.content.as_ref().unwrap().len(), 3);
assert_eq!(takable.updates.len(), 0);
let err = merge(&mut takable, vec![]);
assert!(err.is_err());
//merge(&mut takable, vec![]);
//assert!(err.is_err());
}
fn assert_take_content_map(take_content: &TakeResult<Vec<u64>>, len: usize) {
match take_content {
TakeResult::Taken(_) => assert!(false),
TakeResult::Map(content) => assert_eq!(content.len(), len),
}
}
fn assert_take_content_taken(take_content: &TakeResult<Vec<u64>>) {
match take_content {
TakeResult::Taken(_) => (),
TakeResult::Map(_) => assert!(false),
}
}
}

View File

@ -1,9 +1,14 @@
use crate::utils::TakableContent;
use crate::utils::TakableMap;
use crate::utils::UpdateAction;
use crate::AccountPretty;
use crate::Slot;
use anyhow::bail;
use serde::{Deserialize, Serialize};
use solana_lite_rpc_core::structures::leaderschedule::GetVoteAccountsConfig;
use solana_rpc_client_api::request::MAX_RPC_VOTE_ACCOUNT_INFO_EPOCH_CREDITS_HISTORY;
use solana_rpc_client_api::response::RpcVoteAccountInfo;
use solana_rpc_client_api::response::RpcVoteAccountStatus;
use solana_sdk::account::Account;
use solana_sdk::pubkey::Pubkey;
use solana_sdk::vote::state::VoteState;
@ -12,9 +17,15 @@ use std::sync::Arc;
pub type VoteMap = HashMap<Pubkey, Arc<StoredVote>>;
#[derive(Debug, Clone)]
pub struct EpochVoteStakes {
pub vote_stakes: HashMap<Pubkey, (u64, Arc<StoredVote>)>,
pub epoch: u64,
}
impl TakableContent<StoredVote> for VoteMap {
fn add_value(&mut self, val: StoredVote) {
VoteStore::vote_map_insert_vote(self, val.pubkey, val);
fn add_value(&mut self, val: UpdateAction<StoredVote>) {
VoteStore::process_vote_action(self, val);
}
}
@ -26,29 +37,84 @@ pub struct StoredVote {
pub write_version: u64,
}
impl StoredVote {
pub fn convert_to_rpc_vote_account_info(
&self,
activated_stake: u64,
epoch_vote_account: bool,
) -> RpcVoteAccountInfo {
let last_vote = self
.vote_data
.votes
.iter()
.last()
.map(|vote| vote.slot())
.unwrap_or_default();
RpcVoteAccountInfo {
vote_pubkey: self.pubkey.to_string(),
node_pubkey: self.vote_data.node_pubkey.to_string(),
activated_stake,
commission: self.vote_data.commission,
epoch_vote_account,
epoch_credits: self.vote_data.epoch_credits.clone(),
last_vote,
root_slot: self.vote_data.root_slot.unwrap_or_default(),
}
}
}
#[derive(Default)]
pub struct VoteStore {
votes: TakableMap<StoredVote, VoteMap>,
pub votes: TakableMap<StoredVote, VoteMap>,
epoch_vote_stake_map: HashMap<u64, EpochVoteStakes>,
}
impl VoteStore {
pub fn new(capacity: usize) -> Self {
VoteStore {
votes: TakableMap::new(HashMap::with_capacity(capacity)),
epoch_vote_stake_map: HashMap::new(),
}
}
pub fn add_vote(
pub fn add_epoch_vote_stake(&mut self, stakes: EpochVoteStakes) {
self.epoch_vote_stake_map.insert(stakes.epoch, stakes);
}
pub fn vote_stakes_for_epoch(&self, epoch: u64) -> Option<EpochVoteStakes> {
self.epoch_vote_stake_map.get(&epoch).cloned()
}
pub fn notify_vote_change(
&mut self,
new_account: AccountPretty,
current_end_epoch_slot: Slot,
) -> anyhow::Result<()> {
if new_account.lamports == 0 {
self.remove_from_store(&new_account.pubkey, new_account.slot);
//self.remove_from_store(&new_account.pubkey, new_account.slot);
self.votes.add_value(
UpdateAction::Remove(new_account.pubkey, new_account.slot),
new_account.slot <= current_end_epoch_slot,
);
} else {
let Ok(vote_data) = new_account.read_vote() else {
let Ok(mut vote_data) = new_account.read_vote() else {
bail!("Can't read Vote from account data");
};
//remove unnecessary entry. See Solana code rpc::rpc::get_vote_accounts
let epoch_credits = vote_data.epoch_credits();
vote_data.epoch_credits =
if epoch_credits.len() > MAX_RPC_VOTE_ACCOUNT_INFO_EPOCH_CREDITS_HISTORY {
epoch_credits
.iter()
.skip(epoch_credits.len() - MAX_RPC_VOTE_ACCOUNT_INFO_EPOCH_CREDITS_HISTORY)
.cloned()
.collect()
} else {
epoch_credits.clone()
};
//log::info!("add_vote {} :{vote_data:?}", new_account.pubkey);
let new_voteacc = StoredVote {
@ -59,35 +125,49 @@ impl VoteStore {
};
let action_update_slot = new_voteacc.last_update_slot;
self.votes
.add_value(new_voteacc, action_update_slot <= current_end_epoch_slot);
self.votes.add_value(
UpdateAction::Notify(action_update_slot, new_voteacc),
action_update_slot <= current_end_epoch_slot,
);
}
Ok(())
}
//helper method to extract and merge stakes.
pub fn take_votestore(votestore: &mut VoteStore) -> anyhow::Result<VoteMap> {
crate::utils::take(&mut votestore.votes)
fn process_vote_action(votes: &mut VoteMap, action: UpdateAction<StoredVote>) {
match action {
UpdateAction::Notify(_, vote) => {
Self::vote_map_insert_vote(votes, vote);
}
UpdateAction::Remove(account_pk, slot) => {
Self::remove_from_store(votes, &account_pk, slot)
}
}
}
pub fn merge_votestore(votestore: &mut VoteStore, vote_map: VoteMap) -> anyhow::Result<()> {
crate::utils::merge(&mut votestore.votes, vote_map)
}
// //helper method to extract and merge stakes.
// pub fn take_votestore(&mut self) -> TakeResult<VoteMap> {
// self.votes.take()
// }
fn remove_from_store(&mut self, account_pk: &Pubkey, update_slot: Slot) {
if self
.votes
.content
// pub fn merge_votestore(&mut self, vote_map: VoteMap) -> anyhow::Result<()> {
// self.votes.merge(vote_map)
// }
fn remove_from_store(votes: &mut VoteMap, account_pk: &Pubkey, update_slot: Slot) {
//TODO use action.
if votes
.get(account_pk)
.map(|vote| vote.last_update_slot <= update_slot)
.unwrap_or(true)
{
log::info!("Vote remove_from_store for {}", account_pk.to_string());
self.votes.content.remove(account_pk);
votes.remove(account_pk);
}
}
fn vote_map_insert_vote(map: &mut VoteMap, vote_account_pk: Pubkey, vote_data: StoredVote) {
fn vote_map_insert_vote(map: &mut VoteMap, vote_data: StoredVote) {
let vote_account_pk = vote_data.pubkey;
match map.entry(vote_account_pk) {
std::collections::hash_map::Entry::Occupied(occupied) => {
let voteacc = occupied.into_mut(); // <-- get mut reference to existing value
@ -147,6 +227,54 @@ pub fn merge_program_account_in_vote_map(
last_update_slot,
write_version: 0,
};
VoteStore::vote_map_insert_vote(vote_map, pk, vote);
VoteStore::vote_map_insert_vote(vote_map, vote);
});
}
//TODO put in config instead of const.
// Validators that are this number of slots behind are considered delinquent
pub const DELINQUENT_VALIDATOR_SLOT_DISTANCE: u64 = 128;
pub fn get_rpc_vote_accounts_info(
current_slot: Slot,
votes: &VoteMap,
vote_accounts: &HashMap<Pubkey, (u64, Arc<StoredVote>)>,
config: GetVoteAccountsConfig,
) -> RpcVoteAccountStatus {
//TODO
//manage
//From Solana rpc::rpc::metaz::get_vote_accounts() code.
let (current_vote_accounts, delinquent_vote_accounts): (
Vec<RpcVoteAccountInfo>,
Vec<RpcVoteAccountInfo>,
) = votes
.values()
.map(|vote| {
let (stake, epoch_vote_account) = vote_accounts
.get(&vote.pubkey)
.map(|(stake, _)| (*stake, true))
.unwrap_or((0, false));
vote.convert_to_rpc_vote_account_info(stake, epoch_vote_account)
})
.partition(|vote_account_info| {
if current_slot >= DELINQUENT_VALIDATOR_SLOT_DISTANCE {
vote_account_info.last_vote > current_slot - DELINQUENT_VALIDATOR_SLOT_DISTANCE
} else {
vote_account_info.last_vote > 0
}
});
let keep_unstaked_delinquents = config.keep_unstaked_delinquents.unwrap_or_default();
let delinquent_vote_accounts = if !keep_unstaked_delinquents {
delinquent_vote_accounts
.into_iter()
.filter(|vote_account_info| vote_account_info.activated_stake > 0)
.collect::<Vec<_>>()
} else {
delinquent_vote_accounts
};
RpcVoteAccountStatus {
current: current_vote_accounts,
delinquent: delinquent_vote_accounts,
}
}

107
yarn.lock
View File

@ -22,7 +22,7 @@
resolved "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.20.10.tgz"
integrity sha512-sEnuDPpOJR/fcafHMjpcpGN5M2jbUGUHwmuWKM/YdPzeEDJg8bgmbcWQFUfE32MQjti1koACvoPVsDe8Uq+idg==
"@babel/core@^7.11.6", "@babel/core@^7.12.3":
"@babel/core@^7.0.0", "@babel/core@^7.0.0-0", "@babel/core@^7.11.6", "@babel/core@^7.12.3", "@babel/core@^7.8.0", "@babel/core@>=7.0.0-beta.0 <8":
version "7.20.12"
resolved "https://registry.npmjs.org/@babel/core/-/core-7.20.12.tgz"
integrity sha512-XsMfHovsUYHFMdrIHkZphTN/2Hzzi78R08NuHfDBehym2VsPDL6Zn/JAD/JQdnRvbSsbQc4mVaU1m6JgtTEElg==
@ -501,7 +501,7 @@
slash "^3.0.0"
write-file-atomic "^4.0.1"
"@jest/types@^29.3.1":
"@jest/types@^29.0.0", "@jest/types@^29.3.1":
version "29.3.1"
resolved "https://registry.npmjs.org/@jest/types/-/types-29.3.1.tgz"
integrity sha512-d0S0jmmTpjnhCmNpApgX3jrUZgZ22ivKJRvL2lli5hpCRoNnp1f85r2/wpKfXuYu8E7Jjh1hGfhPyup1NM5AmA==
@ -540,7 +540,7 @@
resolved "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.1.2.tgz"
integrity sha512-xnkseuNADM0gt2bs+BvhO0p78Mk762YnZdsuzFV018NoG1Sj1SCQvpSqa7XUaTam5vAGasABV9qXASMKnFMwMw==
"@jridgewell/sourcemap-codec@1.4.14", "@jridgewell/sourcemap-codec@^1.4.10":
"@jridgewell/sourcemap-codec@^1.4.10", "@jridgewell/sourcemap-codec@1.4.14":
version "1.4.14"
resolved "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.14.tgz"
integrity sha512-XPSJHWmi394fuUuzDnGz1wiKqWfo1yXecHQMRf2l6hztTO+nPru658AyDngaBe7isIxEkRsPR3FZh+s7iVa4Uw==
@ -613,7 +613,7 @@
"@solana/buffer-layout-utils" "^0.2.0"
buffer "^6.0.3"
"@solana/web3.js@^1.32.0", "@solana/web3.js@^1.73.0":
"@solana/web3.js@^1.32.0", "@solana/web3.js@^1.47.4", "@solana/web3.js@^1.73.0":
version "1.73.0"
resolved "https://registry.npmjs.org/@solana/web3.js/-/web3.js-1.73.0.tgz"
integrity sha512-YrgX3Py7ylh8NYkbanoINUPCj//bWUjYZ5/WPy9nQ9SK3Cl7QWCR+NmbDjmC/fTspZGR+VO9LTQslM++jr5PRw==
@ -743,14 +743,6 @@
dependencies:
"@types/yargs-parser" "*"
JSONStream@^1.3.5:
version "1.3.5"
resolved "https://registry.npmjs.org/JSONStream/-/JSONStream-1.3.5.tgz"
integrity sha512-E+iruNOY8VV9s4JEbe1aNEm6MiszPRr/UfcHMz0TQh1BXSxHK+ASV1R6W4HpjBhSeS+54PIsAMCBmwD06LLsqQ==
dependencies:
jsonparse "^1.2.0"
through ">=2.2.7 <3"
agentkeepalive@^4.2.1:
version "4.2.1"
resolved "https://registry.npmjs.org/agentkeepalive/-/agentkeepalive-4.2.1.tgz"
@ -806,7 +798,7 @@ argparse@^1.0.7:
dependencies:
sprintf-js "~1.0.2"
babel-jest@^29.3.1:
babel-jest@^29.0.0, babel-jest@^29.3.1:
version "29.3.1"
resolved "https://registry.npmjs.org/babel-jest/-/babel-jest-29.3.1.tgz"
integrity sha512-aard+xnMoxgjwV70t0L6wkW/3HQQtV+O0PEimxKgzNqCJnbYmroPojdP2tqKSOAt8QAKV/uSZU8851M7B5+fcA==
@ -931,7 +923,7 @@ braces@^3.0.2:
dependencies:
fill-range "^7.0.1"
browserslist@^4.21.3:
browserslist@^4.21.3, "browserslist@>= 4.21.0":
version "4.21.4"
resolved "https://registry.npmjs.org/browserslist/-/browserslist-4.21.4.tgz"
integrity sha512-CBHJJdDmgjl3daYjN5Cp5kbTf1mUhZoS+beLklHIvkOWscs83YAhLlF3Wsh/lciQYAcbBJgTOD44VtG31ZM4Hw==
@ -967,14 +959,6 @@ buffer-from@^1.0.0:
resolved "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz"
integrity sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==
buffer@6.0.1:
version "6.0.1"
resolved "https://registry.npmjs.org/buffer/-/buffer-6.0.1.tgz"
integrity sha512-rVAXBwEcEoYtxnHSO5iWyhzV/O1WMtkUYWlfdLS7FjU4PnSJJHEfHXi/uHPI5EwltmOA794gN3bm3/pzuctWjQ==
dependencies:
base64-js "^1.3.1"
ieee754 "^1.2.1"
buffer@^6.0.3, buffer@~6.0.3:
version "6.0.3"
resolved "https://registry.npmjs.org/buffer/-/buffer-6.0.3.tgz"
@ -983,6 +967,14 @@ buffer@^6.0.3, buffer@~6.0.3:
base64-js "^1.3.1"
ieee754 "^1.2.1"
buffer@6.0.1:
version "6.0.1"
resolved "https://registry.npmjs.org/buffer/-/buffer-6.0.1.tgz"
integrity sha512-rVAXBwEcEoYtxnHSO5iWyhzV/O1WMtkUYWlfdLS7FjU4PnSJJHEfHXi/uHPI5EwltmOA794gN3bm3/pzuctWjQ==
dependencies:
base64-js "^1.3.1"
ieee754 "^1.2.1"
bufferutil@^4.0.1:
version "4.0.7"
resolved "https://registry.npmjs.org/bufferutil/-/bufferutil-4.0.7.tgz"
@ -1075,16 +1067,16 @@ color-convert@^2.0.1:
dependencies:
color-name "~1.1.4"
color-name@1.1.3:
version "1.1.3"
resolved "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz"
integrity sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==
color-name@~1.1.4:
version "1.1.4"
resolved "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz"
integrity sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==
color-name@1.1.3:
version "1.1.3"
resolved "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz"
integrity sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==
commander@^2.20.3:
version "2.20.3"
resolved "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz"
@ -1095,7 +1087,12 @@ concat-map@0.0.1:
resolved "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz"
integrity sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==
convert-source-map@^1.6.0, convert-source-map@^1.7.0:
convert-source-map@^1.6.0:
version "1.9.0"
resolved "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.9.0.tgz"
integrity sha512-ASFBup0Mz1uyiIjANan1jzLQami9z1PoYSZCiiYW2FczPbenXc45FZdBZLzOT+r6+iciuEModtmCti+hjaAk0A==
convert-source-map@^1.7.0:
version "1.9.0"
resolved "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.9.0.tgz"
integrity sha512-ASFBup0Mz1uyiIjANan1jzLQami9z1PoYSZCiiYW2FczPbenXc45FZdBZLzOT+r6+iciuEModtmCti+hjaAk0A==
@ -1116,7 +1113,7 @@ cross-spawn@^7.0.3:
crypto@^1.0.1:
version "1.0.1"
resolved "https://registry.yarnpkg.com/crypto/-/crypto-1.0.1.tgz#2af1b7cad8175d24c8a1b0778255794a21803037"
resolved "https://registry.npmjs.org/crypto/-/crypto-1.0.1.tgz"
integrity sha512-VxBKmeNcqQdiUQUW2Tzq0t377b54N2bMtXO/qiLa+6eRRmmC4qT3D4OnTGoT/U6O9aklQ/jTwbOtRMTTY8G0Ig==
debug@^4.1.0, debug@^4.1.1:
@ -1251,7 +1248,7 @@ eyes@^0.1.8:
resolved "https://registry.npmjs.org/eyes/-/eyes-0.1.8.tgz"
integrity sha512-GipyPsXO1anza0AOZdy69Im7hGFCNB7Y/NGjDlZGJ3GJJLtwNSb2vrzYrTYJRrRloVx7pl+bhUaTB8yiccPvFQ==
fast-json-stable-stringify@2.x, fast-json-stable-stringify@^2.1.0:
fast-json-stable-stringify@^2.1.0, fast-json-stable-stringify@2.x:
version "2.1.0"
resolved "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz"
integrity sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==
@ -1293,11 +1290,6 @@ fs.realpath@^1.0.0:
resolved "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz"
integrity sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==
fsevents@^2.3.2:
version "2.3.2"
resolved "https://registry.yarnpkg.com/fsevents/-/fsevents-2.3.2.tgz#8a526f78b8fdf4623b709e0b975c52c24c02fd1a"
integrity sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==
function-bind@^1.1.1:
version "1.1.1"
resolved "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz"
@ -1502,13 +1494,13 @@ jayson@^3.4.4:
"@types/connect" "^3.4.33"
"@types/node" "^12.12.54"
"@types/ws" "^7.4.4"
JSONStream "^1.3.5"
commander "^2.20.3"
delay "^5.0.0"
es6-promisify "^5.0.0"
eyes "^0.1.8"
isomorphic-ws "^4.0.1"
json-stringify-safe "^5.0.1"
JSONStream "^1.3.5"
lodash "^4.17.20"
uuid "^8.3.2"
ws "^7.4.5"
@ -1716,7 +1708,7 @@ jest-resolve-dependencies@^29.3.1:
jest-regex-util "^29.2.0"
jest-snapshot "^29.3.1"
jest-resolve@^29.3.1:
jest-resolve@*, jest-resolve@^29.3.1:
version "29.3.1"
resolved "https://registry.npmjs.org/jest-resolve/-/jest-resolve-29.3.1.tgz"
integrity sha512-amXJgH/Ng712w3Uz5gqzFBBjxV8WFLSmNjoreBGMqxgCz5cH7swmBZzgBaCIOsvb0NbpJ0vgaSFdJqMdT+rADw==
@ -1864,7 +1856,7 @@ jest-worker@^29.3.1:
merge-stream "^2.0.0"
supports-color "^8.0.0"
jest@^29.3.1:
jest@^29.0.0, jest@^29.3.1:
version "29.3.1"
resolved "https://registry.npmjs.org/jest/-/jest-29.3.1.tgz"
integrity sha512-6iWfL5DTT0Np6UYs/y5Niu7WIfNv/wRTtN5RSXt2DIEft3dx3zPuw/3WJQBCJfmEzvDiEKwoqMbGD9n49+qLSA==
@ -1912,6 +1904,14 @@ jsonparse@^1.2.0:
resolved "https://registry.npmjs.org/jsonparse/-/jsonparse-1.3.1.tgz"
integrity sha512-POQXvpdL69+CluYsillJ7SUhKvytYjW9vG/GKpnf+xP8UWgYEM/RaMzHHofbALDiKbbP1W8UEYmgGl39WkPZsg==
JSONStream@^1.3.5:
version "1.3.5"
resolved "https://registry.npmjs.org/JSONStream/-/JSONStream-1.3.5.tgz"
integrity sha512-E+iruNOY8VV9s4JEbe1aNEm6MiszPRr/UfcHMz0TQh1BXSxHK+ASV1R6W4HpjBhSeS+54PIsAMCBmwD06LLsqQ==
dependencies:
jsonparse "^1.2.0"
through ">=2.2.7 <3"
kleur@^3.0.3:
version "3.0.3"
resolved "https://registry.npmjs.org/kleur/-/kleur-3.0.3.tgz"
@ -2002,7 +2002,7 @@ minimatch@^3.0.4, minimatch@^3.1.1:
dependencies:
brace-expansion "^1.1.7"
ms@2.1.2, ms@^2.0.0:
ms@^2.0.0, ms@2.1.2:
version "2.1.2"
resolved "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz"
integrity sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==
@ -2214,17 +2214,24 @@ safe-buffer@^5.0.1:
resolved "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz"
integrity sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==
semver@7.x, semver@^7.3.5:
semver@^6.0.0, semver@^6.3.0:
version "6.3.0"
resolved "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz"
integrity sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==
semver@^7.3.5:
version "7.3.8"
resolved "https://registry.npmjs.org/semver/-/semver-7.3.8.tgz"
integrity sha512-NB1ctGL5rlHrPJtFDVIVzTyQylMLu9N9VICA6HSFJo8MCGVTMW6gfpicwKmmK/dAjTOrqu5l63JJOpDSrAis3A==
dependencies:
lru-cache "^6.0.0"
semver@^6.0.0, semver@^6.3.0:
version "6.3.0"
resolved "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz"
integrity sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==
semver@7.x:
version "7.3.8"
resolved "https://registry.npmjs.org/semver/-/semver-7.3.8.tgz"
integrity sha512-NB1ctGL5rlHrPJtFDVIVzTyQylMLu9N9VICA6HSFJo8MCGVTMW6gfpicwKmmK/dAjTOrqu5l63JJOpDSrAis3A==
dependencies:
lru-cache "^6.0.0"
shebang-command@^2.0.0:
version "2.0.0"
@ -2413,10 +2420,10 @@ type-fest@^0.21.3:
resolved "https://registry.npmjs.org/type-fest/-/type-fest-0.21.3.tgz"
integrity sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w==
typescript@^4.9.4:
version "4.9.4"
resolved "https://registry.npmjs.org/typescript/-/typescript-4.9.4.tgz"
integrity sha512-Uz+dTXYzxXXbsFpM86Wh3dKCxrQqUcVMxwU54orwlJjOpO3ao8L7j5lH+dWfTwgCwIuM9GQ2kvVotzYJMXTBZg==
typescript@^4.9.5, typescript@>=4.3:
version "4.9.5"
resolved "https://registry.npmjs.org/typescript/-/typescript-4.9.5.tgz"
integrity sha512-1FXk9E2Hm+QzZQ7z+McJiHL4NW1F2EzMu9Nq9i3zAaGqibafqYwCVU6WyWAuyQRRzOlxou8xZSyXLEN8oKj24g==
update-browserslist-db@^1.0.9:
version "1.0.10"
@ -2426,7 +2433,7 @@ update-browserslist-db@^1.0.9:
escalade "^3.1.1"
picocolors "^1.0.0"
utf-8-validate@^5.0.2:
utf-8-validate@^5.0.2, utf-8-validate@>=5.0.2:
version "5.0.10"
resolved "https://registry.npmjs.org/utf-8-validate/-/utf-8-validate-5.0.10.tgz"
integrity sha512-Z6czzLq4u8fPOyx7TU6X3dvUZVvoJmxSQ+IcrlmagKhilxlhZgxPK6C5Jqbkw1IDUmFTM+cz9QDnnLTwDz/2gQ==
@ -2496,7 +2503,7 @@ write-file-atomic@^4.0.1:
imurmurhash "^0.1.4"
signal-exit "^3.0.7"
ws@^7.4.5:
ws@*, ws@^7.4.5:
version "7.5.9"
resolved "https://registry.npmjs.org/ws/-/ws-7.5.9.tgz"
integrity sha512-F+P9Jil7UiSKSkppIiD94dN07AwvFixvLIj1Og1Rl9GGMuNipJnV9JzjD6XuqmAeiswGvUmNLjr5cFuXwNS77Q==