diff --git a/crates/driver/src/domain/competition/mod.rs b/crates/driver/src/domain/competition/mod.rs index 4a13a59d71..46ef2cf941 100644 --- a/crates/driver/src/domain/competition/mod.rs +++ b/crates/driver/src/domain/competition/mod.rs @@ -196,7 +196,7 @@ impl Competition { *score_ref = None; *self.settlement.lock().unwrap() = None; if let Some(id) = settlement.notify_id() { - notify::simulation_failed(&self.solver, auction.id(), id, &err); + notify::simulation_failed(&self.solver, auction.id(), id, &err, true); } return; } diff --git a/crates/driver/src/infra/notify/mod.rs b/crates/driver/src/infra/notify/mod.rs index d09d5d0d8e..febf4f2fca 100644 --- a/crates/driver/src/infra/notify/mod.rs +++ b/crates/driver/src/infra/notify/mod.rs @@ -5,7 +5,7 @@ use { mod notification; -pub use notification::{Kind, Notification, ScoreKind, Settlement}; +pub use notification::{Kind, Notification, ScoreKind, Settlement, SimulationSucceededAtLeastOnce}; use { super::simulator, crate::domain::{competition::score, eth, mempools::Error}, @@ -74,7 +74,7 @@ pub fn encoding_failed( solution::Error::Blockchain(_) => return, solution::Error::Boundary(_) => return, solution::Error::Simulation(error) => { - simulation_failed(solver, auction_id, solution_id, error); + simulation_failed(solver, auction_id, solution_id, error, false); return; } solution::Error::Execution(_) => return, @@ -90,12 +90,17 @@ pub fn simulation_failed( auction_id: Option, solution_id: solution::Id, err: &simulator::Error, + succeeded_at_least_once: SimulationSucceededAtLeastOnce, ) { if let simulator::Error::Revert(error) = err { solver.notify( auction_id, Some(solution_id), - notification::Kind::SimulationFailed(error.block, error.tx.clone()), + notification::Kind::SimulationFailed( + error.block, + error.tx.clone(), + succeeded_at_least_once, + ), ); } } diff --git a/crates/driver/src/infra/notify/notification.rs b/crates/driver/src/infra/notify/notification.rs index 4e7c70bc37..579753199f 100644 --- a/crates/driver/src/infra/notify/notification.rs +++ b/crates/driver/src/infra/notify/notification.rs @@ -10,6 +10,7 @@ type RequiredEther = Ether; type TokensUsed = BTreeSet; type TransactionHash = eth::TxId; type Transaction = eth::Tx; +pub type SimulationSucceededAtLeastOnce = bool; /// A notification sent to solvers in case of important events in the driver. #[derive(Debug)] @@ -27,8 +28,9 @@ pub enum Kind { EmptySolution, /// Solution received from solver engine don't have unique id. DuplicatedSolutionId, - /// Failed simulation during competition. - SimulationFailed(eth::BlockNo, Transaction), + /// Failed simulation during competition. Last parameter is true + /// if has simulated at least once. + SimulationFailed(eth::BlockNo, Transaction, SimulationSucceededAtLeastOnce), /// No valid score could be computed for the solution. ScoringFailed(ScoreKind), /// Solution aimed to internalize tokens that are not considered safe to diff --git a/crates/driver/src/infra/solver/dto/notification.rs b/crates/driver/src/infra/solver/dto/notification.rs index 47d1501af2..aad9a72fb8 100644 --- a/crates/driver/src/infra/solver/dto/notification.rs +++ b/crates/driver/src/infra/solver/dto/notification.rs @@ -4,7 +4,7 @@ use { competition::{auction, solution}, eth, }, - infra::notify, + infra::{notify, notify::SimulationSucceededAtLeastOnce}, util::serialize, }, serde::Serialize, @@ -25,16 +25,19 @@ impl Notification { kind: match kind { notify::Kind::Timeout => Kind::Timeout, notify::Kind::EmptySolution => Kind::EmptySolution, - notify::Kind::SimulationFailed(block, tx) => Kind::SimulationFailed( - block.0, - Tx { - from: tx.from.into(), - to: tx.to.into(), - input: tx.input.into(), - value: tx.value.into(), - access_list: tx.access_list.into(), - }, - ), + notify::Kind::SimulationFailed(block, tx, simulated_once) => { + Kind::SimulationFailed( + block.0, + Tx { + from: tx.from.into(), + to: tx.to.into(), + input: tx.input.into(), + value: tx.value.into(), + access_list: tx.access_list.into(), + }, + simulated_once, + ) + } notify::Kind::ScoringFailed(notify::ScoreKind::ZeroScore) => { Kind::ScoringFailed(ScoreKind::ZeroScore) } @@ -97,7 +100,7 @@ pub enum Kind { Timeout, EmptySolution, DuplicatedSolutionId, - SimulationFailed(BlockNo, Tx), + SimulationFailed(BlockNo, Tx, SimulationSucceededAtLeastOnce), ScoringFailed(ScoreKind), NonBufferableTokensUsed { tokens: BTreeSet, diff --git a/crates/shared/src/http_solver/model.rs b/crates/shared/src/http_solver/model.rs index fb4aa2b322..12bf287d48 100644 --- a/crates/shared/src/http_solver/model.rs +++ b/crates/shared/src/http_solver/model.rs @@ -422,6 +422,8 @@ pub enum AuctionResult { SubmittedOnchain(SubmissionResult), } +type SimulationSucceededAtLeastOnce = bool; + #[serde_as] #[derive(Debug, Serialize)] #[serde(rename_all = "camelCase")] @@ -450,7 +452,7 @@ pub enum SolverRejectionReason { /// The solution didn't pass simulation. Includes all data needed to /// re-create simulation locally - SimulationFailure(TransactionWithError), + SimulationFailure(TransactionWithError, SimulationSucceededAtLeastOnce), /// The solution doesn't have a positive score. Currently this can happen /// only if the objective value is negative. diff --git a/crates/solver/src/settlement_ranker.rs b/crates/solver/src/settlement_ranker.rs index 9fd4479f83..4b95922aa2 100644 --- a/crates/solver/src/settlement_ranker.rs +++ b/crates/solver/src/settlement_ranker.rs @@ -209,6 +209,7 @@ impl SettlementRanker { transaction: error.simulation.transaction.clone(), error: error.error.to_string(), }, + false, )), ); Some((solver, Rating::Err(error))) diff --git a/crates/solvers/src/api/routes/notify/dto/notification.rs b/crates/solvers/src/api/routes/notify/dto/notification.rs index 894112be95..b72be73932 100644 --- a/crates/solvers/src/api/routes/notify/dto/notification.rs +++ b/crates/solvers/src/api/routes/notify/dto/notification.rs @@ -1,6 +1,10 @@ use { crate::{ - domain::{auction, eth, notification}, + domain::{ + auction, + eth, + notification::{self, SimulationSucceededAtLeastOnce}, + }, util::serialize, }, ethereum_types::{H160, H256, U256}, @@ -22,16 +26,19 @@ impl Notification { kind: match &self.kind { Kind::Timeout => notification::Kind::Timeout, Kind::EmptySolution => notification::Kind::EmptySolution, - Kind::SimulationFailed(block, tx) => notification::Kind::SimulationFailed( - *block, - eth::Tx { - from: tx.from.into(), - to: tx.to.into(), - input: tx.input.clone().into(), - value: tx.value.into(), - access_list: tx.access_list.clone(), - }, - ), + Kind::SimulationFailed(block, tx, succeeded_at_least_once) => { + notification::Kind::SimulationFailed( + *block, + eth::Tx { + from: tx.from.into(), + to: tx.to.into(), + input: tx.input.clone().into(), + value: tx.value.into(), + access_list: tx.access_list.clone(), + }, + *succeeded_at_least_once, + ) + } Kind::ScoringFailed(ScoreKind::ObjectiveValueNonPositive { quality, gas_cost }) => { notification::Kind::ScoringFailed( notification::ScoreKind::ObjectiveValueNonPositive( @@ -103,7 +110,7 @@ pub enum Kind { Timeout, EmptySolution, DuplicatedSolutionId, - SimulationFailed(BlockNo, Tx), + SimulationFailed(BlockNo, Tx, SimulationSucceededAtLeastOnce), ScoringFailed(ScoreKind), NonBufferableTokensUsed { tokens: BTreeSet, diff --git a/crates/solvers/src/boundary/legacy.rs b/crates/solvers/src/boundary/legacy.rs index 17e372870f..a6ae7aec46 100644 --- a/crates/solvers/src/boundary/legacy.rs +++ b/crates/solvers/src/boundary/legacy.rs @@ -599,22 +599,25 @@ fn to_boundary_auction_result(notification: ¬ification::Notification) -> (i64 AuctionResult::Rejected(SolverRejectionReason::RunError(SolverRunError::Timeout)) } Kind::EmptySolution => AuctionResult::Rejected(SolverRejectionReason::NoUserOrders), - Kind::SimulationFailed(block_number, tx) => AuctionResult::Rejected( - SolverRejectionReason::SimulationFailure(TransactionWithError { - error: "".to_string(), - transaction: SimulatedTransaction { - from: tx.from.into(), - to: tx.to.into(), - data: tx.input.clone().into(), - internalization: InternalizationStrategy::Unknown, - block_number: *block_number, - tx_index: Default::default(), - access_list: Default::default(), - max_fee_per_gas: Default::default(), - max_priority_fee_per_gas: Default::default(), + Kind::SimulationFailed(block_number, tx, succeeded_at_least_once) => { + AuctionResult::Rejected(SolverRejectionReason::SimulationFailure( + TransactionWithError { + error: "".to_string(), + transaction: SimulatedTransaction { + from: tx.from.into(), + to: tx.to.into(), + data: tx.input.clone().into(), + internalization: InternalizationStrategy::Unknown, + block_number: *block_number, + tx_index: Default::default(), + access_list: Default::default(), + max_fee_per_gas: Default::default(), + max_priority_fee_per_gas: Default::default(), + }, }, - }), - ), + *succeeded_at_least_once, + )) + } Kind::ScoringFailed(ScoreKind::ObjectiveValueNonPositive(quality, gas_cost)) => { AuctionResult::Rejected(SolverRejectionReason::ObjectiveValueNonPositive { quality: quality.0, diff --git a/crates/solvers/src/domain/notification.rs b/crates/solvers/src/domain/notification.rs index 1143fe3ba5..695f2d5c14 100644 --- a/crates/solvers/src/domain/notification.rs +++ b/crates/solvers/src/domain/notification.rs @@ -12,6 +12,7 @@ type TokensUsed = BTreeSet; type TransactionHash = eth::H256; type Transaction = eth::Tx; type BlockNo = u64; +pub type SimulationSucceededAtLeastOnce = bool; /// The notification about important events happened in driver, that solvers /// need to know about. @@ -28,7 +29,7 @@ pub enum Kind { Timeout, EmptySolution, DuplicatedSolutionId, - SimulationFailed(BlockNo, Transaction), + SimulationFailed(BlockNo, Transaction, SimulationSucceededAtLeastOnce), ScoringFailed(ScoreKind), NonBufferableTokensUsed(TokensUsed), SolverAccountInsufficientBalance(RequiredEther),