2023-02-15 08:08:55 -08:00
|
|
|
use std::{
|
|
|
|
ops::{Div, Mul},
|
|
|
|
str::FromStr,
|
|
|
|
sync::{
|
|
|
|
atomic::{AtomicBool, AtomicU64, Ordering},
|
2023-03-14 05:39:19 -07:00
|
|
|
Arc,
|
2023-02-15 08:08:55 -08:00
|
|
|
},
|
|
|
|
time::{Duration, Instant},
|
|
|
|
};
|
|
|
|
|
|
|
|
use chrono::{DateTime, Utc};
|
|
|
|
use fixed::types::I80F48;
|
|
|
|
use log::{debug, info};
|
|
|
|
use mango::state::{MangoCache, MangoGroup, PerpMarket};
|
|
|
|
use mango_common::Loadable;
|
2023-06-15 01:41:58 -07:00
|
|
|
use solana_client::nonblocking::rpc_client::RpcClient;
|
2023-02-15 08:08:55 -08:00
|
|
|
use solana_program::{clock::DEFAULT_MS_PER_SLOT, pubkey::Pubkey};
|
|
|
|
use solana_sdk::hash::Hash;
|
2023-03-14 05:39:19 -07:00
|
|
|
use tokio::{sync::RwLock, task::JoinHandle};
|
2023-02-15 08:08:55 -08:00
|
|
|
|
2023-03-14 05:39:19 -07:00
|
|
|
use crate::{mango::GroupConfig, states::PerpMarketCache};
|
2023-02-15 08:08:55 -08:00
|
|
|
|
|
|
|
// as there are similar modules solana_sdk and solana_program
|
|
|
|
// solana internals use solana_sdk but external dependancies like mango use solana program
|
|
|
|
// that is why we have some helper methods
|
|
|
|
pub fn to_sdk_pk(pubkey: &Pubkey) -> solana_sdk::pubkey::Pubkey {
|
|
|
|
solana_sdk::pubkey::Pubkey::from(pubkey.to_bytes())
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn to_sp_pk(pubkey: &solana_sdk::pubkey::Pubkey) -> Pubkey {
|
|
|
|
Pubkey::new_from_array(pubkey.to_bytes())
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn to_sdk_accountmetas(
|
|
|
|
vec: Vec<solana_program::instruction::AccountMeta>,
|
|
|
|
) -> Vec<solana_sdk::instruction::AccountMeta> {
|
|
|
|
vec.iter()
|
|
|
|
.map(|x| solana_sdk::instruction::AccountMeta {
|
|
|
|
pubkey: to_sdk_pk(&x.pubkey),
|
|
|
|
is_signer: x.is_signer,
|
|
|
|
is_writable: x.is_writable,
|
|
|
|
})
|
|
|
|
.collect::<Vec<solana_sdk::instruction::AccountMeta>>()
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn to_sdk_instruction(
|
|
|
|
instruction: solana_program::instruction::Instruction,
|
|
|
|
) -> solana_sdk::instruction::Instruction {
|
|
|
|
solana_sdk::instruction::Instruction {
|
|
|
|
program_id: to_sdk_pk(&instruction.program_id),
|
|
|
|
accounts: to_sdk_accountmetas(instruction.accounts),
|
|
|
|
data: instruction.data,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-06-15 01:41:58 -07:00
|
|
|
pub async fn load_from_rpc<T: Loadable>(rpc_client: &RpcClient, pk: &Pubkey) -> T {
|
|
|
|
let acc = rpc_client.get_account(&to_sdk_pk(pk)).await.unwrap();
|
2023-02-15 08:08:55 -08:00
|
|
|
return T::load_from_bytes(acc.data.as_slice()).unwrap().clone();
|
|
|
|
}
|
|
|
|
|
2023-03-14 05:39:19 -07:00
|
|
|
pub async fn get_latest_blockhash(rpc_client: &RpcClient) -> Hash {
|
2023-02-15 08:08:55 -08:00
|
|
|
loop {
|
2023-06-15 01:41:58 -07:00
|
|
|
match rpc_client.get_latest_blockhash().await {
|
2023-02-15 08:08:55 -08:00
|
|
|
Ok(blockhash) => return blockhash,
|
|
|
|
Err(err) => {
|
|
|
|
info!("Couldn't get last blockhash: {:?}", err);
|
2023-03-14 05:39:19 -07:00
|
|
|
tokio::time::sleep(Duration::from_secs(1)).await;
|
2023-02-15 08:08:55 -08:00
|
|
|
}
|
|
|
|
};
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-03-14 05:39:19 -07:00
|
|
|
pub async fn get_new_latest_blockhash(client: Arc<RpcClient>, blockhash: &Hash) -> Option<Hash> {
|
2023-02-15 08:08:55 -08:00
|
|
|
let start = Instant::now();
|
|
|
|
while start.elapsed().as_secs() < 5 {
|
2023-06-15 01:41:58 -07:00
|
|
|
if let Ok(new_blockhash) = client.get_latest_blockhash().await {
|
2023-02-15 08:08:55 -08:00
|
|
|
if new_blockhash != *blockhash {
|
|
|
|
debug!("Got new blockhash ({:?})", blockhash);
|
|
|
|
return Some(new_blockhash);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
debug!("Got same blockhash ({:?}), will retry...", blockhash);
|
|
|
|
|
|
|
|
// Retry ~twice during a slot
|
2023-03-14 05:39:19 -07:00
|
|
|
tokio::time::sleep(Duration::from_millis(DEFAULT_MS_PER_SLOT / 2)).await;
|
2023-02-15 08:08:55 -08:00
|
|
|
}
|
|
|
|
None
|
|
|
|
}
|
|
|
|
|
2023-03-14 05:39:19 -07:00
|
|
|
pub async fn poll_blockhash_and_slot(
|
2023-02-15 08:08:55 -08:00
|
|
|
exit_signal: Arc<AtomicBool>,
|
|
|
|
blockhash: Arc<RwLock<Hash>>,
|
|
|
|
slot: &AtomicU64,
|
|
|
|
client: Arc<RpcClient>,
|
|
|
|
) {
|
|
|
|
let mut blockhash_last_updated = Instant::now();
|
|
|
|
//let mut last_error_log = Instant::now();
|
|
|
|
loop {
|
|
|
|
let client = client.clone();
|
2023-03-14 05:39:19 -07:00
|
|
|
let old_blockhash = *blockhash.read().await;
|
2023-02-15 08:08:55 -08:00
|
|
|
if exit_signal.load(Ordering::Relaxed) {
|
2023-03-14 05:39:19 -07:00
|
|
|
info!("Stopping blockhash thread");
|
2023-02-15 08:08:55 -08:00
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
2023-06-15 01:41:58 -07:00
|
|
|
match client.get_slot().await {
|
2023-04-04 07:06:56 -07:00
|
|
|
Ok(new_slot) => slot.store(new_slot, Ordering::Release),
|
|
|
|
Err(e) => {
|
|
|
|
info!("Failed to download slot: {}, skip", e);
|
|
|
|
continue;
|
|
|
|
}
|
2023-02-15 08:08:55 -08:00
|
|
|
}
|
|
|
|
|
2023-03-14 05:39:19 -07:00
|
|
|
if let Some(new_blockhash) = get_new_latest_blockhash(client, &old_blockhash).await {
|
2023-02-15 08:08:55 -08:00
|
|
|
{
|
2023-03-14 05:39:19 -07:00
|
|
|
*blockhash.write().await = new_blockhash;
|
2023-02-15 08:08:55 -08:00
|
|
|
}
|
|
|
|
blockhash_last_updated = Instant::now();
|
|
|
|
} else {
|
|
|
|
if blockhash_last_updated.elapsed().as_secs() > 120 {
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-03-14 05:39:19 -07:00
|
|
|
tokio::time::sleep(Duration::from_millis(100)).await;
|
2023-02-15 08:08:55 -08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn seconds_since(dt: DateTime<Utc>) -> i64 {
|
|
|
|
Utc::now().signed_duration_since(dt).num_seconds()
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn start_blockhash_polling_service(
|
|
|
|
exit_signal: Arc<AtomicBool>,
|
|
|
|
blockhash: Arc<RwLock<Hash>>,
|
|
|
|
current_slot: Arc<AtomicU64>,
|
|
|
|
client: Arc<RpcClient>,
|
|
|
|
) -> JoinHandle<()> {
|
2023-03-14 05:39:19 -07:00
|
|
|
tokio::spawn(async move {
|
|
|
|
poll_blockhash_and_slot(
|
|
|
|
exit_signal,
|
|
|
|
blockhash.clone(),
|
|
|
|
current_slot.as_ref(),
|
|
|
|
client,
|
|
|
|
)
|
|
|
|
.await;
|
|
|
|
})
|
2023-02-15 08:08:55 -08:00
|
|
|
}
|
|
|
|
|
2023-06-15 01:41:58 -07:00
|
|
|
pub async fn get_mango_market_perps_cache(
|
2023-02-15 08:08:55 -08:00
|
|
|
rpc_client: Arc<RpcClient>,
|
|
|
|
mango_group_config: &GroupConfig,
|
2023-03-10 04:38:11 -08:00
|
|
|
mango_program_pk: &Pubkey,
|
2023-02-15 08:08:55 -08:00
|
|
|
) -> Vec<PerpMarketCache> {
|
|
|
|
// fetch group
|
|
|
|
let mango_group_pk = Pubkey::from_str(mango_group_config.public_key.as_str()).unwrap();
|
2023-06-15 01:41:58 -07:00
|
|
|
let mango_group = load_from_rpc::<MangoGroup>(&rpc_client, &mango_group_pk).await;
|
2023-02-15 08:08:55 -08:00
|
|
|
let mango_cache_pk = Pubkey::from_str(mango_group.mango_cache.to_string().as_str()).unwrap();
|
2023-06-15 01:41:58 -07:00
|
|
|
let mango_cache = load_from_rpc::<MangoCache>(&rpc_client, &mango_cache_pk).await;
|
|
|
|
let mut ret = vec![];
|
|
|
|
for market_index in 0..mango_group_config.perp_markets.len() {
|
|
|
|
let perp_maket_config = &mango_group_config.perp_markets[market_index];
|
|
|
|
let perp_market_pk = Pubkey::from_str(perp_maket_config.public_key.as_str()).unwrap();
|
|
|
|
let perp_market = load_from_rpc::<PerpMarket>(&rpc_client, &perp_market_pk).await;
|
|
|
|
|
|
|
|
// fetch price
|
|
|
|
let base_decimals = mango_group_config.tokens[market_index].decimals;
|
|
|
|
let quote_decimals = mango_group_config.tokens[0].decimals;
|
|
|
|
|
|
|
|
let base_unit = I80F48::from_num(10u64.pow(base_decimals as u32));
|
|
|
|
let quote_unit = I80F48::from_num(10u64.pow(quote_decimals as u32));
|
|
|
|
let price = mango_cache.price_cache[market_index].price;
|
|
|
|
println!(
|
|
|
|
"market index {} price of : {}",
|
|
|
|
market_index, mango_cache.price_cache[market_index].price
|
|
|
|
);
|
|
|
|
|
|
|
|
let price_quote_lots: i64 = price
|
|
|
|
.mul(quote_unit)
|
|
|
|
.mul(I80F48::from_num(perp_market.base_lot_size))
|
|
|
|
.div(I80F48::from_num(perp_market.quote_lot_size))
|
|
|
|
.div(base_unit)
|
|
|
|
.to_num();
|
|
|
|
let order_base_lots: i64 = base_unit
|
|
|
|
.div(I80F48::from_num(perp_market.base_lot_size))
|
|
|
|
.to_num();
|
|
|
|
|
|
|
|
let root_bank = &mango_group_config.tokens[market_index].root_key;
|
|
|
|
let root_bank = Pubkey::from_str(root_bank.as_str()).unwrap();
|
|
|
|
let node_banks = mango_group_config.tokens[market_index]
|
|
|
|
.node_keys
|
|
|
|
.iter()
|
|
|
|
.map(|x| Pubkey::from_str(x.as_str()).unwrap())
|
|
|
|
.collect();
|
|
|
|
let price_oracle =
|
|
|
|
Pubkey::from_str(mango_group_config.oracles[market_index].public_key.as_str()).unwrap();
|
|
|
|
ret.push(PerpMarketCache {
|
|
|
|
order_base_lots,
|
|
|
|
price,
|
|
|
|
price_quote_lots,
|
|
|
|
mango_program_pk: mango_program_pk.clone(),
|
|
|
|
mango_group_pk,
|
|
|
|
mango_cache_pk,
|
|
|
|
perp_market_pk,
|
|
|
|
perp_market,
|
|
|
|
root_bank,
|
|
|
|
node_banks,
|
|
|
|
price_oracle,
|
|
|
|
bids: perp_market.bids,
|
|
|
|
asks: perp_market.asks,
|
|
|
|
});
|
|
|
|
}
|
|
|
|
ret
|
2023-02-15 08:08:55 -08:00
|
|
|
}
|