use crate::block_info::TransactionAccount; use dashmap::DashMap; use itertools::Itertools; use prometheus::{opts, register_int_gauge, IntGauge}; use serde::{Deserialize, Serialize}; use solana_address_lookup_table_program::state::AddressLookupTable; use solana_rpc_client::nonblocking::rpc_client::RpcClient; use solana_sdk::{commitment_config::CommitmentConfig, pubkey::Pubkey}; use std::{sync::Arc, time::Duration}; lazy_static::lazy_static! { static ref ALTS_IN_STORE: IntGauge = register_int_gauge!(opts!("banking_stage_sidecar_alts_stored", "Alts stored in sidecar")).unwrap(); static ref ALTS_IN_LOADING_QUEUE: IntGauge = register_int_gauge!(opts!("banking_stage_sidecar_alts_loading_queue", "Alts in loading queue in sidecar")).unwrap(); } #[derive(Clone)] pub struct ALTStore { rpc_client: Arc, pub map: Arc>>, loading_queue: Arc>, } impl ALTStore { pub fn new(rpc_client: Arc) -> Self { let (sx, rx) = async_channel::unbounded(); let alt_store = Self { rpc_client, map: Arc::new(DashMap::new()), loading_queue: Arc::new(sx), }; { let instant = alt_store.clone(); tokio::task::spawn(async move { loop { if let Ok(pk) = rx.recv().await { let mut alts_list = vec![pk]; ALTS_IN_LOADING_QUEUE.dec(); tokio::time::sleep(Duration::from_millis(1)).await; while let Ok(pk) = rx.try_recv() { alts_list.push(pk); ALTS_IN_LOADING_QUEUE.dec(); tokio::time::sleep(Duration::from_millis(1)).await; } instant._load_all_alts(&alts_list).await; } } }); } alt_store } pub async fn load_alts_list(&self, alts_list: Vec<&Pubkey>) { log::info!("Preloading {} ALTs", alts_list.len()); for batches in alts_list.chunks(1000) { let tasks = batches.chunks(100).map(|batch| { let batch: Vec = batch.into_iter().map(|pk| (*pk).clone()).collect_vec(); let rpc_client = self.rpc_client.clone(); let this = self.clone(); tokio::spawn(async move { if let Ok(Ok(multiple_accounts)) = tokio::time::timeout( Duration::from_secs(30), rpc_client.get_multiple_accounts_with_commitment( &batch, CommitmentConfig::processed(), ), ) .await { for (index, acc) in multiple_accounts.value.iter().enumerate() { if let Some(acc) = acc { this.save_account(batch[index], &acc.data); } } } }) }); if let Err(_) = tokio::time::timeout(Duration::from_secs(20), futures::future::join_all(tasks)) .await { log::warn!("timedout getting {} alts", alts_list.len()); } } ALTS_IN_STORE.set(self.map.len() as i64); } async fn _load_all_alts(&self, alts_list: &Vec) { let alts_list = alts_list .iter() .filter(|x| !self.map.contains_key(x)) .collect_vec(); if alts_list.is_empty() { return; } self.load_alts_list(alts_list).await; } pub async fn start_loading_missing_alts(&self, alts_list: &Vec<&Pubkey>) { for key in alts_list.iter().filter(|x| !self.map.contains_key(x)) { ALTS_IN_LOADING_QUEUE.inc(); let _ = self.loading_queue.send(*key.clone()).await; } } pub fn save_account(&self, address: Pubkey, data: &[u8]) { let lookup_table = AddressLookupTable::deserialize(&data).unwrap(); if self .map .insert(address, lookup_table.addresses.to_vec()) .is_none() { ALTS_IN_STORE.inc(); } drop(lookup_table); } async fn load_accounts<'a>( &self, alt: &Pubkey, write_accounts: &Vec, read_account: &Vec, ) -> Option> { match self.map.get(&alt) { Some(lookup_table) => { if write_accounts .iter() .any(|x| *x as usize >= lookup_table.len()) || read_account .iter() .any(|x| *x as usize >= lookup_table.len()) { return None; } let write_accounts = write_accounts .iter() .map(|i| lookup_table[*i as usize]) .collect_vec(); let read_account = read_account .iter() .map(|i| lookup_table[*i as usize]) .collect_vec(); let wa = write_accounts .iter() .map(|key| TransactionAccount { key: key.clone(), is_writable: true, is_signer: false, is_alt: true, }) .collect_vec(); let ra = read_account .iter() .map(|key| TransactionAccount { key: key.clone(), is_writable: false, is_signer: false, is_alt: true, }) .collect_vec(); Some([wa, ra].concat()) } None => None, } } pub async fn get_accounts( &self, alt: &Pubkey, write_accounts: &Vec, read_account: &Vec, ) -> Vec { match self.load_accounts(alt, write_accounts, read_account).await { Some(x) => x, None => { // forget alt for now, start loading it for next blocks // loading should be on its way vec![] } } } pub fn serialize(&self) -> Vec { bincode::serialize::(&BinaryALTData::new(self.map.clone())).unwrap() } pub fn load_binary(&self, binary_data: Vec) { let binary_alt_data = bincode::deserialize::(&binary_data).unwrap(); for (pubkey, accounts) in binary_alt_data.data { self.map.insert(pubkey, accounts); } } } #[derive(Serialize, Deserialize)] pub struct BinaryALTData { pub data: Vec<(Pubkey, Vec)>, } impl BinaryALTData { pub fn new(map: Arc>>) -> Self { let data = map .iter() .map(|x| (x.key().clone(), x.value().clone())) .collect_vec(); Self { data } } }