From 3ad3a46b1d14a05b4389846c875736a8c6a4e998 Mon Sep 17 00:00:00 2001 From: Ali Behjati Date: Tue, 18 Apr 2023 17:50:34 +0200 Subject: [PATCH] [hermes] add get price feed ids + refactor (#747) * [Hermes] Add get price feed ids + refactor * Address feedbacks --- hermes/src/network/rpc.rs | 1 + hermes/src/network/rpc/rest.rs | 24 +++++++++++++++++++++++- hermes/src/store.rs | 4 ++++ hermes/src/store/proof/batch_vaa.rs | 18 +++++++++++++++--- hermes/src/store/storage.rs | 12 +++++------- hermes/src/store/storage/local_cache.rs | 4 ++++ 6 files changed, 52 insertions(+), 11 deletions(-) diff --git a/hermes/src/network/rpc.rs b/hermes/src/network/rpc.rs index ddb090d4..0a7f14c5 100644 --- a/hermes/src/network/rpc.rs +++ b/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. diff --git a/hermes/src/network/rpc/rest.rs b/hermes/src/network/rpc/rest.rs index 5b565763..c9d7df3e 100644 --- a/hermes/src/network/rpc/rest.rs +++ b/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 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, +) -> Result>, 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 { pub async fn index() -> impl IntoResponse { Json([ "/live", + "/api/price_feed_ids", "/api/latest_price_feeds?ids[]=&ids[]=&..", "/api/latest_vaas?ids[]=&ids[]=&...", "/api/get_vaa?id=&publish_time=", diff --git a/hermes/src/store.rs b/hermes/src/store.rs index de222735..fe59b601 100644 --- a/hermes/src/store.rs +++ b/hermes/src/store.rs @@ -81,4 +81,8 @@ impl Store { request_time, ) } + + pub fn get_price_feed_ids(&self) -> Vec { + proof::batch_vaa::get_price_feed_ids(self.state.clone()) + } } diff --git a/hermes/src/store/proof/batch_vaa.rs b/hermes/src/store/proof/batch_vaa.rs index adeaf153..8c521c82 100644 --- a/hermes/src/store/proof/batch_vaa.rs +++ b/hermes/src/store/proof/batch_vaa.rs @@ -58,7 +58,7 @@ pub fn store_vaa_update(state: State, vaa_bytes: Vec) -> 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> = 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 { + // 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. diff --git a/hermes/src/store/storage.rs b/hermes/src/store/storage.rs index f991dd54..5540cddd 100644 --- a/hermes/src/store/storage.rs +++ b/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); - -impl Key { - pub fn new(key: Vec) -> 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>; + fn keys(&self) -> Vec; } diff --git a/hermes/src/store/storage/local_cache.rs b/hermes/src/store/storage/local_cache.rs index 77b51941..bb95bed3 100644 --- a/hermes/src/store/storage/local_cache.rs +++ b/hermes/src/store/storage/local_cache.rs @@ -99,4 +99,8 @@ impl Storage for LocalCache { None => Ok(None), } } + + fn keys(&self) -> Vec { + self.cache.iter().map(|entry| entry.key().clone()).collect() + } }