Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
88 changes: 88 additions & 0 deletions crates/driver/src/tests/cases/deadline.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
use {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

high

No critical issues found

References
  1. Confirm Clean Reviews: If no critical issues are found, post a single summary comment stating 'No critical issues found' rather than staying silent. (link)

crate::tests::{
self,
setup::{ab_order, ab_pool, ab_solution, test_solver},
},
alloy::{primitives::b256, signers::local::PrivateKeySigner},
std::time::Duration,
};

const SOLUTION_COUNT: usize = 30;

fn submission_account() -> PrivateKeySigner {
// Well-known Anvil test key #1. Do not use as a production key.
PrivateKeySigner::from_bytes(&b256!(
"59c6995e998f97a5a0044966f0945389dc9e86dae88c7a8412f4603b6b78690d"
))
.unwrap()
}

#[tokio::test]
#[ignore]
async fn expired_solver_deadline_does_not_send_solve_request() {
let test = tests::setup()
.name("expired solver deadline")
.solvers(vec![test_solver().solving_time_share(0.0)])
.pool(ab_pool())
.order(ab_order())
.solution(ab_solution())
.done()
.await;

test.solve().await.ok().empty();

assert_eq!(test.solve_requests(), 0);
}

#[tokio::test]
#[ignore]
async fn postprocessing_timeout_drops_late_solution() {
let mut setup = tests::setup()
.name("postprocessing timeout")
.solvers(vec![
test_solver()
.solving_time_share(1.0)
.max_solutions_to_propose(SOLUTION_COUNT)
.submission_account(submission_account())
.post_processing_concurrency_limit(1),
])
.deadline_after(Duration::from_millis(600))
.pool(ab_pool())
.order(ab_order());

for _ in 0..SOLUTION_COUNT {
setup = setup.solution(ab_solution());
}

let test = setup.done().await;

let solve = test.solve().await.ok();
let solutions = solve.solutions();

assert!(
solutions.len() < SOLUTION_COUNT,
"expected post-processing timeout to cut off late solutions"
);

assert_eq!(test.solve_requests(), 1);
}

#[tokio::test]
#[ignore]
async fn settlement_rejects_expired_submission_deadline() {
let test = tests::setup()
.name("expired settlement submission deadline")
.pool(ab_pool())
.order(ab_order())
.solution(ab_solution())
.settle_submission_deadline(0)
.done()
.await;

let solution_id = test.solve().await.ok().id();

test.settle(solution_id)
.await
.err()
.kind("DeadlineExceeded");
}
1 change: 1 addition & 0 deletions crates/driver/src/tests/cases/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ use {
};

pub mod buy_eth;
pub mod deadline;
pub mod example_config;
pub mod fees;
mod flashloan_hints;
Expand Down
7 changes: 7 additions & 0 deletions crates/driver/src/tests/setup/driver.rs
Original file line number Diff line number Diff line change
Expand Up @@ -349,6 +349,13 @@ async fn create_config_file(
.join(", ");
writeln!(file, " submission-accounts = [{accounts}]").unwrap();
}
if let Some(limit) = solver.post_processing_concurrency_limit {
writeln!(
file,
" post-processing-concurrency-limit = {limit}"
)
.unwrap();
}
}
file.into_temp_path()
}
45 changes: 42 additions & 3 deletions crates/driver/src/tests/setup/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,11 @@ use {
collections::{HashMap, HashSet},
path::PathBuf,
str::FromStr,
sync::{
Arc,
atomic::{AtomicUsize, Ordering},
},
time::Duration,
},
};

Expand Down Expand Up @@ -365,6 +370,8 @@ pub struct Solver {
max_solutions_to_propose: usize,
/// Additional submission accounts for EIP-7702 parallel settlement.
submission_accounts: Vec<PrivateKeySigner>,
/// Maximum number of solutions post-processed concurrently by the driver.
post_processing_concurrency_limit: Option<usize>,
}

#[derive(Debug, Clone)]
Expand Down Expand Up @@ -394,6 +401,7 @@ pub fn test_solver() -> Solver {
haircut_bps: 0,
max_solutions_to_propose: 1,
submission_accounts: vec![],
post_processing_concurrency_limit: None,
}
}

Expand Down Expand Up @@ -445,6 +453,11 @@ impl Solver {
self
}

pub fn post_processing_concurrency_limit(mut self, n: usize) -> Self {
self.post_processing_concurrency_limit = Some(n);
self
}

