1use std::{
2 collections::HashSet,
3 sync::{
4 atomic::{AtomicU64, Ordering},
5 Arc,
6 },
7};
8
9use anchor_lang::{AccountDeserialize, AnchorDeserialize};
10use dashmap::DashMap;
11use drift_pubsub_client::PubsubClient;
12use futures_util::{stream::FuturesUnordered, StreamExt};
13use serde_json::json;
14use solana_account_decoder_client_types::UiAccountEncoding;
15use solana_rpc_client::nonblocking::rpc_client::RpcClient;
16use solana_rpc_client_api::{
17 config::{RpcAccountInfoConfig, RpcProgramAccountsConfig},
18 request::RpcRequest,
19 response::{OptionalContext, RpcKeyedAccount},
20};
21use solana_sdk::{clock::Slot, commitment_config::CommitmentConfig, pubkey::Pubkey};
22
23use crate::{
24 accounts::State,
25 constants::{self, derive_perp_market_account, derive_spot_market_account, state_account},
26 drift_idl::types::OracleSource,
27 grpc::AccountUpdate,
28 memcmp::get_market_filter,
29 types::MapOf,
30 websocket_account_subscriber::WebsocketAccountSubscriber,
31 DataAndSlot, MarketId, MarketType, PerpMarket, SdkResult, SpotMarket, UnsubHandle,
32};
33
34const LOG_TARGET: &str = "marketmap";
35
36pub trait Market {
37 const MARKET_TYPE: MarketType;
38 fn market_index(&self) -> u16;
39 fn oracle_info(&self) -> (MarketId, Pubkey, OracleSource);
40}
41
42impl Market for PerpMarket {
43 const MARKET_TYPE: MarketType = MarketType::Perp;
44
45 fn market_index(&self) -> u16 {
46 self.market_index
47 }
48
49 fn oracle_info(&self) -> (MarketId, Pubkey, OracleSource) {
50 (
51 MarketId::perp(self.market_index),
52 self.amm.oracle,
53 self.amm.oracle_source,
54 )
55 }
56}
57
58impl Market for SpotMarket {
59 const MARKET_TYPE: MarketType = MarketType::Spot;
60
61 fn market_index(&self) -> u16 {
62 self.market_index
63 }
64
65 fn oracle_info(&self) -> (MarketId, Pubkey, OracleSource) {
66 (
67 MarketId::spot(self.market_index),
68 self.oracle,
69 self.oracle_source,
70 )
71 }
72}
73
74pub struct MarketMap<T: AnchorDeserialize + Send> {
79 pub marketmap: Arc<DashMap<u16, DataAndSlot<T>, ahash::RandomState>>,
80 subscriptions: DashMap<u16, UnsubHandle, ahash::RandomState>,
81 latest_slot: Arc<AtomicU64>,
82 pubsub: Arc<PubsubClient>,
83 commitment: CommitmentConfig,
84}
85
86impl<T> MarketMap<T>
87where
88 T: AnchorDeserialize + Clone + Send + Sync + Market + 'static,
89{
90 pub const SUBSCRIPTION_ID: &'static str = "marketmap";
91
92 pub fn new(pubsub: Arc<PubsubClient>, commitment: CommitmentConfig) -> Self {
93 Self {
94 subscriptions: Default::default(),
95 marketmap: Arc::default(),
96 latest_slot: Arc::new(AtomicU64::new(0)),
97 pubsub,
98 commitment,
99 }
100 }
101
102 pub fn map(&self) -> Arc<MapOf<u16, DataAndSlot<T>>> {
104 Arc::clone(&self.marketmap)
105 }
106
107 pub(crate) fn on_account_fn(&self) -> impl Fn(&AccountUpdate) {
109 let marketmap = self.map();
110 move |update: &AccountUpdate| {
111 let market = T::deserialize(&mut &update.data[8..]).expect("deser market");
112 let idx = market.market_index();
113 marketmap.insert(
114 idx,
115 DataAndSlot {
116 slot: update.slot,
117 data: market,
118 },
119 );
120 }
121 }
122
123 pub async fn subscribe(&self, markets: &[MarketId]) -> SdkResult<()> {
125 log::debug!(target: LOG_TARGET, "subscribing: {:?}", T::MARKET_TYPE);
126
127 let markets = HashSet::<MarketId>::from_iter(markets.iter().copied());
128 let mut pending_subscriptions =
129 Vec::<(u16, WebsocketAccountSubscriber)>::with_capacity(markets.len());
130 for market in markets {
131 if self.subscriptions.contains_key(&market.index()) {
132 continue;
133 }
134
135 let market_pubkey = match T::MARKET_TYPE {
136 MarketType::Perp => derive_perp_market_account(market.index()),
137 MarketType::Spot => derive_spot_market_account(market.index()),
138 };
139 let market_subscriber = WebsocketAccountSubscriber::new(
140 Arc::clone(&self.pubsub),
141 market_pubkey,
142 self.commitment,
143 );
144
145 pending_subscriptions.push((market.index(), market_subscriber));
146 }
147
148 let futs_iter = pending_subscriptions.into_iter().map(|(idx, fut)| {
149 let marketmap = Arc::clone(&self.marketmap);
150 let latest_slot = self.latest_slot.clone();
151 async move {
152 let unsub = fut
153 .subscribe(Self::SUBSCRIPTION_ID, false, {
154 move |update| {
155 if update.slot > latest_slot.load(Ordering::Relaxed) {
156 latest_slot.store(update.slot, Ordering::Relaxed);
157 }
158 marketmap.insert(
159 idx,
160 DataAndSlot {
161 slot: update.slot,
162 data: T::deserialize(&mut &update.data.as_slice()[8..])
163 .expect("valid market"),
164 },
165 );
166 }
167 })
168 .await;
169 (idx, unsub)
170 }
171 });
172
173 let mut subscription_futs = FuturesUnordered::from_iter(futs_iter);
174 while let Some((market, unsub)) = subscription_futs.next().await {
175 log::debug!(target: LOG_TARGET, "subscribed market: {market:?}");
176 self.subscriptions.insert(market, unsub?);
177 }
178
179 log::debug!(target: LOG_TARGET, "subscribed: {:?}", T::MARKET_TYPE);
180
181 Ok(())
182 }
183
184 pub fn is_subscribed(&self, market_index: u16) -> bool {
186 self.subscriptions.contains_key(&market_index)
187 }
188
189 pub fn unsubscribe(&self, markets: &[MarketId]) -> SdkResult<()> {
191 for market in markets {
192 if let Some((market, unsub)) = self.subscriptions.remove(&market.index()) {
193 let _ = unsub.send(());
194 self.marketmap.remove(&market);
195 }
196 }
197 log::debug!(target: LOG_TARGET, "unsubscribed markets: {markets:?}");
198
199 Ok(())
200 }
201
202 pub fn unsubscribe_all(&self) -> SdkResult<()> {
204 let all_markets: Vec<MarketId> = self
205 .subscriptions
206 .iter()
207 .map(|x| (*x.key(), T::MARKET_TYPE).into())
208 .collect();
209 self.unsubscribe(&all_markets)
210 }
211
212 pub fn values(&self) -> Vec<T> {
213 self.marketmap.iter().map(|x| x.data.clone()).collect()
214 }
215
216 pub fn oracles(&self) -> Vec<(MarketId, Pubkey, OracleSource)> {
218 self.values().iter().map(|x| x.oracle_info()).collect()
219 }
220
221 pub fn len(&self) -> usize {
222 self.marketmap.len()
223 }
224
225 pub fn contains(&self, market_index: &u16) -> bool {
226 self.marketmap.contains_key(market_index)
227 }
228
229 pub fn get(&self, market_index: &u16) -> Option<DataAndSlot<T>> {
230 self.marketmap
231 .get(market_index)
232 .map(|market| market.clone())
233 }
234
235 pub async fn sync(&self, rpc: &RpcClient) -> SdkResult<()> {
237 log::debug!(target: LOG_TARGET, "syncing marketmap: {:?}", T::MARKET_TYPE);
238 let (markets, latest_slot) = get_market_accounts_with_fallback::<T>(rpc).await?;
239 for market in markets {
240 self.marketmap.insert(
241 market.market_index(),
242 DataAndSlot {
243 data: market,
244 slot: latest_slot,
245 },
246 );
247 }
248 self.latest_slot.store(latest_slot, Ordering::Relaxed);
249
250 log::debug!(target: LOG_TARGET, "synced {:?} marketmap with {} markets", T::MARKET_TYPE, self.marketmap.len());
251 Ok(())
252 }
253
254 pub fn get_latest_slot(&self) -> u64 {
255 self.latest_slot.load(Ordering::Relaxed)
256 }
257}
258
259pub async fn get_market_accounts_with_fallback<T: Market + AnchorDeserialize>(
266 rpc: &RpcClient,
267) -> SdkResult<(Vec<T>, Slot)> {
268 let mut markets = Vec::<T>::default();
269
270 let account_config = RpcAccountInfoConfig {
271 commitment: Some(rpc.commitment()),
272 encoding: Some(UiAccountEncoding::Base64Zstd),
273 ..RpcAccountInfoConfig::default()
274 };
275
276 let gpa_config = RpcProgramAccountsConfig {
277 filters: Some(vec![get_market_filter(T::MARKET_TYPE)]),
278 account_config: account_config.clone(),
279 with_context: Some(true),
280 sort_results: None,
281 };
282
283 let response: Result<OptionalContext<Vec<RpcKeyedAccount>>, _> = rpc
285 .send(
286 RpcRequest::GetProgramAccounts,
287 json!([constants::PROGRAM_ID.to_string(), gpa_config]),
288 )
289 .await;
290
291 if let Ok(OptionalContext::Context(accounts)) = response {
292 for account in accounts.value {
293 let market_data = account.account.data.decode().expect("Market data");
294 let data = T::deserialize(&mut &market_data[8..]).expect("deserializes Market");
295 markets.push(data);
296 }
297 return Ok((markets, accounts.context.slot));
298 }
299 log::debug!(target: LOG_TARGET, "syncing with getProgramAccounts failed: {:?}", T::MARKET_TYPE);
300
301 let state_response = rpc
302 .get_account_with_config(state_account(), account_config)
303 .await
304 .expect("state account fetch");
305
306 let state_data = state_response.value.expect("state has data").data;
307 let state =
308 State::try_deserialize_unchecked(&mut state_data.as_slice()).expect("state deserializes");
309
310 let market_pdas: Vec<Pubkey> = match T::MARKET_TYPE {
311 MarketType::Spot => (0..state.number_of_spot_markets)
312 .map(derive_spot_market_account)
313 .collect(),
314 MarketType::Perp => (0..state.number_of_markets)
315 .map(derive_perp_market_account)
316 .collect(),
317 };
318
319 let mut market_requests = FuturesUnordered::new();
321 for market_chunk in market_pdas.chunks(64) {
322 market_requests
323 .push(rpc.get_multiple_accounts_with_commitment(market_chunk, rpc.commitment()));
324 }
325
326 while let Some(market_response) = market_requests.next().await {
327 match market_response {
328 Ok(data) => {
329 for market in data.value {
330 match market {
331 Some(market) => {
332 markets.push(
333 T::deserialize(&mut &market.data.as_slice()[8..])
334 .expect("market deserializes"),
335 );
336 }
337 None => {
338 log::warn!(target: LOG_TARGET, "failed to fetch market account (missing)");
339 break;
340 }
341 }
342 }
343 }
344 Err(err) => {
345 log::warn!(target: LOG_TARGET, "failed to fetch market accounts: {err:?}");
346 return Err(err)?;
347 }
348 }
349 }
350 if market_pdas.len() == markets.len() {
351 return Ok((markets, state_response.context.slot));
352 }
353 log::debug!(target: LOG_TARGET, "syncing with getMultipleAccounts failed: {:?}", T::MARKET_TYPE);
354
355 let mut market_requests =
357 FuturesUnordered::from_iter(market_pdas.iter().map(|acc| rpc.get_account_data(acc)));
358
359 while let Some(market_response) = market_requests.next().await {
360 match market_response {
361 Ok(data) => {
362 markets
363 .push(T::deserialize(&mut &data.as_slice()[8..]).expect("market deserializes"));
364 }
365 Err(err) => {
366 log::warn!("failed to fetch market account: {err:?}");
367 return Err(err)?;
368 }
369 }
370 }
371
372 Ok((markets, state_response.context.slot))
373}
374
375#[cfg(test)]
376mod tests {
377 use std::sync::Arc;
378
379 use drift_pubsub_client::PubsubClient;
380 use solana_rpc_client::nonblocking::rpc_client::RpcClient;
381 use solana_sdk::commitment_config::CommitmentConfig;
382
383 use super::{get_market_accounts_with_fallback, MarketMap};
384 use crate::{
385 accounts::{PerpMarket, SpotMarket},
386 utils::{get_ws_url, test_envs::devnet_endpoint},
387 MarketId,
388 };
389
390 #[tokio::test]
391 async fn marketmap_subscribe() {
392 let map = MarketMap::<PerpMarket>::new(
393 Arc::new(
394 PubsubClient::new(&get_ws_url(&devnet_endpoint()).unwrap())
395 .await
396 .expect("ws connects"),
397 ),
398 CommitmentConfig::confirmed(),
399 );
400
401 assert!(map
402 .subscribe(&[MarketId::perp(0), MarketId::perp(1), MarketId::perp(1)])
403 .await
404 .is_ok());
405 assert!(map.is_subscribed(0));
406 assert!(map.is_subscribed(1));
407 assert_eq!(map.subscriptions.len(), 2);
408
409 assert!(map.unsubscribe_all().is_ok());
410 assert_eq!(map.subscriptions.len(), 0);
411 assert!(!map.is_subscribed(0));
412 assert!(!map.is_subscribed(1));
413 }
414
415 #[tokio::test]
416 async fn get_market_accounts_with_fallback_works() {
417 let result: Result<(Vec<PerpMarket>, _), _> =
418 get_market_accounts_with_fallback::<PerpMarket>(&RpcClient::new(devnet_endpoint()))
419 .await;
420
421 assert!(result.is_ok_and(|r| r.0.len() > 0 && r.1 > 0));
422
423 let result =
424 get_market_accounts_with_fallback::<SpotMarket>(&RpcClient::new(devnet_endpoint()))
425 .await;
426
427 assert!(result.is_ok_and(|r| r.0.len() > 0 && r.1 > 0));
428 }
429}
430
431#[cfg(feature = "rpc_tests")]
432mod rpc_tests {
433 use solana_sdk::commitment_config::CommitmentConfig;
434
435 use super::*;
436 use crate::utils::test_envs::mainnet_endpoint;
437
438 #[tokio::test]
439 async fn test_marketmap_perp() {
440 let commitment = CommitmentConfig {
441 commitment: CommitmentConfig::Processed,
442 };
443
444 let marketmap = MarketMap::<PerpMarket>::new(commitment, mainnet_endpoint(), true);
445 marketmap.subscribe().await.unwrap();
446
447 tokio::time::sleep(tokio::time::Duration::from_secs(30)).await;
448
449 dbg!(marketmap.size());
450 assert!(marketmap.size() == 28);
451
452 dbg!(marketmap.get_latest_slot());
453
454 marketmap.unsubscribe().await.unwrap();
455
456 tokio::time::sleep(tokio::time::Duration::from_secs(10)).await;
457
458 assert_eq!(marketmap.size(), 0);
459 assert_eq!(marketmap.subscribed.load(Ordering::Relaxed), false);
460 }
461
462 #[tokio::test]
463 async fn test_marketmap_spot() {
464 let commitment = CommitmentConfig {
465 commitment: CommitmentConfig::Processed,
466 };
467
468 let marketmap = MarketMap::<SpotMarket>::new(commitment, RPC, true);
469 marketmap.subscribe().await.unwrap();
470
471 tokio::time::sleep(tokio::time::Duration::from_secs(30)).await;
472
473 dbg!(marketmap.size());
474 assert!(marketmap.size() == 13);
475
476 dbg!(marketmap.get_latest_slot());
477
478 marketmap.unsubscribe().await.unwrap();
479
480 tokio::time::sleep(tokio::time::Duration::from_secs(10)).await;
481
482 assert_eq!(marketmap.size(), 0);
483 assert_eq!(marketmap.subscribed.get(), false);
484 }
485}