Skip to content

Commit

Permalink
Colocation Notify - failed simulation (#2013)
Browse files Browse the repository at this point in the history
# Description
Implements (2) from #1891

# Changes
- [ ] Simulation failure error (together with tx data) propagated from
the `driver` to the `solvers`.

Unfortunately, not all data can (and should) be populated to support the
legacy version. `block_number` is one of those that is needed but it is
not provided by simulators. This is the never ending issue we generally
have with doing simulations on a specific block number.

The easiest fix would be to add get_block_number() call to each web3
simulation (enso and tenderly seem to give block_number in their
responses), but it wouldn't be accurate.

At the moment I decided to skip it, since, after all, solvers can pretty
much figure out the block number on their own, there are usually 1 or 2
candidates...
  • Loading branch information
sunce86 authored Nov 1, 2023
1 parent b317ab4 commit 7922749
Show file tree
Hide file tree
Showing 15 changed files with 175 additions and 39 deletions.
1 change: 1 addition & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 2 additions & 2 deletions crates/driver/src/domain/competition/solution/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -324,9 +324,9 @@ pub enum Error {
#[error(transparent)]
Execution(#[from] trade::ExecutionError),
#[error(
"invalid internalization: solution attempts to internalize tokens which are not trusted"
"non bufferable tokens used: solution attempts to internalize tokens which are not trusted"
)]
UntrustedInternalization(BTreeSet<TokenAddress>),
NonBufferableTokensUsed(BTreeSet<TokenAddress>),
#[error("invalid internalization: uninternalized solution fails to simulate")]
FailingInternalization,
#[error("insufficient solver account Ether balance, required {0:?}")]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -113,7 +113,7 @@ impl Settlement {
.map(|asset| asset.token)
.collect::<BTreeSet<_>>();
if !untrusted_tokens.is_empty() {
return Err(Error::UntrustedInternalization(untrusted_tokens));
return Err(Error::NonBufferableTokensUsed(untrusted_tokens));
}

// Encode the solution into a settlement.
Expand Down
43 changes: 21 additions & 22 deletions crates/driver/src/infra/notify/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,10 @@ use {
mod notification;

pub use notification::{Kind, Notification, ScoreKind, Settlement};

use crate::domain::{competition::score, eth, mempools::Error};
use {
super::simulator,
crate::domain::{competition::score, eth, mempools::Error},
};

pub fn solver_timeout(solver: &Solver, auction_id: Option<auction::Id>) {
solver.notify(auction_id, None, notification::Kind::Timeout);
Expand Down Expand Up @@ -59,29 +61,26 @@ pub fn encoding_failed(
solution_id: solution::Id,
err: &solution::Error,
) {
match err {
solution::Error::UntrustedInternalization(tokens) => {
solver.notify(
auction_id,
Some(solution_id),
notification::Kind::NonBufferableTokensUsed(tokens.clone()),
);
let notification = match err {
solution::Error::NonBufferableTokensUsed(tokens) => {
notification::Kind::NonBufferableTokensUsed(tokens.clone())
}
solution::Error::SolverAccountInsufficientBalance(required) => {
solver.notify(
auction_id,
Some(solution_id),
notification::Kind::SolverAccountInsufficientBalance(*required),
);
notification::Kind::SolverAccountInsufficientBalance(*required)
}
solution::Error::Blockchain(_) => (),
solution::Error::Boundary(_) => (),
solution::Error::Simulation(_) => (), // todo,
solution::Error::AssetFlow(_) => (),
solution::Error::Execution(_) => (),
solution::Error::FailingInternalization => (),
solution::Error::DifferentSolvers => (),
}
solution::Error::Blockchain(_) => return,
solution::Error::Boundary(_) => return,
solution::Error::Simulation(simulator::Error::WithTx(error)) => {
notification::Kind::SimulationFailed(error.tx.clone())
}
solution::Error::Simulation(simulator::Error::Basic(_)) => return,
solution::Error::AssetFlow(_) => return,
solution::Error::Execution(_) => return,
solution::Error::FailingInternalization => return,
solution::Error::DifferentSolvers => return,
};

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

pub fn executed(
Expand Down
12 changes: 7 additions & 5 deletions crates/driver/src/infra/notify/notification.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,11 @@ use {
std::collections::BTreeSet,
};

type RequiredEther = Ether;
type TokensUsed = BTreeSet<TokenAddress>;
type TransactionHash = eth::TxId;
type Transaction = eth::Tx;

/// A notification sent to solvers in case of important events in the driver.
#[derive(Debug)]
pub struct Notification {
Expand All @@ -14,9 +19,6 @@ pub struct Notification {
pub kind: Kind,
}

pub type RequiredEther = Ether;
pub type TokensUsed = BTreeSet<TokenAddress>;

#[derive(Debug)]
pub enum Kind {
/// Solver engine timed out.
Expand All @@ -25,6 +27,8 @@ pub enum Kind {
EmptySolution,
/// Solution received from solver engine don't have unique id.
DuplicatedSolutionId,
/// Failed simulation during competition.
SimulationFailed(Transaction),
/// No valid score could be computed for the solution.
ScoringFailed(ScoreKind),
/// Solution aimed to internalize tokens that are not considered safe to
Expand Down Expand Up @@ -60,8 +64,6 @@ pub enum ScoreKind {
ObjectiveValueNonPositive,
}

type TransactionHash = eth::TxId;

#[derive(Debug)]
pub enum Settlement {
/// Winning solver settled successfully transaction onchain.
Expand Down
8 changes: 4 additions & 4 deletions crates/driver/src/infra/simulator/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -142,17 +142,17 @@ pub enum SimulatorError {
#[derive(Debug, thiserror::Error)]
#[error("err: {err:?}, tx: {tx:?}")]
pub struct WithTxError {
err: SimulatorError,
tx: eth::Tx,
pub err: SimulatorError,
pub tx: eth::Tx,
}

#[derive(Debug, thiserror::Error)]
pub enum Error {
#[error("basic: {0:?}")]
#[error(transparent)]
Basic(#[from] SimulatorError),
/// If a transaction reverted, forward that transaction together with the
/// error.
#[error("with tx: {0:?}")]
#[error(transparent)]
WithTx(#[from] WithTxError),
}

Expand Down
22 changes: 22 additions & 0 deletions crates/driver/src/infra/solver/dto/notification.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ use {
serde::Serialize,
serde_with::serde_as,
std::collections::BTreeSet,
web3::types::AccessList,
};

impl Notification {
Expand All @@ -24,6 +25,13 @@ impl Notification {
kind: match kind {
notify::Kind::Timeout => Kind::Timeout,
notify::Kind::EmptySolution => Kind::EmptySolution,
notify::Kind::SimulationFailed(tx) => Kind::SimulationFailed(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::ScoringFailed(notify::ScoreKind::ZeroScore) => {
Kind::ScoringFailed(ScoreKind::ZeroScore)
}
Expand Down Expand Up @@ -82,6 +90,7 @@ pub enum Kind {
Timeout,
EmptySolution,
DuplicatedSolutionId,
SimulationFailed(Tx),
ScoringFailed(ScoreKind),
NonBufferableTokensUsed {
tokens: BTreeSet<eth::H160>,
Expand All @@ -93,6 +102,19 @@ pub enum Kind {
Settled(Settlement),
}

#[serde_as]
#[derive(Debug, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct Tx {
pub from: eth::H160,
pub to: eth::H160,
#[serde_as(as = "serialize::Hex")]
pub input: Vec<u8>,
#[serde_as(as = "serialize::U256")]
pub value: eth::U256,
pub access_list: AccessList,
}

#[serde_as]
#[derive(Debug, Serialize)]
#[serde(rename_all = "lowercase")]
Expand Down
2 changes: 2 additions & 0 deletions crates/shared/src/http_solver/model.rs
Original file line number Diff line number Diff line change
Expand Up @@ -509,6 +509,7 @@ pub struct SimulatedTransaction {
/// on
pub tx_index: u64,
/// Is transaction simulated with internalized interactions or without
/// TODO: remove field once the colocation is enabled.
pub internalization: InternalizationStrategy,
/// Which storage the settlement tries to access. Contains `None` if some
/// error happened while estimating the access list.
Expand Down Expand Up @@ -536,6 +537,7 @@ pub enum InternalizationStrategy {
EncodeAllInteractions,
#[serde(rename = "Enabled")]
SkipInternalizableInteraction,
Unknown,
}

#[cfg(test)]
Expand Down
1 change: 1 addition & 0 deletions crates/solvers/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ tokio = { version = "1", features = ["macros", "rt-multi-thread", "signal", "tim
toml = "0.7"
tower = "0.4"
tower-http = { version = "0.4", features = ["trace"] }
web3 = "0.19"

# TODO Once solvers are ported and E2E tests set up, slowly migrate code and
# remove/re-evaluate these dependencies.
Expand Down
22 changes: 22 additions & 0 deletions crates/solvers/src/api/routes/notify/dto/notification.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ use {
serde::Deserialize,
serde_with::{serde_as, DisplayFromStr},
std::collections::BTreeSet,
web3::types::AccessList,
};

impl Notification {
Expand All @@ -21,6 +22,13 @@ impl Notification {
kind: match &self.kind {
Kind::Timeout => notification::Kind::Timeout,
Kind::EmptySolution => notification::Kind::EmptySolution,
Kind::SimulationFailed(tx) => notification::Kind::SimulationFailed(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::ScoringFailed(ScoreKind::ObjectiveValueNonPositive) => {
notification::Kind::ScoringFailed(
notification::ScoreKind::ObjectiveValueNonPositive,
Expand Down Expand Up @@ -89,6 +97,7 @@ pub enum Kind {
Timeout,
EmptySolution,
DuplicatedSolutionId,
SimulationFailed(Tx),
ScoringFailed(ScoreKind),
NonBufferableTokensUsed {
tokens: BTreeSet<H160>,
Expand All @@ -100,6 +109,19 @@ pub enum Kind {
Settled(Settlement),
}

#[serde_as]
#[derive(Debug, Deserialize)]
#[serde(rename_all = "camelCase", deny_unknown_fields)]
pub struct Tx {
from: H160,
to: H160,
#[serde_as(as = "serialize::Hex")]
input: Vec<u8>,
#[serde_as(as = "serialize::U256")]
value: U256,
access_list: AccessList,
}

#[serde_as]
#[derive(Debug, Deserialize)]
#[serde(rename_all = "lowercase")]
Expand Down
21 changes: 21 additions & 0 deletions crates/solvers/src/boundary/legacy.rs
Original file line number Diff line number Diff line change
Expand Up @@ -23,16 +23,19 @@ use {
BatchAuctionModel,
ConcentratedPoolParameters,
ConstantProductPoolParameters,
InternalizationStrategy,
MetadataModel,
OrderModel,
Score,
SettledBatchAuctionModel,
SimulatedTransaction,
SolverRejectionReason,
SolverRunError,
StablePoolParameters,
SubmissionResult,
TokenAmount,
TokenInfoModel,
TransactionWithError,
WeightedPoolTokenData,
WeightedProductPoolParameters,
},
Expand Down Expand Up @@ -572,6 +575,8 @@ fn to_domain_solution(
})
}

const UNKNOWN_BLOCK_NUMBER: u64 = 0;

fn to_boundary_auction_result(notification: &notification::Notification) -> (i64, AuctionResult) {
let auction_id = match notification.auction_id {
auction::Id::Solve(id) => id,
Expand All @@ -583,6 +588,22 @@ fn to_boundary_auction_result(notification: &notification::Notification) -> (i64
AuctionResult::Rejected(SolverRejectionReason::RunError(SolverRunError::Timeout))
}
Kind::EmptySolution => AuctionResult::Rejected(SolverRejectionReason::NoUserOrders),
Kind::SimulationFailed(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: UNKNOWN_BLOCK_NUMBER, // todo #2018
tx_index: Default::default(),
access_list: Default::default(),
max_fee_per_gas: Default::default(),
max_priority_fee_per_gas: Default::default(),
},
}),
),
Kind::ScoringFailed(ScoreKind::ObjectiveValueNonPositive) => {
AuctionResult::Rejected(SolverRejectionReason::ObjectiveValueNonPositive)
}
Expand Down
34 changes: 34 additions & 0 deletions crates/solvers/src/domain/eth/mod.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
use {crate::util::bytes::Bytes, web3::types::AccessList};

mod chain;

pub use {
Expand Down Expand Up @@ -37,9 +39,41 @@ pub struct Asset {
#[derive(Clone, Copy, Debug, Default, Eq, Hash, Ord, PartialEq, PartialOrd)]
pub struct Ether(pub U256);

impl From<U256> for Ether {
fn from(value: U256) -> Self {
Self(value)
}
}

/// Gas amount.
#[derive(Clone, Copy, Debug, Default)]
pub struct Gas(pub U256);

/// A 256-bit rational type.
pub type Rational = num::rational::Ratio<U256>;

/// An address. Can be an EOA or a smart contract address.
#[derive(Debug, Default, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct Address(pub H160);

impl From<H160> for Address {
fn from(value: H160) -> Self {
Self(value)
}
}

impl From<Address> for H160 {
fn from(value: Address) -> Self {
value.0
}
}

/// An onchain transaction.
#[derive(Debug, Clone)]
pub struct Tx {
pub from: Address,
pub to: Address,
pub value: Ether,
pub input: Bytes<Vec<u8>>,
pub access_list: AccessList,
}
Loading

0 comments on commit 7922749

Please sign in to comment.