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

Notify banned solvers #3262

Open
wants to merge 26 commits into
base: blacklist-failing-solvers
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
321f9bc
Use driver directly
squadgazzz Jan 30, 2025
f69e174
Notify external solvers
squadgazzz Jan 30, 2025
1504c48
/notify driver endpoint
squadgazzz Jan 30, 2025
9f0cbc8
OpenApi
squadgazzz Jan 30, 2025
7f86000
Nit
squadgazzz Jan 30, 2025
d233094
Solver's OpenAPI
squadgazzz Jan 30, 2025
105b9a7
Store drivers accepted for the feature
squadgazzz Jan 31, 2025
08924da
Comment
squadgazzz Jan 31, 2025
dfec727
Merge branch 'blacklist-failing-solvers' into notify-banned-solvers
squadgazzz Jan 31, 2025
e370871
Fix after merge
squadgazzz Jan 31, 2025
3c591e4
Merge branch 'blacklist-failing-solvers' into notify-banned-solvers
squadgazzz Jan 31, 2025
85a3d86
Merge branch 'blacklist-failing-solvers' into notify-banned-solvers
squadgazzz Jan 31, 2025
921692f
Notify only accepted solvers
squadgazzz Jan 31, 2025
f3b6415
Minor refactoring
squadgazzz Jan 31, 2025
098113a
Merge branch 'blacklist-failing-solvers' into notify-banned-solvers
squadgazzz Jan 31, 2025
43e650f
Banned notification
squadgazzz Jan 31, 2025
634aec4
Merge branch 'blacklist-failing-solvers' into notify-banned-solvers
squadgazzz Feb 3, 2025
8745e6b
Merge branch 'blacklist-failing-solvers' into notify-banned-solvers
squadgazzz Feb 11, 2025
4bee676
Move notification to the infra mod
squadgazzz Feb 11, 2025
17f3f3a
Ban until timestamp notification
squadgazzz Feb 11, 2025
9ee6af7
Merge remote-tracking branch 'origin/notify-banned-solvers' into noti…
squadgazzz Feb 11, 2025
70d8907
Merge branch 'blacklist-failing-solvers' into notify-banned-solvers
squadgazzz Feb 11, 2025
cca70c8
Refactoring
squadgazzz Feb 12, 2025
7de4dc1
OpenAPI
squadgazzz Feb 12, 2025
5c266d8
Merge branch 'blacklist-failing-solvers' into notify-banned-solvers
squadgazzz Feb 12, 2025
4523b52
Formatting
squadgazzz Feb 12, 2025
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
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ use {
infra,
},
ethrpc::block_stream::CurrentBlockWatcher,
model::time::now_in_epoch_seconds,
std::{
collections::HashMap,
sync::Arc,
Expand Down Expand Up @@ -84,17 +85,27 @@ impl Validator {

tracing::debug!(solvers = ?non_settling_solver_names, "found non-settling solvers");

let now = Instant::now();
non_settling_drivers
let non_settling_drivers = non_settling_drivers
.into_iter()
// Check if solver accepted this feature. This should be removed once a CIP is
// approved.
.filter_map(|driver| {
driver.accepts_unsettled_blocking.then_some(driver.submission_address)
})
.for_each(|solver| {
self_.0.banned_solvers.insert(solver, now);
});
.filter(|driver| driver.accepts_unsettled_blocking)
.collect::<Vec<_>>();

let now = Instant::now();
let banned_until_timestamp =
u64::from(now_in_epoch_seconds()) + self_.0.ttl.as_secs();
infra::notify_non_settling_solvers(
&non_settling_drivers,
banned_until_timestamp,
);

for driver in non_settling_drivers {
self_
.0
.banned_solvers
.insert(driver.submission_address, now);
}
}
Err(err) => {
tracing::warn!(?err, "error while searching for non-settling solvers")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,7 @@ mod db;
mod onchain;

use {
crate::{
arguments::DbBasedSolverParticipationGuardConfig,
domain::eth,
infra::{self, Ethereum},
},
crate::{arguments::DbBasedSolverParticipationGuardConfig, domain::eth, infra},
std::{collections::HashMap, sync::Arc},
};

Expand All @@ -22,7 +18,7 @@ struct Inner {

impl SolverParticipationGuard {
pub fn new(
eth: Ethereum,
eth: infra::Ethereum,
persistence: infra::Persistence,
competition_updates_receiver: tokio::sync::mpsc::UnboundedReceiver<()>,
db_based_validator_config: DbBasedSolverParticipationGuardConfig,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
use crate::{domain::eth, infra::Ethereum};
use crate::{domain::eth, infra};

/// Calls Authenticator contract to check if a solver has a sufficient
/// permission.
pub(super) struct Validator {
pub eth: Ethereum,
pub eth: infra::Ethereum,
}

#[async_trait::async_trait]
Expand Down
2 changes: 1 addition & 1 deletion crates/autopilot/src/infra/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,5 +7,5 @@ pub use {
blockchain::Ethereum,
order_validation::banned,
persistence::Persistence,
solvers::Driver,
solvers::{notify_non_settling_solvers, Driver},
};
1 change: 1 addition & 0 deletions crates/autopilot/src/infra/solvers/dto/mod.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
//! Types for communicating with drivers as defined in
//! `crates/driver/openapi.yml`.

pub mod notify;
pub mod reveal;
pub mod settle;
pub mod solve;
18 changes: 18 additions & 0 deletions crates/autopilot/src/infra/solvers/dto/notify.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
use {serde::Serialize, serde_with::serde_as};

#[serde_as]
#[derive(Debug, Serialize)]
#[serde(rename_all = "camelCase")]
pub enum Request {
Banned {
reason: BanReason,
until_timestamp: u64,
},
}

#[serde_as]
#[derive(Debug, Serialize)]
#[serde(rename_all = "camelCase")]
pub enum BanReason {
UnsettledConsecutiveAuctions,
}
35 changes: 33 additions & 2 deletions crates/autopilot/src/infra/solvers/mod.rs
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
use {
self::dto::{reveal, settle, solve},
crate::{arguments::Account, domain::eth, util},
crate::{arguments::Account, domain::eth, infra::solvers::dto::notify, util},
anyhow::{anyhow, Context, Result},
ethcontract::jsonrpc::futures_util::future::join_all,
reqwest::{Client, StatusCode},
std::time::Duration,
std::{sync::Arc, time::Duration},
thiserror::Error,
url::Url,
};
Expand Down Expand Up @@ -116,6 +117,10 @@ impl Driver {
Ok(())
}

pub async fn notify(&self, request: &notify::Request) -> Result<()> {
self.request_response("notify", request, None).await
}

async fn request_response<Response>(
&self,
path: &str,
Expand Down Expand Up @@ -172,3 +177,29 @@ pub async fn response_body_with_size_limit(
}
Ok(bytes)
}

/// Try to notify all the non-settling solvers in a background task.
pub fn notify_non_settling_solvers(
non_settling_drivers: &[Arc<Driver>],
banned_until_timestamp: u64,
) {
let futures = non_settling_drivers
.iter()
.cloned()
.map(|driver| async move {
if let Err(err) = driver
.notify(&notify::Request::Banned {
reason: notify::BanReason::UnsettledConsecutiveAuctions,
until_timestamp: banned_until_timestamp,
})
.await
{
tracing::debug!(solver = ?driver.name, ?err, "unable to notify external solver");
}
})
.collect::<Vec<_>>();

tokio::spawn(async move {
join_all(futures).await;
});
}
27 changes: 27 additions & 0 deletions crates/driver/openapi.yml
Original file line number Diff line number Diff line change
Expand Up @@ -138,6 +138,33 @@ paths:
$ref: "#/components/responses/BadRequest"
"500":
$ref: "#/components/responses/InternalServerError"
/notify:
post:
description: |
Receive a notification in case the driver was banned for a certain reason.
requestBody:
required: true
content:
application/json:
schema:
type: string
enum:
- banned
description: |-
The reason for the notification with optional additional context.
responses:
"200":
description: notification successfully received.
"500":
description: Unable to notify solver.
content:
application/json:
schema:
type: object
properties:
error:
type: string
example: "Unable to notify solver"
components:
schemas:
Address:
Expand Down
9 changes: 9 additions & 0 deletions crates/driver/src/infra/api/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -112,3 +112,12 @@ impl From<api::routes::OrderError> for (hyper::StatusCode, axum::Json<Error>) {
error.into()
}
}

impl From<api::routes::NotifyError> for (hyper::StatusCode, axum::Json<Error>) {
fn from(value: api::routes::NotifyError) -> Self {
let error = match value {
api::routes::NotifyError::UnableToNotify => Kind::Unknown,
};
error.into()
}
}
1 change: 1 addition & 0 deletions crates/driver/src/infra/api/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,7 @@ impl Api {
let router = routes::solve(router);
let router = routes::reveal(router);
let router = routes::settle(router);
let router = routes::notify(router);

let bad_token_config = solver.bad_token_detection();
let mut bad_tokens =
Expand Down
2 changes: 2 additions & 0 deletions crates/driver/src/infra/api/routes/mod.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
mod healthz;
mod info;
mod metrics;
mod notify;
mod quote;
mod reveal;
mod settle;
Expand All @@ -10,6 +11,7 @@ pub(super) use {
healthz::healthz,
info::info,
metrics::metrics,
notify::{notify, NotifyError},
quote::{quote, OrderError},
reveal::reveal,
settle::settle,
Expand Down
3 changes: 3 additions & 0 deletions crates/driver/src/infra/api/routes/notify/dto/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
mod notify_request;

pub use notify_request::{Error as NotifyError, NotifyRequest};
43 changes: 43 additions & 0 deletions crates/driver/src/infra/api/routes/notify/dto/notify_request.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
use {crate::infra::notify, serde::Deserialize, serde_with::serde_as};

#[serde_as]
#[derive(Debug, Deserialize)]
#[serde(rename_all = "camelCase")]
pub enum NotifyRequest {
Banned {
reason: BanReason,
until_timestamp: u64,
},
}

#[serde_as]
#[derive(Debug, Deserialize)]
#[serde(rename_all = "camelCase")]
pub enum BanReason {
/// The driver won multiple consecutive auctions but never settled them.
UnsettledConsecutiveAuctions,
}

impl From<NotifyRequest> for notify::Kind {
fn from(value: NotifyRequest) -> Self {
match value {
NotifyRequest::Banned {
reason,
until_timestamp,
} => notify::Kind::Banned {
reason: match reason {
BanReason::UnsettledConsecutiveAuctions => {
notify::BanReason::UnsettledConsecutiveAuctions
}
},
until_timestamp,
},
}
}
}

#[derive(Debug, thiserror::Error)]
pub enum Error {
Copy link
Contributor

Choose a reason for hiding this comment

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

nit: Personally I would be ok with omitting completely the error case for notify endpoint (be consistent with how the driver propagates it to solvers) as we always considered this endpoint not too important for system to work and we were not interested to act on failure of notification.

#[error("Unable to notify solver")]
UnableToNotify,
Copy link
Contributor

Choose a reason for hiding this comment

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

Maybe it is worth to add some context information like solver address or name?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

The caller knows for which solver the request was sent. IMO, it is a redundant info here.

}
23 changes: 23 additions & 0 deletions crates/driver/src/infra/api/routes/notify/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
mod dto;

pub use dto::NotifyError;

use crate::infra::api::{Error, State};

pub(in crate::infra::api) fn notify(router: axum::Router<State>) -> axum::Router<State> {
router.route("/notify", axum::routing::post(route))
}

async fn route(
state: axum::extract::State<State>,
req: axum::Json<dto::NotifyRequest>,
) -> Result<hyper::StatusCode, (hyper::StatusCode, axum::Json<Error>)> {
let solver = &state.solver().name().0;
tracing::debug!(?req, ?solver, "received a notification");
state
.solver()
.notify(None, None, req.0.into())
.await
.map(|_| hyper::StatusCode::OK)
.map_err(|_| NotifyError::UnableToNotify.into())
}
25 changes: 16 additions & 9 deletions crates/driver/src/infra/notify/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,18 +5,25 @@ use {

mod notification;

pub use notification::{Kind, Notification, ScoreKind, Settlement, SimulationSucceededAtLeastOnce};
pub use notification::{
BanReason,
Kind,
Notification,
ScoreKind,
Settlement,
SimulationSucceededAtLeastOnce,
};
use {
super::simulator,
crate::domain::{eth, mempools::Error},
};

pub fn solver_timeout(solver: &Solver, auction_id: Option<auction::Id>) {
solver.notify(auction_id, None, notification::Kind::Timeout);
solver.notify_and_forget(auction_id, None, notification::Kind::Timeout);
}

pub fn empty_solution(solver: &Solver, auction_id: Option<auction::Id>, solution: solution::Id) {
solver.notify(
solver.notify_and_forget(
auction_id,
Some(solution),
notification::Kind::EmptySolution,
Expand Down Expand Up @@ -45,7 +52,7 @@ pub fn scoring_failed(
}
};

solver.notify(auction_id, Some(solution_id.clone()), notification);
solver.notify_and_forget(auction_id, Some(solution_id.clone()), notification);
}

pub fn encoding_failed(
Expand Down Expand Up @@ -76,7 +83,7 @@ pub fn encoding_failed(
solution::Error::Encoding(_) => return,
};

solver.notify(auction_id, Some(solution_id.clone()), notification);
solver.notify_and_forget(auction_id, Some(solution_id.clone()), notification);
}

pub fn simulation_failed(
Expand All @@ -94,7 +101,7 @@ pub fn simulation_failed(
),
simulator::Error::Other(error) => notification::Kind::DriverError(error.to_string()),
};
solver.notify(auction_id, Some(solution_id.clone()), kind);
solver.notify_and_forget(auction_id, Some(solution_id.clone()), kind);
}

pub fn executed(
Expand All @@ -111,7 +118,7 @@ pub fn executed(
Err(Error::Other(_) | Error::Disabled) => notification::Settlement::Fail,
};

solver.notify(
solver.notify_and_forget(
Some(auction_id),
Some(solution_id.clone()),
notification::Kind::Settled(kind),
Expand All @@ -123,13 +130,13 @@ pub fn duplicated_solution_id(
auction_id: Option<auction::Id>,
solution_id: &solution::Id,
) {
solver.notify(
solver.notify_and_forget(
auction_id,
Some(solution_id.clone()),
notification::Kind::DuplicatedSolutionId,
);
}

pub fn postprocessing_timed_out(solver: &Solver, auction_id: Option<auction::Id>) {
solver.notify(auction_id, None, notification::Kind::PostprocessingTimedOut);
solver.notify_and_forget(auction_id, None, notification::Kind::PostprocessingTimedOut);
}
Loading
Loading