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

* [Hermes] Add get price feed ids + refactor

* Address feedbacks
This commit is contained in:
Ali Behjati 2023-04-18 17:50:34 +02:00 committed by GitHub
parent 32596d5d4e
commit 3ad3a46b1d
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 52 additions and 11 deletions

View File

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

View File

@ -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)] #[derive(Debug, Clone, Deref, DerefMut)]
pub struct PriceIdInput([u8; 32]); pub struct PriceIdInput([u8; 32]);
// TODO: Use const generics instead of macro. // TODO: Use const generics instead of macro.
@ -42,6 +46,7 @@ impl From<PriceIdInput> for PriceIdentifier {
pub enum RestError { pub enum RestError {
UpdateDataNotFound, UpdateDataNotFound,
CcipUpdateDataNotFound,
} }
impl IntoResponse for RestError { impl IntoResponse for RestError {
@ -50,10 +55,26 @@ impl IntoResponse for RestError {
RestError::UpdateDataNotFound => { RestError::UpdateDataNotFound => {
(StatusCode::NOT_FOUND, "Update data not found").into_response() (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)] #[derive(Debug, serde::Deserialize)]
pub struct LatestVaasQueryParams { pub struct LatestVaasQueryParams {
@ -173,7 +194,7 @@ pub async fn get_vaa_ccip(
let price_feeds_with_update_data = state let price_feeds_with_update_data = state
.store .store
.get_price_feeds_with_update_data(vec![price_id], RequestTime::FirstAfter(publish_time)) .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 let vaa = price_feeds_with_update_data
.update_data .update_data
@ -199,6 +220,7 @@ pub async fn live() -> Result<impl IntoResponse, std::convert::Infallible> {
pub async fn index() -> impl IntoResponse { pub async fn index() -> impl IntoResponse {
Json([ Json([
"/live", "/live",
"/api/price_feed_ids",
"/api/latest_price_feeds?ids[]=<price_feed_id>&ids[]=<price_feed_id_2>&..", "/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/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>", "/api/get_vaa?id=<price_feed_id>&publish_time=<publish_time_in_unix_timestamp>",

View File

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

View File

@ -58,7 +58,7 @@ pub fn store_vaa_update(state: State, vaa_bytes: Vec<u8>) -> Result<()> {
publish_time, 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))?; state.insert(key, publish_time, StorageData::BatchVaa(price_info))?;
} }
Ok(()) Ok(())
@ -73,7 +73,7 @@ pub fn get_price_feeds_with_update_data(
let mut price_feeds = HashMap::new(); let mut price_feeds = HashMap::new();
let mut vaas: HashSet<Vec<u8>> = HashSet::new(); let mut vaas: HashSet<Vec<u8>> = HashSet::new();
for price_id in price_ids { 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())?; let maybe_data = state.get(key, request_time.clone())?;
match maybe_data { match maybe_data {
@ -82,7 +82,6 @@ pub fn get_price_feeds_with_update_data(
vaas.insert(price_info.vaa_bytes); vaas.insert(price_info.vaa_bytes);
} }
None => { None => {
log::info!("No price feed found for price id: {:?}", price_id);
return Err(anyhow!("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. /// 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. /// We cannot implmenet this function as From/Into trait because none of these types are defined in this crate.

View File

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

View File

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