Ver Fonte

[hermes] add get price feed ids + refactor (#747)

* [Hermes] Add get price feed ids + refactor

* Address feedbacks
Ali Behjati há 2 anos atrás
pai
commit
3ad3a46b1d

+ 1 - 0
hermes/src/network/rpc.rs

@@ -43,6 +43,7 @@ pub async fn spawn(rpc_addr: String, store: Store) -> Result<()> {
         .route("/api/latest_vaas", get(rest::latest_vaas))
         .route("/api/get_vaa", get(rest::get_vaa))
         .route("/api/get_vaa_ccip", get(rest::get_vaa_ccip))
+        .route("/api/price_feed_ids", get(rest::price_feed_ids))
         .with_state(state.clone());
 
     // Listen in the background for new VAA's from the Wormhole RPC.

+ 23 - 1
hermes/src/network/rpc/rest.rs

@@ -29,6 +29,10 @@ use {
     },
 };
 
+/// PriceIdInput is a wrapper around a 32-byte hex string.
+/// that supports a flexible deserialization from a hex string.
+/// It supports both 0x-prefixed and non-prefixed hex strings,
+/// and also supports both lower and upper case characters.
 #[derive(Debug, Clone, Deref, DerefMut)]
 pub struct PriceIdInput([u8; 32]);
 // TODO: Use const generics instead of macro.
@@ -42,6 +46,7 @@ impl From<PriceIdInput> for PriceIdentifier {
 
 pub enum RestError {
     UpdateDataNotFound,
+    CcipUpdateDataNotFound,
 }
 
 impl IntoResponse for RestError {
@@ -50,10 +55,26 @@ impl IntoResponse for RestError {
             RestError::UpdateDataNotFound => {
                 (StatusCode::NOT_FOUND, "Update data not found").into_response()
             }
+            RestError::CcipUpdateDataNotFound => {
+                // Returning Bad Gateway error because CCIP expects a 5xx error if it needs to
+                // retry or try other endpoints. Bad Gateway seems the best choice here as this
+                // is not an internal error and could happen on two scenarios:
+                // 1. DB Api is not responding well (Bad Gateway is appropriate here)
+                // 2. Publish time is a few seconds before current time and a VAA
+                //    Will be available in a few seconds. So we want the client to retry.
+
+                (StatusCode::BAD_GATEWAY, "CCIP update data not found").into_response()
+            }
         }
     }
 }
 
+pub async fn price_feed_ids(
+    State(state): State<super::State>,
+) -> Result<Json<Vec<PriceIdentifier>>, RestError> {
+    let price_feeds = state.store.get_price_feed_ids();
+    Ok(Json(price_feeds))
+}
 
 #[derive(Debug, serde::Deserialize)]
 pub struct LatestVaasQueryParams {
@@ -173,7 +194,7 @@ pub async fn get_vaa_ccip(
     let price_feeds_with_update_data = state
         .store
         .get_price_feeds_with_update_data(vec![price_id], RequestTime::FirstAfter(publish_time))
-        .map_err(|_| RestError::UpdateDataNotFound)?;
+        .map_err(|_| RestError::CcipUpdateDataNotFound)?;
 
     let vaa = price_feeds_with_update_data
         .update_data
@@ -199,6 +220,7 @@ pub async fn live() -> Result<impl IntoResponse, std::convert::Infallible> {
 pub async fn index() -> impl IntoResponse {
     Json([
         "/live",
+        "/api/price_feed_ids",
         "/api/latest_price_feeds?ids[]=<price_feed_id>&ids[]=<price_feed_id_2>&..",
         "/api/latest_vaas?ids[]=<price_feed_id>&ids[]=<price_feed_id_2>&...",
         "/api/get_vaa?id=<price_feed_id>&publish_time=<publish_time_in_unix_timestamp>",

+ 4 - 0
hermes/src/store.rs

@@ -81,4 +81,8 @@ impl Store {
             request_time,
         )
     }
+
+    pub fn get_price_feed_ids(&self) -> Vec<PriceIdentifier> {
+        proof::batch_vaa::get_price_feed_ids(self.state.clone())
+    }
 }

+ 15 - 3
hermes/src/store/proof/batch_vaa.rs

@@ -58,7 +58,7 @@ pub fn store_vaa_update(state: State, vaa_bytes: Vec<u8>) -> Result<()> {
             publish_time,
         };
 
-        let key = Key::new(price_feed.id.to_bytes().to_vec());
+        let key = Key::BatchVaa(price_feed.id);
         state.insert(key, publish_time, StorageData::BatchVaa(price_info))?;
     }
     Ok(())
@@ -73,7 +73,7 @@ pub fn get_price_feeds_with_update_data(
     let mut price_feeds = HashMap::new();
     let mut vaas: HashSet<Vec<u8>> = HashSet::new();
     for price_id in price_ids {
-        let key = Key::new(price_id.to_bytes().to_vec());
+        let key = Key::BatchVaa(price_id);
         let maybe_data = state.get(key, request_time.clone())?;
 
         match maybe_data {
@@ -82,7 +82,6 @@ pub fn get_price_feeds_with_update_data(
                 vaas.insert(price_info.vaa_bytes);
             }
             None => {
-                log::info!("No price feed found for price id: {:?}", price_id);
                 return Err(anyhow!("No price feed found for price id: {:?}", price_id));
             }
         }
@@ -97,6 +96,19 @@ pub fn get_price_feeds_with_update_data(
 }
 
 
+pub fn get_price_feed_ids(state: State) -> Vec<PriceIdentifier> {
+    // Currently we have only one type and filter map is not necessary.
+    // But we might have more types in the future.
+    #[allow(clippy::unnecessary_filter_map)]
+    state
+        .keys()
+        .into_iter()
+        .filter_map(|key| match key {
+            Key::BatchVaa(price_id) => Some(price_id),
+        })
+        .collect()
+}
+
 /// Convert a PriceAttestation to a PriceFeed.
 ///
 /// We cannot implmenet this function as From/Into trait because none of these types are defined in this crate.

+ 5 - 7
hermes/src/store/storage.rs

@@ -9,6 +9,7 @@ use {
         Deref,
         DerefMut,
     },
+    pyth_sdk::PriceIdentifier,
 };
 
 pub mod local_cache;
@@ -18,13 +19,9 @@ pub enum StorageData {
     BatchVaa(PriceInfo),
 }
 
-#[derive(Clone, PartialEq, Eq, Debug, Hash, Deref, DerefMut)]
-pub struct Key(Vec<u8>);
-
-impl Key {
-    pub fn new(key: Vec<u8>) -> Self {
-        Self(key)
-    }
+#[derive(Clone, PartialEq, Eq, Debug, Hash)]
+pub enum Key {
+    BatchVaa(PriceIdentifier),
 }
 
 /// This trait defines the interface for update data storage
@@ -37,4 +34,5 @@ impl Key {
 pub trait Storage: Sync + Send {
     fn insert(&self, key: Key, time: UnixTimestamp, value: StorageData) -> Result<()>;
     fn get(&self, key: Key, request_time: RequestTime) -> Result<Option<StorageData>>;
+    fn keys(&self) -> Vec<Key>;
 }

+ 4 - 0
hermes/src/store/storage/local_cache.rs

@@ -99,4 +99,8 @@ impl Storage for LocalCache {
             None => Ok(None),
         }
     }
+
+    fn keys(&self) -> Vec<Key> {
+        self.cache.iter().map(|entry| entry.key().clone()).collect()
+    }
 }