Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Bid Status Visibility #42

Merged
merged 21 commits into from
Apr 11, 2024

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 1 addition & 2 deletions auction-server/migrations/20240326063340_bids.up.sql
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,5 @@ CREATE TABLE bid
target_calldata BYTEA NOT NULL,
bid_amount NUMERIC(78, 0) NOT NULL,
status bid_status NOT NULL,
auction_id UUID, -- TODO: should be linked to the auction table in the future
removal_time TIMESTAMP -- TODO: should be removed and read from the auction table in the future
auction_id UUID
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

let's add another column here that shows the position of this bid in the bundle. Having a null value while having an auction id means that this bid was not submitted on-chain (it lost)

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We should add a foreign key with protect constraint.

);
1 change: 1 addition & 0 deletions auction-server/migrations/20240404172845_auction.down.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
DROP TABLE auction;
8 changes: 8 additions & 0 deletions auction-server/migrations/20240404172845_auction.up.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
CREATE TABLE auction
(
id UUID PRIMARY KEY,
conclusion_time TIMESTAMP NOT NULL,
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

let's add another column creation_time with default current_timestamp to keep track of when we have added this row to the db. This can be helpful next to this conclusion_time to measure latency, etc.

permission_key BYTEA NOT NULL,
chain_id TEXT NOT NULL,
tx_hash BYTEA NOT NULL CHECK (LENGTH(tx_hash) = 32)
);
14 changes: 13 additions & 1 deletion auction-server/src/api.rs
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ use {
SHOULD_EXIT,
},
state::{
AuctionParams,
BidStatus,
BidStatusWithId,
OpportunityParams,
Expand Down Expand Up @@ -62,6 +63,7 @@ async fn root() -> String {
format!("Express Relay Auction Server API {}", crate_version!())
}

pub(crate) mod auction;
mod bid;
pub(crate) mod opportunity;
pub(crate) mod ws;
Expand All @@ -75,7 +77,9 @@ pub enum RestError {
InvalidChainId,
/// The simulation failed
SimulationError { result: Bytes, reason: String },
/// The order was not found
/// The auction was not found
AuctionNotFound,
/// The opportunity was not found
OpportunityNotFound,
/// The bid was not found
BidNotFound,
Expand All @@ -101,6 +105,10 @@ impl RestError {
StatusCode::BAD_REQUEST,
format!("Simulation failed: {} ({})", result, reason),
),
RestError::AuctionNotFound => (
StatusCode::NOT_FOUND,
"Auction with the specified id was not found".to_string(),
),
RestError::OpportunityNotFound => (
StatusCode::NOT_FOUND,
"Opportunity with the specified id was not found".to_string(),
Expand Down Expand Up @@ -144,10 +152,12 @@ pub async fn start_api(run_options: RunOptions, store: Arc<Store>) -> Result<()>
opportunity::post_opportunity,
opportunity::opportunity_bid,
opportunity::get_opportunities,
auction::get_auctions,
),
components(
schemas(
APIResponse,
AuctionParams,
Bid,
BidStatus,
BidStatusWithId,
Expand All @@ -169,6 +179,7 @@ pub async fn start_api(run_options: RunOptions, store: Arc<Store>) -> Result<()>
ErrorBodyResponse,
OpportunityParamsWithMetadata,
BidResult,
AuctionParams
),
),
tags(
Expand All @@ -189,6 +200,7 @@ pub async fn start_api(run_options: RunOptions, store: Arc<Store>) -> Result<()>
"/v1/opportunities/:opportunity_id/bids",
post(opportunity::opportunity_bid),
)
.route("/v1/auctions/:permission_key", get(auction::get_auctions))
.route("/v1/ws", get(ws::ws_route_handler))
.route("/live", get(live))
.layer(CorsLayer::permissive())
Expand Down
117 changes: 117 additions & 0 deletions auction-server/src/api/auction.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
use {
crate::{
api::{
opportunity::ChainIdQueryParams,
ErrorBodyResponse,
RestError,
},
state::{
AuctionId,
AuctionParams,
PermissionKey,
Store,
},
},
axum::{
extract::{
Path,
Query,
State,
},
Json,
},
ethers::types::H256,
serde::{
Deserialize,
Serialize,
},
std::sync::Arc,
utoipa::ToSchema,
};

#[derive(Serialize, Deserialize, ToSchema, Clone)]
pub struct AuctionParamsWithId {
#[schema(value_type = String)]
pub id: AuctionId,
pub params: AuctionParams,
}

/// Query for auctions with the permission key and (optionally) chain ID specified.
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I am not sure who is gonna use this endpoint, let's discuss the access patterns and how we want searchers to have more visibility.

#[utoipa::path(get, path = "/v1/auctions/{permission_key}",
params(
("permission_key"=String, description = "Permission key to query for"),
ChainIdQueryParams
),
responses(
(status = 200, description = "Array of auctions with the permission key", body = Vec<AuctionParams>),
(status = 400, response = ErrorBodyResponse),
(status = 404, description = "Permission key was not found", body = ErrorBodyResponse),
)
)]
pub async fn get_auctions(
State(store): State<Arc<Store>>,
Path(permission_key): Path<PermissionKey>,
query_params: Query<ChainIdQueryParams>,
) -> Result<Json<Vec<AuctionParams>>, RestError> {
let auctions = match &query_params.chain_id {
Some(chain_id) => {
let auction_records = sqlx::query!(
"SELECT * FROM auction WHERE permission_key = $1 AND chain_id = $2",
permission_key.as_ref(),
chain_id
)
.fetch_all(&store.db)
.await
.map_err(|_| RestError::AuctionNotFound)?;

auction_records
.into_iter()
.map(|auction| AuctionParams {
chain_id: auction.chain_id,
permission_key: auction.permission_key.into(),
tx_hash: H256::from_slice(auction.tx_hash.as_ref()),
})
.collect()
}
None => {
let auction_records = sqlx::query!(
"SELECT * FROM auction WHERE permission_key = $1",
permission_key.as_ref()
)
.fetch_all(&store.db)
.await
.map_err(|_| RestError::AuctionNotFound)?;

auction_records
.into_iter()
.map(|auction| AuctionParams {
chain_id: auction.chain_id,
permission_key: auction.permission_key.into(),
tx_hash: H256::from_slice(auction.tx_hash.as_ref()),
})
.collect()
}
};

Ok(Json(auctions))
}

