From a3fff435a494a5fa1de235b9d0a0bf3739c853d3 Mon Sep 17 00:00:00 2001 From: soundsonacid Date: Thu, 21 Mar 2024 13:52:23 -0500 Subject: [PATCH] oracle switchovers --- Cargo.lock | 94 +++++++++++++++++++++++++++++++++++++++++++++--- Cargo.toml | 1 + src/lib.rs | 57 ++++++++++++++++++++--------- src/marketmap.rs | 28 +++++++-------- src/oraclemap.rs | 85 +++++++++++++++++++++++++++++++++++++------ 5 files changed, 216 insertions(+), 49 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 6239c51..34d714c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -313,6 +313,12 @@ version = "0.3.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6b4930d2cb77ce62f89ee5d5289b4ac049559b1c45539271f5ed4fdc7db34545" +[[package]] +name = "arrayvec" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "23b62fc65de8e4e7f52534fb52b0f3ed04746ae267519eef2a83941e8085068b" + [[package]] name = "arrayvec" version = "0.7.4" @@ -484,6 +490,17 @@ dependencies = [ "typenum", ] +[[package]] +name = "blake2b_simd" +version = "0.5.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "afa748e348ad3be8263be728124b24a24f268266f6f5d58af9d75f6a40b5c587" +dependencies = [ + "arrayref", + "arrayvec 0.5.2", + "constant_time_eq 0.1.5", +] + [[package]] name = "blake3" version = "1.5.0" @@ -491,10 +508,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0231f06152bf547e9c2b5194f247cd97aacf6dcd8b15d8e5ec0663f64580da87" dependencies = [ "arrayref", - "arrayvec", + "arrayvec 0.7.4", "cc", "cfg-if", - "constant_time_eq", + "constant_time_eq 0.3.0", "digest 0.10.7", ] @@ -764,6 +781,15 @@ dependencies = [ "os_str_bytes", ] +[[package]] +name = "clippy" +version = "0.0.302" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d911ee15579a3f50880d8c1d59ef6e79f9533127a3bd342462f5d584f5e8c294" +dependencies = [ + "term", +] + [[package]] name = "console" version = "0.15.8" @@ -803,6 +829,12 @@ version = "0.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e4c78c047431fee22c1a7bb92e00ad095a02a983affe4d8a72e2a2c62c1b94f3" +[[package]] +name = "constant_time_eq" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "245097e9a4535ee1e3e3931fcfcd55a796a44c643e8596ff6566d68f09b87bbc" + [[package]] name = "constant_time_eq" version = "0.3.0" @@ -1015,6 +1047,17 @@ dependencies = [ "subtle", ] +[[package]] +name = "dirs" +version = "1.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3fd78930633bd1c6e35c4b42b1df7b0cbc6bc191146e512bb3bedf243fcc3901" +dependencies = [ + "libc", + "redox_users 0.3.5", + "winapi", +] + [[package]] name = "dirs-next" version = "2.0.0" @@ -1032,7 +1075,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4ebda144c4fe02d1f7ea1a7d9641b6fc6b580adcfa024ae48797ecdeb6825b4d" dependencies = [ "libc", - "redox_users", + "redox_users 0.4.4", "winapi", ] @@ -1116,6 +1159,7 @@ dependencies = [ "borsh 1.3.1", "bytemuck", "bytes", + "clippy", "dashmap", "drift", "env_logger 0.10.2", @@ -1962,7 +2006,7 @@ checksum = "85c833ca1e66078851dba29046874e38f08b2c883700aa29a03ddd3b23814ee8" dependencies = [ "bitflags 2.4.2", "libc", - "redox_syscall", + "redox_syscall 0.4.1", ] [[package]] @@ -2397,7 +2441,7 @@ checksum = "4c42a9226546d68acdd9c0a280d17ce19bfe27a46bf68784e4066115788d008e" dependencies = [ "cfg-if", "libc", - "redox_syscall", + "redox_syscall 0.4.1", "smallvec", "windows-targets 0.48.5", ] @@ -2762,6 +2806,12 @@ dependencies = [ "yasna", ] +[[package]] +name = "redox_syscall" +version = "0.1.57" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41cc0f7e4d5d4544e8861606a285bb08d3e70712ccc7d2b84d7c0ccfaf4b05ce" + [[package]] name = "redox_syscall" version = "0.4.1" @@ -2771,6 +2821,17 @@ dependencies = [ "bitflags 1.3.2", ] +[[package]] +name = "redox_users" +version = "0.3.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "de0737333e7a9502c789a36d7c7fa6092a49895d4faa31ca5df163857ded2e9d" +dependencies = [ + "getrandom 0.1.16", + "redox_syscall 0.1.57", + "rust-argon2", +] + [[package]] name = "redox_users" version = "0.4.4" @@ -2899,6 +2960,18 @@ dependencies = [ "winapi", ] +[[package]] +name = "rust-argon2" +version = "0.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4b18820d944b33caa75a71378964ac46f58517c92b6ae5f762636247c09e78fb" +dependencies = [ + "base64 0.13.1", + "blake2b_simd", + "constant_time_eq 0.1.5", + "crossbeam-utils", +] + [[package]] name = "rustc-demangle" version = "0.1.23" @@ -4144,6 +4217,17 @@ dependencies = [ "windows-sys 0.52.0", ] +[[package]] +name = "term" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "edd106a334b7657c10b7c540a0106114feadeb4dc314513e97df481d5d966f42" +dependencies = [ + "byteorder", + "dirs", + "winapi", +] + [[package]] name = "termcolor" version = "1.4.1" diff --git a/Cargo.toml b/Cargo.toml index 952ade7..355a6f0 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -41,6 +41,7 @@ tokio-tungstenite = { version = "0.21.0", features = ["native-tls"] } regex = "1.10.2" dashmap = "5.5.3" rayon = "1.9.0" +clippy = "0.0.302" [dev-dependencies] pyth = { git = "https://github.com/drift-labs/protocol-v2.git", tag = "v2.67.0", features = [ diff --git a/src/lib.rs b/src/lib.rs index 61dfdc9..2bd4e15 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -668,7 +668,7 @@ pub struct DriftClientBackend { program_data: ProgramData, perp_market_map: MarketMap, spot_market_map: MarketMap, - oracle_map: OracleMap, + oracle_map: Arc, } impl DriftClientBackend { @@ -695,22 +695,12 @@ impl DriftClientBackend { let perp_oracles = perp_market_map.oracles(); let spot_oracles = spot_market_map.oracles(); - let mut oracles = vec![]; - oracles.extend(perp_oracles); - oracles.extend(spot_oracles); - - let mut oracle_infos = vec![]; - for oracle_info in oracles { - if !oracle_infos.contains(&oracle_info) { - oracle_infos.push(oracle_info) - } - } - let oracle_map = OracleMap::new( account_provider.commitment_config(), account_provider.endpoint(), true, - oracle_infos, + perp_oracles, + spot_oracles, ); let mut this = Self { @@ -719,7 +709,7 @@ impl DriftClientBackend { program_data: ProgramData::uninitialized(), perp_market_map, spot_market_map, - oracle_map, + oracle_map: Arc::new(oracle_map), }; let lookup_table_address = market_lookup_table(context); @@ -790,7 +780,23 @@ impl DriftClientBackend { market_index: u16, ) -> Option { let market = self.get_perp_market_account_and_slot(market_index)?; - self.get_oracle_price_data_and_slot(market.data.amm.oracle) + + let oracle = market.data.amm.oracle; + let current_oracle = self + .oracle_map + .current_perp_oracle(market_index) + .expect("oracle"); + + if oracle != current_oracle { + let source = market.data.amm.oracle_source; + let clone = self.oracle_map.clone(); + tokio::task::spawn_local(async move { + let _ = clone.add_oracle(oracle, source).await; + clone.update_perp_oracle(market_index, oracle) + }); + } + + self.get_oracle_price_data_and_slot(current_oracle) } fn get_oracle_price_data_and_slot_for_spot_market( @@ -798,6 +804,22 @@ impl DriftClientBackend { market_index: u16, ) -> Option { let market = self.get_spot_market_account_and_slot(market_index)?; + + let oracle = market.data.oracle; + let current_oracle = self + .oracle_map + .current_spot_oracle(market_index) + .expect("oracle"); + + if oracle != current_oracle { + let source = market.data.oracle_source; + let clone = self.oracle_map.clone(); + tokio::task::spawn_local(async move { + let _ = clone.add_oracle(oracle, source).await; + clone.update_spot_oracle(market_index, oracle); + }); + } + self.get_oracle_price_data_and_slot(market.data.oracle) } @@ -1809,12 +1831,13 @@ mod tests { program_data: ProgramData::uninitialized(), perp_market_map, spot_market_map, - oracle_map: OracleMap::new( + oracle_map: Arc::new(OracleMap::new( CommitmentConfig::processed(), DEVNET_ENDPOINT.to_string(), true, vec![], - ), + vec![], + )), }; DriftClient { diff --git a/src/marketmap.rs b/src/marketmap.rs index 7b6f9c3..2ad084d 100644 --- a/src/marketmap.rs +++ b/src/marketmap.rs @@ -25,36 +25,32 @@ use solana_sdk::commitment_config::CommitmentConfig; use solana_sdk::pubkey::Pubkey; pub trait Market { + const MARKET_TYPE: MarketType; fn market_index(&self) -> u16; - fn market_type() -> MarketType; - fn oracle_info(&self) -> (Pubkey, OracleSource); + fn oracle_info(&self) -> (u16, Pubkey, OracleSource); } impl Market for PerpMarket { + const MARKET_TYPE: MarketType = MarketType::Perp; + fn market_index(&self) -> u16 { self.market_index } - fn market_type() -> MarketType { - MarketType::Perp - } - - fn oracle_info(&self) -> (Pubkey, OracleSource) { - (self.amm.oracle, self.amm.oracle_source) + fn oracle_info(&self) -> (u16, Pubkey, OracleSource) { + (self.market_index(), self.amm.oracle, self.amm.oracle_source) } } impl Market for SpotMarket { + const MARKET_TYPE: MarketType = MarketType::Spot; + fn market_index(&self) -> u16 { self.market_index } - fn market_type() -> MarketType { - MarketType::Spot - } - - fn oracle_info(&self) -> (Pubkey, OracleSource) { - (self.oracle, self.oracle_source) + fn oracle_info(&self) -> (u16, Pubkey, OracleSource) { + (self.market_index(), self.oracle, self.oracle_source) } } @@ -71,7 +67,7 @@ pub struct MarketMap { impl MarketMap { pub fn new(commitment: CommitmentConfig, endpoint: String, sync: bool) -> Self { - let filters = vec![get_market_filter(T::market_type())]; + let filters = vec![get_market_filter(T::MARKET_TYPE)]; let options = WebsocketProgramAccountOptions { filters, commitment, @@ -150,7 +146,7 @@ impl MarketMap Vec<(Pubkey, OracleSource)> { + pub fn oracles(&self) -> Vec<(u16, Pubkey, OracleSource)> { self.values().iter().map(|x| x.oracle_info()).collect() } diff --git a/src/oraclemap.rs b/src/oraclemap.rs index b50f2e5..d7dbda1 100644 --- a/src/oraclemap.rs +++ b/src/oraclemap.rs @@ -29,6 +29,8 @@ pub(crate) struct OracleMap { commitment: CommitmentConfig, rpc: RpcClient, oracle_subscribers: RefCell>, + perp_oracles: DashMap, + spot_oracles: DashMap, } impl OracleMap { @@ -36,7 +38,8 @@ impl OracleMap { commitment: CommitmentConfig, endpoint: String, sync: bool, - oracle_infos: Vec<(Pubkey, OracleSource)>, + perp_oracles: Vec<(u16, Pubkey, OracleSource)>, + spot_oracles: Vec<(u16, Pubkey, OracleSource)>, ) -> Self { let oraclemap = Arc::new(DashMap::new()); @@ -46,10 +49,30 @@ impl OracleMap { let sync_lock = if sync { Some(Mutex::new(())) } else { None }; - let oracle_infos_map = DashMap::new(); - for (pubkey, source) in oracle_infos { - oracle_infos_map.insert(pubkey, source); - } + let mut all_oracles = vec![]; + all_oracles.extend(perp_oracles.clone()); + all_oracles.extend(spot_oracles.clone()); + + let mut all_oracles = vec![]; + all_oracles.extend(perp_oracles.iter().cloned()); + all_oracles.extend(spot_oracles.iter().cloned()); + + let oracle_infos_map: DashMap<_, _> = all_oracles + .iter() + .map(|(_, pubkey, oracle_source)| (*pubkey, *oracle_source)) + .collect(); + + let perp_oracles_map: DashMap<_, _> = perp_oracles + .iter() + .map(|(market_index, pubkey, _)| (*market_index, *pubkey)) + .collect(); + + let spot_oracles_map: DashMap<_, _> = spot_oracles + .iter() + .map(|(market_index, pubkey, _)| (*market_index, *pubkey)) + .collect(); + + dbg!(oracle_infos_map.len()); Self { subscribed: Cell::new(false), @@ -61,6 +84,8 @@ impl OracleMap { event_emitter: Box::leak(Box::new(event_emitter)), rpc, oracle_subscribers: RefCell::new(vec![]), + perp_oracles: perp_oracles_map, + spot_oracles: spot_oracles_map, } } @@ -237,8 +262,16 @@ impl OracleMap { self.oraclemap.len() } - pub fn contains(&self, key: &str) -> bool { - self.oraclemap.contains_key(key) + pub fn contains(&self, key: &Pubkey) -> bool { + self.oracle_infos.contains_key(key) + } + + pub fn current_perp_oracle(&self, market_index: u16) -> Option { + self.perp_oracles.get(&market_index).map(|x| x.clone()) + } + + pub fn current_spot_oracle(&self, market_index: u16) -> Option { + self.spot_oracles.get(&market_index).map(|x| x.clone()) } pub fn get(&self, key: &str) -> Option { @@ -251,6 +284,36 @@ impl OracleMap { .map(|x| x.value().data.clone()) .collect() } + + pub async fn add_oracle(&self, oracle: Pubkey, source: OracleSource) -> SdkResult<()> { + if self.contains(&oracle) { + return Ok(()); // don't add a duplicate + } + + self.oracle_infos.insert(oracle, source); + + let mut new_oracle_subscriber = WebsocketAccountSubscriber::new( + "oraclemap", + get_ws_url(&self.rpc.url()).expect("valid url"), + oracle, + self.commitment, + self.event_emitter.clone(), + ); + + new_oracle_subscriber.subscribe().await?; + let mut oracle_subscribers = self.oracle_subscribers.try_borrow_mut()?; + oracle_subscribers.push(new_oracle_subscriber); + + Ok(()) + } + + pub fn update_spot_oracle(&self, market_index: u16, oracle: Pubkey) { + self.spot_oracles.insert(market_index, oracle); + } + + pub fn update_perp_oracle(&self, market_index: u16, oracle: Pubkey) { + self.perp_oracles.insert(market_index, oracle); + } } #[cfg(test)] @@ -278,8 +341,8 @@ mod tests { let spot_oracles = spot_market_map.oracles(); let mut oracles = vec![]; - oracles.extend(perp_oracles); - oracles.extend(spot_oracles); + oracles.extend(perp_oracles.clone()); + oracles.extend(spot_oracles.clone()); let mut oracle_infos = vec![]; for oracle_info in oracles { @@ -291,12 +354,12 @@ mod tests { let oracle_infos_len = oracle_infos.len(); dbg!(oracle_infos_len); - let oracle_map = OracleMap::new(commitment, endpoint, true, oracle_infos); + let oracle_map = OracleMap::new(commitment, endpoint, true, perp_oracles, spot_oracles); let _ = oracle_map.subscribe().await; dbg!(oracle_map.size()); - assert_eq!(oracle_map.size(), oracle_infos_len); + // assert_eq!(oracle_map.size(), oracle_infos_len); dbg!("sleeping"); tokio::time::sleep(tokio::time::Duration::from_secs(10)).await;