Skip to content

Commit

Permalink
Order status API (#2804)
Browse files Browse the repository at this point in the history
Co-authored-by: ilya <[email protected]>
  • Loading branch information
MartinquaXD and squadgazzz authored Aug 1, 2024
1 parent 40ea6cc commit d8964a5
Show file tree
Hide file tree
Showing 12 changed files with 308 additions and 3 deletions.
23 changes: 22 additions & 1 deletion crates/database/src/order_events.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
//! This information gets used to compuate service level indicators.
use {
crate::OrderUid,
crate::{byte_array::ByteArray, OrderUid},
chrono::Utc,
sqlx::{types::chrono::DateTime, PgConnection, PgPool},
};
Expand Down Expand Up @@ -92,6 +92,18 @@ pub async fn delete_order_events_before(
.map(|result| result.rows_affected())
}

pub async fn get_latest(
ex: &mut PgConnection,
order: &OrderUid,
) -> Result<Option<OrderEvent>, sqlx::Error> {
const QUERY: &str =
r#"SELECT * FROM order_events WHERE order_uid = $1 ORDER BY timestamp DESC LIMIT 1"#;
sqlx::query_as(QUERY)
.bind(ByteArray(order.0))
.fetch_optional(ex)
.await
}

#[cfg(test)]
mod tests {
use {
Expand Down Expand Up @@ -149,6 +161,15 @@ mod tests {
assert_eq!(ids[1].label, OrderEventLabel::Invalid);
assert_eq!(ids[2].order_uid, uid_b);
assert_eq!(ids[2].label, OrderEventLabel::Invalid);

let latest = get_latest(&mut db, &uid_a).await.unwrap().unwrap();
assert_eq!(latest.order_uid, event_b.order_uid);
assert_eq!(latest.label, event_b.label);
// Postgres returns micros only while DateTime has nanos.
assert_eq!(
latest.timestamp.timestamp_micros(),
event_b.timestamp.timestamp_micros()
);
}

async fn all_order_events(ex: &mut PgConnection) -> Vec<OrderEvent> {
Expand Down
2 changes: 1 addition & 1 deletion crates/e2e/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ hex-literal = { workspace = true }
model = { path = "../model", features = ["e2e"] }
number = { path = "../number" }
observe = { path = "../observe" }
orderbook = { path = "../orderbook" }
orderbook = { path = "../orderbook", features = ["e2e"] }
reqwest = { workspace = true, features = ["blocking"] }
secp256k1 = { workspace = true }
serde = { workspace = true }
Expand Down
26 changes: 26 additions & 0 deletions crates/e2e/src/setup/services.rs
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,10 @@ pub const VERSION_ENDPOINT: &str = "/api/v1/version";
pub const SOLVER_COMPETITION_ENDPOINT: &str = "/api/v1/solver_competition";
const LOCAL_DB_URL: &str = "postgresql://";

pub fn order_status_endpoint(uid: &OrderUid) -> String {
format!("/api/v1/orders/{uid}/status")
}

pub struct ServicesBuilder {
timeout: Duration,
}
Expand Down Expand Up @@ -446,6 +450,28 @@ impl<'a> Services<'a> {
}
}

pub async fn get_order_status(
&self,
uid: &OrderUid,
) -> Result<orderbook::dto::order::Status, (StatusCode, String)> {
let response = self
.http
.get(format!("{API_HOST}{}", order_status_endpoint(uid)))
.send()
.await
.unwrap();

let status = response.status();
let body = response.text().await.unwrap();

match status {
StatusCode::OK => {
Ok(serde_json::from_str::<orderbook::dto::order::Status>(&body).unwrap())
}
code => Err((code, body)),
}
}

pub async fn get_app_data_document(
&self,
app_data: AppDataHash,
Expand Down
24 changes: 24 additions & 0 deletions crates/e2e/tests/e2e/order_cancellation.rs
Original file line number Diff line number Diff line change
Expand Up @@ -188,6 +188,13 @@ async fn order_cancellation(web3: Web3) {
let events = crate::database::events_of_order(services.db(), uid).await;
assert_eq!(events.first().unwrap().label, OrderEventLabel::Created);
}
for uid in &order_uids {
let order_status = services.get_order_status(uid).await.unwrap();
assert!(matches!(
order_status,
orderbook::dto::order::Status::Active
));
}

// Cancel one of them.
cancel_order(order_uids[0]).await;
Expand All @@ -205,6 +212,10 @@ async fn order_cancellation(web3: Web3) {
.status,
OrderStatus::Cancelled,
);
assert_eq!(
services.get_order_status(&order_uids[0]).await.unwrap(),
orderbook::dto::order::Status::Cancelled,
);

// Cancel the other two.
cancel_orders(vec![order_uids[1], order_uids[2]]).await;
Expand All @@ -231,6 +242,19 @@ async fn order_cancellation(web3: Web3) {
.status,
OrderStatus::Cancelled,
);
assert_eq!(
services
.get_order(&order_uids[2])
.await
.unwrap()
.metadata
.status,
OrderStatus::Cancelled,
);
assert_eq!(
services.get_order_status(&order_uids[2]).await.unwrap(),
orderbook::dto::order::Status::Cancelled,
);

for uid in &order_uids {
let events = crate::database::events_of_order(services.db(), uid).await;
Expand Down
13 changes: 12 additions & 1 deletion crates/e2e/tests/e2e/partial_fill.rs
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ async fn test(web3: Web3) {

tracing::info!("Starting services.");
let services = Services::new(onchain.contracts()).await;
services.start_protocol(solver).await;
services.start_protocol(solver.clone()).await;

tracing::info!("Placing order");
let balance = token.balance_of(trader.address()).call().await.unwrap();
Expand Down Expand Up @@ -104,4 +104,15 @@ async fn test(web3: Web3) {
assert!(competition.common.auction.orders.contains(&uid));
let latest_competition = services.get_latest_solver_competition().await.unwrap();
assert_eq!(latest_competition, competition);
assert!(matches!(
services.get_order_status(&uid).await.unwrap(),
orderbook::dto::order::Status::Traded(ref solutions)
if solutions.len() == 1 && matches!(
&solutions[0],
orderbook::dto::order::SolutionInclusion {
solver: ref s,
executed_amounts: Some(_),
} if s == "test_solver"
)
));
}
3 changes: 3 additions & 0 deletions crates/orderbook/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -68,3 +68,6 @@ vergen = { version = "8", features = ["git", "gitcl"] }

[lints]
workspace = true

[features]
e2e = []
55 changes: 55 additions & 0 deletions crates/orderbook/openapi.yml
Original file line number Diff line number Diff line change
Expand Up @@ -138,6 +138,22 @@ paths:
description: Invalid signature.
404:
description: Order was not found.
/api/v1/orders/{UID}/status:
get:
summary: Get the status of an order.
parameters:
- in: path
name: UID
schema:
$ref: "#/components/schemas/UID"
required: true
responses:
200:
description: The order status with a list of solvers that proposed solutions (if applicable).
content:
application/json:
schema:
$ref: "#/components/schemas/CompetitionOrderStatus"
/api/v1/transactions/{txHash}/orders:
get:
summary: Get orders by settlement transaction hash.
Expand Down Expand Up @@ -1038,6 +1054,45 @@ components:
The UIDs of the orders included in the auction.
prices:
$ref: "#/components/schemas/AuctionPrices"
ExecutedAmounts:
type: object
properties:
sell:
$ref: "#/components/schemas/BigUint"
buy:
$ref: "#/components/schemas/BigUint"
required:
- sell
- buy
CompetitionOrderStatus:
type: object
properties:
type:
type: string
enum:
- Open
- Scheduled
- Active
- Solved
- Executing
- Traded
- Cancelled
value:
description: |
A list of solvers who participated in the latest competition. The presence of executed amounts defines whether the solver provided a solution for the desired order.
type: array
items:
type: object
properties:
solver:
type: string
description: Name of the solver.
executedAmounts:
$ref: "#/components/schemas/ExecutedAmounts"
required:
- solver
required:
- type
AuctionPrices:
description: |
The reference prices for all traded tokens in the auction as a mapping from token
Expand Down
5 changes: 5 additions & 0 deletions crates/orderbook/src/api.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ mod get_app_data;
mod get_auction;
mod get_native_price;
mod get_order_by_uid;
mod get_order_status;
mod get_orders_by_tx;
mod get_solver_competition;
mod get_total_surplus;
Expand Down Expand Up @@ -44,6 +45,10 @@ pub fn handle_all_routes(
"v1/get_order",
box_filter(get_order_by_uid::get_order_by_uid(orderbook.clone())),
),
(
"v1/get_order_status",
box_filter(get_order_status::get_status(orderbook.clone())),
),
(
"v1/get_trades",
box_filter(get_trades::get_trades(database.clone())),
Expand Down
36 changes: 36 additions & 0 deletions crates/orderbook/src/api/get_order_status.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
use {
crate::orderbook::Orderbook,
anyhow::Result,
model::order::OrderUid,
shared::api::ApiReply,
std::{convert::Infallible, sync::Arc},
warp::{hyper::StatusCode, Filter, Rejection},
};

fn get_status_request() -> impl Filter<Extract = (OrderUid,), Error = Rejection> + Clone {
warp::path!("v1" / "orders" / OrderUid / "status").and(warp::get())
}

pub fn get_status(
orderbook: Arc<Orderbook>,
) -> impl Filter<Extract = (ApiReply,), Error = Rejection> + Clone {
get_status_request().and_then(move |uid| {
let orderbook = orderbook.clone();
async move {
let status = orderbook.get_order_status(&uid).await;
Result::<_, Infallible>::Ok(match status {
Ok(Some(status)) => {
warp::reply::with_status(warp::reply::json(&status), StatusCode::OK)
}
Ok(None) => warp::reply::with_status(
super::error("OrderNotFound", "Order not located in database"),
StatusCode::NOT_FOUND,
),
Err(err) => {
tracing::error!(?err, "get_order_status");
*Box::new(shared::api::internal_error_reply())
}
})
}
})
}
13 changes: 13 additions & 0 deletions crates/orderbook/src/database/orders.rs
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,7 @@ pub trait OrderStoring: Send + Sync {
offset: u64,
limit: Option<u64>,
) -> Result<Vec<Order>>;
async fn latest_order_event(&self, order_uid: &OrderUid) -> Result<Option<OrderEvent>>;
}

pub struct SolvableOrders {
Expand Down Expand Up @@ -356,6 +357,18 @@ impl OrderStoring for Postgres {
.try_collect()
.await
}

async fn latest_order_event(&self, order_uid: &OrderUid) -> Result<Option<OrderEvent>> {
let mut ex = self.pool.begin().await.context("could not init tx")?;
let _timer = super::Metrics::get()
.database_queries
.with_label_values(&["latest_order_event"])
.start_timer();

database::order_events::get_latest(&mut ex, &ByteArray(order_uid.0))
.await
.context("order_events::get_latest")
}
}

#[async_trait]
Expand Down
48 changes: 48 additions & 0 deletions crates/orderbook/src/dto/order.rs
Original file line number Diff line number Diff line change
Expand Up @@ -68,3 +68,51 @@ pub enum FeePolicy {
quote: Quote,
},
}

#[serde_as]
#[derive(Serialize, PartialEq, Debug, Clone)]
#[cfg_attr(any(test, feature = "e2e"), derive(serde::Deserialize))]
#[serde(rename_all = "camelCase")]
pub struct ExecutedAmounts {
#[serde_as(as = "HexOrDecimalU256")]
pub sell: U256,
#[serde_as(as = "HexOrDecimalU256")]
pub buy: U256,
}

/// Indicates that a solver has provided a solution, with `executed_amounts`
/// determining whether the solution was provided for the desired order.
#[derive(Serialize, PartialEq, Debug, Clone)]
#[cfg_attr(any(test, feature = "e2e"), derive(serde::Deserialize))]
#[serde(rename_all = "camelCase")]
pub struct SolutionInclusion {
/// The name or identifier of the solver.
pub solver: String,
/// The executed amounts for the order as proposed by the solver, included
/// if the solution was for the desired order, or omitted otherwise.
pub executed_amounts: Option<ExecutedAmounts>,
}

#[derive(Serialize, PartialEq, Debug, Clone)]
#[cfg_attr(any(test, feature = "e2e"), derive(serde::Deserialize))]
#[serde(tag = "type", rename_all = "camelCase", content = "value")]
pub enum Status {
/// Order is part of the orderbook but not actively being worked on. This
/// can for example happen if the necessary balances are missing or if
/// the order's signature check fails.
Open,
/// Order awaits being put into the current auction.
Scheduled,
/// Order is part of the current and solvers are computing solutions for it.
Active,
/// Some solvers proposed solutions for the orders but did not win the
/// competition.
Solved(Vec<SolutionInclusion>),
/// The order was contained in the winning solution which the solver
/// currently tries to submit onchain.
Executing(Vec<SolutionInclusion>),
/// The order was successfully executed onchain.
Traded(Vec<SolutionInclusion>),
/// The user cancelled the order. It will no longer show up in any auctions.
Cancelled,
}
Loading

0 comments on commit d8964a5

Please sign in to comment.