|
|
@@ -0,0 +1,134 @@
|
|
|
+//! This module communicates with Pyth Benchmarks, an API for historical price feeds and their updates.
|
|
|
+
|
|
|
+use {
|
|
|
+ crate::{
|
|
|
+ aggregate::{
|
|
|
+ PriceFeedUpdate,
|
|
|
+ PriceFeedsWithUpdateData,
|
|
|
+ UnixTimestamp,
|
|
|
+ },
|
|
|
+ api::types::PriceUpdate,
|
|
|
+ },
|
|
|
+ anyhow::Result,
|
|
|
+ base64::{
|
|
|
+ engine::general_purpose::STANDARD as base64_standard_engine,
|
|
|
+ Engine as _,
|
|
|
+ },
|
|
|
+ pyth_sdk::{
|
|
|
+ Price,
|
|
|
+ PriceFeed,
|
|
|
+ PriceIdentifier,
|
|
|
+ },
|
|
|
+ serde::Deserialize,
|
|
|
+};
|
|
|
+
|
|
|
+const BENCHMARKS_REQUEST_TIMEOUT: std::time::Duration = std::time::Duration::from_secs(30);
|
|
|
+
|
|
|
+#[derive(Deserialize, Debug, Clone)]
|
|
|
+enum BlobEncoding {
|
|
|
+ #[serde(rename = "base64")]
|
|
|
+ Base64,
|
|
|
+ #[serde(rename = "hex")]
|
|
|
+ Hex,
|
|
|
+}
|
|
|
+
|
|
|
+#[derive(Deserialize, Debug, Clone)]
|
|
|
+struct BinaryBlob {
|
|
|
+ pub encoding: BlobEncoding,
|
|
|
+ pub data: Vec<String>,
|
|
|
+}
|
|
|
+
|
|
|
+impl TryFrom<PriceUpdate> for PriceFeedsWithUpdateData {
|
|
|
+ type Error = anyhow::Error;
|
|
|
+ fn try_from(price_update: PriceUpdate) -> Result<Self> {
|
|
|
+ let price_feeds = match price_update.parsed {
|
|
|
+ Some(parsed_updates) => parsed_updates
|
|
|
+ .into_iter()
|
|
|
+ .map(|parsed_price_update| {
|
|
|
+ Ok(PriceFeedUpdate {
|
|
|
+ price_feed: PriceFeed::new(
|
|
|
+ parsed_price_update.id,
|
|
|
+ Price {
|
|
|
+ price: parsed_price_update.price.price,
|
|
|
+ conf: parsed_price_update.price.conf,
|
|
|
+ expo: parsed_price_update.price.expo,
|
|
|
+ publish_time: parsed_price_update.price.publish_time,
|
|
|
+ },
|
|
|
+ Price {
|
|
|
+ price: parsed_price_update.ema_price.price,
|
|
|
+ conf: parsed_price_update.ema_price.conf,
|
|
|
+ expo: parsed_price_update.ema_price.expo,
|
|
|
+ publish_time: parsed_price_update.ema_price.publish_time,
|
|
|
+ },
|
|
|
+ ),
|
|
|
+ slot: parsed_price_update.metadata.slot,
|
|
|
+ received_at: parsed_price_update.metadata.proof_available_time,
|
|
|
+ update_data: None, // This field is not available in ParsedPriceUpdate
|
|
|
+ prev_publish_time: parsed_price_update.metadata.prev_publish_time,
|
|
|
+ })
|
|
|
+ })
|
|
|
+ .collect::<Result<Vec<_>>>(),
|
|
|
+ None => Err(anyhow::anyhow!("No parsed price updates available")),
|
|
|
+ }?;
|
|
|
+
|
|
|
+ let update_data = price_update
|
|
|
+ .binary
|
|
|
+ .data
|
|
|
+ .iter()
|
|
|
+ .map(|hex_str| hex::decode(hex_str).unwrap_or_default())
|
|
|
+ .collect::<Vec<Vec<u8>>>();
|
|
|
+
|
|
|
+ Ok(PriceFeedsWithUpdateData {
|
|
|
+ price_feeds,
|
|
|
+ update_data,
|
|
|
+ })
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+#[async_trait::async_trait]
|
|
|
+pub trait Benchmarks {
|
|
|
+ async fn get_verified_price_feeds(
|
|
|
+ &self,
|
|
|
+ price_ids: &[PriceIdentifier],
|
|
|
+ publish_time: UnixTimestamp,
|
|
|
+ ) -> Result<PriceFeedsWithUpdateData>;
|
|
|
+}
|
|
|
+
|
|
|
+#[async_trait::async_trait]
|
|
|
+impl Benchmarks for crate::state::State {
|
|
|
+ async fn get_verified_price_feeds(
|
|
|
+ &self,
|
|
|
+ price_ids: &[PriceIdentifier],
|
|
|
+ publish_time: UnixTimestamp,
|
|
|
+ ) -> Result<PriceFeedsWithUpdateData> {
|
|
|
+ let endpoint = self
|
|
|
+ .benchmarks_endpoint
|
|
|
+ .as_ref()
|
|
|
+ .ok_or_else(|| anyhow::anyhow!("Benchmarks endpoint is not set"))?
|
|
|
+ .join(&format!("/v1/updates/price/{}", publish_time))
|
|
|
+ .unwrap();
|
|
|
+
|
|
|
+ let client = reqwest::Client::new();
|
|
|
+ let mut request = client
|
|
|
+ .get(endpoint)
|
|
|
+ .timeout(BENCHMARKS_REQUEST_TIMEOUT)
|
|
|
+ .query(&[("encoding", "hex")])
|
|
|
+ .query(&[("parsed", "true")]);
|
|
|
+
|
|
|
+ for price_id in price_ids {
|
|
|
+ request = request.query(&[("ids", price_id)])
|
|
|
+ }
|
|
|
+
|
|
|
+ let response = request.send().await?;
|
|
|
+
|
|
|
+ if response.status() != reqwest::StatusCode::OK {
|
|
|
+ return Err(anyhow::anyhow!(format!(
|
|
|
+ "Price update for price ids {:?} with publish time {} not found in benchmarks. Status code: {}, message: {}",
|
|
|
+ price_ids, publish_time, response.status(), response.text().await?
|
|
|
+ )));
|
|
|
+ }
|
|
|
+
|
|
|
+ let price_update: PriceUpdate = response.json().await?;
|
|
|
+ price_update.try_into()
|
|
|
+ }
|
|
|
+}
|