Skip to content

Commit

Permalink
Add temporary fix for multiple get quote requests at the same time (#404
Browse files Browse the repository at this point in the history
)
  • Loading branch information
danimhr authored Feb 26, 2025
1 parent 51706ed commit 4d37dda
Show file tree
Hide file tree
Showing 7 changed files with 67 additions and 44 deletions.
6 changes: 6 additions & 0 deletions auction-server/src/api.rs
Original file line number Diff line number Diff line change
Expand Up @@ -113,6 +113,8 @@ pub enum RestError {
ProfileNotFound,
/// The quote was not found.
QuoteNotFound,
/// Duplicate opportunity.
DuplicateOpportunity,
}

impl RestError {
Expand Down Expand Up @@ -159,6 +161,10 @@ impl RestError {
StatusCode::NOT_FOUND,
"No quote is currently available".to_string(),
),
RestError::DuplicateOpportunity => (
StatusCode::BAD_REQUEST,
"Same opportunity is submitted recently".to_string(),
),
}
}
}
Expand Down
3 changes: 2 additions & 1 deletion auction-server/src/auction/service/cancel_bid.rs
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,8 @@ impl Service<Svm> {
},
},
})
.await
.await?;
Ok(())
}
_ => Err(RestError::BadParameters(
"Bid is only cancellable in awaiting_signature state".to_string(),
Expand Down
7 changes: 5 additions & 2 deletions auction-server/src/auction/service/update_bid_status.rs
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,10 @@ pub struct UpdateBidStatusInput<T: ChainTrait> {

impl<T: ChainTrait> Service<T> {
#[tracing::instrument(skip_all, fields(bid_id, status))]
pub async fn update_bid_status(&self, input: UpdateBidStatusInput<T>) -> Result<(), RestError> {
pub async fn update_bid_status(
&self,
input: UpdateBidStatusInput<T>,
) -> Result<bool, RestError> {
tracing::Span::current().record("bid_id", input.bid.id.to_string());
tracing::Span::current().record("status", format!("{:?}", input.new_status));

Expand Down Expand Up @@ -51,6 +54,6 @@ impl<T: ChainTrait> Service<T> {
tracing::error!(error = e.to_string(), "Failed to send update event");
}
}
Ok(())
Ok(is_updated)
}
}
4 changes: 1 addition & 3 deletions auction-server/src/opportunity/service/add_opportunity.rs
Original file line number Diff line number Diff line change
Expand Up @@ -69,9 +69,7 @@ where
let action = self.assess_action(&opportunity_create).await;
if let OpportunityAction::Ignore = action {
tracing::info!("Submitted opportunity ignored: {:?}", opportunity_create);
return Err(RestError::BadParameters(
"Same opportunity is submitted recently".to_string(),
));
return Err(RestError::DuplicateOpportunity);
}

self.verify_opportunity(VerifyOpportunityInput {
Expand Down
72 changes: 38 additions & 34 deletions auction-server/src/opportunity/service/get_quote.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,13 +10,11 @@ use {
entities::{
Auction,
BidPaymentInstructionType,
BidStatus,
BidStatusAuction,
},
service::{
add_auction::AddAuctionInput,
auction_manager::AuctionManager,
get_auction_by_id::GetAuctionByIdInput,
get_pending_bids::GetLiveBidsInput,
update_bid_status::UpdateBidStatusInput,
Service as AuctionService,
Expand All @@ -41,7 +39,6 @@ use {
},
axum_prometheus::metrics,
express_relay_api_types::opportunity::ProgramSvm,
futures::future::join_all,
solana_sdk::pubkey::Pubkey,
spl_associated_token_account::get_associated_token_address_with_program_id,
std::{
Expand Down Expand Up @@ -446,47 +443,54 @@ impl Service<ChainTypeSvm> {
self.remove_quote_opportunity(opportunity.clone()).await;

let signature = winner_bid.chain_data.transaction.signatures[0];
join_all(auction.bids.iter().map(|bid| {
auction_service.update_bid_status(UpdateBidStatusInput {
// Update the status of all bids in the auction except the winner bid
let auction_bids = auction.bids.clone();
auction_bids.into_iter().for_each(|bid| {
if bid.id != winner_bid.id {
self.task_tracker.spawn({
let (auction_service, winner_bid) =
(auction_service.clone(), winner_bid.clone());
async move {
auction_service
.update_bid_status(UpdateBidStatusInput {
new_status: AuctionService::get_new_status(
&bid,
&vec![winner_bid],
BidStatusAuction {
tx_hash: signature,
id: auction.id,
},
false,
),
bid,
})
.await
}
});
}
});

// We check if the winner bid status is successfully updated.
// This is important for the submit_quote function to work correctly.
if !auction_service
.update_bid_status(UpdateBidStatusInput {
new_status: AuctionService::get_new_status(
bid,
winner_bid,
&vec![winner_bid.clone()],
BidStatusAuction {
tx_hash: signature,
id: auction.id,
},
false,
),
bid: bid.clone(),
bid: winner_bid.clone(),
})
}))
.await;

// We check if the winner bid status is successfully updated.
// This is important for the submit_quote function to work correctly.
match auction_service
.get_auction_by_id(GetAuctionByIdInput {
auction_id: auction.id,
})
.await
.await?
{
Some(auction) => match auction.bids.iter().find(|bid| bid.id == winner_bid.id) {
Some(bid) => {
if !bid.status.is_awaiting_signature() {
tracing::error!(winner_bid = ?winner_bid, opportunity = ?opportunity, "Failed to update winner bid status");
return Err(RestError::TemporarilyUnavailable);
}
}
None => {
tracing::error!(auction = ?auction, winner_bid = ?winner_bid, "Failed to winner bid from auction");
return Err(RestError::TemporarilyUnavailable);
}
},
None => {
tracing::error!(auction = ?auction, opportunity = ?opportunity, winner_bid = ?winner_bid, "Failed to get auction by id");
return Err(RestError::TemporarilyUnavailable);
}
};
// This can only happen if the bid is already updated by another auction for another get_quote request
// TODO We should handle this case more gracefully
return Err(RestError::DuplicateOpportunity);
}

let metadata = self
.get_express_relay_metadata(GetExpressRelayMetadata {
Expand Down
17 changes: 13 additions & 4 deletions auction-server/src/opportunity/service/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@ use {
sync::Arc,
},
tokio::sync::RwLock,
tokio_util::task::TaskTracker,
};

pub mod add_opportunity;
Expand Down Expand Up @@ -258,18 +259,25 @@ impl ChainType for ChainTypeSvm {

// TODO maybe just create a service per chain_id?
pub struct Service<T: ChainType, U: OpportunityTable<T::InMemoryStore> = DB> {
store: Arc<Store>,
store: Arc<Store>,
// TODO maybe after adding state for opportunity we can remove the arc
repo: Arc<Repository<T::InMemoryStore, U>>,
config: HashMap<ChainId, T::Config>,
repo: Arc<Repository<T::InMemoryStore, U>>,
config: HashMap<ChainId, T::Config>,
task_tracker: TaskTracker,
}

impl<T: ChainType, U: OpportunityTable<T::InMemoryStore>> Service<T, U> {
pub fn new(store: Arc<Store>, db: U, config: HashMap<ChainId, T::Config>) -> Self {
pub fn new(
store: Arc<Store>,
task_tracker: TaskTracker,
db: U,
config: HashMap<ChainId, T::Config>,
) -> Self {
Self {
store,
repo: Arc::new(Repository::new(db)),
config,
task_tracker,
}
}
pub async fn update_metrics(&self) {
Expand Down Expand Up @@ -333,6 +341,7 @@ pub mod tests {

let service = Service::<ChainTypeSvm, MockOpportunityTable<InMemoryStoreSvm>>::new(
store.clone(),
TaskTracker::new(),
db,
chains_svm,
);
Expand Down
2 changes: 2 additions & 0 deletions auction-server/src/server.rs
Original file line number Diff line number Diff line change
Expand Up @@ -331,13 +331,15 @@ pub async fn start_server(run_options: RunOptions) -> Result<()> {
opportunity_service::ChainTypeEvm,
>::new(
store.clone(),
task_tracker.clone(),
pool.clone(),
config_opportunity_service_evm,
));
let opportunity_service_svm = Arc::new(opportunity_service::Service::<
opportunity_service::ChainTypeSvm,
>::new(
store.clone(),
task_tracker.clone(),
pool.clone(),
config_opportunity_service_svm,
));
Expand Down

0 comments on commit 4d37dda

Please sign in to comment.