From 50b8f5fe10e737342bc44fcea279667bf553627d Mon Sep 17 00:00:00 2001 From: Dani Mehrjerdi Date: Fri, 31 Jan 2025 20:55:21 +0100 Subject: [PATCH] Add new status for svm bid (#372) --- ...d91daf52246ba0ebad020330288d05ac5b3c9.json | 50 ++++++ ...85aa5e1962ad3aa9c70bd34472f24cef77306.json | 8 +- ...b497337c599c11c9cf3d29c58fbbf7501779d.json | 8 +- ...1bb38e8bebeba59012b86ac6b0de653332cea.json | 66 ++++++++ ...56109d0a3ba60cce3536dc5ac2e4eb3682a23.json | 8 +- ...6f410e1b1943d6429d73ddd724c075027b70.json} | 7 +- ...402fe80ecee20caf9001b88bf8d885b0524e2.json | 8 +- ...1467782c8bec86f6f8b52fe11325b96edbdb4.json | 4 +- ...dedeafe298047458c91bf3bf0540274c4c508.json | 8 +- ...ec8a0b4e7ab51c728bac77f79ffbd896d8c27.json | 8 +- ...74e841a9a847493c398cd543136193a81feda.json | 83 ++++++++++ ...44a734f7cfc94637ff3a8f1b52a329a704522.json | 8 +- ...6e432a314051f152854620bd2dfcbd66434e2.json | 66 ++++++++ auction-server/api-types/src/bid.rs | 19 ++- auction-server/api-types/src/quote.rs | 4 +- ...50130104828_bid_status_alter_type.down.sql | 8 + ...0250130104828_bid_status_alter_type.up.sql | 2 + auction-server/src/auction/api.rs | 10 +- .../src/auction/entities/auction.rs | 1 + auction-server/src/auction/entities/bid.rs | 40 +++++ .../src/auction/repository/add_auction.rs | 16 +- .../src/auction/repository/add_bid.rs | 2 +- .../auction/repository/conclude_auction.rs | 8 +- .../repository/get_in_memory_auction_by_id.rs | 19 +++ .../repository/get_in_memory_auctions.rs | 13 ++ .../repository/get_in_memory_bid_by_id.rs | 24 --- ..._bids.rs => get_in_memory_pending_bids.rs} | 4 +- ..._memory_pending_bids_by_permission_key.rs} | 4 +- .../get_in_memory_submitted_auctions.rs | 13 -- ...et_in_memory_submitted_bids_for_auction.rs | 32 ---- auction-server/src/auction/repository/mod.rs | 27 ++-- .../src/auction/repository/models.rs | 57 ++++++- .../repository/remove_in_memory_auction.rs | 16 ++ .../remove_in_memory_pending_bids.rs | 28 ++++ .../remove_in_memory_submitted_auction.rs | 16 -- .../src/auction/repository/submit_auction.rs | 7 +- .../auction/repository/update_bid_status.rs | 50 ++---- .../repository/update_in_memory_auction.rs | 23 +++ .../src/auction/service/auction_manager.rs | 82 ++++++---- .../src/auction/service/conclude_auction.rs | 48 ++++-- .../src/auction/service/conclude_auctions.rs | 2 +- .../src/auction/service/get_auction_by_id.rs | 22 +++ .../{get_live_bids.rs => get_pending_bids.rs} | 4 +- .../get_permission_keys_for_auction.rs | 4 +- .../src/auction/service/handle_auction.rs | 5 +- auction-server/src/auction/service/mod.rs | 4 +- .../src/auction/service/submit_quote.rs | 144 ++++++++++-------- .../service/update_submitted_auction.rs | 30 ---- .../src/auction/service/verification.rs | 8 +- auction-server/src/auction/service/workers.rs | 11 +- .../src/opportunity/service/get_quote.rs | 79 +++++----- 51 files changed, 851 insertions(+), 367 deletions(-) create mode 100644 auction-server/.sqlx/query-12869f012538d108ee2a3e9c98ad91daf52246ba0ebad020330288d05ac5b3c9.json create mode 100644 auction-server/.sqlx/query-744013026c0b31750c2452f98851bb38e8bebeba59012b86ac6b0de653332cea.json rename auction-server/.sqlx/{query-779b7870017a025ee0e5cd7972b71480c99ca9b1bcb22e62f6c8a2722133061b.json => query-7903d56563e684d92cb266cb7f556f410e1b1943d6429d73ddd724c075027b70.json} (69%) create mode 100644 auction-server/.sqlx/query-c0fe5ae79fe510767cf7eea939b74e841a9a847493c398cd543136193a81feda.json create mode 100644 auction-server/.sqlx/query-fa6d4023356c380d05db09464366e432a314051f152854620bd2dfcbd66434e2.json create mode 100644 auction-server/migrations/20250130104828_bid_status_alter_type.down.sql create mode 100644 auction-server/migrations/20250130104828_bid_status_alter_type.up.sql create mode 100644 auction-server/src/auction/repository/get_in_memory_auction_by_id.rs create mode 100644 auction-server/src/auction/repository/get_in_memory_auctions.rs delete mode 100644 auction-server/src/auction/repository/get_in_memory_bid_by_id.rs rename auction-server/src/auction/repository/{get_in_memory_bids.rs => get_in_memory_pending_bids.rs} (71%) rename auction-server/src/auction/repository/{get_in_memory_bids_by_permission_key.rs => get_in_memory_pending_bids_by_permission_key.rs} (83%) delete mode 100644 auction-server/src/auction/repository/get_in_memory_submitted_auctions.rs delete mode 100644 auction-server/src/auction/repository/get_in_memory_submitted_bids_for_auction.rs create mode 100644 auction-server/src/auction/repository/remove_in_memory_auction.rs create mode 100644 auction-server/src/auction/repository/remove_in_memory_pending_bids.rs delete mode 100644 auction-server/src/auction/repository/remove_in_memory_submitted_auction.rs create mode 100644 auction-server/src/auction/repository/update_in_memory_auction.rs create mode 100644 auction-server/src/auction/service/get_auction_by_id.rs rename auction-server/src/auction/service/{get_live_bids.rs => get_pending_bids.rs} (76%) delete mode 100644 auction-server/src/auction/service/update_submitted_auction.rs diff --git a/auction-server/.sqlx/query-12869f012538d108ee2a3e9c98ad91daf52246ba0ebad020330288d05ac5b3c9.json b/auction-server/.sqlx/query-12869f012538d108ee2a3e9c98ad91daf52246ba0ebad020330288d05ac5b3c9.json new file mode 100644 index 00000000..4f59807d --- /dev/null +++ b/auction-server/.sqlx/query-12869f012538d108ee2a3e9c98ad91daf52246ba0ebad020330288d05ac5b3c9.json @@ -0,0 +1,50 @@ +{ + "db_name": "PostgreSQL", + "query": "UPDATE bid SET status = $1, conclusion_time = $2, auction_id = $3 WHERE id = $4 AND status = $5", + "describe": { + "columns": [], + "parameters": { + "Left": [ + { + "Custom": { + "name": "bid_status", + "kind": { + "Enum": [ + "pending", + "lost", + "submitted", + "won", + "expired", + "failed", + "awaiting_signature", + "cancelled" + ] + } + } + }, + "Timestamp", + "Uuid", + "Uuid", + { + "Custom": { + "name": "bid_status", + "kind": { + "Enum": [ + "pending", + "lost", + "submitted", + "won", + "expired", + "failed", + "awaiting_signature", + "cancelled" + ] + } + } + } + ] + }, + "nullable": [] + }, + "hash": "12869f012538d108ee2a3e9c98ad91daf52246ba0ebad020330288d05ac5b3c9" +} diff --git a/auction-server/.sqlx/query-5143a4ff9e62fdfc64d652d778485aa5e1962ad3aa9c70bd34472f24cef77306.json b/auction-server/.sqlx/query-5143a4ff9e62fdfc64d652d778485aa5e1962ad3aa9c70bd34472f24cef77306.json index c86e12fc..c4b8d12e 100644 --- a/auction-server/.sqlx/query-5143a4ff9e62fdfc64d652d778485aa5e1962ad3aa9c70bd34472f24cef77306.json +++ b/auction-server/.sqlx/query-5143a4ff9e62fdfc64d652d778485aa5e1962ad3aa9c70bd34472f24cef77306.json @@ -15,7 +15,9 @@ "submitted", "won", "expired", - "failed" + "failed", + "awaiting_signature", + "cancelled" ] } } @@ -32,7 +34,9 @@ "submitted", "won", "expired", - "failed" + "failed", + "awaiting_signature", + "cancelled" ] } } diff --git a/auction-server/.sqlx/query-71a59d9696f9bac1b6d31ce4926b497337c599c11c9cf3d29c58fbbf7501779d.json b/auction-server/.sqlx/query-71a59d9696f9bac1b6d31ce4926b497337c599c11c9cf3d29c58fbbf7501779d.json index 0c081c0e..b16b7739 100644 --- a/auction-server/.sqlx/query-71a59d9696f9bac1b6d31ce4926b497337c599c11c9cf3d29c58fbbf7501779d.json +++ b/auction-server/.sqlx/query-71a59d9696f9bac1b6d31ce4926b497337c599c11c9cf3d29c58fbbf7501779d.json @@ -15,7 +15,9 @@ "submitted", "won", "expired", - "failed" + "failed", + "awaiting_signature", + "cancelled" ] } } @@ -31,7 +33,9 @@ "submitted", "won", "expired", - "failed" + "failed", + "awaiting_signature", + "cancelled" ] } } diff --git a/auction-server/.sqlx/query-744013026c0b31750c2452f98851bb38e8bebeba59012b86ac6b0de653332cea.json b/auction-server/.sqlx/query-744013026c0b31750c2452f98851bb38e8bebeba59012b86ac6b0de653332cea.json new file mode 100644 index 00000000..eee05ccc --- /dev/null +++ b/auction-server/.sqlx/query-744013026c0b31750c2452f98851bb38e8bebeba59012b86ac6b0de653332cea.json @@ -0,0 +1,66 @@ +{ + "db_name": "PostgreSQL", + "query": "UPDATE bid SET status = $1, auction_id = $2 WHERE id = $3 AND status IN ($4, $5)", + "describe": { + "columns": [], + "parameters": { + "Left": [ + { + "Custom": { + "name": "bid_status", + "kind": { + "Enum": [ + "pending", + "lost", + "submitted", + "won", + "expired", + "failed", + "awaiting_signature", + "cancelled" + ] + } + } + }, + "Uuid", + "Uuid", + { + "Custom": { + "name": "bid_status", + "kind": { + "Enum": [ + "pending", + "lost", + "submitted", + "won", + "expired", + "failed", + "awaiting_signature", + "cancelled" + ] + } + } + }, + { + "Custom": { + "name": "bid_status", + "kind": { + "Enum": [ + "pending", + "lost", + "submitted", + "won", + "expired", + "failed", + "awaiting_signature", + "cancelled" + ] + } + } + } + ] + }, + "nullable": [] + }, + "hash": "744013026c0b31750c2452f98851bb38e8bebeba59012b86ac6b0de653332cea" +} diff --git a/auction-server/.sqlx/query-756fe3557caee2202d61430982956109d0a3ba60cce3536dc5ac2e4eb3682a23.json b/auction-server/.sqlx/query-756fe3557caee2202d61430982956109d0a3ba60cce3536dc5ac2e4eb3682a23.json index 4025b555..19c70945 100644 --- a/auction-server/.sqlx/query-756fe3557caee2202d61430982956109d0a3ba60cce3536dc5ac2e4eb3682a23.json +++ b/auction-server/.sqlx/query-756fe3557caee2202d61430982956109d0a3ba60cce3536dc5ac2e4eb3682a23.json @@ -15,7 +15,9 @@ "submitted", "won", "expired", - "failed" + "failed", + "awaiting_signature", + "cancelled" ] } } @@ -33,7 +35,9 @@ "submitted", "won", "expired", - "failed" + "failed", + "awaiting_signature", + "cancelled" ] } } diff --git a/auction-server/.sqlx/query-779b7870017a025ee0e5cd7972b71480c99ca9b1bcb22e62f6c8a2722133061b.json b/auction-server/.sqlx/query-7903d56563e684d92cb266cb7f556f410e1b1943d6429d73ddd724c075027b70.json similarity index 69% rename from auction-server/.sqlx/query-779b7870017a025ee0e5cd7972b71480c99ca9b1bcb22e62f6c8a2722133061b.json rename to auction-server/.sqlx/query-7903d56563e684d92cb266cb7f556f410e1b1943d6429d73ddd724c075027b70.json index 8a14d984..df8e42e5 100644 --- a/auction-server/.sqlx/query-779b7870017a025ee0e5cd7972b71480c99ca9b1bcb22e62f6c8a2722133061b.json +++ b/auction-server/.sqlx/query-7903d56563e684d92cb266cb7f556f410e1b1943d6429d73ddd724c075027b70.json @@ -1,6 +1,6 @@ { "db_name": "PostgreSQL", - "query": "INSERT INTO auction (id, creation_time, permission_key, chain_id, chain_type, bid_collection_time) VALUES ($1, $2, $3, $4, $5, $6)", + "query": "INSERT INTO auction (id, creation_time, permission_key, chain_id, chain_type, bid_collection_time, tx_hash) VALUES ($1, $2, $3, $4, $5, $6, $7)", "describe": { "columns": [], "parameters": { @@ -20,10 +20,11 @@ } } }, - "Timestamp" + "Timestamp", + "Bytea" ] }, "nullable": [] }, - "hash": "779b7870017a025ee0e5cd7972b71480c99ca9b1bcb22e62f6c8a2722133061b" + "hash": "7903d56563e684d92cb266cb7f556f410e1b1943d6429d73ddd724c075027b70" } diff --git a/auction-server/.sqlx/query-82192571d3edf3a46265ac00702402fe80ecee20caf9001b88bf8d885b0524e2.json b/auction-server/.sqlx/query-82192571d3edf3a46265ac00702402fe80ecee20caf9001b88bf8d885b0524e2.json index 6d823d3d..b1db7cf6 100644 --- a/auction-server/.sqlx/query-82192571d3edf3a46265ac00702402fe80ecee20caf9001b88bf8d885b0524e2.json +++ b/auction-server/.sqlx/query-82192571d3edf3a46265ac00702402fe80ecee20caf9001b88bf8d885b0524e2.json @@ -15,7 +15,9 @@ "submitted", "won", "expired", - "failed" + "failed", + "awaiting_signature", + "cancelled" ] } } @@ -33,7 +35,9 @@ "submitted", "won", "expired", - "failed" + "failed", + "awaiting_signature", + "cancelled" ] } } diff --git a/auction-server/.sqlx/query-9526c7c80829a0dbeea7763b3381467782c8bec86f6f8b52fe11325b96edbdb4.json b/auction-server/.sqlx/query-9526c7c80829a0dbeea7763b3381467782c8bec86f6f8b52fe11325b96edbdb4.json index c20d5ef9..d3ed8026 100644 --- a/auction-server/.sqlx/query-9526c7c80829a0dbeea7763b3381467782c8bec86f6f8b52fe11325b96edbdb4.json +++ b/auction-server/.sqlx/query-9526c7c80829a0dbeea7763b3381467782c8bec86f6f8b52fe11325b96edbdb4.json @@ -31,7 +31,9 @@ "submitted", "won", "expired", - "failed" + "failed", + "awaiting_signature", + "cancelled" ] } } diff --git a/auction-server/.sqlx/query-a25adc19c687c4988ca108b2ef7dedeafe298047458c91bf3bf0540274c4c508.json b/auction-server/.sqlx/query-a25adc19c687c4988ca108b2ef7dedeafe298047458c91bf3bf0540274c4c508.json index 3d0a4d1f..939f8e95 100644 --- a/auction-server/.sqlx/query-a25adc19c687c4988ca108b2ef7dedeafe298047458c91bf3bf0540274c4c508.json +++ b/auction-server/.sqlx/query-a25adc19c687c4988ca108b2ef7dedeafe298047458c91bf3bf0540274c4c508.json @@ -15,7 +15,9 @@ "submitted", "won", "expired", - "failed" + "failed", + "awaiting_signature", + "cancelled" ] } } @@ -33,7 +35,9 @@ "submitted", "won", "expired", - "failed" + "failed", + "awaiting_signature", + "cancelled" ] } } diff --git a/auction-server/.sqlx/query-b54b6d2c1d4d7957fb7188eec17ec8a0b4e7ab51c728bac77f79ffbd896d8c27.json b/auction-server/.sqlx/query-b54b6d2c1d4d7957fb7188eec17ec8a0b4e7ab51c728bac77f79ffbd896d8c27.json index c5040d60..64ef4fcc 100644 --- a/auction-server/.sqlx/query-b54b6d2c1d4d7957fb7188eec17ec8a0b4e7ab51c728bac77f79ffbd896d8c27.json +++ b/auction-server/.sqlx/query-b54b6d2c1d4d7957fb7188eec17ec8a0b4e7ab51c728bac77f79ffbd896d8c27.json @@ -15,7 +15,9 @@ "submitted", "won", "expired", - "failed" + "failed", + "awaiting_signature", + "cancelled" ] } } @@ -32,7 +34,9 @@ "submitted", "won", "expired", - "failed" + "failed", + "awaiting_signature", + "cancelled" ] } } diff --git a/auction-server/.sqlx/query-c0fe5ae79fe510767cf7eea939b74e841a9a847493c398cd543136193a81feda.json b/auction-server/.sqlx/query-c0fe5ae79fe510767cf7eea939b74e841a9a847493c398cd543136193a81feda.json new file mode 100644 index 00000000..d021ec93 --- /dev/null +++ b/auction-server/.sqlx/query-c0fe5ae79fe510767cf7eea939b74e841a9a847493c398cd543136193a81feda.json @@ -0,0 +1,83 @@ +{ + "db_name": "PostgreSQL", + "query": "UPDATE bid SET status = $1, conclusion_time = $2 WHERE id = $3 AND status IN ($4, $5, $6)", + "describe": { + "columns": [], + "parameters": { + "Left": [ + { + "Custom": { + "name": "bid_status", + "kind": { + "Enum": [ + "pending", + "lost", + "submitted", + "won", + "expired", + "failed", + "awaiting_signature", + "cancelled" + ] + } + } + }, + "Timestamp", + "Uuid", + { + "Custom": { + "name": "bid_status", + "kind": { + "Enum": [ + "pending", + "lost", + "submitted", + "won", + "expired", + "failed", + "awaiting_signature", + "cancelled" + ] + } + } + }, + { + "Custom": { + "name": "bid_status", + "kind": { + "Enum": [ + "pending", + "lost", + "submitted", + "won", + "expired", + "failed", + "awaiting_signature", + "cancelled" + ] + } + } + }, + { + "Custom": { + "name": "bid_status", + "kind": { + "Enum": [ + "pending", + "lost", + "submitted", + "won", + "expired", + "failed", + "awaiting_signature", + "cancelled" + ] + } + } + } + ] + }, + "nullable": [] + }, + "hash": "c0fe5ae79fe510767cf7eea939b74e841a9a847493c398cd543136193a81feda" +} diff --git a/auction-server/.sqlx/query-e57e8bee9fbc6c2c16396d0900f44a734f7cfc94637ff3a8f1b52a329a704522.json b/auction-server/.sqlx/query-e57e8bee9fbc6c2c16396d0900f44a734f7cfc94637ff3a8f1b52a329a704522.json index 569722a7..27e0638f 100644 --- a/auction-server/.sqlx/query-e57e8bee9fbc6c2c16396d0900f44a734f7cfc94637ff3a8f1b52a329a704522.json +++ b/auction-server/.sqlx/query-e57e8bee9fbc6c2c16396d0900f44a734f7cfc94637ff3a8f1b52a329a704522.json @@ -15,7 +15,9 @@ "submitted", "won", "expired", - "failed" + "failed", + "awaiting_signature", + "cancelled" ] } } @@ -32,7 +34,9 @@ "submitted", "won", "expired", - "failed" + "failed", + "awaiting_signature", + "cancelled" ] } } diff --git a/auction-server/.sqlx/query-fa6d4023356c380d05db09464366e432a314051f152854620bd2dfcbd66434e2.json b/auction-server/.sqlx/query-fa6d4023356c380d05db09464366e432a314051f152854620bd2dfcbd66434e2.json new file mode 100644 index 00000000..03900cad --- /dev/null +++ b/auction-server/.sqlx/query-fa6d4023356c380d05db09464366e432a314051f152854620bd2dfcbd66434e2.json @@ -0,0 +1,66 @@ +{ + "db_name": "PostgreSQL", + "query": "UPDATE bid SET status = $1, conclusion_time = $2 WHERE id = $3 AND status IN ($4, $5)", + "describe": { + "columns": [], + "parameters": { + "Left": [ + { + "Custom": { + "name": "bid_status", + "kind": { + "Enum": [ + "pending", + "lost", + "submitted", + "won", + "expired", + "failed", + "awaiting_signature", + "cancelled" + ] + } + } + }, + "Timestamp", + "Uuid", + { + "Custom": { + "name": "bid_status", + "kind": { + "Enum": [ + "pending", + "lost", + "submitted", + "won", + "expired", + "failed", + "awaiting_signature", + "cancelled" + ] + } + } + }, + { + "Custom": { + "name": "bid_status", + "kind": { + "Enum": [ + "pending", + "lost", + "submitted", + "won", + "expired", + "failed", + "awaiting_signature", + "cancelled" + ] + } + } + } + ] + }, + "nullable": [] + }, + "hash": "fa6d4023356c380d05db09464366e432a314051f152854620bd2dfcbd66434e2" +} diff --git a/auction-server/api-types/src/bid.rs b/auction-server/api-types/src/bid.rs index 10d5ac36..2ee04ed0 100644 --- a/auction-server/api-types/src/bid.rs +++ b/auction-server/api-types/src/bid.rs @@ -84,9 +84,18 @@ pub enum BidStatusEvm { #[serde(tag = "type", rename_all = "snake_case")] pub enum BidStatusSvm { /// The temporary state which means the auction for this bid is pending. - /// It will be updated to Lost or Submitted after the auction takes place. + /// It will be updated to AwaitingSignature, Lost or Submitted after the auction takes place. #[schema(title = "Pending")] Pending, + /// The bid is waiting for the remaining parties to sign the transaction. + /// After all parties sign, the bid will be submitted to the chain. + /// During this state, the bid can be canceled by the owner. + #[schema(title = "AwaitingSignature")] + AwaitingSignature { + #[schema(example = "Jb2urXPyEh4xiBgzYvwEFe4q1iMxG1DNxWGGQg94AmKgqFTwLAiTiHrYiYxwHUB4DV8u5ahNEVtMMDm3sNSRdTg", value_type = String)] + #[serde_as(as = "DisplayFromStr")] + result: Signature, + }, /// The bid lost the auction. /// This bid status will have a result field containing the signature of the transaction corresponding to the winning bid, /// unless the auction had no winner (because all bids were found to be invalid). @@ -125,6 +134,14 @@ pub enum BidStatusSvm { #[serde_as(as = "DisplayFromStr")] result: Signature, }, + /// The bid was canceled by the owner. + /// Owner can only cancel the bid when the bid is in AwaitingSignature state. + #[schema(title = "Cancelled")] + Cancelled { + #[schema(example = "Jb2urXPyEh4xiBgzYvwEFe4q1iMxG1DNxWGGQg94AmKgqFTwLAiTiHrYiYxwHUB4DV8u5ahNEVtMMDm3sNSRdTg", value_type = String)] + #[serde_as(as = "DisplayFromStr")] + result: Signature, + }, } #[derive(Serialize, Deserialize, ToSchema, Clone, PartialEq, Debug)] diff --git a/auction-server/api-types/src/quote.rs b/auction-server/api-types/src/quote.rs index 3107787d..c4f12afd 100644 --- a/auction-server/api-types/src/quote.rs +++ b/auction-server/api-types/src/quote.rs @@ -1,5 +1,4 @@ use { - crate::bid::BidId, serde::{ Deserialize, Serialize, @@ -13,6 +12,7 @@ use { transaction::VersionedTransaction, }, utoipa::ToSchema, + uuid::Uuid, }; /// Parameters needed to submit a quote from server. @@ -21,7 +21,7 @@ use { pub struct SubmitQuote { /// The reference id for the quote that should be submitted. #[schema(example = "beedbeed-58cc-4372-a567-0e02b2c3d479", value_type=String)] - pub reference_id: BidId, + pub reference_id: Uuid, /// The signature of the user for the quote. #[schema(example = "Jb2urXPyEh4xiBgzYvwEFe4q1iMxG1DNxWGGQg94AmKgqFTwLAiTiHrYiYxwHUB4DV8u5ahNEVtMMDm3sNSRdTg", value_type = String)] #[serde_as(as = "DisplayFromStr")] diff --git a/auction-server/migrations/20250130104828_bid_status_alter_type.down.sql b/auction-server/migrations/20250130104828_bid_status_alter_type.down.sql new file mode 100644 index 00000000..e0faf2c6 --- /dev/null +++ b/auction-server/migrations/20250130104828_bid_status_alter_type.down.sql @@ -0,0 +1,8 @@ +UPDATE bid SET status = 'submitted' WHERE status = 'awaiting_signature'; +UPDATE bid SET status = 'expired' WHERE status = 'cancelled'; +CREATE TYPE temp_bid_status AS ENUM ('pending', 'lost', 'submitted', 'won', 'expired', 'failed'); +ALTER TABLE bid + ALTER COLUMN status TYPE temp_bid_status + USING status::text::temp_bid_status; +DROP TYPE IF EXISTS bid_status; +ALTER TYPE temp_bid_status RENAME TO bid_status; diff --git a/auction-server/migrations/20250130104828_bid_status_alter_type.up.sql b/auction-server/migrations/20250130104828_bid_status_alter_type.up.sql new file mode 100644 index 00000000..23075f3b --- /dev/null +++ b/auction-server/migrations/20250130104828_bid_status_alter_type.up.sql @@ -0,0 +1,2 @@ +ALTER TYPE bid_status ADD VALUE 'awaiting_signature'; +ALTER TYPE bid_status ADD VALUE 'cancelled'; diff --git a/auction-server/src/auction/api.rs b/auction-server/src/auction/api.rs index c0c30440..4f8f1296 100644 --- a/auction-server/src/auction/api.rs +++ b/auction-server/src/auction/api.rs @@ -256,7 +256,7 @@ pub async fn post_submit_quote( ServiceEnum::Svm(service) => { let transaction = service .submit_quote(SubmitQuoteInput { - bid_id: submit_quote.reference_id, + auction_id: submit_quote.reference_id, user_signature: submit_quote.user_signature, }) .await?; @@ -307,6 +307,11 @@ impl From for BidStatusSvm { fn from(status: entities::BidStatusSvm) -> Self { match status { entities::BidStatusSvm::Pending => BidStatusSvm::Pending, + entities::BidStatusSvm::AwaitingSignature { auction } => { + BidStatusSvm::AwaitingSignature { + result: auction.tx_hash, + } + } entities::BidStatusSvm::Submitted { auction } => BidStatusSvm::Submitted { result: auction.tx_hash, }, @@ -322,6 +327,9 @@ impl From for BidStatusSvm { entities::BidStatusSvm::Expired { auction } => BidStatusSvm::Expired { result: auction.tx_hash, }, + entities::BidStatusSvm::Cancelled { auction } => BidStatusSvm::Cancelled { + result: auction.tx_hash, + }, } } } diff --git a/auction-server/src/auction/entities/auction.rs b/auction-server/src/auction/entities/auction.rs index 4def74d4..2195cc5f 100644 --- a/auction-server/src/auction/entities/auction.rs +++ b/auction-server/src/auction/entities/auction.rs @@ -26,6 +26,7 @@ pub struct Auction { pub chain_id: ChainId, pub permission_key: super::PermissionKey, pub creation_time: OffsetDateTime, + #[allow(dead_code)] pub conclusion_time: Option, pub bid_collection_time: OffsetDateTime, pub submission_time: Option, diff --git a/auction-server/src/auction/entities/bid.rs b/auction-server/src/auction/entities/bid.rs index 5e6c760c..fb39b9f4 100644 --- a/auction-server/src/auction/entities/bid.rs +++ b/auction-server/src/auction/entities/bid.rs @@ -61,10 +61,13 @@ pub trait BidStatus: } fn is_pending(&self) -> bool; + fn is_awaiting_signature(&self) -> bool; fn is_submitted(&self) -> bool; fn is_finalized(&self) -> bool; fn new_lost() -> Self; + + fn get_auction_id(&self) -> Option; } #[derive(Clone, Debug, PartialEq)] @@ -76,6 +79,9 @@ pub struct BidStatusAuction { #[derive(Clone, Debug, PartialEq)] pub enum BidStatusSvm { Pending, + AwaitingSignature { + auction: BidStatusAuction, + }, Submitted { auction: BidStatusAuction, }, @@ -91,6 +97,9 @@ pub enum BidStatusSvm { Expired { auction: BidStatusAuction, }, + Cancelled { + auction: BidStatusAuction, + }, } #[derive(Clone, Debug, PartialEq)] @@ -117,6 +126,10 @@ impl BidStatus for BidStatusSvm { matches!(self, BidStatusSvm::Pending) } + fn is_awaiting_signature(&self) -> bool { + matches!(self, BidStatusSvm::AwaitingSignature { .. }) + } + fn is_submitted(&self) -> bool { matches!(self, BidStatusSvm::Submitted { .. }) } @@ -128,12 +141,26 @@ impl BidStatus for BidStatusSvm { | BidStatusSvm::Won { .. } | BidStatusSvm::Failed { .. } | BidStatusSvm::Expired { .. } + | BidStatusSvm::Cancelled { .. } ) } fn new_lost() -> Self { BidStatusSvm::Lost { auction: None } } + + fn get_auction_id(&self) -> Option { + match self { + BidStatusSvm::Pending => None, + BidStatusSvm::AwaitingSignature { auction } => Some(auction.id), + BidStatusSvm::Submitted { auction } => Some(auction.id), + BidStatusSvm::Lost { auction } => auction.as_ref().map(|a| a.id), + BidStatusSvm::Won { auction } => Some(auction.id), + BidStatusSvm::Failed { auction } => Some(auction.id), + BidStatusSvm::Expired { auction } => Some(auction.id), + BidStatusSvm::Cancelled { auction } => Some(auction.id), + } + } } impl BidStatus for BidStatusEvm { @@ -143,6 +170,10 @@ impl BidStatus for BidStatusEvm { matches!(self, BidStatusEvm::Pending) } + fn is_awaiting_signature(&self) -> bool { + false + } + fn is_submitted(&self) -> bool { matches!(self, BidStatusEvm::Submitted { .. }) } @@ -157,6 +188,15 @@ impl BidStatus for BidStatusEvm { index: None, } } + + fn get_auction_id(&self) -> Option { + match self { + BidStatusEvm::Pending => None, + BidStatusEvm::Submitted { auction, .. } => Some(auction.id), + BidStatusEvm::Lost { auction, .. } => auction.as_ref().map(|a| a.id), + BidStatusEvm::Won { auction, .. } => Some(auction.id), + } + } } #[derive(Clone, Debug, PartialEq)] diff --git a/auction-server/src/auction/repository/add_auction.rs b/auction-server/src/auction/repository/add_auction.rs index 52f7738f..13044ee5 100644 --- a/auction-server/src/auction/repository/add_auction.rs +++ b/auction-server/src/auction/repository/add_auction.rs @@ -1,7 +1,10 @@ use { super::Repository, crate::auction::{ - entities, + entities::{ + self, + BidStatus, + }, service::ChainTrait, }, time::PrimitiveDateTime, @@ -15,16 +18,25 @@ impl Repository { ) -> anyhow::Result> { tracing::Span::current().record("auction_id", auction.id.to_string()); sqlx::query!( - "INSERT INTO auction (id, creation_time, permission_key, chain_id, chain_type, bid_collection_time) VALUES ($1, $2, $3, $4, $5, $6)", + "INSERT INTO auction (id, creation_time, permission_key, chain_id, chain_type, bid_collection_time, tx_hash) VALUES ($1, $2, $3, $4, $5, $6, $7)", auction.id, PrimitiveDateTime::new(auction.creation_time.date(), auction.creation_time.time()), T::convert_permission_key(&auction.permission_key), auction.chain_id, T::get_chain_type() as _, PrimitiveDateTime::new(auction.bid_collection_time.date(), auction.bid_collection_time.time()), + auction.tx_hash.clone().map(|tx_hash| T::BidStatusType::convert_tx_hash(&tx_hash)), ) .execute(&self.db) .await?; + + self.remove_in_memory_pending_bids(auction.bids.as_slice()) + .await; + self.in_memory_store + .auctions + .write() + .await + .push(auction.clone()); Ok(auction) } } diff --git a/auction-server/src/auction/repository/add_bid.rs b/auction-server/src/auction/repository/add_bid.rs index b138e126..124f5697 100644 --- a/auction-server/src/auction/repository/add_bid.rs +++ b/auction-server/src/auction/repository/add_bid.rs @@ -46,7 +46,7 @@ impl Repository { })?; self.in_memory_store - .bids + .pending_bids .write() .await .entry(bid.chain_data.get_permission_key()) diff --git a/auction-server/src/auction/repository/conclude_auction.rs b/auction-server/src/auction/repository/conclude_auction.rs index fcf07e4d..fb06c987 100644 --- a/auction-server/src/auction/repository/conclude_auction.rs +++ b/auction-server/src/auction/repository/conclude_auction.rs @@ -14,17 +14,17 @@ use { impl Repository { #[tracing::instrument(skip_all, name = "conclude_auction_repo", fields(auction_id))] - pub async fn conclude_auction(&self, auction: &mut entities::Auction) -> anyhow::Result<()> { - tracing::Span::current().record("auction_id", auction.id.to_string()); + pub async fn conclude_auction(&self, auction_id: entities::AuctionId) -> anyhow::Result<()> { + tracing::Span::current().record("auction_id", auction_id.to_string()); let now = OffsetDateTime::now_utc(); - auction.conclusion_time = Some(now); sqlx::query!( "UPDATE auction SET conclusion_time = $1 WHERE id = $2 AND conclusion_time IS NULL", PrimitiveDateTime::new(now.date(), now.time()), - auction.id, + auction_id, ) .execute(&self.db) .await?; + self.remove_in_memory_auction(auction_id).await; Ok(()) } } diff --git a/auction-server/src/auction/repository/get_in_memory_auction_by_id.rs b/auction-server/src/auction/repository/get_in_memory_auction_by_id.rs new file mode 100644 index 00000000..5ff6085a --- /dev/null +++ b/auction-server/src/auction/repository/get_in_memory_auction_by_id.rs @@ -0,0 +1,19 @@ +use { + super::Repository, + crate::auction::{ + entities, + service::ChainTrait, + }, +}; + +impl Repository { + pub async fn get_in_memory_auction_by_id( + &self, + auction_id: entities::AuctionId, + ) -> Option> { + self.get_in_memory_auctions() + .await + .into_iter() + .find(|auction| auction.id == auction_id) + } +} diff --git a/auction-server/src/auction/repository/get_in_memory_auctions.rs b/auction-server/src/auction/repository/get_in_memory_auctions.rs new file mode 100644 index 00000000..4899fa43 --- /dev/null +++ b/auction-server/src/auction/repository/get_in_memory_auctions.rs @@ -0,0 +1,13 @@ +use { + super::Repository, + crate::auction::{ + entities, + service::ChainTrait, + }, +}; + +impl Repository { + pub async fn get_in_memory_auctions(&self) -> Vec> { + self.in_memory_store.auctions.read().await.clone() + } +} diff --git a/auction-server/src/auction/repository/get_in_memory_bid_by_id.rs b/auction-server/src/auction/repository/get_in_memory_bid_by_id.rs deleted file mode 100644 index 67b41310..00000000 --- a/auction-server/src/auction/repository/get_in_memory_bid_by_id.rs +++ /dev/null @@ -1,24 +0,0 @@ -use { - super::Repository, - crate::auction::{ - entities, - service::ChainTrait, - }, -}; - -impl Repository { - pub async fn get_in_memory_bid_by_id( - &self, - bid_id: entities::BidId, - ) -> Option> { - let in_memory_bids = self.get_in_memory_bids().await; - for bids in in_memory_bids.values() { - for bid in bids { - if bid.id == bid_id { - return Some(bid.clone()); - } - } - } - None - } -} diff --git a/auction-server/src/auction/repository/get_in_memory_bids.rs b/auction-server/src/auction/repository/get_in_memory_pending_bids.rs similarity index 71% rename from auction-server/src/auction/repository/get_in_memory_bids.rs rename to auction-server/src/auction/repository/get_in_memory_pending_bids.rs index 7cf7efca..fcada2a1 100644 --- a/auction-server/src/auction/repository/get_in_memory_bids.rs +++ b/auction-server/src/auction/repository/get_in_memory_pending_bids.rs @@ -8,9 +8,9 @@ use { }; impl Repository { - pub async fn get_in_memory_bids( + pub async fn get_in_memory_pending_bids( &self, ) -> HashMap, Vec>> { - self.in_memory_store.bids.read().await.clone() + self.in_memory_store.pending_bids.read().await.clone() } } diff --git a/auction-server/src/auction/repository/get_in_memory_bids_by_permission_key.rs b/auction-server/src/auction/repository/get_in_memory_pending_bids_by_permission_key.rs similarity index 83% rename from auction-server/src/auction/repository/get_in_memory_bids_by_permission_key.rs rename to auction-server/src/auction/repository/get_in_memory_pending_bids_by_permission_key.rs index 9dd04a35..4af93e7c 100644 --- a/auction-server/src/auction/repository/get_in_memory_bids_by_permission_key.rs +++ b/auction-server/src/auction/repository/get_in_memory_pending_bids_by_permission_key.rs @@ -7,12 +7,12 @@ use { }; impl Repository { - pub async fn get_in_memory_bids_by_permission_key( + pub async fn get_in_memory_pending_bids_by_permission_key( &self, permission_key: &::PermissionKey, ) -> Vec> { self.in_memory_store - .bids + .pending_bids .read() .await .get(permission_key) diff --git a/auction-server/src/auction/repository/get_in_memory_submitted_auctions.rs b/auction-server/src/auction/repository/get_in_memory_submitted_auctions.rs deleted file mode 100644 index 05c17c6e..00000000 --- a/auction-server/src/auction/repository/get_in_memory_submitted_auctions.rs +++ /dev/null @@ -1,13 +0,0 @@ -use { - super::Repository, - crate::auction::{ - entities, - service::ChainTrait, - }, -}; - -impl Repository { - pub async fn get_in_memory_submitted_auctions(&self) -> Vec> { - self.in_memory_store.submitted_auctions.read().await.clone() - } -} diff --git a/auction-server/src/auction/repository/get_in_memory_submitted_bids_for_auction.rs b/auction-server/src/auction/repository/get_in_memory_submitted_bids_for_auction.rs deleted file mode 100644 index 2c4c02ea..00000000 --- a/auction-server/src/auction/repository/get_in_memory_submitted_bids_for_auction.rs +++ /dev/null @@ -1,32 +0,0 @@ -use { - super::Repository, - crate::auction::{ - entities::{ - self, - BidStatus, - }, - service::ChainTrait, - }, -}; - -impl Repository { - pub async fn get_in_memory_submitted_bids_for_auction( - &self, - auction: &entities::Auction, - ) -> Vec> { - // Filter out the bids that are in the auction and submitted - let bids = self - .get_in_memory_bids_by_permission_key(&auction.permission_key) - .await; - bids.iter() - .filter(|bid| { - auction - .bids - .iter() - .any(|auction_bid| auction_bid.id == bid.id) - && bid.status.is_submitted() - }) - .cloned() - .collect() - } -} diff --git a/auction-server/src/auction/repository/mod.rs b/auction-server/src/auction/repository/mod.rs index a1fdcf48..312b19ec 100644 --- a/auction-server/src/auction/repository/mod.rs +++ b/auction-server/src/auction/repository/mod.rs @@ -26,19 +26,20 @@ mod add_recent_priotization_fee; mod conclude_auction; mod get_bid; mod get_bids; -mod get_in_memory_bid_by_id; -mod get_in_memory_bids; -mod get_in_memory_bids_by_permission_key; -mod get_in_memory_submitted_auctions; -mod get_in_memory_submitted_bids_for_auction; +mod get_in_memory_auction_by_id; +mod get_in_memory_auctions; +mod get_in_memory_pending_bids; +mod get_in_memory_pending_bids_by_permission_key; mod get_lookup_table; mod get_or_create_in_memory_auction_lock; mod get_priority_fees; mod models; +mod remove_in_memory_auction; mod remove_in_memory_auction_lock; -mod remove_in_memory_submitted_auction; +mod remove_in_memory_pending_bids; mod submit_auction; mod update_bid_status; +mod update_in_memory_auction; pub use models::*; @@ -61,9 +62,9 @@ pub struct ChainStoreEvm {} #[derive(Debug)] pub struct InMemoryStore { - pub bids: RwLock, Vec>>>, - pub auction_lock: Mutex, entities::AuctionLock>>, - pub submitted_auctions: RwLock>>, + pub pending_bids: RwLock, Vec>>>, + pub auction_lock: Mutex, entities::AuctionLock>>, + pub auctions: RwLock>>, pub chain_store: T::ChainStore, } @@ -71,10 +72,10 @@ pub struct InMemoryStore { impl Default for InMemoryStore { fn default() -> Self { Self { - bids: RwLock::new(HashMap::new()), - auction_lock: Mutex::new(HashMap::new()), - submitted_auctions: RwLock::new(Vec::new()), - chain_store: T::ChainStore::default(), + pending_bids: RwLock::new(HashMap::new()), + auction_lock: Mutex::new(HashMap::new()), + auctions: RwLock::new(Vec::new()), + chain_store: T::ChainStore::default(), } } } diff --git a/auction-server/src/auction/repository/models.rs b/auction-server/src/auction/repository/models.rs index 8d148d67..c01917cd 100644 --- a/auction-server/src/auction/repository/models.rs +++ b/auction-server/src/auction/repository/models.rs @@ -73,14 +73,16 @@ pub struct Auction { } #[derive(Clone, Debug, PartialEq, PartialOrd, sqlx::Type)] -#[sqlx(type_name = "bid_status", rename_all = "lowercase")] +#[sqlx(type_name = "bid_status", rename_all = "snake_case")] pub enum BidStatus { Pending, + AwaitingSignature, Submitted, Lost, Won, Failed, Expired, + Cancelled, } #[derive(Clone, Debug, Serialize, Deserialize)] @@ -182,6 +184,9 @@ impl ModelTrait for Evm { let index = Self::get_bid_bundle_index(bid); match bid.status { BidStatus::Pending => Ok(entities::BidStatusEvm::Pending), + BidStatus::AwaitingSignature => { + Err(anyhow::anyhow!("Evm bid cannot be awaiting signature")) + } BidStatus::Submitted => { if bid_status_auction.is_none() || index.is_none() { return Err(anyhow::anyhow!( @@ -212,6 +217,7 @@ impl ModelTrait for Evm { }), BidStatus::Failed => Err(anyhow::anyhow!("Evm bid cannot be failed")), BidStatus::Expired => Err(anyhow::anyhow!("Evm bid cannot be expired")), + BidStatus::Cancelled => Err(anyhow::anyhow!("Evm bid cannot be cancelled")), } } fn convert_bid_status(status: &entities::BidStatusEvm) -> BidStatus { @@ -365,6 +371,14 @@ impl ModelTrait for Svm { bid.status )), + (BidStatus::AwaitingSignature, Some(auction)) => { + Ok(entities::BidStatusSvm::AwaitingSignature { + auction: entities::BidStatusAuction { + tx_hash: sig, + id: auction.id, + }, + }) + } (BidStatus::Submitted, Some(auction)) => Ok(entities::BidStatusSvm::Submitted { auction: entities::BidStatusAuction { tx_hash: sig, @@ -389,17 +403,25 @@ impl ModelTrait for Svm { id: auction.id, }, }), + (BidStatus::Cancelled, Some(auction)) => Ok(entities::BidStatusSvm::Cancelled { + auction: entities::BidStatusAuction { + tx_hash: sig, + id: auction.id, + }, + }), } } fn convert_bid_status(status: &entities::BidStatusSvm) -> BidStatus { match status { entities::BidStatusSvm::Pending => BidStatus::Pending, + entities::BidStatusSvm::AwaitingSignature { .. } => BidStatus::AwaitingSignature, entities::BidStatusSvm::Submitted { .. } => BidStatus::Submitted, entities::BidStatusSvm::Lost { .. } => BidStatus::Lost, entities::BidStatusSvm::Won { .. } => BidStatus::Won, entities::BidStatusSvm::Failed { .. } => BidStatus::Failed, entities::BidStatusSvm::Expired { .. } => BidStatus::Expired, + entities::BidStatusSvm::Cancelled { .. } => BidStatus::Cancelled, } } @@ -461,12 +483,20 @@ impl ModelTrait for Svm { entities::BidStatusSvm::Pending => { Err(anyhow::anyhow!("Cannot update bid status to pending")) } - entities::BidStatusSvm::Submitted { auction } => Ok(sqlx::query!( + entities::BidStatusSvm::AwaitingSignature { auction } => Ok(sqlx::query!( "UPDATE bid SET status = $1, auction_id = $2 WHERE id = $3 AND status = $4", + BidStatus::AwaitingSignature as _, + auction.id, + bid.id, + BidStatus::Pending as _, + )), + entities::BidStatusSvm::Submitted { auction } => Ok(sqlx::query!( + "UPDATE bid SET status = $1, auction_id = $2 WHERE id = $3 AND status IN ($4, $5)", BidStatus::Submitted as _, auction.id, bid.id, BidStatus::Pending as _, + BidStatus::AwaitingSignature as _, )), entities::BidStatusSvm::Lost { auction: Some(auction) } => Ok(sqlx::query!( "UPDATE bid SET status = $1, auction_id = $2, conclusion_time = $3 WHERE id = $4 AND status = $5", @@ -483,12 +513,31 @@ impl ModelTrait for Svm { bid.id, BidStatus::Pending as _ )), - entities::BidStatusSvm::Won { .. } | entities::BidStatusSvm::Expired { .. } | entities::BidStatusSvm::Failed { .. } => Ok(sqlx::query!( - "UPDATE bid SET status = $1, conclusion_time = $2 WHERE id = $3 AND status = $4", + entities::BidStatusSvm::Won { .. } | entities::BidStatusSvm::Failed { .. } => Ok(sqlx::query!( + "UPDATE bid SET status = $1, conclusion_time = $2 WHERE id = $3 AND status IN ($4, $5)", Self::convert_bid_status(&new_status) as _, PrimitiveDateTime::new(now.date(), now.time()), bid.id, BidStatus::Submitted as _, + // TODO Remove it after all tasks for the last look are done + BidStatus::AwaitingSignature as _, + )), + &entities::BidStatusSvm::Expired { .. } => Ok(sqlx::query!( + "UPDATE bid SET status = $1, conclusion_time = $2 WHERE id = $3 AND status IN ($4, $5, $6)", + BidStatus::Expired as _, + PrimitiveDateTime::new(now.date(), now.time()), + bid.id, + BidStatus::Pending as _, + BidStatus::Submitted as _, + BidStatus::AwaitingSignature as _, + )), + entities::BidStatusSvm::Cancelled { auction } => Ok(sqlx::query!( + "UPDATE bid SET status = $1, conclusion_time = $2, auction_id = $3 WHERE id = $4 AND status = $5", + BidStatus::Cancelled as _, + PrimitiveDateTime::new(now.date(), now.time()), + auction.id, + bid.id, + BidStatus::AwaitingSignature as _, )), } } diff --git a/auction-server/src/auction/repository/remove_in_memory_auction.rs b/auction-server/src/auction/repository/remove_in_memory_auction.rs new file mode 100644 index 00000000..eb7aa816 --- /dev/null +++ b/auction-server/src/auction/repository/remove_in_memory_auction.rs @@ -0,0 +1,16 @@ +use { + super::Repository, + crate::auction::{ + entities, + service::ChainTrait, + }, +}; + +impl Repository { + #[tracing::instrument(skip_all, fields(auction_id))] + pub async fn remove_in_memory_auction(&self, auction_id: entities::AuctionId) { + tracing::Span::current().record("auction_id", auction_id.to_string()); + let mut write_guard = self.in_memory_store.auctions.write().await; + write_guard.retain(|a| a.id != auction_id); + } +} diff --git a/auction-server/src/auction/repository/remove_in_memory_pending_bids.rs b/auction-server/src/auction/repository/remove_in_memory_pending_bids.rs new file mode 100644 index 00000000..a61c0eea --- /dev/null +++ b/auction-server/src/auction/repository/remove_in_memory_pending_bids.rs @@ -0,0 +1,28 @@ +use { + super::Repository, + crate::auction::{ + entities::{ + self, + BidChainData, + }, + service::ChainTrait, + }, + std::collections::hash_map::Entry, +}; + +impl Repository { + // Remove a bid from the in memory pending bids if it exists + pub async fn remove_in_memory_pending_bids(&self, bids: &[entities::Bid]) { + let mut write_guard = self.in_memory_store.pending_bids.write().await; + for bid in bids { + let key = bid.chain_data.get_permission_key(); + if let Entry::Occupied(mut entry) = write_guard.entry(key.clone()) { + let bids = entry.get_mut(); + bids.retain(|b| b.id != bid.id); + if bids.is_empty() { + entry.remove(); + } + } + } + } +} diff --git a/auction-server/src/auction/repository/remove_in_memory_submitted_auction.rs b/auction-server/src/auction/repository/remove_in_memory_submitted_auction.rs deleted file mode 100644 index fcc240c1..00000000 --- a/auction-server/src/auction/repository/remove_in_memory_submitted_auction.rs +++ /dev/null @@ -1,16 +0,0 @@ -use { - super::Repository, - crate::auction::{ - entities, - service::ChainTrait, - }, -}; - -impl Repository { - #[tracing::instrument(skip_all, fields(auction_id))] - pub async fn remove_in_memory_submitted_auction(&self, auction: entities::Auction) { - tracing::Span::current().record("auction_id", auction.id.to_string()); - let mut write_guard = self.in_memory_store.submitted_auctions.write().await; - write_guard.retain(|a| a.id != auction.id); - } -} diff --git a/auction-server/src/auction/repository/submit_auction.rs b/auction-server/src/auction/repository/submit_auction.rs index 0ecca68b..fbb11541 100644 --- a/auction-server/src/auction/repository/submit_auction.rs +++ b/auction-server/src/auction/repository/submit_auction.rs @@ -32,12 +32,7 @@ impl Repository { T::BidStatusType::convert_tx_hash(&transaction_hash), auction.id, ).execute(&self.db).await?; - - self.in_memory_store - .submitted_auctions - .write() - .await - .push(auction.clone()); + self.update_in_memory_auction(auction.clone()).await; Ok(auction) } } diff --git a/auction-server/src/auction/repository/update_bid_status.rs b/auction-server/src/auction/repository/update_bid_status.rs index a6ffd9c0..ff0ba569 100644 --- a/auction-server/src/auction/repository/update_bid_status.rs +++ b/auction-server/src/auction/repository/update_bid_status.rs @@ -3,46 +3,27 @@ use { crate::auction::{ entities::{ self, - BidChainData, BidStatus, }, service::ChainTrait, }, - std::collections::hash_map::Entry, }; impl Repository { - async fn remove_in_memory_bid(&self, bid: &entities::Bid) { - let mut write_guard = self.in_memory_store.bids.write().await; - let key = bid.chain_data.get_permission_key(); - if let Entry::Occupied(mut entry) = write_guard.entry(key.clone()) { - let bids = entry.get_mut(); - bids.retain(|b| b.id != bid.id); - if bids.is_empty() { - entry.remove(); - } - } - } - - async fn update_in_memory_bid(&self, bid: &entities::Bid, new_status: T::BidStatusType) { - let mut new_bid = bid.clone(); - new_bid.status = new_status.clone(); - - let mut write_guard = self.in_memory_store.bids.write().await; - let key = bid.chain_data.get_permission_key(); - match write_guard.entry(key.clone()) { - Entry::Occupied(mut entry) => { - let bids = entry.get_mut(); - match bids.iter().position(|b| *b == *bid) { - Some(index) => bids[index] = new_bid, - None => { - tracing::error!(bid = ?bid, "Update bid failed, bid not found"); - } + // Find the in memory auction which contains the bid and update the bid status + async fn update_in_memory_auction_bid( + &self, + bid: &entities::Bid, + new_status: T::BidStatusType, + ) { + if let Some(auction_id) = new_status.get_auction_id() { + let mut write_guard = self.in_memory_store.auctions.write().await; + if let Some(auction) = write_guard.iter_mut().find(|a| a.id == auction_id) { + let bid_index = auction.bids.iter().position(|b| b.id == bid.id); + if let Some(index) = bid_index { + auction.bids[index].status = new_status; } } - Entry::Vacant(_) => { - tracing::error!(key = ?key, bid = ?bid, "Update bid failed, entry not found"); - } } } @@ -55,10 +36,9 @@ impl Repository { let update_query = T::get_update_bid_query(&bid, new_status.clone())?; let query_result = update_query.execute(&self.db).await?; - if new_status.is_submitted() { - self.update_in_memory_bid(&bid, new_status).await; - } else if new_status.is_finalized() { - self.remove_in_memory_bid(&bid).await; + if query_result.rows_affected() > 0 && !new_status.is_pending() { + self.remove_in_memory_pending_bids(&[bid.clone()]).await; + self.update_in_memory_auction_bid(&bid, new_status).await; } Ok(query_result.rows_affected() > 0) diff --git a/auction-server/src/auction/repository/update_in_memory_auction.rs b/auction-server/src/auction/repository/update_in_memory_auction.rs new file mode 100644 index 00000000..c7125612 --- /dev/null +++ b/auction-server/src/auction/repository/update_in_memory_auction.rs @@ -0,0 +1,23 @@ +use { + super::Repository, + crate::auction::{ + entities, + service::ChainTrait, + }, +}; + +impl Repository { + #[tracing::instrument(skip_all, fields(auction_id))] + pub async fn update_in_memory_auction(&self, auction: entities::Auction) { + tracing::Span::current().record("auction_id", auction.id.to_string()); + let mut write_gaurd = self.in_memory_store.auctions.write().await; + match write_gaurd.iter_mut().find(|a| a.id == auction.id) { + Some(a) => { + *a = auction; + } + None => { + tracing::error!(auction = ?auction, "Auction not found in in-memory store"); + } + }; + } +} diff --git a/auction-server/src/auction/service/auction_manager.rs b/auction-server/src/auction/service/auction_manager.rs index 65281688..25505268 100644 --- a/auction-server/src/auction/service/auction_manager.rs +++ b/auction-server/src/auction/service/auction_manager.rs @@ -7,6 +7,7 @@ use { auction::entities::{ self, BidPaymentInstructionType, + BidStatus, BidStatusAuction, }, kernel::{ @@ -91,6 +92,7 @@ pub trait AuctionManager { type WsClient; /// The conclusion result type when try to conclude the auction for the chain. type ConclusionResult; + /// The minimum lifetime for an auction. If any bid for auction is older than this, the auction is ready to be submitted. const AUCTION_MINIMUM_LIFETIME: Duration; @@ -112,7 +114,7 @@ pub trait AuctionManager { permission_key: entities::PermissionKey, bids: Vec>, ) -> Result>; - /// Get the bid results for the bids submitted for the auction after the transaction is concluded. + /// Get the on chain bid results for the bids. /// Order of the returned BidStatus is as same as the order of the bids. /// Returns None for each bid if the bid is not yet confirmed on chain. async fn get_bid_results( @@ -127,12 +129,16 @@ pub trait AuctionManager { permission_key: &entities::PermissionKey, ) -> entities::SubmitType; - /// Get the new status for the bid after the bids of the auction are submitted. + /// Get the new status for the bid based on the auction result. fn get_new_status( bid: &entities::Bid, - submitted_bids: &[entities::Bid], + winner_bids: &[entities::Bid], bid_status_auction: entities::BidStatusAuction, + is_submitted: bool, ) -> T::BidStatusType; + + /// Check if the auction is expired based on the creation time of the auction. + fn is_auction_expired(auction: &entities::Auction) -> bool; } // While we are submitting bids together, increasing this number will have the following effects: @@ -141,6 +147,7 @@ pub trait AuctionManager { // 3. Gas consumption limit will decrease for the bid pub const TOTAL_BIDS_PER_AUCTION_EVM: usize = 3; const EXTRA_GAS_FOR_SUBMISSION: u32 = 500 * 1000; +const BID_MAXIMUM_LIFE_TIME_EVM: Duration = Duration::from_secs(600); #[async_trait] impl AuctionManager for Service { @@ -305,6 +312,7 @@ impl AuctionManager for Service { bid: &entities::Bid, submitted_bids: &[entities::Bid], bid_status_auction: entities::BidStatusAuction, + _is_submitted: bool, ) -> entities::BidStatusEvm { let index = submitted_bids.iter().position(|b| b.id == bid.id); match index { @@ -318,12 +326,16 @@ impl AuctionManager for Service { }, } } + + fn is_auction_expired(auction: &entities::Auction) -> bool { + auction.creation_time + BID_MAXIMUM_LIFE_TIME_EVM < OffsetDateTime::now_utc() + } } /// This is to make sure we are not missing any transaction. /// We run this once every minute (150 * 0.4). const CONCLUSION_TRIGGER_INTERVAL_SVM: u64 = 150; -const BID_MAXIMUM_LIFE_TIME_SVM: Duration = Duration::from_secs(120); +const BID_MAXIMUM_LIFE_TIME_SVM: Duration = Duration::from_secs(10); const TRIGGER_DURATION_SVM: Duration = Duration::from_millis(400); pub struct TriggerStreamSvm { @@ -472,19 +484,23 @@ impl AuctionManager for Service { .expect("Signature array is empty on svm bid tx") }) .collect(); - let statuses: Vec<_> = self - .config - .chain_config - .client - // TODO: Chunk this if signatures.len() > 256, RPC can only handle 256 signatures at a time - .get_signature_statuses(&signatures) - .await? - .value - .into_iter() - .map(|status| { - status.filter(|status| status.satisfies_commitment(CommitmentConfig::confirmed())) - }) - .collect(); + let statuses = if bids.iter().any(|bid| bid.status.is_submitted()) { + self.config + .chain_config + .client + // TODO: Chunk this if signatures.len() > 256, RPC can only handle 256 signatures at a time + .get_signature_statuses(&signatures) + .await? + .value + .into_iter() + .map(|status| { + status + .filter(|status| status.satisfies_commitment(CommitmentConfig::confirmed())) + }) + .collect() + } else { + vec![None; bids.len()] + }; tracing::Span::current().record("bid_statuses", format!("{:?}", statuses)); // TODO: find a better place to put this @@ -575,20 +591,24 @@ impl AuctionManager for Service { fn get_new_status( bid: &entities::Bid, - submitted_bids: &[entities::Bid], + winner_bids: &[entities::Bid], bid_status_auction: entities::BidStatusAuction, + is_submitted: bool, ) -> entities::BidStatusSvm { - if submitted_bids.iter().any(|b| b.id == bid.id) { - entities::BidStatusSvm::Submitted { - auction: BidStatusAuction { - id: bid_status_auction.id, - tx_hash: *bid - .chain_data - .transaction - .signatures - .first() - .expect("Bid has no signature"), - }, + if winner_bids.iter().any(|b| b.id == bid.id) { + let auction = BidStatusAuction { + id: bid_status_auction.id, + tx_hash: *bid + .chain_data + .transaction + .signatures + .first() + .expect("Bid has no signature"), + }; + if is_submitted { + entities::BidStatusSvm::Submitted { auction } + } else { + entities::BidStatusSvm::AwaitingSignature { auction } } } else { entities::BidStatusSvm::Lost { @@ -596,6 +616,10 @@ impl AuctionManager for Service { } } } + + fn is_auction_expired(auction: &entities::Auction) -> bool { + auction.creation_time + BID_MAXIMUM_LIFE_TIME_SVM < OffsetDateTime::now_utc() + } } const SEND_TRANSACTION_RETRY_COUNT_SVM: i32 = 30; diff --git a/auction-server/src/auction/service/conclude_auction.rs b/auction-server/src/auction/service/conclude_auction.rs index 397f8b36..37af8118 100644 --- a/auction-server/src/auction/service/conclude_auction.rs +++ b/auction-server/src/auction/service/conclude_auction.rs @@ -7,6 +7,7 @@ use { }, crate::auction::entities::{ self, + BidStatus, }, futures::future::join_all, }; @@ -29,12 +30,11 @@ where &self, input: ConcludeAuctionWithStatusesInput, ) -> anyhow::Result<()> { - let mut auction = input.auction; tracing::Span::current().record( "bid_ids", - tracing::field::display(entities::BidContainerTracing(&auction.bids)), + tracing::field::display(entities::BidContainerTracing(&input.auction.bids)), ); - tracing::Span::current().record("auction_id", auction.id.to_string()); + tracing::Span::current().record("auction_id", input.auction.id.to_string()); tracing::Span::current().record("bid_statuses", format!("{:?}", input.bid_statuses)); join_all(input.bid_statuses.into_iter().map(|(status, bid)| { self.update_bid_status(UpdateBidStatusInput { @@ -44,17 +44,18 @@ where })) .await; - if self + // Refetch the auction from the in-memory store to check if all bids are finalized + if let Some(auction) = self .repo - .get_in_memory_submitted_bids_for_auction(&auction) + .get_in_memory_auction_by_id(input.auction.id) .await - .is_empty() { - self.repo - .conclude_auction(&mut auction) - .await - .map_err(|e| anyhow::anyhow!("Failed to conclude auction: {:?}", e))?; - self.repo.remove_in_memory_submitted_auction(auction).await; + if auction.bids.iter().all(|bid| bid.status.is_finalized()) { + self.repo + .conclude_auction(auction.id) + .await + .map_err(|e| anyhow::anyhow!("Failed to conclude auction: {:?}", e))?; + } } Ok(()) @@ -66,11 +67,12 @@ where let auction = input.auction; tracing::info!(chain_id = self.config.chain_id, auction_id = ?auction.id, permission_key = auction.permission_key.to_string(), "Concluding auction"); if let Some(tx_hash) = auction.tx_hash.clone() { - let bids = self - .repo - .get_in_memory_submitted_bids_for_auction(&auction) - .await; - + let bids: Vec> = auction + .bids + .iter() + .filter(|bid| !bid.status.is_finalized()) + .cloned() + .collect(); let bid_statuses = self .get_bid_results( bids.clone(), @@ -90,6 +92,20 @@ where .collect(), }) .await?; + } else if Self::is_auction_expired(&auction) { + // This only happens if auction submission to chain failes + // This is a very rare case and should not happen + tracing::warn!("Auction has no transaction hash and is expired"); + let lost_status = T::BidStatusType::new_lost(); + self.conclude_auction_with_statuses(ConcludeAuctionWithStatusesInput { + auction: auction.clone(), + bid_statuses: auction + .bids + .iter() + .map(|bid| (lost_status.clone(), bid.clone())) + .collect(), + }) + .await?; } Ok(()) } diff --git a/auction-server/src/auction/service/conclude_auctions.rs b/auction-server/src/auction/service/conclude_auctions.rs index f7207b9e..7796c21a 100644 --- a/auction-server/src/auction/service/conclude_auctions.rs +++ b/auction-server/src/auction/service/conclude_auctions.rs @@ -12,7 +12,7 @@ where Service: AuctionManager, { pub async fn conclude_auctions(&self) { - let auctions = self.repo.get_in_memory_submitted_auctions().await; + let auctions = self.repo.get_in_memory_auctions().await; for auction in auctions { self.task_tracker.spawn({ let service = self.clone(); diff --git a/auction-server/src/auction/service/get_auction_by_id.rs b/auction-server/src/auction/service/get_auction_by_id.rs new file mode 100644 index 00000000..e46a5124 --- /dev/null +++ b/auction-server/src/auction/service/get_auction_by_id.rs @@ -0,0 +1,22 @@ +use { + super::{ + ChainTrait, + Service, + }, + crate::auction::entities, +}; + +pub struct GetAuctionByIdInput { + pub auction_id: entities::AuctionId, +} + +impl Service { + pub async fn get_auction_by_id( + &self, + input: GetAuctionByIdInput, + ) -> Option> { + self.repo + .get_in_memory_auction_by_id(input.auction_id) + .await + } +} diff --git a/auction-server/src/auction/service/get_live_bids.rs b/auction-server/src/auction/service/get_pending_bids.rs similarity index 76% rename from auction-server/src/auction/service/get_live_bids.rs rename to auction-server/src/auction/service/get_pending_bids.rs index bbdfc4cf..4b250df5 100644 --- a/auction-server/src/auction/service/get_live_bids.rs +++ b/auction-server/src/auction/service/get_pending_bids.rs @@ -11,12 +11,12 @@ pub struct GetLiveBidsInput { } impl Service { - pub async fn get_live_bids( + pub async fn get_pending_bids( &self, input: GetLiveBidsInput, ) -> Vec> { self.repo - .get_in_memory_bids_by_permission_key(&input.permission_key) + .get_in_memory_pending_bids_by_permission_key(&input.permission_key) .await } } diff --git a/auction-server/src/auction/service/get_permission_keys_for_auction.rs b/auction-server/src/auction/service/get_permission_keys_for_auction.rs index 537938e7..df23a2a4 100644 --- a/auction-server/src/auction/service/get_permission_keys_for_auction.rs +++ b/auction-server/src/auction/service/get_permission_keys_for_auction.rs @@ -11,8 +11,8 @@ use { impl Service { pub async fn get_permission_keys_for_auction(&self) -> Vec> { - let live_bids = self.repo.get_in_memory_bids().await; - live_bids + let pending_bids = self.repo.get_in_memory_pending_bids().await; + pending_bids .iter() .filter(|(_, bids)| bids.iter().any(|bid| bid.status.is_pending())) .map(|(key, _)| key.clone()) diff --git a/auction-server/src/auction/service/handle_auction.rs b/auction-server/src/auction/service/handle_auction.rs index ca121eb7..631411a3 100644 --- a/auction-server/src/auction/service/handle_auction.rs +++ b/auction-server/src/auction/service/handle_auction.rs @@ -84,6 +84,7 @@ where tx_hash: tx_hash.clone(), id: auction.id, }, + true, ), bid: bid.clone(), }) @@ -108,7 +109,7 @@ where let bid_collection_time = OffsetDateTime::now_utc(); let bids = self .repo - .get_in_memory_bids_by_permission_key(permission_key) + .get_in_memory_pending_bids_by_permission_key(permission_key) .await; tracing::Span::current().record( @@ -151,7 +152,7 @@ where // Fetch all pending bids and mark them as lost let bids: Vec> = self .repo - .get_in_memory_bids_by_permission_key(&permission_key) + .get_in_memory_pending_bids_by_permission_key(&permission_key) .await .into_iter() .filter(|bid| bid.status.is_pending()) diff --git a/auction-server/src/auction/service/mod.rs b/auction-server/src/auction/service/mod.rs index 52e4a923..40f89e06 100644 --- a/auction-server/src/auction/service/mod.rs +++ b/auction-server/src/auction/service/mod.rs @@ -70,10 +70,11 @@ pub mod add_auction; pub mod auction_manager; pub mod conclude_auction; pub mod conclude_auctions; +pub mod get_auction_by_id; pub mod get_bid; pub mod get_bids; pub mod get_express_relay_program_id; -pub mod get_live_bids; +pub mod get_pending_bids; pub mod get_permission_keys_for_auction; pub mod handle_auction; pub mod handle_auctions; @@ -82,7 +83,6 @@ pub mod simulator; pub mod submit_quote; pub mod update_bid_status; pub mod update_recent_prioritization_fee; -pub mod update_submitted_auction; pub mod verification; pub mod workers; diff --git a/auction-server/src/auction/service/submit_quote.rs b/auction-server/src/auction/service/submit_quote.rs index d1d6abdb..0f2720aa 100644 --- a/auction-server/src/auction/service/submit_quote.rs +++ b/auction-server/src/auction/service/submit_quote.rs @@ -1,5 +1,7 @@ use { super::{ + get_auction_by_id::GetAuctionByIdInput, + update_bid_status::UpdateBidStatusInput, verification::SwapAccounts, Service, }, @@ -20,7 +22,7 @@ use { }; pub struct SubmitQuoteInput { - pub bid_id: entities::BidId, + pub auction_id: entities::AuctionId, pub user_signature: Signature, } @@ -31,70 +33,92 @@ impl Service { &self, input: SubmitQuoteInput, ) -> Result { - let bid: Option> = self.repo.get_in_memory_bid_by_id(input.bid_id).await; - match bid { - Some(mut bid) => { - let swap_instruction = self - .extract_express_relay_instruction( - bid.chain_data.transaction.clone(), - entities::BidPaymentInstructionType::Swap, - ) - .map_err(|_| RestError::BadParameters("Invalid quote".to_string()))?; - let SwapAccounts { user_wallet, .. } = self - .extract_swap_accounts(&bid.chain_data.transaction, &swap_instruction) - .await - .map_err(|_| RestError::BadParameters("Invalid quote".to_string()))?; - let swap_args = Self::extract_swap_data(&swap_instruction) - .map_err(|_| RestError::BadParameters("Invalid quote".to_string()))?; + let auction: entities::Auction = self + .get_auction_by_id(GetAuctionByIdInput { + auction_id: input.auction_id, + }) + .await + .ok_or(RestError::BadParameters("Invalid quote".to_string()))?; - if swap_args.deadline - < (OffsetDateTime::now_utc() - DEADLINE_BUFFER).unix_timestamp() - { - return Err(RestError::BadParameters("Quote is expired".to_string())); - } + let winner_bid = auction + .bids + .iter() + .find(|bid| bid.status.is_awaiting_signature() || bid.status.is_submitted()) + .cloned() + .ok_or(RestError::BadParameters("Invalid quote".to_string()))?; - if !input.user_signature.verify( - &user_wallet.to_bytes(), - &bid.chain_data.transaction.message.serialize(), - ) { - return Err(RestError::BadParameters("Invalid signature".to_string())); - } + if winner_bid.status.is_submitted() { + return Err(RestError::BadParameters( + "Quote is already submitted".to_string(), + )); + } + + let mut bid = winner_bid.clone(); + let swap_instruction = self + .extract_express_relay_instruction( + bid.chain_data.transaction.clone(), + entities::BidPaymentInstructionType::Swap, + ) + .map_err(|_| RestError::BadParameters("Invalid quote".to_string()))?; + let SwapAccounts { user_wallet, .. } = self + .extract_swap_accounts(&bid.chain_data.transaction, &swap_instruction) + .await + .map_err(|_| RestError::BadParameters("Invalid quote".to_string()))?; + let swap_args = Self::extract_swap_data(&swap_instruction) + .map_err(|_| RestError::BadParameters("Invalid quote".to_string()))?; + + if swap_args.deadline < (OffsetDateTime::now_utc() - DEADLINE_BUFFER).unix_timestamp() { + return Err(RestError::BadParameters("Quote is expired".to_string())); + } - let user_signature_pos = bid - .chain_data - .transaction - .message - .static_account_keys() - .iter() - .position(|p| p.eq(&user_wallet)) - .expect("User wallet not found in transaction"); - bid.chain_data.transaction.signatures[user_signature_pos] = input.user_signature; + if !input.user_signature.verify( + &user_wallet.to_bytes(), + &bid.chain_data.transaction.message.serialize(), + ) { + return Err(RestError::BadParameters("Invalid signature".to_string())); + } + + let user_signature_pos = bid + .chain_data + .transaction + .message + .static_account_keys() + .iter() + .position(|p| p.eq(&user_wallet)) + .expect("User wallet not found in transaction"); + bid.chain_data.transaction.signatures[user_signature_pos] = input.user_signature; - // TODO add relayer signature after program update - // self.add_relayer_signature(&mut bid); + // TODO add relayer signature after program update + // self.add_relayer_signature(&mut bid); - // TODO change it to a better state (Wait for user signature) - if bid.status.is_submitted() { - if bid.chain_data.bid_payment_instruction_type - == entities::BidPaymentInstructionType::Swap - { - self.send_transaction(&bid).await.map_err(|e| { - tracing::error!(error = ?e, "Error sending quote transaction to network"); - RestError::TemporarilyUnavailable - })?; - Ok(bid.chain_data.transaction) - } else { - Err(RestError::BadParameters("Invalid quote".to_string())) - } - } else { - Err(RestError::BadParameters( - "Quote is not valid anymore".to_string(), - )) - } - } - None => Err(RestError::BadParameters( - "Quote is not valid anymore".to_string(), - )), + if bid.chain_data.bid_payment_instruction_type != entities::BidPaymentInstructionType::Swap + { + return Err(RestError::BadParameters("Invalid quote".to_string())); } + + let tx_hash = self.send_transaction(&bid).await.map_err(|e| { + tracing::error!(error = ?e, "Error sending quote transaction to network"); + RestError::TemporarilyUnavailable + })?; + let auction = self + .repo + .submit_auction(auction, tx_hash) + .await + .map_err(|e| { + tracing::error!(error = ?e, "Error repo submitting auction"); + RestError::TemporarilyUnavailable + })?; + self.update_bid_status(UpdateBidStatusInput { + bid: winner_bid.clone(), + new_status: entities::BidStatusSvm::Submitted { + auction: entities::BidStatusAuction { + id: auction.id, + tx_hash, + }, + }, + }) + .await?; + + Ok(bid.chain_data.transaction) } } diff --git a/auction-server/src/auction/service/update_submitted_auction.rs b/auction-server/src/auction/service/update_submitted_auction.rs deleted file mode 100644 index 49a96d5e..00000000 --- a/auction-server/src/auction/service/update_submitted_auction.rs +++ /dev/null @@ -1,30 +0,0 @@ -use { - super::{ - ChainTrait, - Service, - }, - crate::{ - api::RestError, - auction::entities, - }, -}; - -pub struct UpdateSubmittedAuctionInput { - pub auction: entities::Auction, - pub transaction_hash: entities::TxHash, -} - -impl Service { - pub async fn update_submitted_auction( - &self, - input: UpdateSubmittedAuctionInput, - ) -> Result, RestError> { - self.repo - .submit_auction(input.auction, input.transaction_hash) - .await - .map_err(|e| { - tracing::error!(error = ?e, "Failed to update submitted auction"); - RestError::TemporarilyUnavailable - }) - } -} diff --git a/auction-server/src/auction/service/verification.rs b/auction-server/src/auction/service/verification.rs index f06211c5..52c3609b 100644 --- a/auction-server/src/auction/service/verification.rs +++ b/auction-server/src/auction/service/verification.rs @@ -15,7 +15,7 @@ use { BidPaymentInstructionType, SubmitType, }, - service::get_live_bids::GetLiveBidsInput, + service::get_pending_bids::GetLiveBidsInput, }, kernel::{ contracts::{ @@ -1100,10 +1100,10 @@ impl Verification for Service { self.simulate_bid(&bid).await?; // Check if the bid is not duplicate - let live_bids = self - .get_live_bids(GetLiveBidsInput { permission_key }) + let pending_bids = self + .get_pending_bids(GetLiveBidsInput { permission_key }) .await; - if live_bids.iter().any(|b| bid == *b) { + if pending_bids.iter().any(|b| bid == *b) { return Err(RestError::BadParameters("Duplicate bid".to_string())); } diff --git a/auction-server/src/auction/service/workers.rs b/auction-server/src/auction/service/workers.rs index f95524e5..fd6cc0c3 100644 --- a/auction-server/src/auction/service/workers.rs +++ b/auction-server/src/auction/service/workers.rs @@ -143,11 +143,8 @@ impl Service { log: RpcLogsResponse, ) -> Result<()> { let signature = Signature::from_str(&log.signature)?; - let submitted_bids = self - .repo - .get_in_memory_submitted_bids_for_auction(&auction) - .await; - if let Some(bid) = submitted_bids + if let Some(bid) = auction + .bids .iter() .find(|bid| bid.chain_data.transaction.signatures[0] == signature) { @@ -207,8 +204,8 @@ impl Service { self.task_tracker.spawn({ let service = self.clone(); async move { - let submitted_auctions = service.repo.get_in_memory_submitted_auctions().await; - let auctions = submitted_auctions.iter().filter(|auction| { + let in_memory_auctions = service.repo.get_in_memory_auctions().await; + let auctions = in_memory_auctions.iter().filter(|auction| { auction.bids.iter().any(|bid| { bid.chain_data.transaction.signatures[0] == signature }) diff --git a/auction-server/src/opportunity/service/get_quote.rs b/auction-server/src/opportunity/service/get_quote.rs index d3f84919..6ef394f5 100644 --- a/auction-server/src/opportunity/service/get_quote.rs +++ b/auction-server/src/opportunity/service/get_quote.rs @@ -16,9 +16,9 @@ use { service::{ add_auction::AddAuctionInput, auction_manager::AuctionManager, - get_live_bids::GetLiveBidsInput, + get_auction_by_id::GetAuctionByIdInput, + get_pending_bids::GetLiveBidsInput, update_bid_status::UpdateBidStatusInput, - update_submitted_auction::UpdateSubmittedAuctionInput, Service as AuctionService, }, }, @@ -354,7 +354,7 @@ impl Service { let bid_collection_time = OffsetDateTime::now_utc(); let mut bids = auction_service - .get_live_bids(GetLiveBidsInput { + .get_pending_bids(GetLiveBidsInput { permission_key: permission_key_svm.clone(), }) .await; @@ -409,23 +409,24 @@ impl Service { })?; let deadline = swap_data.deadline; - // Bids is not empty - let auction = Auction::try_new(bids.clone(), bid_collection_time) + // Bids are not empty + let mut auction = Auction::try_new(bids.clone(), bid_collection_time) .expect("Failed to create auction for bids"); - - let mut auction = auction_service + // Add tx_hash to auction to make sure conclude_auction works correctly + // TODO These are auctions that are not submitted but has tx_hash of the winner bid inside + // TODO Maybe we should think a bit more about how to handle these auctions and the overall auction model + auction.tx_hash = Some(winner_bid.chain_data.transaction.signatures[0]); + // NOTE: These auctions need user signature to be submitted later. + // Later if we have a quote without last look, we can assume these auctions are submitted. + let auction = auction_service .add_auction(AddAuctionInput { auction }) .await?; - let bid = winner_bid.clone(); - let signature = bid.chain_data.transaction.signatures[0]; - auction = auction_service - .update_submitted_auction(UpdateSubmittedAuctionInput { - auction, - transaction_hash: signature, - }) - .await?; + // Remove opportunity to prevent further bids + // The handle auction loop will take care of the bids that were submitted late + 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 { new_status: AuctionService::get_new_status( @@ -435,33 +436,39 @@ impl Service { tx_hash: signature, id: auction.id, }, + false, ), bid: bid.clone(), }) })) .await; - // Remove opportunity to prevent further bids - // The handle auction loop will take care of the bids that were submitted late - self.remove_quote_opportunity(opportunity.clone()).await; - - // we check the winner bid status here to make sure the winner bid was successfully entered into the db as submitted - // this is because: if the winner bid was not successfully entered as submitted, that could indicate the presence of - // duplicate auctions for the same quote. in such a scenario, one auction will conclude first and update the bid status - // of its winner bid, and we want to ensure that a winner bid whose status is already updated is not further updated - // to prevent a new status update from being broadcast - let live_bids = auction_service - .get_live_bids(GetLiveBidsInput { - permission_key: permission_key_svm, + // 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; - if !live_bids - .iter() - .any(|bid| bid.id == winner_bid.id && bid.status.is_submitted()) + .await { - tracing::error!(winner_bid = ?winner_bid, opportunity = ?opportunity, "Failed to update winner bid status"); - return Err(RestError::TemporarilyUnavailable); - } + 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); + } + }; + let metadata = self .get_express_relay_metadata(GetExpressRelayMetadata { chain_id: input.quote_create.chain_id.clone(), @@ -497,7 +504,7 @@ impl Service { }; Ok(entities::Quote { - transaction: bid.chain_data.transaction.clone(), + transaction: winner_bid.chain_data.transaction.clone(), expiration_time: deadline, searcher_token: TokenAmountSvm { @@ -517,7 +524,7 @@ impl Service { amount: fees.express_relay_fee + fees.relayer_fee, }, chain_id: input.quote_create.chain_id, - reference_id: winner_bid.id, + reference_id: auction.id, }) } }