2022-07-16 05:37:15 -07:00
|
|
|
use std::sync::{Arc, RwLock};
|
2022-08-07 11:04:19 -07:00
|
|
|
use std::thread;
|
|
|
|
use std::time::{Duration, Instant};
|
2022-07-16 05:37:15 -07:00
|
|
|
|
|
|
|
use crate::chain_data::*;
|
|
|
|
|
2022-08-01 10:08:59 -07:00
|
|
|
use anchor_lang::Discriminator;
|
|
|
|
|
2023-08-11 03:08:34 -07:00
|
|
|
use fixed::types::I80F48;
|
|
|
|
use mango_v4::accounts_zerocopy::{KeyedAccountSharedData, LoadZeroCopy};
|
|
|
|
use mango_v4::state::{Bank, MangoAccount, MangoAccountValue};
|
2022-07-16 05:37:15 -07:00
|
|
|
|
|
|
|
use anyhow::Context;
|
|
|
|
|
2022-12-16 04:10:46 -08:00
|
|
|
use solana_client::nonblocking::rpc_client::RpcClient as RpcClientAsync;
|
2022-07-25 07:07:53 -07:00
|
|
|
use solana_sdk::account::{AccountSharedData, ReadableAccount};
|
2022-08-07 11:04:19 -07:00
|
|
|
use solana_sdk::clock::Slot;
|
2022-07-16 05:37:15 -07:00
|
|
|
use solana_sdk::pubkey::Pubkey;
|
2022-08-07 11:04:19 -07:00
|
|
|
use solana_sdk::signature::Signature;
|
2022-07-16 05:37:15 -07:00
|
|
|
|
2023-08-11 03:08:34 -07:00
|
|
|
/// A complex account fetcher that mostly depends on an external job keeping
|
|
|
|
/// the chain_data up to date.
|
|
|
|
///
|
|
|
|
/// In addition to the usual async fetching interface, it also has synchronous
|
|
|
|
/// functions to access some kinds of data with less overhead.
|
|
|
|
///
|
|
|
|
/// Also, there's functions for fetching up to date data via rpc.
|
2022-07-21 04:03:28 -07:00
|
|
|
pub struct AccountFetcher {
|
2022-07-16 05:37:15 -07:00
|
|
|
pub chain_data: Arc<RwLock<ChainData>>,
|
2022-12-16 04:10:46 -08:00
|
|
|
pub rpc: RpcClientAsync,
|
2022-07-16 05:37:15 -07:00
|
|
|
}
|
|
|
|
|
2022-07-21 04:03:28 -07:00
|
|
|
impl AccountFetcher {
|
2022-07-16 05:37:15 -07:00
|
|
|
// loads from ChainData
|
|
|
|
pub fn fetch<T: anchor_lang::ZeroCopy + anchor_lang::Owner>(
|
|
|
|
&self,
|
|
|
|
address: &Pubkey,
|
|
|
|
) -> anyhow::Result<T> {
|
2022-08-30 04:46:39 -07:00
|
|
|
Ok(*self
|
2022-07-16 05:37:15 -07:00
|
|
|
.fetch_raw(address)?
|
|
|
|
.load::<T>()
|
2022-08-30 04:46:39 -07:00
|
|
|
.with_context(|| format!("loading account {}", address))?)
|
2022-07-16 05:37:15 -07:00
|
|
|
}
|
|
|
|
|
2022-07-25 07:07:53 -07:00
|
|
|
pub fn fetch_mango_account(&self, address: &Pubkey) -> anyhow::Result<MangoAccountValue> {
|
|
|
|
let acc = self.fetch_raw(address)?;
|
2022-08-01 10:08:59 -07:00
|
|
|
|
|
|
|
let data = acc.data();
|
2022-09-01 03:00:09 -07:00
|
|
|
if data.len() < 8 {
|
|
|
|
anyhow::bail!(
|
|
|
|
"account at {} has only {} bytes of data",
|
|
|
|
address,
|
|
|
|
data.len()
|
|
|
|
);
|
|
|
|
}
|
2022-08-01 10:08:59 -07:00
|
|
|
let disc_bytes = &data[0..8];
|
2022-08-30 04:46:39 -07:00
|
|
|
if disc_bytes != MangoAccount::discriminator() {
|
2022-08-01 10:08:59 -07:00
|
|
|
anyhow::bail!("not a mango account at {}", address);
|
|
|
|
}
|
|
|
|
|
2022-08-30 04:46:39 -07:00
|
|
|
MangoAccountValue::from_bytes(&data[8..])
|
|
|
|
.with_context(|| format!("loading mango account {}", address))
|
2022-07-25 07:07:53 -07:00
|
|
|
}
|
|
|
|
|
2023-10-07 12:27:19 -07:00
|
|
|
pub fn fetch_bank_and_price(&self, bank: &Pubkey) -> anyhow::Result<(Bank, I80F48)> {
|
2023-08-11 03:08:34 -07:00
|
|
|
let bank: Bank = self.fetch(bank)?;
|
|
|
|
let oracle = self.fetch_raw(&bank.oracle)?;
|
|
|
|
let price = bank.oracle_price(&KeyedAccountSharedData::new(bank.oracle, oracle), None)?;
|
2023-10-07 12:27:19 -07:00
|
|
|
Ok((bank, price))
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn fetch_bank_price(&self, bank: &Pubkey) -> anyhow::Result<I80F48> {
|
|
|
|
self.fetch_bank_and_price(bank).map(|(_, p)| p)
|
2023-08-11 03:08:34 -07:00
|
|
|
}
|
|
|
|
|
2022-07-16 05:37:15 -07:00
|
|
|
// fetches via RPC, stores in ChainData, returns new version
|
2022-12-16 04:10:46 -08:00
|
|
|
pub async fn fetch_fresh<T: anchor_lang::ZeroCopy + anchor_lang::Owner>(
|
2022-07-16 05:37:15 -07:00
|
|
|
&self,
|
|
|
|
address: &Pubkey,
|
|
|
|
) -> anyhow::Result<T> {
|
2022-12-16 04:10:46 -08:00
|
|
|
self.refresh_account_via_rpc(address).await?;
|
2022-07-16 05:37:15 -07:00
|
|
|
self.fetch(address)
|
|
|
|
}
|
|
|
|
|
2022-12-16 04:10:46 -08:00
|
|
|
pub async fn fetch_fresh_mango_account(
|
|
|
|
&self,
|
|
|
|
address: &Pubkey,
|
|
|
|
) -> anyhow::Result<MangoAccountValue> {
|
|
|
|
self.refresh_account_via_rpc(address).await?;
|
2022-07-25 07:07:53 -07:00
|
|
|
self.fetch_mango_account(address)
|
|
|
|
}
|
|
|
|
|
2022-07-16 05:37:15 -07:00
|
|
|
pub fn fetch_raw(&self, address: &Pubkey) -> anyhow::Result<AccountSharedData> {
|
|
|
|
let chain_data = self.chain_data.read().unwrap();
|
|
|
|
Ok(chain_data
|
|
|
|
.account(address)
|
2023-03-29 00:46:06 -07:00
|
|
|
.map(|d| d.account.clone())
|
|
|
|
.with_context(|| format!("fetch account {} via chain_data", address))?)
|
2022-07-16 05:37:15 -07:00
|
|
|
}
|
|
|
|
|
2022-12-16 04:10:46 -08:00
|
|
|
pub async fn refresh_account_via_rpc(&self, address: &Pubkey) -> anyhow::Result<Slot> {
|
2022-07-16 05:37:15 -07:00
|
|
|
let response = self
|
|
|
|
.rpc
|
2022-08-30 04:46:39 -07:00
|
|
|
.get_account_with_commitment(address, self.rpc.commitment())
|
2022-12-16 04:10:46 -08:00
|
|
|
.await
|
2022-07-16 05:37:15 -07:00
|
|
|
.with_context(|| format!("refresh account {} via rpc", address))?;
|
2022-08-07 11:04:19 -07:00
|
|
|
let slot = response.context.slot;
|
2022-07-16 05:37:15 -07:00
|
|
|
let account = response
|
|
|
|
.value
|
|
|
|
.ok_or(anchor_client::ClientError::AccountNotFound)
|
|
|
|
.with_context(|| format!("refresh account {} via rpc", address))?;
|
|
|
|
|
|
|
|
let mut chain_data = self.chain_data.write().unwrap();
|
2023-03-29 00:46:06 -07:00
|
|
|
let best_chain_slot = chain_data.best_chain_slot();
|
|
|
|
|
|
|
|
// The RPC can get information for slots that haven't been seen yet on chaindata. That means
|
|
|
|
// that the rpc thinks that slot is valid. Make it so by telling chain data about it.
|
|
|
|
if best_chain_slot < slot {
|
|
|
|
chain_data.update_slot(SlotData {
|
|
|
|
slot,
|
|
|
|
parent: Some(best_chain_slot),
|
|
|
|
status: SlotStatus::Processed,
|
|
|
|
chain: 0,
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
chain_data.update_account(
|
|
|
|
*address,
|
|
|
|
AccountData {
|
|
|
|
slot,
|
2022-07-16 05:37:15 -07:00
|
|
|
account: account.into(),
|
2023-03-29 00:46:06 -07:00
|
|
|
write_version: 1,
|
2022-07-16 05:37:15 -07:00
|
|
|
},
|
|
|
|
);
|
|
|
|
|
2022-08-07 11:04:19 -07:00
|
|
|
Ok(slot)
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Return the maximum slot reported for the processing of the signatures
|
2022-12-16 04:10:46 -08:00
|
|
|
pub async fn transaction_max_slot(&self, signatures: &[Signature]) -> anyhow::Result<Slot> {
|
|
|
|
let statuses = self.rpc.get_signature_statuses(signatures).await?.value;
|
2022-08-07 11:04:19 -07:00
|
|
|
Ok(statuses
|
|
|
|
.iter()
|
|
|
|
.map(|status_opt| status_opt.as_ref().map(|status| status.slot).unwrap_or(0))
|
|
|
|
.max()
|
|
|
|
.unwrap_or(0))
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Return success once all addresses have data >= min_slot
|
2022-12-16 04:10:46 -08:00
|
|
|
pub async fn refresh_accounts_via_rpc_until_slot(
|
2022-08-07 11:04:19 -07:00
|
|
|
&self,
|
|
|
|
addresses: &[Pubkey],
|
|
|
|
min_slot: Slot,
|
|
|
|
timeout: Duration,
|
|
|
|
) -> anyhow::Result<()> {
|
|
|
|
let start = Instant::now();
|
|
|
|
for address in addresses {
|
|
|
|
loop {
|
|
|
|
if start.elapsed() > timeout {
|
|
|
|
anyhow::bail!(
|
|
|
|
"timeout while waiting for data for {} that's newer than slot {}",
|
|
|
|
address,
|
|
|
|
min_slot
|
|
|
|
);
|
|
|
|
}
|
2022-12-16 04:10:46 -08:00
|
|
|
let data_slot = self.refresh_account_via_rpc(address).await?;
|
2022-08-07 11:04:19 -07:00
|
|
|
if data_slot >= min_slot {
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
thread::sleep(Duration::from_millis(500));
|
|
|
|
}
|
|
|
|
}
|
2022-07-16 05:37:15 -07:00
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-01-04 07:15:46 -08:00
|
|
|
#[async_trait::async_trait]
|
2022-07-21 04:03:28 -07:00
|
|
|
impl crate::AccountFetcher for AccountFetcher {
|
2022-12-16 04:10:46 -08:00
|
|
|
async fn fetch_raw_account(
|
2022-09-23 02:59:18 -07:00
|
|
|
&self,
|
|
|
|
address: &Pubkey,
|
|
|
|
) -> anyhow::Result<solana_sdk::account::AccountSharedData> {
|
2022-12-16 04:10:46 -08:00
|
|
|
self.fetch_raw(address)
|
|
|
|
}
|
|
|
|
|
|
|
|
async fn fetch_raw_account_lookup_table(
|
|
|
|
&self,
|
|
|
|
address: &Pubkey,
|
|
|
|
) -> anyhow::Result<AccountSharedData> {
|
|
|
|
// Fetch data via RPC if missing: the chain data updater doesn't know about all the
|
|
|
|
// lookup talbes we may need.
|
|
|
|
if let Ok(alt) = self.fetch_raw(address) {
|
|
|
|
return Ok(alt);
|
|
|
|
}
|
|
|
|
self.refresh_account_via_rpc(address).await?;
|
|
|
|
self.fetch_raw(address)
|
2022-09-23 02:59:18 -07:00
|
|
|
}
|
|
|
|
|
2022-12-16 04:10:46 -08:00
|
|
|
async fn fetch_program_accounts(
|
2022-09-23 02:59:18 -07:00
|
|
|
&self,
|
|
|
|
program: &Pubkey,
|
|
|
|
discriminator: [u8; 8],
|
|
|
|
) -> anyhow::Result<Vec<(Pubkey, AccountSharedData)>> {
|
|
|
|
let chain_data = self.chain_data.read().unwrap();
|
|
|
|
Ok(chain_data
|
|
|
|
.iter_accounts()
|
|
|
|
.filter_map(|(pk, data)| {
|
|
|
|
if data.account.owner() != program {
|
|
|
|
return None;
|
|
|
|
}
|
|
|
|
let acc_data = data.account.data();
|
|
|
|
if acc_data.len() < 8 || acc_data[..8] != discriminator {
|
|
|
|
return None;
|
|
|
|
}
|
|
|
|
Some((*pk, data.account.clone()))
|
|
|
|
})
|
|
|
|
.collect::<Vec<_>>())
|
2022-07-16 05:37:15 -07:00
|
|
|
}
|
|
|
|
}
|