first impl for RPC get_vote_accounts
This commit is contained in:
parent
7fba107165
commit
b3317667a6
|
@ -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,
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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!();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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>;
|
||||
}
|
||||
|
|
|
@ -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(
|
||||
¤t_stakes,
|
||||
¤t_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(¤t_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,
|
||||
}),
|
||||
})
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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(
|
||||
|
|
|
@ -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),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
107
yarn.lock
|
@ -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==
|
||||
|
|
Loading…
Reference in New Issue