pub fn submission_account(mut self, signer: PrivateKeySigner) -> Self {
self.submission_accounts.push(signer);
self
Expand Down Expand Up @@ -560,6 +573,8 @@ pub struct Setup {
settle_submission_deadline: u64,
/// Should flashloan-hint orders be accepted by the driver? True by default.
flashloans_enabled: bool,
/// Deadline offset from fixed test time. Defaults to 2 seconds.
deadline_after: Option<Duration>,
}

/// The validity of a solution.
Expand Down Expand Up @@ -909,6 +924,11 @@ impl Setup {
self
}

pub fn deadline_after(mut self, deadline_after: Duration) -> Self {
self.deadline_after = Some(deadline_after);
self
}

/// Toggle acceptance of flashloan-hint orders at the driver level.
pub fn flashloans_enabled(mut self, flashloans_enabled: bool) -> Self {
self.flashloans_enabled = flashloans_enabled;
Expand Down Expand Up @@ -995,7 +1015,7 @@ impl Setup {
.into_iter()
.map(|order| blockchain.quote(&order))
.collect::<Vec<_>>();
let solvers_with_address = join_all(self.solvers.iter().map(|solver| async {
let solver_instances = join_all(self.solvers.iter().map(|solver| async {
let instance = SolverInstance::new(solver::Config {
blockchain: &blockchain,
solutions: &solutions,
Expand All @@ -1012,9 +1032,17 @@ impl Setup {
})
.await;

(solver.clone(), instance.addr)
(solver.clone(), instance)
}))
.await;
let solve_requests = solver_instances
.iter()
.find(|(solver, _)| solver.name == solver::NAME)
.map(|(_, instance)| Arc::clone(&instance.requests));
let solvers_with_address = solver_instances
.iter()
.map(|(solver, instance)| (solver.clone(), instance.addr))
.collect();

let driver = Driver::new(
&driver::Config {
Expand Down Expand Up @@ -1043,6 +1071,7 @@ impl Setup {
quote: self.quote,
surplus_capturing_jit_order_owners,
auction_id: self.auction_id,
solve_requests,
}
}

Expand All @@ -1061,7 +1090,9 @@ impl Setup {
}

fn deadline(&self) -> chrono::DateTime<chrono::Utc> {
crate::infra::time::now() + chrono::Duration::seconds(2)
crate::infra::time::now()
+ chrono::Duration::from_std(self.deadline_after.unwrap_or(Duration::from_secs(2)))
.unwrap()
}

pub fn allow_multiple_solve_requests(mut self) -> Self {
Expand All @@ -1085,6 +1116,7 @@ pub struct Test {
/// List of surplus capturing JIT-order owners
surplus_capturing_jit_order_owners: Vec<eth::Address>,
auction_id: i64,
solve_requests: Option<Arc<AtomicUsize>>,
}

impl Test {
Expand Down Expand Up @@ -1248,6 +1280,13 @@ impl Test {
pub async fn set_auto_mining(&self, enabled: bool) {
self.blockchain.set_auto_mining(enabled).await
}

pub fn solve_requests(&self) -> usize {
self.solve_requests
.as_ref()
.expect("default solver not found")
.load(Ordering::SeqCst)
}
}

/// A /solve response.
Expand Down
40 changes: 19 additions & 21 deletions crates/driver/src/tests/setup/solver.rs
Original file line number Diff line number Diff line change
Expand Up @@ -30,14 +30,18 @@ use {
std::{
collections::{HashMap, HashSet},
net::SocketAddr,
sync::{Arc, Mutex},
sync::{
Arc,
atomic::{AtomicUsize, Ordering},
},
},
};

pub const NAME: &str = "test-solver";

pub struct Solver {
pub addr: SocketAddr,
pub requests: Arc<AtomicUsize>,
}

#[derive(Debug)]
Expand Down Expand Up @@ -501,16 +505,22 @@ impl Solver {
)
.await;

let state = Arc::new(Mutex::new(StateInner {
called: false,
let requests = Arc::new(AtomicUsize::new(0));
let state = State {
requests: Arc::clone(&requests),
allow_multiple_solve_requests: config.allow_multiple_solve_requests,
}));
};
let app = axum::Router::new()
.route(
"/solve",
axum::routing::post(
move |axum::extract::State(state): axum::extract::State<State>,
axum::extract::Json(req): axum::extract::Json<serde_json::Value>| async move {
axum::extract::Json(req): axum::extract::Json<serde_json::Value>| async move {
let previous_requests = state.requests.fetch_add(1, Ordering::SeqCst);
assert!(
previous_requests == 0 || state.allow_multiple_solve_requests,
"can't call /solve multiple times"
);
let base_fee = eth.current_block().borrow().base_fee;
let effective_gas_price = eth.gas_price().await.unwrap().effective(base_fee).to_string();
let expected = json!({
Expand All @@ -523,23 +533,17 @@ impl Solver {
"surplusCapturingJitOrderOwners": config.expected_surplus_capturing_jit_order_owners,
});
check_solve_request(req, expected);
let mut state = state.0.lock().unwrap();
assert!(
!state.called || state.allow_multiple_solve_requests,
"can't call /solve multiple times"
);
state.called = true;
axum::response::Json(json!({
"solutions": solutions_json,
}))
},
),
)
.with_state(State(state));
.with_state(state);
let listener = tokio::net::TcpListener::bind("0.0.0.0:0").await.unwrap();
let addr = listener.local_addr().unwrap();
tokio::spawn(async move { axum::serve(listener, app).await.unwrap() });
Self { addr }
Self { addr, requests }
}
}

Expand Down Expand Up @@ -582,13 +586,7 @@ fn check_solve_request(request: Value, expected: Value) {
}

#[derive(Debug, Clone)]
struct StateInner {
/// Has this solver been called yet? If so, attempting to make another call
/// will result in a failed test.
called: bool,
/// In case you want to allow calling a solver multiple times.
struct State {
requests: Arc<AtomicUsize>,
allow_multiple_solve_requests: bool,
}

#[derive(Debug, Clone)]
struct State(Arc<Mutex<StateInner>>);
Loading