[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/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.

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)]
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>",

View File

@ -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())
}
}

View File

@ -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.

View File

@ -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>;
}

View File

@ -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()
}
}