219 lines
7.6 KiB
Rust
219 lines
7.6 KiB
Rust
use crate::cli_args::Cli;
|
|
use crate::metrics::Metrics;
|
|
use crate::token_swap_info::TokenSwapInfoUpdater;
|
|
use crate::{trigger_tcs, LiqErrorType, SharedState};
|
|
use anchor_lang::prelude::Pubkey;
|
|
use anyhow::Context;
|
|
use itertools::Itertools;
|
|
use mango_v4_client::error_tracking::ErrorTracking;
|
|
use mango_v4_client::{chain_data, MangoClient};
|
|
use std::sync::{Arc, RwLock};
|
|
use std::time::{Duration, Instant};
|
|
use tokio::task::JoinHandle;
|
|
use tracing::{error, info, trace};
|
|
|
|
pub fn spawn_tcs_job(
|
|
cli: &Cli,
|
|
shared_state: &Arc<RwLock<SharedState>>,
|
|
tx_trigger_sender: async_channel::Sender<()>,
|
|
mut tcs: Box<TcsState>,
|
|
metrics: &Metrics,
|
|
) -> JoinHandle<()> {
|
|
tokio::spawn({
|
|
let mut interval =
|
|
mango_v4_client::delay_interval(Duration::from_millis(cli.tcs_check_interval_ms));
|
|
let mut tcs_start_time = None;
|
|
let mut metric_tcs_start_end = metrics.register_latency("tcs_start_end".into());
|
|
let shared_state = shared_state.clone();
|
|
|
|
async move {
|
|
loop {
|
|
interval.tick().await;
|
|
|
|
let account_addresses = {
|
|
let state = shared_state.write().unwrap();
|
|
if !state.one_snapshot_done {
|
|
continue;
|
|
}
|
|
state.mango_accounts.iter().cloned().collect_vec()
|
|
};
|
|
|
|
tcs.errors.write().unwrap().update();
|
|
|
|
tcs_start_time = Some(tcs_start_time.unwrap_or(Instant::now()));
|
|
|
|
let found_candidates = tcs
|
|
.find_candidates(account_addresses.iter(), |candidate| {
|
|
if shared_state
|
|
.write()
|
|
.unwrap()
|
|
.interesting_tcs
|
|
.insert(candidate)
|
|
{
|
|
tx_trigger_sender.try_send(())?;
|
|
}
|
|
|
|
Ok(())
|
|
})
|
|
.await
|
|
.unwrap_or_else(|err| {
|
|
error!("error during find_candidate: {err}");
|
|
0
|
|
});
|
|
|
|
if found_candidates > 0 {
|
|
tracing::debug!("found {} candidates for triggering", found_candidates);
|
|
}
|
|
|
|
let current_time = Instant::now();
|
|
metric_tcs_start_end.push(current_time - tcs_start_time.unwrap());
|
|
tcs_start_time = None;
|
|
}
|
|
}
|
|
})
|
|
}
|
|
|
|
#[derive(Clone)]
|
|
pub struct TcsState {
|
|
pub mango_client: Arc<MangoClient>,
|
|
pub account_fetcher: Arc<chain_data::AccountFetcher>,
|
|
pub token_swap_info: Arc<TokenSwapInfoUpdater>,
|
|
pub trigger_tcs_config: trigger_tcs::Config,
|
|
|
|
pub errors: Arc<RwLock<ErrorTracking<Pubkey, LiqErrorType>>>,
|
|
}
|
|
|
|
impl TcsState {
|
|
async fn find_candidates(
|
|
&mut self,
|
|
accounts_iter: impl Iterator<Item = &Pubkey>,
|
|
action: impl Fn((Pubkey, u64, u64)) -> anyhow::Result<()>,
|
|
) -> anyhow::Result<usize> {
|
|
let accounts = accounts_iter.collect::<Vec<&Pubkey>>();
|
|
|
|
let now = Instant::now();
|
|
let now_ts: u64 = std::time::SystemTime::now()
|
|
.duration_since(std::time::UNIX_EPOCH)?
|
|
.as_secs();
|
|
|
|
let tcs_context = trigger_tcs::Context {
|
|
mango_client: self.mango_client.clone(),
|
|
account_fetcher: self.account_fetcher.clone(),
|
|
token_swap_info: self.token_swap_info.clone(),
|
|
config: self.trigger_tcs_config.clone(),
|
|
jupiter_quote_cache: Arc::new(trigger_tcs::JupiterQuoteCache::default()),
|
|
now_ts,
|
|
};
|
|
|
|
let mut found_counter = 0;
|
|
|
|
// Find interesting (pubkey, tcsid, volume)
|
|
for pubkey in accounts.iter() {
|
|
if let Some(error_entry) = self.errors.read().unwrap().had_too_many_errors(
|
|
LiqErrorType::TcsCollectionHard,
|
|
pubkey,
|
|
now,
|
|
) {
|
|
trace!(
|
|
%pubkey,
|
|
error_entry.count,
|
|
"skip checking account for tcs, had errors recently",
|
|
);
|
|
continue;
|
|
}
|
|
|
|
let candidates = tcs_context.find_interesting_tcs_for_account(pubkey);
|
|
let mut error_guard = self.errors.write().unwrap();
|
|
|
|
match candidates {
|
|
Ok(v) => {
|
|
error_guard.clear(LiqErrorType::TcsCollectionHard, pubkey);
|
|
if v.is_empty() {
|
|
error_guard.clear(LiqErrorType::TcsCollectionPartial, pubkey);
|
|
error_guard.clear(LiqErrorType::TcsExecution, pubkey);
|
|
} else if v.iter().all(|it| it.is_ok()) {
|
|
error_guard.clear(LiqErrorType::TcsCollectionPartial, pubkey);
|
|
} else {
|
|
for it in v.iter() {
|
|
if let Err(e) = it {
|
|
error_guard.record(
|
|
LiqErrorType::TcsCollectionPartial,
|
|
pubkey,
|
|
e.to_string(),
|
|
);
|
|
}
|
|
}
|
|
}
|
|
for interesting_candidate_res in v.iter() {
|
|
if let Ok(interesting_candidate) = interesting_candidate_res {
|
|
action(*interesting_candidate).expect("failed to send TCS candidate");
|
|
found_counter += 1;
|
|
}
|
|
}
|
|
}
|
|
Err(e) => {
|
|
error_guard.record(LiqErrorType::TcsCollectionHard, pubkey, e.to_string());
|
|
}
|
|
}
|
|
}
|
|
|
|
return Ok(found_counter);
|
|
}
|
|
|
|
pub async fn maybe_take_token_conditional_swap(
|
|
&mut self,
|
|
mut interesting_tcs: Vec<(Pubkey, u64, u64)>,
|
|
) -> anyhow::Result<bool> {
|
|
let now_ts: u64 = std::time::SystemTime::now()
|
|
.duration_since(std::time::UNIX_EPOCH)?
|
|
.as_secs();
|
|
|
|
let tcs_context = trigger_tcs::Context {
|
|
mango_client: self.mango_client.clone(),
|
|
account_fetcher: self.account_fetcher.clone(),
|
|
token_swap_info: self.token_swap_info.clone(),
|
|
config: self.trigger_tcs_config.clone(),
|
|
jupiter_quote_cache: Arc::new(trigger_tcs::JupiterQuoteCache::default()),
|
|
now_ts,
|
|
};
|
|
|
|
if interesting_tcs.is_empty() {
|
|
return Ok(false);
|
|
}
|
|
|
|
let (txsigs, mut changed_pubkeys) = tcs_context
|
|
.execute_tcs(&mut interesting_tcs, self.errors.clone())
|
|
.await?;
|
|
for pubkey in changed_pubkeys.iter() {
|
|
self.errors
|
|
.write()
|
|
.unwrap()
|
|
.clear(LiqErrorType::TcsExecution, pubkey);
|
|
}
|
|
if txsigs.is_empty() {
|
|
return Ok(false);
|
|
}
|
|
changed_pubkeys.push(self.mango_client.mango_account_address);
|
|
|
|
// Force a refresh of affected accounts
|
|
let slot = self
|
|
.account_fetcher
|
|
.transaction_max_slot(&txsigs)
|
|
.await
|
|
.context("transaction_max_slot")?;
|
|
if let Err(e) = self
|
|
.account_fetcher
|
|
.refresh_accounts_via_rpc_until_slot(
|
|
&changed_pubkeys,
|
|
slot,
|
|
self.trigger_tcs_config.refresh_timeout,
|
|
)
|
|
.await
|
|
{
|
|
info!(slot, "could not refresh after tcs execution: {}", e);
|
|
}
|
|
|
|
Ok(true)
|
|
}
|
|
}
|