From ad7023e3d25579a1e033f3f48d25349108508a68 Mon Sep 17 00:00:00 2001 From: MartinquaXD Date: Sat, 2 Nov 2024 20:03:52 +0000 Subject: [PATCH 1/2] Move API code into orderbook crate --- crates/orderbook/src/api.rs | 322 +++++++++++++++++- crates/orderbook/src/api/cancel_order.rs | 8 +- crates/orderbook/src/api/cancel_orders.rs | 6 +- crates/orderbook/src/api/get_app_data.rs | 2 +- crates/orderbook/src/api/get_auction.rs | 5 +- crates/orderbook/src/api/get_native_price.rs | 6 +- crates/orderbook/src/api/get_order_by_uid.rs | 4 +- crates/orderbook/src/api/get_order_status.rs | 5 +- crates/orderbook/src/api/get_orders_by_tx.rs | 5 +- .../src/api/get_solver_competition.rs | 2 +- crates/orderbook/src/api/get_total_surplus.rs | 2 +- crates/orderbook/src/api/get_trades.rs | 12 +- crates/orderbook/src/api/get_user_orders.rs | 5 +- crates/orderbook/src/api/post_order.rs | 28 +- crates/orderbook/src/api/post_quote.rs | 15 +- crates/orderbook/src/api/put_app_data.rs | 2 +- crates/shared/src/api.rs | 320 ----------------- crates/shared/src/lib.rs | 1 - 18 files changed, 371 insertions(+), 379 deletions(-) delete mode 100644 crates/shared/src/api.rs diff --git a/crates/orderbook/src/api.rs b/crates/orderbook/src/api.rs index 3a07ff38e7..a272950148 100644 --- a/crates/orderbook/src/api.rs +++ b/crates/orderbook/src/api.rs @@ -1,11 +1,17 @@ use { crate::{app_data, database::Postgres, orderbook::Orderbook, quoter::QuoteHandler}, - shared::{ - api::{box_filter, error, finalize_router, ApiReply}, - price_estimation::native::NativePriceEstimating, + anyhow::Result, + serde::{de::DeserializeOwned, Serialize}, + shared::price_estimation::{native::NativePriceEstimating, PriceEstimationError}, + std::{convert::Infallible, fmt::Debug, sync::Arc, time::Instant}, + warp::{ + filters::BoxedFilter, + hyper::StatusCode, + reply::{json, with_status, Json, WithStatus}, + Filter, + Rejection, + Reply, }, - std::sync::Arc, - warp::{Filter, Rejection, Reply}, }; mod cancel_order; @@ -105,3 +111,309 @@ pub fn handle_all_routes( finalize_router(routes, "orderbook::api::request_summary") } + +pub type ApiReply = WithStatus; + +// We turn Rejection into Reply to workaround warp not setting CORS headers on +// rejections. +async fn handle_rejection(err: Rejection) -> Result { + let response = err.default_response(); + + let metrics = ApiMetrics::instance(observe::metrics::get_storage_registry()).unwrap(); + metrics + .requests_rejected + .with_label_values(&[response.status().as_str()]) + .inc(); + + Ok(response) +} + +#[derive(prometheus_metric_storage::MetricStorage, Clone, Debug)] +#[metric(subsystem = "api")] +struct ApiMetrics { + /// Number of completed API requests. + #[metric(labels("method", "status_code"))] + requests_complete: prometheus::IntCounterVec, + + /// Number of rejected API requests. + #[metric(labels("status_code"))] + requests_rejected: prometheus::IntCounterVec, + + /// Execution time for each API request. + #[metric(labels("method"), buckets(0.1, 0.5, 1, 2, 4, 6, 8, 10))] + requests_duration_seconds: prometheus::HistogramVec, +} + +impl ApiMetrics { + // Status codes we care about in our application. Populated with: + // `rg -oIN 'StatusCode::[A-Z_]+' | sort | uniq`. + const INITIAL_STATUSES: &'static [StatusCode] = &[ + StatusCode::OK, + StatusCode::CREATED, + StatusCode::BAD_REQUEST, + StatusCode::UNAUTHORIZED, + StatusCode::FORBIDDEN, + StatusCode::NOT_FOUND, + StatusCode::INTERNAL_SERVER_ERROR, + StatusCode::SERVICE_UNAVAILABLE, + ]; + + fn reset_requests_rejected(&self) { + for status in Self::INITIAL_STATUSES { + self.requests_rejected + .with_label_values(&[status.as_str()]) + .reset(); + } + } + + fn reset_requests_complete(&self, method: &str) { + for status in Self::INITIAL_STATUSES { + self.requests_complete + .with_label_values(&[method, status.as_str()]) + .reset(); + } + } + + fn on_request_completed(&self, method: &str, status: StatusCode, timer: Instant) { + self.requests_complete + .with_label_values(&[method, status.as_str()]) + .inc(); + self.requests_duration_seconds + .with_label_values(&[method]) + .observe(timer.elapsed().as_secs_f64()); + } +} + +#[derive(Serialize)] +#[serde(rename_all = "camelCase")] +struct Error<'a> { + error_type: &'a str, + description: &'a str, + /// Additional arbitrary data that can be attached to an API error. + #[serde(skip_serializing_if = "Option::is_none")] + data: Option, +} + +pub fn error(error_type: &str, description: impl AsRef) -> Json { + json(&Error { + error_type, + description: description.as_ref(), + data: None, + }) +} + +pub fn rich_error(error_type: &str, description: impl AsRef, data: impl Serialize) -> Json { + let data = match serde_json::to_value(&data) { + Ok(value) => Some(value), + Err(err) => { + tracing::warn!(?err, "failed to serialize error data"); + None + } + }; + + json(&Error { + error_type, + description: description.as_ref(), + data, + }) +} + +pub fn internal_error_reply() -> ApiReply { + with_status( + error("InternalServerError", ""), + StatusCode::INTERNAL_SERVER_ERROR, + ) +} + +pub fn convert_json_response(result: Result) -> WithStatus +where + T: Serialize, + E: IntoWarpReply + Debug, +{ + match result { + Ok(response) => with_status(warp::reply::json(&response), StatusCode::OK), + Err(err) => err.into_warp_reply(), + } +} + +pub trait IntoWarpReply { + fn into_warp_reply(self) -> ApiReply; +} + +pub async fn response_body(response: warp::hyper::Response) -> Vec { + let mut body = response.into_body(); + let mut result = Vec::new(); + while let Some(bytes) = futures::StreamExt::next(&mut body).await { + result.extend_from_slice(bytes.unwrap().as_ref()); + } + result +} + +const MAX_JSON_BODY_PAYLOAD: u64 = 1024 * 16; + +pub fn extract_payload( +) -> impl Filter + Clone { + // (rejecting huge payloads)... + extract_payload_with_max_size(MAX_JSON_BODY_PAYLOAD) +} + +pub fn extract_payload_with_max_size( + max_size: u64, +) -> impl Filter + Clone { + warp::body::content_length_limit(max_size).and(warp::body::json()) +} + +pub type BoxedRoute = BoxedFilter<(Box,)>; + +pub fn box_filter(filter: Filter_) -> BoxedFilter<(Box,)> +where + Filter_: Filter + Send + Sync + 'static, + Reply_: Reply + Send + 'static, +{ + filter.map(|a| Box::new(a) as Box).boxed() +} + +/// Sets up basic metrics, cors and proper log tracing for all routes. +/// +/// # Panics +/// +/// This method panics if `routes` is empty. +pub fn finalize_router( + routes: Vec<(&'static str, BoxedRoute)>, + log_prefix: &'static str, +) -> impl Filter + Clone { + let metrics = ApiMetrics::instance(observe::metrics::get_storage_registry()).unwrap(); + metrics.reset_requests_rejected(); + for (method, _) in &routes { + metrics.reset_requests_complete(method); + } + + let router = routes + .into_iter() + .fold( + Option::)>>::None, + |router, (method, route)| { + let route = route.map(move |result| (method, result)).untuple_one(); + let next = match router { + Some(router) => router.or(route).unify().boxed(), + None => route.boxed(), + }; + Some(next) + }, + ) + .expect("routes cannot be empty"); + + let instrumented = + warp::any() + .map(Instant::now) + .and(router) + .map(|timer, method, reply: Box| { + let response = reply.into_response(); + metrics.on_request_completed(method, response.status(), timer); + response + }); + + // Final setup + let cors = warp::cors() + .allow_any_origin() + .allow_methods(vec!["GET", "POST", "DELETE", "OPTIONS", "PUT", "PATCH"]) + .allow_headers(vec!["Origin", "Content-Type", "X-Auth-Token", "X-AppId"]); + + warp::path!("api" / ..) + .and(instrumented) + .recover(handle_rejection) + .with(cors) + .with(warp::log::log(log_prefix)) +} + +impl IntoWarpReply for PriceEstimationError { + fn into_warp_reply(self) -> WithStatus { + match self { + Self::UnsupportedToken { token, reason } => with_status( + error( + "UnsupportedToken", + format!("Token {token:?} is unsupported: {reason:}"), + ), + StatusCode::BAD_REQUEST, + ), + Self::UnsupportedOrderType(order_type) => with_status( + error( + "UnsupportedOrderType", + format!("{order_type} not supported"), + ), + StatusCode::BAD_REQUEST, + ), + Self::NoLiquidity | Self::RateLimited | Self::EstimatorInternal(_) => with_status( + error("NoLiquidity", "no route found"), + StatusCode::NOT_FOUND, + ), + Self::ProtocolInternal(err) => { + tracing::error!(?err, "PriceEstimationError::Other"); + internal_error_reply() + } + } + } +} + +#[cfg(test)] +mod tests { + use {super::*, serde::ser, serde_json::json}; + + #[test] + fn rich_errors_skip_unset_data_field() { + assert_eq!( + serde_json::to_value(&Error { + error_type: "foo", + description: "bar", + data: None, + }) + .unwrap(), + json!({ + "errorType": "foo", + "description": "bar", + }), + ); + assert_eq!( + serde_json::to_value(Error { + error_type: "foo", + description: "bar", + data: Some(json!(42)), + }) + .unwrap(), + json!({ + "errorType": "foo", + "description": "bar", + "data": 42, + }), + ); + } + + #[tokio::test] + async fn rich_errors_handle_serialization_errors() { + struct AlwaysErrors; + impl Serialize for AlwaysErrors { + fn serialize(&self, _: S) -> Result + where + S: serde::Serializer, + { + Err(ser::Error::custom("error")) + } + } + + let body = warp::hyper::body::to_bytes( + rich_error("foo", "bar", AlwaysErrors) + .into_response() + .into_body(), + ) + .await + .unwrap(); + + assert_eq!( + serde_json::from_slice::(&body).unwrap(), + json!({ + "errorType": "foo", + "description": "bar", + }) + ); + } +} diff --git a/crates/orderbook/src/api/cancel_order.rs b/crates/orderbook/src/api/cancel_order.rs index f86989f442..b0a76b0f6c 100644 --- a/crates/orderbook/src/api/cancel_order.rs +++ b/crates/orderbook/src/api/cancel_order.rs @@ -1,8 +1,10 @@ use { - crate::orderbook::{OrderCancellationError, Orderbook}, + crate::{ + api::{convert_json_response, extract_payload, IntoWarpReply}, + orderbook::{OrderCancellationError, Orderbook}, + }, anyhow::Result, model::order::{CancellationPayload, OrderCancellation, OrderUid}, - shared::api::{convert_json_response, extract_payload, IntoWarpReply}, std::{convert::Infallible, sync::Arc}, warp::{hyper::StatusCode, reply::with_status, Filter, Rejection}, }; @@ -55,7 +57,7 @@ impl IntoWarpReply for OrderCancellationError { ), Self::Other(err) => { tracing::error!(?err, "cancel_order"); - shared::api::internal_error_reply() + crate::api::internal_error_reply() } } } diff --git a/crates/orderbook/src/api/cancel_orders.rs b/crates/orderbook/src/api/cancel_orders.rs index 5054ccf87d..b252fd8dae 100644 --- a/crates/orderbook/src/api/cancel_orders.rs +++ b/crates/orderbook/src/api/cancel_orders.rs @@ -1,8 +1,10 @@ use { - crate::orderbook::{OrderCancellationError, Orderbook}, + crate::{ + api::{convert_json_response, extract_payload}, + orderbook::{OrderCancellationError, Orderbook}, + }, anyhow::Result, model::order::SignedOrderCancellations, - shared::api::{convert_json_response, extract_payload}, std::{convert::Infallible, sync::Arc}, warp::{Filter, Rejection}, }; diff --git a/crates/orderbook/src/api/get_app_data.rs b/crates/orderbook/src/api/get_app_data.rs index e7ba39051a..1dccf1b6d3 100644 --- a/crates/orderbook/src/api/get_app_data.rs +++ b/crates/orderbook/src/api/get_app_data.rs @@ -34,7 +34,7 @@ pub fn get( )), Err(err) => { tracing::error!(?err, "get_app_data_by_hash"); - Box::new(shared::api::internal_error_reply()) + Box::new(crate::api::internal_error_reply()) } }) } diff --git a/crates/orderbook/src/api/get_auction.rs b/crates/orderbook/src/api/get_auction.rs index 8ce1c00898..42122dc82a 100644 --- a/crates/orderbook/src/api/get_auction.rs +++ b/crates/orderbook/src/api/get_auction.rs @@ -1,8 +1,7 @@ use { - crate::orderbook::Orderbook, + crate::{api::ApiReply, orderbook::Orderbook}, anyhow::Result, reqwest::StatusCode, - shared::api::ApiReply, std::{convert::Infallible, sync::Arc}, warp::{reply::with_status, Filter, Rejection}, }; @@ -26,7 +25,7 @@ pub fn get_auction( ), Err(err) => { tracing::error!(?err, "/api/v1/get_auction"); - shared::api::internal_error_reply() + crate::api::internal_error_reply() } }; Result::<_, Infallible>::Ok(reply) diff --git a/crates/orderbook/src/api/get_native_price.rs b/crates/orderbook/src/api/get_native_price.rs index 22e224acdd..6b00fc8810 100644 --- a/crates/orderbook/src/api/get_native_price.rs +++ b/crates/orderbook/src/api/get_native_price.rs @@ -1,11 +1,9 @@ use { + crate::api::{ApiReply, IntoWarpReply}, anyhow::Result, ethcontract::H160, serde::Serialize, - shared::{ - api::{ApiReply, IntoWarpReply}, - price_estimation::native::NativePriceEstimating, - }, + shared::price_estimation::native::NativePriceEstimating, std::{convert::Infallible, sync::Arc}, warp::{hyper::StatusCode, reply::with_status, Filter, Rejection}, }; diff --git a/crates/orderbook/src/api/get_order_by_uid.rs b/crates/orderbook/src/api/get_order_by_uid.rs index 98e58d1927..d3d7c8ac3c 100644 --- a/crates/orderbook/src/api/get_order_by_uid.rs +++ b/crates/orderbook/src/api/get_order_by_uid.rs @@ -15,7 +15,7 @@ pub fn get_order_by_uid_response(result: Result>) -> super::ApiRep Ok(order) => order, Err(err) => { tracing::error!(?err, "get_order_by_uid_response"); - return shared::api::internal_error_reply(); + return crate::api::internal_error_reply(); } }; match order { @@ -43,7 +43,7 @@ pub fn get_order_by_uid( mod tests { use { super::*, - shared::api::response_body, + crate::api::response_body, warp::{test::request, Reply}, }; diff --git a/crates/orderbook/src/api/get_order_status.rs b/crates/orderbook/src/api/get_order_status.rs index 8f6af424e2..f53294bc76 100644 --- a/crates/orderbook/src/api/get_order_status.rs +++ b/crates/orderbook/src/api/get_order_status.rs @@ -1,8 +1,7 @@ use { - crate::orderbook::Orderbook, + crate::{api::ApiReply, orderbook::Orderbook}, anyhow::Result, model::order::OrderUid, - shared::api::ApiReply, std::{convert::Infallible, sync::Arc}, warp::{hyper::StatusCode, Filter, Rejection}, }; @@ -28,7 +27,7 @@ pub fn get_status( ), Err(err) => { tracing::error!(?err, "get_order_status"); - *Box::new(shared::api::internal_error_reply()) + *Box::new(crate::api::internal_error_reply()) } }) } diff --git a/crates/orderbook/src/api/get_orders_by_tx.rs b/crates/orderbook/src/api/get_orders_by_tx.rs index 716c6e7ef6..52cd8313e1 100644 --- a/crates/orderbook/src/api/get_orders_by_tx.rs +++ b/crates/orderbook/src/api/get_orders_by_tx.rs @@ -1,9 +1,8 @@ use { - crate::orderbook::Orderbook, + crate::{api::ApiReply, orderbook::Orderbook}, anyhow::Result, ethcontract::H256, reqwest::StatusCode, - shared::api::ApiReply, std::{convert::Infallible, sync::Arc}, warp::{reply::with_status, Filter, Rejection}, }; @@ -23,7 +22,7 @@ pub fn get_orders_by_tx( Ok(response) => with_status(warp::reply::json(&response), StatusCode::OK), Err(err) => { tracing::error!(?err, "get_orders_by_tx"); - shared::api::internal_error_reply() + crate::api::internal_error_reply() } }) } diff --git a/crates/orderbook/src/api/get_solver_competition.rs b/crates/orderbook/src/api/get_solver_competition.rs index 2121a12253..9472f8a44a 100644 --- a/crates/orderbook/src/api/get_solver_competition.rs +++ b/crates/orderbook/src/api/get_solver_competition.rs @@ -65,7 +65,7 @@ fn response( ), Err(LoadSolverCompetitionError::Other(err)) => { tracing::error!(?err, "load solver competition"); - shared::api::internal_error_reply() + crate::api::internal_error_reply() } } } diff --git a/crates/orderbook/src/api/get_total_surplus.rs b/crates/orderbook/src/api/get_total_surplus.rs index b01a863c52..5f4db37d7a 100644 --- a/crates/orderbook/src/api/get_total_surplus.rs +++ b/crates/orderbook/src/api/get_total_surplus.rs @@ -22,7 +22,7 @@ pub fn get(db: Postgres) -> impl Filter { tracing::error!(?err, ?user, "failed to compute total surplus"); - shared::api::internal_error_reply() + crate::api::internal_error_reply() } }) } diff --git a/crates/orderbook/src/api/get_trades.rs b/crates/orderbook/src/api/get_trades.rs index e6fafc294c..604bc7dbdd 100644 --- a/crates/orderbook/src/api/get_trades.rs +++ b/crates/orderbook/src/api/get_trades.rs @@ -1,13 +1,15 @@ use { - crate::database::{ - trades::{TradeFilter, TradeRetrieving}, - Postgres, + crate::{ + api::{error, ApiReply}, + database::{ + trades::{TradeFilter, TradeRetrieving}, + Postgres, + }, }, anyhow::{Context, Result}, model::order::OrderUid, primitive_types::H160, serde::Deserialize, - shared::api::{error, ApiReply}, std::convert::Infallible, warp::{hyper::StatusCode, reply::with_status, Filter, Rejection}, }; @@ -61,7 +63,7 @@ pub fn get_trades(db: Postgres) -> impl Filter with_status(warp::reply::json(&reply), StatusCode::OK), Err(err) => { tracing::error!(?err, "get_trades"); - shared::api::internal_error_reply() + crate::api::internal_error_reply() } } } diff --git a/crates/orderbook/src/api/get_user_orders.rs b/crates/orderbook/src/api/get_user_orders.rs index a08b343fc3..e7c152dd83 100644 --- a/crates/orderbook/src/api/get_user_orders.rs +++ b/crates/orderbook/src/api/get_user_orders.rs @@ -1,9 +1,8 @@ use { - crate::orderbook::Orderbook, + crate::{api::ApiReply, orderbook::Orderbook}, anyhow::Result, primitive_types::H160, serde::Deserialize, - shared::api::ApiReply, std::{convert::Infallible, sync::Arc}, warp::{hyper::StatusCode, reply::with_status, Filter, Rejection}, }; @@ -46,7 +45,7 @@ pub fn get_user_orders( Ok(reply) => with_status(warp::reply::json(&reply), StatusCode::OK), Err(err) => { tracing::error!(?err, "get_user_orders"); - shared::api::internal_error_reply() + crate::api::internal_error_reply() } }) } diff --git a/crates/orderbook/src/api/post_order.rs b/crates/orderbook/src/api/post_order.rs index 6533e7f34e..2208bff6fd 100644 --- a/crates/orderbook/src/api/post_order.rs +++ b/crates/orderbook/src/api/post_order.rs @@ -1,19 +1,19 @@ use { - crate::orderbook::{AddOrderError, Orderbook}, + crate::{ + api::{error, extract_payload, ApiReply, IntoWarpReply}, + orderbook::{AddOrderError, Orderbook}, + }, anyhow::Result, model::{ order::{AppdataFromMismatch, OrderCreation, OrderUid}, quote::QuoteId, signature, }, - shared::{ - api::{error, extract_payload, ApiReply, IntoWarpReply}, - order_validation::{ - AppDataValidationError, - OrderValidToError, - PartialValidationError, - ValidationError, - }, + shared::order_validation::{ + AppDataValidationError, + OrderValidToError, + PartialValidationError, + ValidationError, }, std::{convert::Infallible, sync::Arc}, warp::{ @@ -88,7 +88,7 @@ impl IntoWarpReply for PartialValidationErrorWrapper { ), PartialValidationError::Other(err) => { tracing::error!(?err, "PartialValidatonError"); - shared::api::internal_error_reply() + crate::api::internal_error_reply() } } } @@ -226,7 +226,7 @@ impl IntoWarpReply for ValidationErrorWrapper { ValidationError::Other(err) => { tracing::error!(?err, "ValidationErrorWrapper"); - shared::api::internal_error_reply() + crate::api::internal_error_reply() } } } @@ -242,7 +242,7 @@ impl IntoWarpReply for AddOrderError { ), Self::Database(err) => { tracing::error!(?err, "AddOrderError"); - shared::api::internal_error_reply() + crate::api::internal_error_reply() } err @ AddOrderError::AppDataMismatch { .. } => { tracing::error!( @@ -251,7 +251,7 @@ impl IntoWarpReply for AddOrderError { because we already stored different full app data for the same contract app \ data. This should be impossible." ); - shared::api::internal_error_reply() + crate::api::internal_error_reply() } AddOrderError::OrderNotFound(err) => err.into_warp_reply(), AddOrderError::InvalidAppData(err) => reply::with_status( @@ -298,9 +298,9 @@ pub fn post_order( mod tests { use { super::*, + crate::api::response_body, model::order::{OrderCreation, OrderUid}, serde_json::json, - shared::api::response_body, warp::{test::request, Reply}, }; diff --git a/crates/orderbook/src/api/post_quote.rs b/crates/orderbook/src/api/post_quote.rs index 49fa7a094e..f175e169a2 100644 --- a/crates/orderbook/src/api/post_quote.rs +++ b/crates/orderbook/src/api/post_quote.rs @@ -1,13 +1,13 @@ use { super::post_order::{AppDataValidationErrorWrapper, PartialValidationErrorWrapper}, - crate::quoter::{OrderQuoteError, QuoteHandler}, + crate::{ + api::{self, convert_json_response, error, rich_error, ApiReply, IntoWarpReply}, + quoter::{OrderQuoteError, QuoteHandler}, + }, anyhow::Result, model::quote::OrderQuoteRequest, reqwest::StatusCode, - shared::{ - api::{self, convert_json_response, error, rich_error, ApiReply, IntoWarpReply}, - order_quoting::CalculateQuoteError, - }, + shared::order_quoting::CalculateQuoteError, std::{convert::Infallible, sync::Arc}, warp::{Filter, Rejection}, }; @@ -75,7 +75,7 @@ impl IntoWarpReply for CalculateQuoteErrorWrapper { ), CalculateQuoteError::Other(err) => { tracing::error!(?err, "CalculateQuoteErrorWrapper"); - shared::api::internal_error_reply() + crate::api::internal_error_reply() } } } @@ -85,6 +85,7 @@ impl IntoWarpReply for CalculateQuoteErrorWrapper { mod tests { use { super::*, + crate::api::response_body, anyhow::anyhow, app_data::AppDataHash, chrono::{TimeZone, Utc}, @@ -104,7 +105,7 @@ mod tests { number::nonzero::U256 as NonZeroU256, reqwest::StatusCode, serde_json::json, - shared::{api::response_body, order_quoting::CalculateQuoteError}, + shared::order_quoting::CalculateQuoteError, warp::{test::request, Reply}, }; diff --git a/crates/orderbook/src/api/put_app_data.rs b/crates/orderbook/src/api/put_app_data.rs index 17819026d9..901464d679 100644 --- a/crates/orderbook/src/api/put_app_data.rs +++ b/crates/orderbook/src/api/put_app_data.rs @@ -1,8 +1,8 @@ use { + crate::api::{internal_error_reply, IntoWarpReply}, anyhow::Result, app_data::{AppDataDocument, AppDataHash}, reqwest::StatusCode, - shared::api::{internal_error_reply, IntoWarpReply}, std::{convert::Infallible, sync::Arc}, warp::{body, reply, Filter, Rejection}, }; diff --git a/crates/shared/src/api.rs b/crates/shared/src/api.rs deleted file mode 100644 index 36cffd02ab..0000000000 --- a/crates/shared/src/api.rs +++ /dev/null @@ -1,320 +0,0 @@ -use { - crate::price_estimation::PriceEstimationError, - anyhow::Result, - serde::{de::DeserializeOwned, Serialize}, - std::{convert::Infallible, fmt::Debug, time::Instant}, - warp::{ - filters::BoxedFilter, - hyper::StatusCode, - reply::{json, with_status, Json, WithStatus}, - Filter, - Rejection, - Reply, - }, -}; - -pub type ApiReply = WithStatus; - -// We turn Rejection into Reply to workaround warp not setting CORS headers on -// rejections. -async fn handle_rejection(err: Rejection) -> Result { - let response = err.default_response(); - - let metrics = ApiMetrics::instance(observe::metrics::get_storage_registry()).unwrap(); - metrics - .requests_rejected - .with_label_values(&[response.status().as_str()]) - .inc(); - - Ok(response) -} - -#[derive(prometheus_metric_storage::MetricStorage, Clone, Debug)] -#[metric(subsystem = "api")] -struct ApiMetrics { - /// Number of completed API requests. - #[metric(labels("method", "status_code"))] - requests_complete: prometheus::IntCounterVec, - - /// Number of rejected API requests. - #[metric(labels("status_code"))] - requests_rejected: prometheus::IntCounterVec, - - /// Execution time for each API request. - #[metric(labels("method"), buckets(0.1, 0.5, 1, 2, 4, 6, 8, 10))] - requests_duration_seconds: prometheus::HistogramVec, -} - -impl ApiMetrics { - // Status codes we care about in our application. Populated with: - // `rg -oIN 'StatusCode::[A-Z_]+' | sort | uniq`. - const INITIAL_STATUSES: &'static [StatusCode] = &[ - StatusCode::OK, - StatusCode::CREATED, - StatusCode::BAD_REQUEST, - StatusCode::UNAUTHORIZED, - StatusCode::FORBIDDEN, - StatusCode::NOT_FOUND, - StatusCode::INTERNAL_SERVER_ERROR, - StatusCode::SERVICE_UNAVAILABLE, - ]; - - fn reset_requests_rejected(&self) { - for status in Self::INITIAL_STATUSES { - self.requests_rejected - .with_label_values(&[status.as_str()]) - .reset(); - } - } - - fn reset_requests_complete(&self, method: &str) { - for status in Self::INITIAL_STATUSES { - self.requests_complete - .with_label_values(&[method, status.as_str()]) - .reset(); - } - } - - fn on_request_completed(&self, method: &str, status: StatusCode, timer: Instant) { - self.requests_complete - .with_label_values(&[method, status.as_str()]) - .inc(); - self.requests_duration_seconds - .with_label_values(&[method]) - .observe(timer.elapsed().as_secs_f64()); - } -} - -#[derive(Serialize)] -#[serde(rename_all = "camelCase")] -struct Error<'a> { - error_type: &'a str, - description: &'a str, - /// Additional arbitrary data that can be attached to an API error. - #[serde(skip_serializing_if = "Option::is_none")] - data: Option, -} - -pub fn error(error_type: &str, description: impl AsRef) -> Json { - json(&Error { - error_type, - description: description.as_ref(), - data: None, - }) -} - -pub fn rich_error(error_type: &str, description: impl AsRef, data: impl Serialize) -> Json { - let data = match serde_json::to_value(&data) { - Ok(value) => Some(value), - Err(err) => { - tracing::warn!(?err, "failed to serialize error data"); - None - } - }; - - json(&Error { - error_type, - description: description.as_ref(), - data, - }) -} - -pub fn internal_error_reply() -> ApiReply { - with_status( - error("InternalServerError", ""), - StatusCode::INTERNAL_SERVER_ERROR, - ) -} - -pub fn convert_json_response(result: Result) -> WithStatus -where - T: Serialize, - E: IntoWarpReply + Debug, -{ - match result { - Ok(response) => with_status(warp::reply::json(&response), StatusCode::OK), - Err(err) => err.into_warp_reply(), - } -} - -pub trait IntoWarpReply { - fn into_warp_reply(self) -> ApiReply; -} - -pub async fn response_body(response: warp::hyper::Response) -> Vec { - let mut body = response.into_body(); - let mut result = Vec::new(); - while let Some(bytes) = futures::StreamExt::next(&mut body).await { - result.extend_from_slice(bytes.unwrap().as_ref()); - } - result -} - -const MAX_JSON_BODY_PAYLOAD: u64 = 1024 * 16; - -pub fn extract_payload( -) -> impl Filter + Clone { - // (rejecting huge payloads)... - extract_payload_with_max_size(MAX_JSON_BODY_PAYLOAD) -} - -pub fn extract_payload_with_max_size( - max_size: u64, -) -> impl Filter + Clone { - warp::body::content_length_limit(max_size).and(warp::body::json()) -} - -pub type BoxedRoute = BoxedFilter<(Box,)>; - -pub fn box_filter(filter: Filter_) -> BoxedFilter<(Box,)> -where - Filter_: Filter + Send + Sync + 'static, - Reply_: Reply + Send + 'static, -{ - filter.map(|a| Box::new(a) as Box).boxed() -} - -/// Sets up basic metrics, cors and proper log tracing for all routes. -/// -/// # Panics -/// -/// This method panics if `routes` is empty. -pub fn finalize_router( - routes: Vec<(&'static str, BoxedRoute)>, - log_prefix: &'static str, -) -> impl Filter + Clone { - let metrics = ApiMetrics::instance(observe::metrics::get_storage_registry()).unwrap(); - metrics.reset_requests_rejected(); - for (method, _) in &routes { - metrics.reset_requests_complete(method); - } - - let router = routes - .into_iter() - .fold( - Option::)>>::None, - |router, (method, route)| { - let route = route.map(move |result| (method, result)).untuple_one(); - let next = match router { - Some(router) => router.or(route).unify().boxed(), - None => route.boxed(), - }; - Some(next) - }, - ) - .expect("routes cannot be empty"); - - let instrumented = - warp::any() - .map(Instant::now) - .and(router) - .map(|timer, method, reply: Box| { - let response = reply.into_response(); - metrics.on_request_completed(method, response.status(), timer); - response - }); - - // Final setup - let cors = warp::cors() - .allow_any_origin() - .allow_methods(vec!["GET", "POST", "DELETE", "OPTIONS", "PUT", "PATCH"]) - .allow_headers(vec!["Origin", "Content-Type", "X-Auth-Token", "X-AppId"]); - - warp::path!("api" / ..) - .and(instrumented) - .recover(handle_rejection) - .with(cors) - .with(warp::log::log(log_prefix)) -} - -impl IntoWarpReply for PriceEstimationError { - fn into_warp_reply(self) -> WithStatus { - match self { - Self::UnsupportedToken { token, reason } => with_status( - error( - "UnsupportedToken", - format!("Token {token:?} is unsupported: {reason:}"), - ), - StatusCode::BAD_REQUEST, - ), - Self::UnsupportedOrderType(order_type) => with_status( - error( - "UnsupportedOrderType", - format!("{order_type} not supported"), - ), - StatusCode::BAD_REQUEST, - ), - Self::NoLiquidity | Self::RateLimited | Self::EstimatorInternal(_) => with_status( - error("NoLiquidity", "no route found"), - StatusCode::NOT_FOUND, - ), - Self::ProtocolInternal(err) => { - tracing::error!(?err, "PriceEstimationError::Other"); - internal_error_reply() - } - } - } -} - -#[cfg(test)] -mod tests { - use {super::*, serde::ser, serde_json::json}; - - #[test] - fn rich_errors_skip_unset_data_field() { - assert_eq!( - serde_json::to_value(&Error { - error_type: "foo", - description: "bar", - data: None, - }) - .unwrap(), - json!({ - "errorType": "foo", - "description": "bar", - }), - ); - assert_eq!( - serde_json::to_value(Error { - error_type: "foo", - description: "bar", - data: Some(json!(42)), - }) - .unwrap(), - json!({ - "errorType": "foo", - "description": "bar", - "data": 42, - }), - ); - } - - #[tokio::test] - async fn rich_errors_handle_serialization_errors() { - struct AlwaysErrors; - impl Serialize for AlwaysErrors { - fn serialize(&self, _: S) -> Result - where - S: serde::Serializer, - { - Err(ser::Error::custom("error")) - } - } - - let body = warp::hyper::body::to_bytes( - rich_error("foo", "bar", AlwaysErrors) - .into_response() - .into_body(), - ) - .await - .unwrap(); - - assert_eq!( - serde_json::from_slice::(&body).unwrap(), - json!({ - "errorType": "foo", - "description": "bar", - }) - ); - } -} diff --git a/crates/shared/src/lib.rs b/crates/shared/src/lib.rs index 9543c870d7..d599752fed 100644 --- a/crates/shared/src/lib.rs +++ b/crates/shared/src/lib.rs @@ -2,7 +2,6 @@ pub mod macros; pub mod account_balances; -pub mod api; pub mod arguments; pub mod bad_token; pub mod baseline_solver; From 0acf9deff8908b721b8376ee148ef3b3420be249 Mon Sep 17 00:00:00 2001 From: MartinquaXD Date: Sat, 2 Nov 2024 20:13:17 +0000 Subject: [PATCH 2/2] Move app-data-hash into app-data crate --- Cargo.lock | 13 +---- crates/app-data-hash/Cargo.toml | 13 ----- crates/app-data-hash/LICENSE-APACHE | 1 - crates/app-data-hash/LICENSE-MIT | 1 - crates/app-data-hash/src/lib.rs | 76 --------------------------- crates/app-data/Cargo.toml | 3 +- crates/app-data/src/app_data.rs | 4 +- crates/app-data/src/app_data_hash.rs | 71 +++++++++++++++++++++++++ crates/e2e/Cargo.toml | 1 - crates/e2e/tests/e2e/app_data.rs | 10 ++-- crates/model/Cargo.toml | 1 - crates/model/src/order.rs | 4 +- crates/orderbook/Cargo.toml | 1 - crates/orderbook/src/ipfs_app_data.rs | 4 +- crates/shared/Cargo.toml | 1 - crates/shared/src/app_data/compat.rs | 19 ------- 16 files changed, 84 insertions(+), 139 deletions(-) delete mode 100644 crates/app-data-hash/Cargo.toml delete mode 120000 crates/app-data-hash/LICENSE-APACHE delete mode 120000 crates/app-data-hash/LICENSE-MIT delete mode 100644 crates/app-data-hash/src/lib.rs delete mode 100644 crates/shared/src/app_data/compat.rs diff --git a/Cargo.lock b/Cargo.lock index 24526a3e81..59864f52e6 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -166,21 +166,14 @@ name = "app-data" version = "0.1.0" dependencies = [ "anyhow", - "app-data-hash", "bytes-hex", "ethcontract", "hex", + "hex-literal", "primitive-types", "serde", "serde_json", "serde_with", -] - -[[package]] -name = "app-data-hash" -version = "0.1.0" -dependencies = [ - "hex-literal", "tiny-keccak", ] @@ -1838,7 +1831,6 @@ version = "1.0.0" dependencies = [ "anyhow", "app-data", - "app-data-hash", "async-trait", "autopilot", "axum", @@ -3101,7 +3093,6 @@ version = "0.1.0" dependencies = [ "anyhow", "app-data", - "app-data-hash", "bigdecimal", "bytes-hex", "chrono", @@ -3398,7 +3389,6 @@ version = "0.1.0" dependencies = [ "anyhow", "app-data", - "app-data-hash", "async-trait", "bigdecimal", "cached", @@ -4462,7 +4452,6 @@ version = "0.1.0" dependencies = [ "anyhow", "app-data", - "app-data-hash", "async-stream", "async-trait", "bigdecimal", diff --git a/crates/app-data-hash/Cargo.toml b/crates/app-data-hash/Cargo.toml deleted file mode 100644 index 33c032e838..0000000000 --- a/crates/app-data-hash/Cargo.toml +++ /dev/null @@ -1,13 +0,0 @@ -[package] -name = "app-data-hash" -version = "0.1.0" -authors = ["Cow Protocol Developers "] -edition = "2021" -license = "MIT OR Apache-2.0" - -[dependencies] -hex-literal = { workspace = true } -tiny-keccak = { version = "2.0.2", features = ["keccak"] } - -[lints] -workspace = true diff --git a/crates/app-data-hash/LICENSE-APACHE b/crates/app-data-hash/LICENSE-APACHE deleted file mode 120000 index 1cd601d0a3..0000000000 --- a/crates/app-data-hash/LICENSE-APACHE +++ /dev/null @@ -1 +0,0 @@ -../../LICENSE-APACHE \ No newline at end of file diff --git a/crates/app-data-hash/LICENSE-MIT b/crates/app-data-hash/LICENSE-MIT deleted file mode 120000 index b2cfbdc7b0..0000000000 --- a/crates/app-data-hash/LICENSE-MIT +++ /dev/null @@ -1 +0,0 @@ -../../LICENSE-MIT \ No newline at end of file diff --git a/crates/app-data-hash/src/lib.rs b/crates/app-data-hash/src/lib.rs deleted file mode 100644 index ea290b5d78..0000000000 --- a/crates/app-data-hash/src/lib.rs +++ /dev/null @@ -1,76 +0,0 @@ -//! App data refers to extra information that is associated with orders. This -//! information is not validated by the contract but it is used by other parts -//! of the system. For example, a user could specify that they want their order -//! to be COW only, which is something only the backend understands. Or what -//! their intended slippage when creating the order with the frontend was, which -//! adjusts the signed prices. -//! -//! On the smart contract level app data is freely choosable 32 bytes of signed -//! order data. This isn't enough space for some purposes so we interpret those -//! bytes as a hash of the full app data of arbitrary length. The full app data -//! is thus signed by the user when they sign the order. -//! -//! This crate specifies how the hash is calculated. It takes the keccak256 hash -//! of the input bytes. Additionally, it provides a canonical way to calculate -//! an IPFS CID from the hash. This allows full app data to be uploaded to IPFS. -//! -//! Note that not all app data hashes were created this way. As of 2023-05-25 we -//! are planning to move to the scheme implemented by this crate but orders have -//! been created with arbitrary app data hashes until now. See [this issue][0] -//! for more information. -//! -//! [0]: https://github.com/cowprotocol/services/issues/1465 - -use tiny_keccak::{Hasher, Keccak}; - -/// Hash full app data to get the bytes expected to be set as the contract level -/// app data. -pub fn hash_full_app_data(app_data: &[u8]) -> [u8; 32] { - let mut hasher = Keccak::v256(); - hasher.update(app_data); - let mut hash = [0u8; 32]; - hasher.finalize(&mut hash); - hash -} - -/// Create an IPFS CIDv1 from a hash created by `hash_full_app_data`. -/// -/// The return value is the raw bytes of the CID. It is not multibase encoded. -pub fn create_ipfs_cid(app_data_hash: &[u8; 32]) -> [u8; 36] { - let mut cid = [0u8; 4 + 32]; - cid[0] = 1; // cid version - cid[1] = 0x55; // raw codec - cid[2] = 0x1b; // keccak hash algorithm - cid[3] = 32; // keccak hash length - cid[4..].copy_from_slice(app_data_hash); - cid -} - -#[cfg(test)] -mod tests { - use super::*; - - // Alternative way of calculating the expected values: - // cat appdata | ipfs block put --mhtype keccak-256 - // -> bafkrwiek6tumtfzvo6yivqq5c7jtdkw6q3ar5pgfcjdujvrbzkbwl3eueq - // ipfs cid format -b base16 - // bafkrwiek6tumtfzvo6yivqq5c7jtdkw6q3ar5pgfcjdujvrbzkbwl3eueq - // -> f01551b208af4e8c9973577b08ac21d17d331aade86c11ebcc5124744d621ca8365ec9424 - // Remove the f prefix and you have the same CID. - // Or check out the cid explorer: - // - https://cid.ipfs.tech/#f01551b208af4e8c9973577b08ac21d17d331aade86c11ebcc5124744d621ca8365ec9424 - // - https://cid.ipfs.tech/#bafkrwiek6tumtfzvo6yivqq5c7jtdkw6q3ar5pgfcjdujvrbzkbwl3eueq - #[test] - fn known_good() { - let full_app_data = r#"{"appCode":"CoW Swap","environment":"production","metadata":{"quote":{"slippageBips":"50","version":"0.2.0"},"orderClass":{"orderClass":"market","version":"0.1.0"}},"version":"0.6.0"}"#; - let expected_hash = - hex_literal::hex!("8af4e8c9973577b08ac21d17d331aade86c11ebcc5124744d621ca8365ec9424"); - let expected_cid = hex_literal::hex!( - "01551b208af4e8c9973577b08ac21d17d331aade86c11ebcc5124744d621ca8365ec9424" - ); - let hash = hash_full_app_data(full_app_data.as_bytes()); - let cid = create_ipfs_cid(&hash); - assert_eq!(hash, expected_hash); - assert_eq!(cid, expected_cid); - } -} diff --git a/crates/app-data/Cargo.toml b/crates/app-data/Cargo.toml index 7e8eb86926..ced73b6fcd 100644 --- a/crates/app-data/Cargo.toml +++ b/crates/app-data/Cargo.toml @@ -6,14 +6,15 @@ edition = "2021" license = "MIT OR Apache-2.0" [dependencies] -app-data-hash = { path = "../app-data-hash" } bytes-hex = { path = "../bytes-hex" } anyhow = { workspace = true } +tiny-keccak = { version = "2.0.2", features = ["keccak"] } serde = { workspace = true } serde_json = { workspace = true } serde_with = { workspace = true } primitive-types = { workspace = true } hex = { workspace = true } +hex-literal = { workspace = true } [dev-dependencies] ethcontract = { workspace = true } diff --git a/crates/app-data/src/app_data.rs b/crates/app-data/src/app_data.rs index 85a4a39105..98de8de9aa 100644 --- a/crates/app-data/src/app_data.rs +++ b/crates/app-data/src/app_data.rs @@ -1,5 +1,5 @@ use { - crate::{AppDataHash, Hooks}, + crate::{app_data_hash::hash_full_app_data, AppDataHash, Hooks}, anyhow::{anyhow, Context, Result}, primitive_types::H160, serde::{de, Deserialize, Deserializer, Serialize, Serializer}, @@ -79,7 +79,7 @@ impl Validator { .unwrap_or_default(); Ok(ValidatedAppData { - hash: AppDataHash(app_data_hash::hash_full_app_data(full_app_data)), + hash: AppDataHash(hash_full_app_data(full_app_data)), document, protocol, }) diff --git a/crates/app-data/src/app_data_hash.rs b/crates/app-data/src/app_data_hash.rs index ec31a40901..f905b1db9f 100644 --- a/crates/app-data/src/app_data_hash.rs +++ b/crates/app-data/src/app_data_hash.rs @@ -1,3 +1,26 @@ +//! App data refers to extra information that is associated with orders. This +//! information is not validated by the contract but it is used by other parts +//! of the system. For example, a user could specify that they want their order +//! to be COW only, which is something only the backend understands. Or what +//! their intended slippage when creating the order with the frontend was, which +//! adjusts the signed prices. +//! +//! On the smart contract level app data is freely choosable 32 bytes of signed +//! order data. This isn't enough space for some purposes so we interpret those +//! bytes as a hash of the full app data of arbitrary length. The full app data +//! is thus signed by the user when they sign the order. +//! +//! This crate specifies how the hash is calculated. It takes the keccak256 hash +//! of the input bytes. Additionally, it provides a canonical way to calculate +//! an IPFS CID from the hash. This allows full app data to be uploaded to IPFS. +//! +//! Note that not all app data hashes were created this way. As of 2023-05-25 we +//! are planning to move to the scheme implemented by this crate but orders have +//! been created with arbitrary app data hashes until now. See [this issue][0] +//! for more information. +//! +//! [0]: https://github.com/cowprotocol/services/issues/1465 + use { serde::{de, Deserializer, Serializer}, serde_with::serde::{Deserialize, Serialize}, @@ -6,6 +29,7 @@ use { fmt::{self, Debug, Formatter}, str::FromStr, }, + tiny_keccak::{Hasher, Keccak}, }; /// A JSON object used to represent app data documents for uploading and @@ -81,6 +105,29 @@ impl PartialEq<[u8; 32]> for AppDataHash { } } +/// Hash full app data to get the bytes expected to be set as the contract level +/// app data. +pub fn hash_full_app_data(app_data: &[u8]) -> [u8; 32] { + let mut hasher = Keccak::v256(); + hasher.update(app_data); + let mut hash = [0u8; 32]; + hasher.finalize(&mut hash); + hash +} + +/// Create an IPFS CIDv1 from a hash created by `hash_full_app_data`. +/// +/// The return value is the raw bytes of the CID. It is not multibase encoded. +pub fn create_ipfs_cid(app_data_hash: &[u8; 32]) -> [u8; 36] { + let mut cid = [0u8; 4 + 32]; + cid[0] = 1; // cid version + cid[1] = 0x55; // raw codec + cid[2] = 0x1b; // keccak hash algorithm + cid[3] = 32; // keccak hash length + cid[4..].copy_from_slice(app_data_hash); + cid +} + #[cfg(test)] mod tests { use {super::*, serde_json::json}; @@ -125,4 +172,28 @@ mod tests { assert!(AppDataHash::deserialize(json!("asdf")).is_err()); assert!(AppDataHash::deserialize(json!("0x00")).is_err()); } + + // Alternative way of calculating the expected values: + // cat appdata | ipfs block put --mhtype keccak-256 + // -> bafkrwiek6tumtfzvo6yivqq5c7jtdkw6q3ar5pgfcjdujvrbzkbwl3eueq + // ipfs cid format -b base16 + // bafkrwiek6tumtfzvo6yivqq5c7jtdkw6q3ar5pgfcjdujvrbzkbwl3eueq + // -> f01551b208af4e8c9973577b08ac21d17d331aade86c11ebcc5124744d621ca8365ec9424 + // Remove the f prefix and you have the same CID. + // Or check out the cid explorer: + // - https://cid.ipfs.tech/#f01551b208af4e8c9973577b08ac21d17d331aade86c11ebcc5124744d621ca8365ec9424 + // - https://cid.ipfs.tech/#bafkrwiek6tumtfzvo6yivqq5c7jtdkw6q3ar5pgfcjdujvrbzkbwl3eueq + #[test] + fn known_good() { + let full_app_data = r#"{"appCode":"CoW Swap","environment":"production","metadata":{"quote":{"slippageBips":"50","version":"0.2.0"},"orderClass":{"orderClass":"market","version":"0.1.0"}},"version":"0.6.0"}"#; + let expected_hash = + hex_literal::hex!("8af4e8c9973577b08ac21d17d331aade86c11ebcc5124744d621ca8365ec9424"); + let expected_cid = hex_literal::hex!( + "01551b208af4e8c9973577b08ac21d17d331aade86c11ebcc5124744d621ca8365ec9424" + ); + let hash = hash_full_app_data(full_app_data.as_bytes()); + let cid = create_ipfs_cid(&hash); + assert_eq!(hash, expected_hash); + assert_eq!(cid, expected_cid); + } } diff --git a/crates/e2e/Cargo.toml b/crates/e2e/Cargo.toml index deaeacd4cd..2540329abc 100644 --- a/crates/e2e/Cargo.toml +++ b/crates/e2e/Cargo.toml @@ -44,7 +44,6 @@ web3 = { workspace = true, features = ["http"] } uuid = { version = "1.8.0", features = ["v4"] } [dev-dependencies] -app-data-hash = { path = "../app-data-hash" } futures = { workspace = true } rand = { workspace = true } refunder = { path = "../refunder" } diff --git a/crates/e2e/tests/e2e/app_data.rs b/crates/e2e/tests/e2e/app_data.rs index 99b3a162ec..b6e9bbac14 100644 --- a/crates/e2e/tests/e2e/app_data.rs +++ b/crates/e2e/tests/e2e/app_data.rs @@ -1,5 +1,5 @@ use { - app_data::AppDataHash, + app_data::{hash_full_app_data, AppDataHash}, e2e::{setup::*, tx}, ethcontract::prelude::U256, model::{ @@ -74,7 +74,7 @@ async fn app_data(web3: Web3) { // hash matches let app_data = "{}"; - let app_data_hash = AppDataHash(app_data_hash::hash_full_app_data(app_data.as_bytes())); + let app_data_hash = AppDataHash(hash_full_app_data(app_data.as_bytes())); let order1 = create_order(OrderCreationAppData::Both { full: app_data.to_string(), expected: app_data_hash, @@ -133,7 +133,7 @@ async fn app_data(web3: Web3) { // pre-register some app-data with the API. let pre_app_data = r#"{"pre":"registered"}"#; - let pre_app_data_hash = AppDataHash(app_data_hash::hash_full_app_data(pre_app_data.as_bytes())); + let pre_app_data_hash = AppDataHash(hash_full_app_data(pre_app_data.as_bytes())); let err = services.get_app_data(pre_app_data_hash).await.unwrap_err(); dbg!(err); @@ -170,9 +170,7 @@ async fn app_data(web3: Web3) { // pre-registering invalid app-data fails. let err = services .put_app_data( - Some(AppDataHash(app_data_hash::hash_full_app_data( - invalid_app_data.as_bytes(), - ))), + Some(AppDataHash(hash_full_app_data(invalid_app_data.as_bytes()))), invalid_app_data, ) .await diff --git a/crates/model/Cargo.toml b/crates/model/Cargo.toml index d3ef8935df..1fa2a8eaae 100644 --- a/crates/model/Cargo.toml +++ b/crates/model/Cargo.toml @@ -11,7 +11,6 @@ doctest = false [dependencies] anyhow = { workspace = true } app-data = { path = "../app-data" } -app-data-hash = { path = "../app-data-hash" } bytes-hex = { path = "../bytes-hex" } bigdecimal = { workspace = true } chrono = { workspace = true, features = ["serde", "clock"] } diff --git a/crates/model/src/order.rs b/crates/model/src/order.rs index 1ba1d55209..e3a5aef562 100644 --- a/crates/model/src/order.rs +++ b/crates/model/src/order.rs @@ -10,7 +10,7 @@ use { TokenPair, }, anyhow::{anyhow, Result}, - app_data::AppDataHash, + app_data::{hash_full_app_data, AppDataHash}, chrono::{offset::Utc, DateTime}, derivative::Derivative, hex_literal::hex, @@ -465,7 +465,7 @@ impl OrderCreationAppData { match self { Self::Hash { hash } => *hash, Self::Full { full } | Self::Both { full, .. } => { - AppDataHash(app_data_hash::hash_full_app_data(full.as_bytes())) + AppDataHash(hash_full_app_data(full.as_bytes())) } } } diff --git a/crates/orderbook/Cargo.toml b/crates/orderbook/Cargo.toml index ddb3b05a7c..88ed70160a 100644 --- a/crates/orderbook/Cargo.toml +++ b/crates/orderbook/Cargo.toml @@ -18,7 +18,6 @@ path = "src/main.rs" [dependencies] anyhow = { workspace = true } app-data = { path = "../app-data" } -app-data-hash = { path = "../app-data-hash" } async-trait = { workspace = true } bigdecimal = { workspace = true } cached = { workspace = true } diff --git a/crates/orderbook/src/ipfs_app_data.rs b/crates/orderbook/src/ipfs_app_data.rs index feaedfd470..f00eca682e 100644 --- a/crates/orderbook/src/ipfs_app_data.rs +++ b/crates/orderbook/src/ipfs_app_data.rs @@ -1,7 +1,7 @@ use { crate::ipfs::Ipfs, anyhow::Result, - app_data::AppDataHash, + app_data::{create_ipfs_cid, AppDataHash}, cached::{Cached, TimedSizedCache}, std::sync::Mutex, }; @@ -121,7 +121,7 @@ impl IpfsAppData { } fn new_app_data_cid(contract_app_data: &AppDataHash) -> String { - let raw_cid = app_data_hash::create_ipfs_cid(&contract_app_data.0); + let raw_cid = create_ipfs_cid(&contract_app_data.0); multibase::encode(multibase::Base::Base32Lower, raw_cid) } diff --git a/crates/shared/Cargo.toml b/crates/shared/Cargo.toml index 01dce3e1d9..104127e75e 100644 --- a/crates/shared/Cargo.toml +++ b/crates/shared/Cargo.toml @@ -11,7 +11,6 @@ doctest = false [dependencies] anyhow = { workspace = true } app-data = { path = "../app-data" } -app-data-hash = { path = "../app-data-hash" } bytes-hex = { path = "../bytes-hex" } async-trait = { workspace = true } bigdecimal = { workspace = true } diff --git a/crates/shared/src/app_data/compat.rs b/crates/shared/src/app_data/compat.rs deleted file mode 100644 index 381c408366..0000000000 --- a/crates/shared/src/app_data/compat.rs +++ /dev/null @@ -1,19 +0,0 @@ -use {super::ProtocolAppData, model::order::Hooks, serde::Deserialize}; - -/// The legacy `backend` app data object. -#[derive(Debug, Default, Deserialize)] -pub struct BackendAppData { - #[serde(default)] - pub hooks: Hooks, -} - -impl From for ProtocolAppData { - fn from(value: BackendAppData) -> Self { - Self { - hooks: value.hooks, - signer: None, - replaced_order: None, - partner_fee: None, - } - } -}