From 22721daba13627620dd1d107fbf08a29eefb6a20 Mon Sep 17 00:00:00 2001 From: ilya Date: Fri, 21 Feb 2025 13:29:18 +0000 Subject: [PATCH 1/3] Settlement executions API --- crates/database/src/settlement_executions.rs | 98 ++++++++++++------- crates/orderbook/src/api.rs | 7 +- .../src/api/get_settlement_executions.rs | 41 ++++++++ crates/orderbook/src/database/orders.rs | 44 ++++++++- crates/orderbook/src/dto/mod.rs | 17 +++- 5 files changed, 170 insertions(+), 37 deletions(-) create mode 100644 crates/orderbook/src/api/get_settlement_executions.rs diff --git a/crates/database/src/settlement_executions.rs b/crates/database/src/settlement_executions.rs index fc51551065..3c8ab264d1 100644 --- a/crates/database/src/settlement_executions.rs +++ b/crates/database/src/settlement_executions.rs @@ -4,6 +4,18 @@ use { sqlx::PgConnection, }; +#[derive(Debug, Clone, Eq, PartialEq, sqlx::FromRow)] +pub struct ExecutionRow { + pub auction_id: AuctionId, + pub solver: Address, + pub start_timestamp: DateTime, + pub end_timestamp: Option>, + pub start_block: i64, + pub end_block: Option, + pub deadline_block: i64, + pub outcome: Option, +} + pub async fn insert( ex: &mut PgConnection, auction_id: AuctionId, @@ -55,6 +67,25 @@ WHERE auction_id = $1 AND solver = $2 Ok(()) } +pub async fn find( + ex: &mut PgConnection, + from_auction_id: AuctionId, + to_auction_id: AuctionId, +) -> Result, sqlx::Error> { + const QUERY: &str = r#" +SELECT * FROM settlement_executions +WHERE auction_id >= $1 AND auction_id <= $2 + ;"#; + + let rows = sqlx::query_as::<_, ExecutionRow>(QUERY) + .bind(from_auction_id) + .bind(to_auction_id) + .fetch_all(ex) + .await?; + + Ok(rows) +} + #[cfg(test)] mod tests { use { @@ -71,7 +102,8 @@ mod tests { let mut db = db.begin().await.unwrap(); crate::clear_DANGER_(&mut db).await.unwrap(); - let auction_id = 1; + let auction_id_a = 1; + let auction_id_b = 2; let solver_a = ByteArray([1u8; 20]); let solver_b = ByteArray([2u8; 20]); let start_timestamp = now_truncated_to_microseconds(); @@ -80,7 +112,7 @@ mod tests { insert( &mut db, - auction_id, + auction_id_a, solver_a, start_timestamp, start_block, @@ -90,7 +122,7 @@ mod tests { .unwrap(); insert( &mut db, - auction_id, + auction_id_a, solver_b, start_timestamp, start_block, @@ -98,11 +130,21 @@ mod tests { ) .await .unwrap(); + insert( + &mut db, + auction_id_b, + solver_a, + start_timestamp, + start_block, + deadline_block, + ) + .await + .unwrap(); - let output = fetch(&mut db, auction_id).await.unwrap(); - assert_eq!(output.len(), 2); + let output = find(&mut db, auction_id_a, auction_id_b).await.unwrap(); + assert_eq!(output.len(), 3); let expected_a = ExecutionRow { - auction_id, + auction_id: auction_id_a, solver: solver_a, start_timestamp, end_timestamp: None, @@ -112,7 +154,7 @@ mod tests { outcome: None, }; let expected_b = ExecutionRow { - auction_id, + auction_id: auction_id_a, solver: solver_b, start_timestamp, end_timestamp: None, @@ -121,15 +163,26 @@ mod tests { deadline_block, outcome: None, }; + let expected_c = ExecutionRow { + auction_id: auction_id_b, + solver: solver_a, + start_timestamp, + end_timestamp: None, + start_block, + end_block: None, + deadline_block, + outcome: None, + }; assert!(output.contains(&expected_a)); assert!(output.contains(&expected_b)); + assert!(output.contains(&expected_c)); let end_timestamp_a = now_truncated_to_microseconds(); let end_block_a = 8; let outcome_a = "success".to_string(); update( &mut db, - auction_id, + auction_id_a, solver_a, end_timestamp_a, end_block_a, @@ -143,7 +196,7 @@ mod tests { let outcome_b = "failure".to_string(); update( &mut db, - auction_id, + auction_id_a, solver_b, end_timestamp_b, end_block_b, @@ -152,10 +205,10 @@ mod tests { .await .unwrap(); - let output = fetch(&mut db, auction_id).await.unwrap(); + let output = find(&mut db, auction_id_a, auction_id_a).await.unwrap(); assert_eq!(output.len(), 2); let expected_a = ExecutionRow { - auction_id, + auction_id: auction_id_a, solver: solver_a, start_timestamp, end_timestamp: Some(end_timestamp_a), @@ -165,7 +218,7 @@ mod tests { outcome: Some(outcome_a), }; let expected_b = ExecutionRow { - auction_id, + auction_id: auction_id_a, solver: solver_b, start_timestamp, end_timestamp: Some(end_timestamp_b), @@ -178,27 +231,6 @@ mod tests { assert!(output.contains(&expected_b)); } - #[derive(Debug, Clone, Eq, PartialEq, sqlx::FromRow)] - struct ExecutionRow { - pub auction_id: AuctionId, - pub solver: Address, - pub start_timestamp: DateTime, - pub end_timestamp: Option>, - pub start_block: i64, - pub end_block: Option, - pub deadline_block: i64, - pub outcome: Option, - } - - async fn fetch( - ex: &mut PgConnection, - auction_id: AuctionId, - ) -> Result, sqlx::Error> { - const QUERY: &str = r#"SELECT * FROM settlement_executions WHERE auction_id = $1;"#; - - sqlx::query_as(QUERY).bind(auction_id).fetch_all(ex).await - } - /// In the DB we use `timestampz` which doesn't store nanoseconds, so we /// truncate them to make the comparison work. fn now_truncated_to_microseconds() -> DateTime { diff --git a/crates/orderbook/src/api.rs b/crates/orderbook/src/api.rs index 8eb7321f1f..1233fdc418 100644 --- a/crates/orderbook/src/api.rs +++ b/crates/orderbook/src/api.rs @@ -22,6 +22,7 @@ mod get_native_price; mod get_order_by_uid; mod get_order_status; mod get_orders_by_tx; +mod get_settlement_executions; mod get_solver_competition; mod get_token_metadata; mod get_total_surplus; @@ -110,7 +111,11 @@ pub fn handle_all_routes( ), ( "v1/get_token_metadata", - box_filter(get_token_metadata::get_token_metadata(database)), + box_filter(get_token_metadata::get_token_metadata(database.clone())), + ), + ( + "v1/get_settlement_executions", + box_filter(get_settlement_executions::get(database)), ), ]; diff --git a/crates/orderbook/src/api/get_settlement_executions.rs b/crates/orderbook/src/api/get_settlement_executions.rs new file mode 100644 index 0000000000..f421d04c55 --- /dev/null +++ b/crates/orderbook/src/api/get_settlement_executions.rs @@ -0,0 +1,41 @@ +use { + crate::{database::Postgres, dto::AuctionId}, + hyper::StatusCode, + serde::Deserialize, + std::convert::Infallible, + warp::{reply, Filter, Rejection}, +}; + +pub fn get_settlement_executions_request( +) -> impl Filter + Clone { + warp::path!("v1" / "settlement_executions") + .and(warp::query::()) + .and_then(|query: SettlementQuery| async move { + Result::<_, Infallible>::Ok((query.from_auction, query.to_auction)) + }) +} + +#[derive(Debug, Deserialize)] +pub struct SettlementQuery { + pub from_auction: AuctionId, + pub to_auction: AuctionId, +} + +pub fn get(db: Postgres) -> impl Filter + Clone { + get_settlement_executions_request().and_then(move |(from_auction, to_auction)| { + let db = db.clone(); + async move { + let result = db + .find_settlement_executions(from_auction, to_auction) + .await; + + Result::<_, Infallible>::Ok(match result { + Ok(executions) => reply::with_status(reply::json(&executions), StatusCode::OK), + Err(err) => { + tracing::error!(?err, "get_settlement_executions"); + crate::api::internal_error_reply() + } + }) + } + }) +} diff --git a/crates/orderbook/src/database/orders.rs b/crates/orderbook/src/database/orders.rs index b9e3b86d8c..cc9a73eca9 100644 --- a/crates/orderbook/src/database/orders.rs +++ b/crates/orderbook/src/database/orders.rs @@ -1,6 +1,9 @@ use { super::Postgres, - crate::{dto::TokenMetadata, orderbook::AddOrderError}, + crate::{ + dto::{AuctionId, SettlementExecution, TokenMetadata}, + orderbook::AddOrderError, + }, anyhow::{Context as _, Result}, app_data::AppDataHash, async_trait::async_trait, @@ -27,7 +30,7 @@ use { signature::Signature, time::now_in_epoch_seconds, }, - num::Zero, + num::{ToPrimitive, Zero}, number::conversions::{big_decimal_to_big_uint, big_decimal_to_u256, u256_to_big_decimal}, primitive_types::{H160, U256}, shared::{ @@ -520,6 +523,43 @@ impl Postgres { }) } + pub async fn find_settlement_executions( + &self, + from_auction: AuctionId, + to_auction: AuctionId, + ) -> Result> { + let _timer = super::Metrics::get() + .database_queries + .with_label_values(&["find_settlement_executions"]) + .start_timer(); + + let mut ex = self.pool.acquire().await?; + let executions = database::settlement_executions::find(&mut ex, from_auction, to_auction) + .await? + .into_iter() + .map(|row| { + Ok(SettlementExecution { + auction_id: row.auction_id, + solver: H160(row.solver.0), + start_timestamp: row.start_timestamp, + end_timestamp: row.end_timestamp, + start_block: row.start_block.to_u32().context("start block is not u32")?, + end_block: row + .end_block + .map(|block| block.to_u32().context("end block is not u32")) + .transpose()?, + deadline_block: row + .deadline_block + .to_u32() + .context("deadline block is not u32")?, + outcome: row.outcome, + }) + }) + .collect::>>()?; + + Ok(executions) + } + async fn execute_instrumented(&self, label: &str, f: F) -> Result where F: std::future::Future>, diff --git a/crates/orderbook/src/dto/mod.rs b/crates/orderbook/src/dto/mod.rs index 1fd8284dbf..d55ab35baa 100644 --- a/crates/orderbook/src/dto/mod.rs +++ b/crates/orderbook/src/dto/mod.rs @@ -6,8 +6,9 @@ pub use { order::Order, }; use { + chrono::{DateTime, Utc}, number::serialization::HexOrDecimalU256, - primitive_types::U256, + primitive_types::{H160, U256}, serde::Serialize, serde_with::serde_as, }; @@ -20,3 +21,17 @@ pub struct TokenMetadata { #[serde_as(as = "Option")] pub native_price: Option, } + +#[serde_as] +#[derive(Serialize)] +#[serde(rename_all = "camelCase")] +pub struct SettlementExecution { + pub auction_id: AuctionId, + pub solver: H160, + pub start_timestamp: DateTime, + pub end_timestamp: Option>, + pub start_block: u32, + pub end_block: Option, + pub deadline_block: u32, + pub outcome: Option, +} From 6d3243fe7e200ba50c676c050e43a94f3b29f042 Mon Sep 17 00:00:00 2001 From: ilya Date: Fri, 21 Feb 2025 13:37:37 +0000 Subject: [PATCH 2/3] Doc --- crates/orderbook/src/database/orders.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/crates/orderbook/src/database/orders.rs b/crates/orderbook/src/database/orders.rs index cc9a73eca9..03a4f95d04 100644 --- a/crates/orderbook/src/database/orders.rs +++ b/crates/orderbook/src/database/orders.rs @@ -523,6 +523,7 @@ impl Postgres { }) } + /// Retrieve all settlements executions for the given auction IDs range. pub async fn find_settlement_executions( &self, from_auction: AuctionId, From 2b6b60a12d3aa8e2d20b49ec32492b9a035fae18 Mon Sep 17 00:00:00 2001 From: ilya Date: Fri, 21 Feb 2025 13:39:45 +0000 Subject: [PATCH 3/3] Minor --- .../orderbook/src/api/get_settlement_executions.rs | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/crates/orderbook/src/api/get_settlement_executions.rs b/crates/orderbook/src/api/get_settlement_executions.rs index f421d04c55..90d9747c63 100644 --- a/crates/orderbook/src/api/get_settlement_executions.rs +++ b/crates/orderbook/src/api/get_settlement_executions.rs @@ -28,14 +28,20 @@ pub fn get(db: Postgres) -> impl Filter::Ok(match result { + let response = match result { Ok(executions) => reply::with_status(reply::json(&executions), StatusCode::OK), Err(err) => { - tracing::error!(?err, "get_settlement_executions"); + tracing::error!( + ?err, + ?from_auction, + ?to_auction, + "Failed to fetch settlement executions" + ); crate::api::internal_error_reply() } - }) + }; + + Result::<_, Infallible>::Ok(response) } }) }