// Get auction with the specified ID.
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

no additional information in this comment.

pub async fn get_auction_with_id(
store: Arc<Store>,
auction_id: AuctionId,
) -> Result<AuctionParamsWithId, RestError> {
let auction = sqlx::query!("SELECT * FROM auction WHERE id = $1", auction_id)
.fetch_one(&store.db)
.await
.map_err(|_| RestError::BidNotFound)?;
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

should be AuctionNotFound


Ok(AuctionParamsWithId {
id: auction.id,
params: AuctionParams {
chain_id: auction.chain_id,
permission_key: auction.permission_key.into(),
tx_hash: H256::from_slice(auction.tx_hash.as_ref()),
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

from_slice can panic. Isn't there any safer way to do this?

},
})
}
51 changes: 48 additions & 3 deletions auction-server/src/api/bid.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
use {
crate::{
api::{
auction::get_auction_with_id,
ErrorBodyResponse,
RestError,
},
Expand Down Expand Up @@ -80,8 +81,52 @@ pub async fn bid_status(
State(store): State<Arc<Store>>,
Path(bid_id): Path<BidId>,
) -> Result<Json<BidStatus>, RestError> {
match store.bids.read().await.get(&bid_id) {
Some(bid) => Ok(bid.status.clone().into()),
None => Err(RestError::BidNotFound),
let status_data = sqlx::query!(
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would move this to state. Also no need to have 2 separate queries, we can join these 2 tables and have a single query. I think the code will become simpler.

// TODO: improve the call here to not cast to text
"SELECT status::text, auction_id FROM bid WHERE id = $1",
bid_id
)
.fetch_one(&store.db)
.await
.map_err(|_| RestError::BidNotFound)?;

let status_json: Json<BidStatus>;
match status_data.status {
Some(status) => {
if status == "pending" {
status_json = BidStatus::Pending.into();
} else if status == "lost" {
match status_data.auction_id {
Some(auction_id) => {
let auction_info = get_auction_with_id(store.clone(), auction_id).await?;
status_json = BidStatus::Lost(auction_info.params.tx_hash).into();
}
None => {
return Err(RestError::BadParameters(
"Lost bid must have auction id".to_string(),
));
}
}
} else if status == "submitted" {
match status_data.auction_id {
Some(auction_id) => {
let auction_info = get_auction_with_id(store.clone(), auction_id).await?;
status_json = BidStatus::Submitted(auction_info.params.tx_hash).into();
}
None => {
return Err(RestError::BadParameters(
"Submitted bid must have auction id".to_string(),
));
}
}
} else {
return Err(RestError::BadParameters("Invalid status".to_string()));
}
}
None => {
return Err(RestError::BidNotFound);
}
}

Ok(status_json)
}
4 changes: 2 additions & 2 deletions auction-server/src/api/opportunity.rs
Original file line number Diff line number Diff line change
Expand Up @@ -142,8 +142,8 @@ pub async fn post_opportunity(

#[derive(Serialize, Deserialize, IntoParams)]
pub struct ChainIdQueryParams {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

let's move this to some more general file (maybe the base api.rs?) . This doesn't particularly belong to opportunity or bid or ...

#[param(example = "sepolia", value_type = Option < String >)]
chain_id: Option<ChainId>,
#[param(example = "op_sepolia", value_type = Option < String >)]
pub chain_id: Option<ChainId>,
}

/// Fetch all opportunities ready to be exectued.
Expand Down
Loading
Loading