Skip to content

Commit 7347bc1

Browse files
authored
Merge pull request #102 from 0dotxyz/1.9
1.9
2 parents 6201e37 + d92a470 commit 7347bc1

9 files changed

Lines changed: 175 additions & 387 deletions

File tree

src/cache/banks.rs

Lines changed: 3 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -3,11 +3,9 @@ use crate::{
33
wrappers::bank::BankWrapper,
44
};
55
use anyhow::{anyhow, Result};
6+
use marginfi::utils::is_marginfi_asset_tag;
67
use marginfi_type_crate::{
7-
constants::{
8-
ASSET_TAG_DEFAULT, ASSET_TAG_DRIFT, ASSET_TAG_JUPLEND, ASSET_TAG_KAMINO, ASSET_TAG_SOL,
9-
ASSET_TAG_STAKED,
10-
},
8+
constants::{ASSET_TAG_DRIFT, ASSET_TAG_JUPLEND, ASSET_TAG_KAMINO},
119
types::{Bank, OracleSetup},
1210
};
1311
use solana_sdk::{account::Account, pubkey::Pubkey};
@@ -54,10 +52,7 @@ impl BanksCache {
5452
inner
5553
.banks
5654
.insert(bank_address, BankWrapper::new(bank_address, bank, account));
57-
if matches!(
58-
bank.config.asset_tag,
59-
ASSET_TAG_DEFAULT | ASSET_TAG_SOL | ASSET_TAG_STAKED
60-
) {
55+
if is_marginfi_asset_tag(bank.config.asset_tag) {
6156
inner.mint_to_p0_bank.insert(bank.mint, bank_address);
6257
}
6358
Ok(())

src/cache_loader.rs

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -295,7 +295,10 @@ impl CacheLoader {
295295
// data over this placeholder.
296296
for onramp in cache.banks.get_staked_onramps() {
297297
if !oracle_map.contains_key(&onramp) {
298-
debug!("Inserting empty placeholder for staked on-ramp {:?}.", onramp);
298+
debug!(
299+
"Inserting empty placeholder for staked on-ramp {:?}.",
300+
onramp
301+
);
299302
cache.oracles.try_insert(onramp, Account::default())?;
300303
}
301304
}

src/geyser.rs

Lines changed: 48 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,8 @@ use yellowstone_grpc_client::{ClientTlsConfig, GeyserGrpcClient};
2020
use yellowstone_grpc_proto::prelude::*;
2121

2222
const RATE_LIMIT_LOG_INTERVAL_SECS: u64 = 60;
23+
const INITIAL_RECONNECT_BACKOFF: Duration = Duration::from_secs(1);
24+
const MAX_RECONNECT_BACKOFF: Duration = Duration::from_secs(30);
2325

2426
#[derive(Debug, Clone)]
2527
pub struct GeyserUpdate {
@@ -112,6 +114,7 @@ impl GeyserService {
112114
let tracked_accounts_vec: Vec<Pubkey> = self.tracked_accounts.keys().copied().collect();
113115
let tls_config = ClientTlsConfig::new().with_native_roots();
114116
let mut from_slot: Option<u64> = None;
117+
let mut backoff = INITIAL_RECONNECT_BACKOFF;
115118

116119
while !self.stop.load(Ordering::Relaxed) {
117120
info!("Connecting to Geyser...");
@@ -124,21 +127,43 @@ impl GeyserService {
124127

125128
// TODO: replace from_slot with auto-reconnect once we migrate to the up-to-date client (requires updating Solana deps):
126129
// https://docs.triton.one/project-yellowstone/dragons-mouth-grpc-subscriptions#auto-reconnect-rust-client
127-
let mut client = self.tokio_rt.block_on(
128-
GeyserGrpcClient::build_from_shared(self.endpoint.clone())?
130+
//
131+
// Establish the connection and subscription inside the reconnect loop so that a
132+
// transient Geyser outage (e.g. connection refused) triggers a retry with backoff
133+
// instead of propagating out of `start()` and panicking the whole process.
134+
let connect_result = self.tokio_rt.block_on(async {
135+
let mut client = GeyserGrpcClient::build_from_shared(self.endpoint.clone())?
129136
.x_token(self.x_token.clone())?
130137
.tls_config(tls_config.clone())?
131-
.connect(),
132-
)?;
138+
.connect()
139+
.await?;
140+
let (_, stream) = client.subscribe_with_request(Some(sub_req.clone())).await?;
141+
Ok::<_, anyhow::Error>(stream)
142+
});
143+
144+
let mut stream = match connect_result {
145+
// Don't reset the backoff yet: a server can accept the connection and then
146+
// immediately reset the stream (REFUSED_STREAM). Only treat the connection as
147+
// healthy once it actually delivers a message (see below).
148+
Ok(stream) => stream,
149+
Err(e) => {
150+
self.error_logger.warn(&format!(
151+
"Failed to connect to Geyser, retrying in {:?}: {:?}",
152+
backoff, e
153+
));
154+
self.sleep_interruptible(backoff);
155+
backoff = (backoff * 2).min(MAX_RECONNECT_BACKOFF);
156+
continue;
157+
}
158+
};
133159

134-
let (_, mut stream) = self
135-
.tokio_rt
136-
.block_on(client.subscribe_with_request(Some(sub_req.clone())))?;
137160
// TODO: use IndexerFlags
138161
info!("Entering the GeyserService loop");
139162
while let Some(msg) = self.tokio_rt.block_on(stream.next()) {
140163
match msg {
141164
Ok(msg) => {
165+
// A delivered message proves the connection is healthy: reset the backoff.
166+
backoff = INITIAL_RECONNECT_BACKOFF;
142167
let update_oneof = ward!(msg.update_oneof, continue);
143168
if let subscribe_update::UpdateOneof::Account(account) = update_oneof {
144169
from_slot = Some(account.slot);
@@ -176,10 +201,15 @@ impl GeyserService {
176201
}
177202
Err(error) => {
178203
self.error_logger.warn(&format!(
179-
"Received error message from Geyser, reconnecting: {:?}",
180-
error
204+
"Received error message from Geyser, reconnecting in {:?}: {:?}",
205+
backoff, error
181206
));
182207

208+
// Back off before reconnecting so a server that keeps resetting the
209+
// stream isn't hammered in a tight loop.
210+
self.sleep_interruptible(backoff);
211+
backoff = (backoff * 2).min(MAX_RECONNECT_BACKOFF);
212+
183213
// Break the inner loop so the outer loop reconnects.
184214
break;
185215
}
@@ -196,6 +226,15 @@ impl GeyserService {
196226
Ok(())
197227
}
198228

229+
/// Sleeps up to `duration`, waking early if a stop is requested so the reconnect
230+
/// backoff never delays a clean shutdown.
231+
fn sleep_interruptible(&self, duration: Duration) {
232+
let deadline = Instant::now() + duration;
233+
while Instant::now() < deadline && !self.stop.load(Ordering::Relaxed) {
234+
std::thread::sleep(Duration::from_millis(200).min(deadline - Instant::now()));
235+
}
236+
}
237+
199238
fn send_update(&self, account_type: AccountType, address: Pubkey, account: &Account) {
200239
let update = GeyserUpdate {
201240
account_type,

src/liquidator.rs

Lines changed: 0 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,6 @@ use std::{
5050
const MAX_CONCURRENT_LIQUIDATIONS: usize = 8;
5151

5252
pub struct Liquidator {
53-
liquidator_account: Arc<LiquidatorAccount>,
5453
rebalancer: Rebalancer,
5554
executor: Executor,
5655
strategy: InventoryStrategy,
@@ -121,7 +120,6 @@ impl Liquidator {
121120
)?;
122121

123122
Ok(Liquidator {
124-
liquidator_account,
125123
rebalancer,
126124
executor,
127125
strategy,
@@ -314,10 +312,6 @@ impl Liquidator {
314312
let mut result: Vec<PreparedLiquidatableAccount> = vec![];
315313

316314
for account_address in account_addresses {
317-
if account_address == self.liquidator_account.liquidator_address {
318-
continue;
319-
}
320-
321315
let account = self
322316
.cache
323317
.marginfi_accounts

src/marginfi_ixs.rs

Lines changed: 1 addition & 62 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
use anchor_lang::{Id, InstructionData, Key, ToAccountMetas};
22

33
use anchor_spl::{associated_token, token_2022};
4-
use log::{debug, info, trace};
4+
use log::{debug, trace};
55
use marginfi_type_crate::{
66
constants::LIQUIDATION_RECORD_SEED,
77
pdas::{
@@ -10,13 +10,9 @@ use marginfi_type_crate::{
1010
derive_kamino_user_state,
1111
},
1212
};
13-
use solana_client::{rpc_client::RpcClient, rpc_config::RpcSendTransactionConfig};
14-
use solana_commitment_config::CommitmentConfig;
1513
use solana_sdk::{
1614
instruction::{AccountMeta, Instruction},
1715
pubkey::Pubkey,
18-
signature::Keypair,
19-
signer::Signer,
2016
sysvar,
2117
};
2218
use solana_sdk_ids::system_program;
@@ -213,25 +209,6 @@ fn maybe_add_bank_mint(accounts: &mut Vec<AccountMeta>, mint: Pubkey, token_prog
213209
}
214210
}
215211

216-
pub fn make_create_ix(
217-
marginfi_group: Pubkey,
218-
marginfi_account: Pubkey,
219-
signer: Pubkey,
220-
) -> Instruction {
221-
Instruction {
222-
program_id: marginfi_type_crate::ID,
223-
accounts: marginfi::accounts::MarginfiAccountInitialize {
224-
marginfi_group,
225-
marginfi_account,
226-
system_program: solana_sdk_ids::system_program::ID,
227-
authority: signer,
228-
fee_payer: signer,
229-
}
230-
.to_account_metas(Some(true)),
231-
data: marginfi::instruction::MarginfiAccountInitialize.data(),
232-
}
233-
}
234-
235212
fn mark_signer(
236213
accounts: &mut [solana_sdk::instruction::AccountMeta],
237214
signer: solana_sdk::pubkey::Pubkey,
@@ -241,44 +218,6 @@ fn mark_signer(
241218
}
242219
}
243220

244-
pub fn initialize_marginfi_account(
245-
rpc_client: &RpcClient,
246-
marginfi_group: Pubkey,
247-
signer_keypair: &Keypair,
248-
) -> anyhow::Result<Pubkey> {
249-
let marginfi_account_key = Keypair::new();
250-
251-
let ix = make_create_ix(
252-
marginfi_group,
253-
marginfi_account_key.pubkey(),
254-
signer_keypair.pubkey(),
255-
);
256-
257-
let recent_blockhash = rpc_client.get_latest_blockhash()?;
258-
let tx = solana_sdk::transaction::Transaction::new_signed_with_payer(
259-
&[ix],
260-
Some(&signer_keypair.pubkey()),
261-
&[signer_keypair, &marginfi_account_key],
262-
recent_blockhash,
263-
);
264-
265-
let res = rpc_client.send_and_confirm_transaction_with_spinner_and_config(
266-
&tx,
267-
CommitmentConfig::finalized(),
268-
RpcSendTransactionConfig {
269-
skip_preflight: true,
270-
..Default::default()
271-
},
272-
);
273-
info!(
274-
"Initialized new Marginfi account {:?} (without preflight check): {:?} ",
275-
marginfi_account_key.pubkey(),
276-
res
277-
);
278-
279-
Ok(marginfi_account_key.pubkey())
280-
}
281-
282221
#[allow(clippy::too_many_arguments)]
283222
pub fn make_kamino_withdraw_ix(
284223
group: Pubkey,

src/rebalancer.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ use std::{collections::HashSet, sync::Arc};
1212
use tokio::runtime::{Builder, Runtime};
1313

1414
/// Don't bother selling a position worth less than this (USD); the swap fee/dust isn't worth it.
15-
const MIN_REBALANCE_VALUE: I80F48 = I80F48!(0.5);
15+
const MIN_REBALANCE_VALUE: I80F48 = I80F48!(1.0);
1616

1717
/// The rebalancer keeps the liquidator holding only the swap mint (USDC): every other token it ends
1818
/// up with — seized collateral from a liquidation, or a JIT-buy overshoot — is sold back to USDC on

src/wrappers/liquidator_account.rs

Lines changed: 6 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -6,9 +6,9 @@ use crate::{
66
juplend_ixs::make_update_lending_rate_ix,
77
kamino_ixs::{make_refresh_obligation_ix, make_refresh_reserve_ix},
88
marginfi_ixs::{
9-
initialize_marginfi_account, make_drift_withdraw_ix, make_end_liquidate_ix,
10-
make_init_liquidation_record_ix, make_juplend_withdraw_ix, make_kamino_withdraw_ix,
11-
make_repay_ix, make_start_liquidate_ix, make_withdraw_ix,
9+
make_drift_withdraw_ix, make_end_liquidate_ix, make_init_liquidation_record_ix,
10+
make_juplend_withdraw_ix, make_kamino_withdraw_ix, make_repay_ix, make_start_liquidate_ix,
11+
make_withdraw_ix,
1212
},
1313
utils::{self, marginfi_account_by_authority},
1414
};
@@ -35,7 +35,7 @@ use solana_sdk::{
3535
signer::Signer,
3636
transaction::VersionedTransaction,
3737
};
38-
use std::{collections::HashSet, sync::Arc, thread, time::Duration};
38+
use std::{collections::HashSet, sync::Arc};
3939

4040
pub const PROFIT_SHARE: f64 = 0.085;
4141

@@ -53,7 +53,6 @@ pub struct PreparedLiquidatableAccount {
5353
}
5454

5555
pub struct LiquidatorAccount {
56-
pub liquidator_address: Pubkey,
5756
pub signer: Keypair,
5857
group: Pubkey,
5958
rpc_client: RpcClient,
@@ -79,27 +78,7 @@ impl LiquidatorAccount {
7978
accounts
8079
);
8180

82-
let liquidator_address = if accounts.is_empty() {
83-
info!("No MarginFi account found for the provided signer. Creating it...");
84-
let liquidator_marginfi_account =
85-
initialize_marginfi_account(&rpc_client, marginfi_group_id, &signer)?;
86-
87-
while cache
88-
.marginfi_accounts
89-
.try_get_account(&liquidator_marginfi_account)
90-
.is_err()
91-
{
92-
info!("Waiting for the new account info to arrive...");
93-
thread::sleep(Duration::from_secs(5));
94-
}
95-
96-
liquidator_marginfi_account
97-
} else {
98-
accounts[0]
99-
};
100-
10181
Ok(Self {
102-
liquidator_address,
10382
signer,
10483
group: marginfi_group_id,
10584
rpc_client,
@@ -113,8 +92,8 @@ impl LiquidatorAccount {
11392

11493
pub fn init_liq_record(&self, liquidatee_account: &MarginfiAccountWrapper) -> Result<Pubkey> {
11594
info!(
116-
"Initializing liquidation record for account {:?} with liquidator account {:?}.",
117-
liquidatee_account.address, self.liquidator_address
95+
"Initializing liquidation record for account {:?}",
96+
liquidatee_account.address
11897
);
11998

12099
let signer_pk = self.signer.pubkey();

0 commit comments

Comments
 (0)