-
Notifications
You must be signed in to change notification settings - Fork 90
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
[ON HOLD] Reject solution containing outside-of-auction order #2712
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change | ||||
---|---|---|---|---|---|---|
@@ -1,7 +1,7 @@ | ||||||
use { | ||||||
crate::{ | ||||||
database::competition::Competition, | ||||||
domain::{self, auction::order::Class, OrderUid}, | ||||||
domain::{self, auction::order::Class, settlement::Settlement, OrderUid}, | ||||||
infra::{ | ||||||
self, | ||||||
solvers::dto::{reveal, settle, solve}, | ||||||
|
@@ -141,6 +141,23 @@ impl RunLoop { | |||||
} | ||||||
}; | ||||||
|
||||||
// validate solution | ||||||
let settlement = match Settlement::new( | ||||||
&revealed.calldata.internalized.clone().into(), | ||||||
self.eth.contracts().settlement_domain_separator(), | ||||||
) { | ||||||
Ok(settlement) => settlement, | ||||||
Err(err) => { | ||||||
tracing::error!(driver = %driver.name, ?err, "unexpected calldata format"); | ||||||
return; | ||||||
} | ||||||
}; | ||||||
|
||||||
if !self.validate_settlement(&auction, &settlement).await { | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think before we enforce any checks like this we should ensure that we await the correct settlement transaction. It's possible that a malicious losing solver would settle a solution with the expected auction_id out of competition. |
||||||
tracing::error!(driver = %driver.name, "invalid settlement"); | ||||||
return; | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Shouldn't we still store all the relevant data for this competition when the solution fails some checks? |
||||||
} | ||||||
|
||||||
let order_uids = solution.order_ids().copied().collect(); | ||||||
self.persistence | ||||||
.store_order_events(order_uids, OrderEventLabel::Considered); | ||||||
|
@@ -348,6 +365,32 @@ impl RunLoop { | |||||
.collect() | ||||||
} | ||||||
|
||||||
/// Settlement is allowed to contain orders that are part of the Auction or | ||||||
/// JIT orders. | ||||||
async fn validate_settlement( | ||||||
&self, | ||||||
auction: &domain::Auction, | ||||||
settlement: &Settlement, | ||||||
) -> bool { | ||||||
let auction_orders = auction.orders.iter().map(|o| o.uid).collect::<HashSet<_>>(); | ||||||
|
||||||
// all JIT orders will be non_auction_orders | ||||||
let non_auction_orders = settlement | ||||||
.order_uids() | ||||||
.filter(|order| !auction_orders.contains(order)) | ||||||
.cloned() | ||||||
.collect::<Vec<_>>(); | ||||||
|
||||||
// make sure all non-auction orders are also non-database | ||||||
self.persistence | ||||||
.orders_do_not_exist(&non_auction_orders) | ||||||
.await | ||||||
.unwrap_or_else(|err| { | ||||||
tracing::warn!(?err, "failed to check if all orders exist"); | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. These checks are pretty important so they should fail loudly IMO (e.g.
Suggested change
|
||||||
false | ||||||
}) | ||||||
} | ||||||
|
||||||
/// Computes a driver's solutions for the solver competition. | ||||||
async fn solve( | ||||||
&self, | ||||||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -755,6 +755,27 @@ pub async fn user_orders_with_quote( | |
.await | ||
} | ||
|
||
pub async fn orders_do_not_exist( | ||
ex: &mut PgConnection, | ||
uids: &[OrderUid], | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Could take an |
||
) -> Result<bool, sqlx::Error> { | ||
if uids.is_empty() { | ||
return Ok(true); | ||
} | ||
|
||
let mut query_builder = | ||
QueryBuilder::new("SELECT NOT EXISTS (SELECT 1 FROM orders WHERE uid IN ("); | ||
|
||
let mut separated = query_builder.separated(", "); | ||
for value_type in uids { | ||
separated.push_bind(value_type); | ||
} | ||
separated.push_unseparated(")) "); | ||
Comment on lines
+769
to
+773
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Looks other than the usual query builder code. Is the explicit use of |
||
|
||
let query = query_builder.build_query_scalar(); | ||
query.fetch_one(ex).await | ||
} | ||
|
||
#[cfg(test)] | ||
mod tests { | ||
use { | ||
|
@@ -1891,4 +1912,33 @@ mod tests { | |
.unwrap(); | ||
assert_eq!(full_order.full_app_data, Some(full_app_data)); | ||
} | ||
|
||
#[tokio::test] | ||
#[ignore] | ||
async fn postgres_all_orders_not_exist() { | ||
let mut db = PgConnection::connect("postgresql://").await.unwrap(); | ||
let mut db = db.begin().await.unwrap(); | ||
crate::clear_DANGER_(&mut db).await.unwrap(); | ||
|
||
let order1 = Order { | ||
uid: ByteArray([1; 56]), | ||
..Default::default() | ||
}; | ||
let order2 = Order { | ||
uid: ByteArray([2; 56]), | ||
..Default::default() | ||
}; | ||
|
||
assert!(orders_do_not_exist(&mut db, &[order1.uid, order2.uid]) | ||
.await | ||
.unwrap()); | ||
|
||
insert_order(&mut db, &order2).await.unwrap(); | ||
|
||
assert!(orders_do_not_exist(&mut db, &[order1.uid]).await.unwrap()); | ||
assert!(!orders_do_not_exist(&mut db, &[order2.uid]).await.unwrap()); | ||
assert!(!orders_do_not_exist(&mut db, &[order1.uid, order2.uid]) | ||
.await | ||
.unwrap()); | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There is only a single DB call so we don't need a transaction here.