-
Notifications
You must be signed in to change notification settings - Fork 151
Refactor participant score #4011
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
Changes from all commits
e0d08c2
6140d10
dd8b019
bbfdfff
d9a5606
ce49ae1
8aa8ebf
45b8130
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 |
|---|---|---|
|
|
@@ -32,7 +32,7 @@ use { | |
| Prices, | ||
| order::{self, TargetAmount}, | ||
| }, | ||
| competition::{Participant, Ranked, Score, Solution, Unranked}, | ||
| competition::{Participant, RankType, Ranked, Score, Scored, Solution, Unscored}, | ||
| eth::{self, WrappedNativeToken}, | ||
| fee, | ||
| settlement::{ | ||
|
|
@@ -57,14 +57,14 @@ impl Arbitrator { | |
| /// Runs the entire auction mechanism on the passed in solutions. | ||
| pub fn arbitrate( | ||
| &self, | ||
| participants: Vec<Participant<Unranked>>, | ||
| participants: Vec<Participant<Unscored>>, | ||
| auction: &domain::Auction, | ||
| ) -> Ranking { | ||
| let partitioned = self.partition_unfair_solutions(participants, auction); | ||
| let filtered_out = partitioned | ||
| .discarded | ||
| .into_iter() | ||
| .map(|participant| participant.rank(Ranked::FilteredOut)) | ||
| .map(|participant| participant.with_rank(RankType::FilteredOut)) | ||
| .collect(); | ||
|
|
||
| let mut ranked = self.mark_winners(partitioned.kept); | ||
|
|
@@ -73,7 +73,7 @@ impl Arbitrator { | |
| // winners before non-winners | ||
| std::cmp::Reverse(participant.is_winner()), | ||
| // high score before low score | ||
| std::cmp::Reverse(participant.solution().score()), | ||
| std::cmp::Reverse(participant.score()), | ||
|
Contributor
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. Although it gets the job done having the score on the participant is a bit awkward IMO. This implies to me that the same solution submitted by different participants could have a different score.
Contributor
Author
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.
But each participant has a unique solution. Even if internally it has the same numbers, we don't share it across different participants, and the score will be the same with the old approach as well. So this doesn't change the logic.
Contributor
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. Yeah, the logic - or rather the outcome - didn't change. But I'm wondering where the correct place for the score is in the first place. For example if you were to store the solution in the driver instance the outcome would also not change but it would be a weird place to put the data, no? (I'm aware that your PR did not suddenly make the name worse but it somewhat signalled that
Contributor
Author
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. Totally makes sense, thanks! But probably as a separate PR. |
||
| ) | ||
| }); | ||
| Ranking { | ||
|
|
@@ -85,16 +85,17 @@ impl Arbitrator { | |
| /// Removes unfair solutions from the set of all solutions. | ||
| fn partition_unfair_solutions( | ||
| &self, | ||
| mut participants: Vec<Participant<Unranked>>, | ||
| participants: Vec<Participant<Unscored>>, | ||
| auction: &domain::Auction, | ||
| ) -> PartitionedSolutions { | ||
| // Discard all solutions where we can't compute the aggregate scores | ||
| // accurately because the fairness guarantees heavily rely on them. | ||
| let scores_by_solution = compute_scores_by_solution(&mut participants, auction); | ||
| let (mut participants, scores_by_solution) = | ||
| compute_scores_by_solution(participants, auction); | ||
| participants.sort_by_key(|participant| { | ||
| std::cmp::Reverse( | ||
| // we use the computed score to not trust the score provided by solvers | ||
| participant.solution().score().get().0, | ||
| participant.score().get().0, | ||
| ) | ||
| }); | ||
| let baseline_scores = compute_baseline_scores(&scores_by_solution); | ||
|
|
@@ -130,17 +131,17 @@ impl Arbitrator { | |
|
|
||
| /// Picks winners and sorts all solutions where winners come before | ||
| /// non-winners and higher scores come before lower scores. | ||
| fn mark_winners(&self, participants: Vec<Participant<Unranked>>) -> Vec<Participant> { | ||
| fn mark_winners(&self, participants: Vec<Participant<Scored>>) -> Vec<Participant<Ranked>> { | ||
| let winner_indexes = self.pick_winners(participants.iter().map(|p| p.solution())); | ||
| participants | ||
| .into_iter() | ||
| .enumerate() | ||
| .map(|(index, participant)| { | ||
| let rank = match winner_indexes.contains(&index) { | ||
| true => Ranked::Winner, | ||
| false => Ranked::NonWinner, | ||
| true => RankType::Winner, | ||
| false => RankType::NonWinner, | ||
| }; | ||
| participant.rank(rank) | ||
| participant.with_rank(rank) | ||
| }) | ||
| .collect() | ||
| } | ||
|
|
@@ -165,17 +166,17 @@ impl Arbitrator { | |
| continue; | ||
| } | ||
|
|
||
| let solutions_without_solver = ranking | ||
| let participants_without_solver = ranking | ||
| .ranked | ||
| .iter() | ||
| .filter(|p| p.driver().submission_address != solver) | ||
| .map(|p| p.solution()); | ||
| let winner_indices = self.pick_winners(solutions_without_solver.clone()); | ||
| .filter(|p| p.driver().submission_address != solver); | ||
| let solutions = participants_without_solver.clone().map(|p| p.solution()); | ||
| let winner_indices = self.pick_winners(solutions); | ||
|
|
||
| let score = solutions_without_solver | ||
| let score = participants_without_solver | ||
| .enumerate() | ||
| .filter(|(index, _)| winner_indices.contains(index)) | ||
| .filter_map(|(_, solution)| solution.score) | ||
| .map(|(_, p)| p.score()) | ||
| .reduce(Score::saturating_add) | ||
| .unwrap_or_default(); | ||
| reference_scores.insert(solver, score); | ||
|
|
@@ -244,39 +245,40 @@ fn compute_baseline_scores(scores_by_solution: &ScoresBySolution) -> ScoreByDire | |
| /// Solutions get discarded because fairness guarantees heavily | ||
| /// depend on these scores being accurate. | ||
| fn compute_scores_by_solution( | ||
| participants: &mut Vec<Participant<Unranked>>, | ||
| participants: Vec<Participant<Unscored>>, | ||
| auction: &domain::Auction, | ||
| ) -> ScoresBySolution { | ||
| ) -> (Vec<Participant<Scored>>, ScoresBySolution) { | ||
| let auction = Auction::from(auction); | ||
| let mut scores = HashMap::default(); | ||
|
|
||
| participants.retain_mut(|p| match score_by_token_pair(p.solution(), &auction) { | ||
| Ok(score) => { | ||
| let total_score = score | ||
| .values() | ||
| .fold(Score::default(), |acc, score| acc.saturating_add(*score)); | ||
| scores.insert( | ||
| SolutionKey { | ||
| driver: p.driver().submission_address, | ||
| solution_id: p.solution().id, | ||
| }, | ||
| score, | ||
| ); | ||
| p.set_score(total_score); | ||
| true | ||
| } | ||
| Err(err) => { | ||
| tracing::warn!( | ||
| driver = p.driver().name, | ||
| ?err, | ||
| solution = ?p.solution(), | ||
| "discarding solution where scores could not be computed" | ||
| ); | ||
| false | ||
| let mut scores_by_solution = HashMap::default(); | ||
| let mut scored_participants = Vec::with_capacity(participants.len()); | ||
|
|
||
| for participant in participants { | ||
| match score_by_token_pair(participant.solution(), &auction) { | ||
| Ok(score) => { | ||
| let total_score = score | ||
| .values() | ||
| .fold(Score::default(), |acc, score| acc.saturating_add(*score)); | ||
| scores_by_solution.insert( | ||
| SolutionKey { | ||
| driver: participant.driver().submission_address, | ||
| solution_id: participant.solution().id(), | ||
| }, | ||
| score, | ||
| ); | ||
| scored_participants.push(participant.with_score(total_score)); | ||
| } | ||
| Err(err) => { | ||
| tracing::warn!( | ||
| driver = participant.driver().name, | ||
| ?err, | ||
| solution = ?participant.solution(), | ||
| "discarding solution where scores could not be computed" | ||
| ); | ||
| } | ||
| } | ||
| }); | ||
| } | ||
|
|
||
| scores | ||
| (scored_participants, scores_by_solution) | ||
| } | ||
|
|
||
| /// Returns the total scores for each directed token pair of the solution. | ||
|
|
@@ -450,8 +452,8 @@ impl Ranking { | |
| } | ||
|
|
||
| struct PartitionedSolutions { | ||
| kept: Vec<Participant<Unranked>>, | ||
| discarded: Vec<Participant<Unranked>>, | ||
| kept: Vec<Participant<Scored>>, | ||
| discarded: Vec<Participant<Scored>>, | ||
| } | ||
|
|
||
| #[cfg(test)] | ||
|
|
@@ -466,7 +468,7 @@ mod tests { | |
| Price, | ||
| order::{self, AppDataHash}, | ||
| }, | ||
| competition::{Participant, Solution, TradedOrder, Unranked}, | ||
| competition::{Participant, Solution, TradedOrder, Unscored}, | ||
| eth::{self, TokenAddress}, | ||
| }, | ||
| infra::Driver, | ||
|
|
@@ -1214,7 +1216,7 @@ mod tests { | |
| // winners before non-winners | ||
| std::cmp::Reverse(a.is_winner()), | ||
| // high score before low score | ||
| std::cmp::Reverse(a.solution().score()) | ||
| std::cmp::Reverse(a.score()) | ||
| ))); | ||
| assert_eq!(winners.len(), self.expected_winners.len()); | ||
| for (actual, expected) in winners.iter().zip(&self.expected_winners) { | ||
|
|
@@ -1374,7 +1376,7 @@ mod tests { | |
| solver_address: Address, | ||
| trades: Vec<(OrderUid, TradedOrder)>, | ||
| prices: Option<HashMap<TokenAddress, Price>>, | ||
| ) -> Participant<Unranked> { | ||
| ) -> Participant<Unscored> { | ||
| // The prices of the tokens do not affect the result but they keys must exist | ||
| // for every token of every trade | ||
| let prices = prices.unwrap_or({ | ||
|
|
||
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.