Skip to content
New issue

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

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

Already on GitHub? Sign in to your account

Multiple winners in autopilot #2996

Merged
merged 16 commits into from
Oct 4, 2024
Merged
7 changes: 7 additions & 0 deletions crates/autopilot/src/arguments.rs
Original file line number Diff line number Diff line change
Expand Up @@ -232,6 +232,11 @@ pub struct Arguments {
/// If the value is 0, the native prices are fetched from the cache
#[clap(long, env, default_value = "0s", value_parser = humantime::parse_duration)]
pub run_loop_native_price_timeout: Duration,

#[clap(long, env, default_value = "1")]
/// The maximum number of winners per auction. Each winner will be allowed
/// to settle their winning orders at the same time.
pub max_winners_per_auction: usize,
}

impl std::fmt::Display for Arguments {
Expand Down Expand Up @@ -277,6 +282,7 @@ impl std::fmt::Display for Arguments {
run_loop_mode,
max_run_loop_delay,
run_loop_native_price_timeout,
max_winners_per_auction,
} = self;

write!(f, "{}", shared)?;
Expand Down Expand Up @@ -356,6 +362,7 @@ impl std::fmt::Display for Arguments {
"run_loop_native_price_timeout: {:?}",
run_loop_native_price_timeout
)?;
writeln!(f, "max_winners_per_auction: {:?}", max_winners_per_auction)?;
Ok(())
}
}
Expand Down
4 changes: 2 additions & 2 deletions crates/autopilot/src/domain/competition/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ use {

type SolutionId = u64;

#[derive(Debug)]
#[derive(Debug, Clone)]
pub struct SolutionWithId {
id: SolutionId,
solution: Solution,
Expand Down Expand Up @@ -55,7 +55,7 @@ impl SolutionWithId {
}
}

#[derive(Debug)]
#[derive(Debug, Clone)]
pub struct Solution {
solver: eth::Address,
score: Score,
Expand Down
2 changes: 2 additions & 0 deletions crates/autopilot/src/run.rs
Original file line number Diff line number Diff line change
Expand Up @@ -531,6 +531,7 @@ pub async fn run(args: Arguments) {
solve_deadline: args.solve_deadline,
synchronization: args.run_loop_mode,
max_run_loop_delay: args.max_run_loop_delay,
max_winners_per_auction: args.max_winners_per_auction,
};

let run = RunLoop::new(
Expand Down Expand Up @@ -625,6 +626,7 @@ async fn shadow_mode(args: Arguments) -> ! {
liveness.clone(),
args.run_loop_mode,
current_block,
args.max_winners_per_auction,
);
shadow.run_forever().await;

Expand Down
83 changes: 60 additions & 23 deletions crates/autopilot/src/run_loop.rs
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@ pub struct Config {
/// allowed to start before it has to re-synchronize to the blockchain
/// by waiting for the next block to appear.
pub max_run_loop_delay: Duration,
pub max_winners_per_auction: usize,
}

pub struct RunLoop {
Expand Down Expand Up @@ -233,36 +234,42 @@ impl RunLoop {
return;
}

let competition_simulation_block = self.eth.current_block().borrow().number;
let winners = self.select_winners(&solutions);
m-lord-renkse marked this conversation as resolved.
Show resolved Hide resolved
if winners.is_empty() {
tracing::info!("no winners for auction");
return;
}

// TODO: Keep going with other solutions until some deadline.
if let Some(Participant { driver, solution }) = solutions.last() {
tracing::info!(driver = %driver.name, solution = %solution.id(), "winner");
let competition_simulation_block = self.eth.current_block().borrow().number;
let block_deadline = competition_simulation_block + self.config.submission_deadline;

let block_deadline = competition_simulation_block + self.config.submission_deadline;
// Post-processing should not be executed asynchronously since it includes steps
// of storing all the competition/auction-related data to the DB.
if let Err(err) = self
.post_processing(
auction_id,
auction,
competition_simulation_block,
// TODO: Support multiple winners
// https://github.com/cowprotocol/services/issues/3021
&winners.first().expect("must exist").solution,
&solutions,
block_deadline,
)
.await
{
tracing::error!(?err, "failed to post-process competition");
return;
}

// Post-processing should not be executed asynchronously since it includes steps
// of storing all the competition/auction-related data to the DB.
if let Err(err) = self
.post_processing(
auction_id,
auction,
competition_simulation_block,
solution,
&solutions,
block_deadline,
)
.await
{
tracing::error!(?err, "failed to post-process competition");
return;
}
for Participant { driver, solution } in winners {
tracing::info!(driver = %driver.name, solution = %solution.id(), "winner");
m-lord-renkse marked this conversation as resolved.
Show resolved Hide resolved

self.start_settlement_execution(
auction_id,
single_run_start,
driver,
solution,
&driver,
&solution,
block_deadline,
)
.await;
Expand Down Expand Up @@ -525,6 +532,35 @@ impl RunLoop {
solutions
}

/// Chooses the winners from the given participants.
///
/// Participants are already sorted by their score (worst to best).
///
/// Winners are selected one by one, starting from the best solution,
/// until `max_winners_per_auction` is hit. The solution can become winner
/// it is swaps tokens that are not yet swapped by any other already
/// selected winner.
fn select_winners(&self, participants: &[Participant]) -> Vec<Participant> {
let mut winners = Vec::new();
let mut already_swapped_tokens = HashSet::new();
for participant in participants.iter().rev() {
let swapped_tokens = participant
sunce86 marked this conversation as resolved.
Show resolved Hide resolved
.solution
.orders()
.iter()
.map(|(_, order)| (order.sell.token, order.buy.token))
.collect::<HashSet<_>>();
sunce86 marked this conversation as resolved.
Show resolved Hide resolved
if swapped_tokens.is_disjoint(&already_swapped_tokens) {
sunce86 marked this conversation as resolved.
Show resolved Hide resolved
winners.push(participant.clone());
already_swapped_tokens.extend(swapped_tokens);
if winners.len() >= self.config.max_winners_per_auction {
break;
}
}
}
winners
}

/// Records metrics, order events and logs for the given solutions.
/// Expects the winning solution to be the last in the list.
fn report_on_solutions(&self, solutions: &[Participant], auction: &domain::Auction) {
Expand Down Expand Up @@ -852,6 +888,7 @@ impl RunLoop {
}
}

#[derive(Clone)]
sunce86 marked this conversation as resolved.
Show resolved Hide resolved
struct Participant {
driver: Arc<infra::Driver>,
solution: competition::SolutionWithId,
Expand Down
4 changes: 4 additions & 0 deletions crates/autopilot/src/shadow.rs
Original file line number Diff line number Diff line change
Expand Up @@ -38,9 +38,11 @@ pub struct RunLoop {
liveness: Arc<Liveness>,
synchronization: RunLoopMode,
current_block: CurrentBlockWatcher,
_max_winners_per_auction: usize,
}

impl RunLoop {
#[allow(clippy::too_many_arguments)]
pub fn new(
orderbook: infra::shadow::Orderbook,
drivers: Vec<infra::Driver>,
Expand All @@ -49,6 +51,7 @@ impl RunLoop {
liveness: Arc<Liveness>,
synchronization: RunLoopMode,
current_block: CurrentBlockWatcher,
_max_winners_per_auction: usize,
) -> Self {
Self {
orderbook,
Expand All @@ -60,6 +63,7 @@ impl RunLoop {
liveness,
synchronization,
current_block,
_max_winners_per_auction,
}
}

Expand Down
Loading