Skip to content

Commit

Permalink
Add block deadline to settle payload (#2784)
Browse files Browse the repository at this point in the history
# Description
Add block deadline to settle payload. As it is right now, the driver is
not aware of the last block number in which they can still include their
TX. This information is very important to communicate to drivers,
especially in collocation mode.

Feature requested by @harisang .

# Changes
Extend the `/settle` payload (from autopilot) to include the submission
deadline in blocks.

## How to test
1. Acceptance tests

---------

Co-authored-by: Felix Leupold <[email protected]>
  • Loading branch information
m-lord-renkse and fleupold authored Jul 10, 2024
1 parent d6f17f3 commit 4b3964c
Show file tree
Hide file tree
Showing 15 changed files with 53 additions and 40 deletions.
2 changes: 2 additions & 0 deletions crates/autopilot/src/infra/solvers/dto/settle.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@ pub struct Request {
/// Unique ID of the solution (per driver competition), to settle.
#[serde_as(as = "serde_with::DisplayFromStr")]
pub solution_id: u64,
/// The last block number in which the solution TX can be included
pub submission_deadline_latest_block: u64,
}

#[serde_as]
Expand Down
7 changes: 6 additions & 1 deletion crates/autopilot/src/run_loop.rs
Original file line number Diff line number Diff line change
Expand Up @@ -282,7 +282,10 @@ impl RunLoop {

tracing::info!(driver = %driver.name, "settling");
let submission_start = Instant::now();
match self.settle(driver, solution, auction_id).await {
match self
.settle(driver, solution, auction_id, block_deadline)
.await
{
Ok(()) => Metrics::settle_ok(driver, submission_start.elapsed()),
Err(err) => {
Metrics::settle_err(driver, &err, submission_start.elapsed());
Expand Down Expand Up @@ -441,13 +444,15 @@ impl RunLoop {
driver: &infra::Driver,
solved: &competition::Solution,
auction_id: i64,
submission_deadline_latest_block: u64,
) -> Result<(), SettleError> {
let order_ids = solved.order_ids().copied().collect();
self.persistence
.store_order_events(order_ids, OrderEventLabel::Executing);

let request = settle::Request {
solution_id: solved.id(),
submission_deadline_latest_block,
};
let tx_hash = self
.wait_for_settlement(driver, auction_id, request)
Expand Down
4 changes: 4 additions & 0 deletions crates/driver/openapi.yml
Original file line number Diff line number Diff line change
Expand Up @@ -394,6 +394,10 @@ components:
description: Id of the solution that should be executed.
type: string
example: "123"
submissionDeadlineLatestBlock:
description: The last block number in which the solution TX can be included.
type: integer
example: 12345
RevealedResponse:
description: Response of the reveal endpoint.
type: object
Expand Down
7 changes: 5 additions & 2 deletions crates/driver/src/domain/competition/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -272,15 +272,18 @@ impl Competition {

/// Execute the solution generated as part of this competition. Use
/// [`Competition::solve`] to generate the solution.
pub async fn settle(&self) -> Result<Settled, Error> {
pub async fn settle(&self, submission_deadline: u64) -> Result<Settled, Error> {
let settlement = self
.settlement
.lock()
.unwrap()
.take()
.ok_or(Error::SolutionNotAvailable)?;

let executed = self.mempools.execute(&self.solver, &settlement).await;
let executed = self
.mempools
.execute(&self.solver, &settlement, submission_deadline)
.await;
notify::executed(
&self.solver,
settlement.auction_id,
Expand Down
33 changes: 19 additions & 14 deletions crates/driver/src/domain/mempools.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ use {
eth,
},
crate::{
domain::{competition::solution::Settlement, eth::TxStatus},
domain::{competition::solution::Settlement, eth::TxStatus, BlockNo},
infra::{self, observe, solver::Solver, Ethereum},
},
ethrpc::current_block::into_stream,
Expand Down Expand Up @@ -41,12 +41,13 @@ impl Mempools {
&self,
solver: &Solver,
settlement: &Settlement,
submission_deadline: BlockNo,
) -> Result<eth::TxId, Error> {
let (tx_hash, _remaining_futures) =
select_ok(self.mempools.iter().cloned().map(|mempool| {
async move {
let result = self
.submit(&mempool, solver, settlement)
.submit(&mempool, solver, settlement, submission_deadline)
.instrument(tracing::info_span!("mempool", kind = mempool.to_string()))
.await;
observe::mempool_executed(&mempool, settlement, &result);
Expand Down Expand Up @@ -79,6 +80,7 @@ impl Mempools {
mempool: &infra::mempool::Mempool,
solver: &Solver,
settlement: &Settlement,
submission_deadline: BlockNo,
) -> Result<eth::TxId, Error> {
// Don't submit risky transactions if revert protection is
// enabled and the settlement may revert in this mempool.
Expand All @@ -89,7 +91,6 @@ impl Mempools {
return Err(Error::Disabled);
}

let deadline = mempool.config().deadline();
let tx = settlement.transaction(settlement::Internalization::Enable);

// Instantiate block stream and skip the current block before we submit the
Expand All @@ -102,18 +103,8 @@ impl Mempools {

// Wait for the transaction to be mined, expired or failing.
let result = async {
loop {
// Wait for the next block to be mined or we time out.
if tokio::time::timeout_at(deadline, block_stream.next())
.await
.is_err()
{
tracing::info!(?hash, "tx not confirmed in time, cancelling");
self.cancel(mempool, settlement.gas.price, solver).await?;
return Err(Error::Expired);
}
while let Some(block) = block_stream.next().await {
tracing::debug!(?hash, "checking if tx is confirmed");

let receipt = self
.ethereum
.transaction_status(&hash)
Expand All @@ -126,6 +117,17 @@ impl Mempools {
TxStatus::Executed => return Ok(hash.clone()),
TxStatus::Reverted => return Err(Error::Revert(hash.clone())),
TxStatus::Pending => {
// Check if the current block reached the submission deadline block number
if block.number >= submission_deadline {
tracing::info!(
?hash,
deadline = submission_deadline,
current_block = block.number,
"tx not confirmed in time, cancelling",
);
self.cancel(mempool, settlement.gas.price, solver).await?;
return Err(Error::Expired);
}
// Check if transaction still simulates
if let Err(err) = self.ethereum.estimate_gas(tx).await {
if err.is_revert() {
Expand All @@ -143,6 +145,9 @@ impl Mempools {
}
}
}
Err(Error::Other(anyhow::anyhow!(
"Block stream finished unexpectedly"
)))
}
.await;

Expand Down
2 changes: 2 additions & 0 deletions crates/driver/src/domain/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,3 +10,5 @@ pub use {
liquidity::Liquidity,
mempools::{Mempools, RevertProtection},
};

pub type BlockNo = u64;
2 changes: 2 additions & 0 deletions crates/driver/src/infra/api/routes/settle/dto/solution.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,4 +8,6 @@ pub struct Solution {
/// Unique ID of the solution (per driver competition), to settle.
#[serde_as(as = "serde_with::DisplayFromStr")]
solution_id: u64,
/// The last block number in which the solution TX can be included
pub submission_deadline_latest_block: u64,
}
6 changes: 4 additions & 2 deletions crates/driver/src/infra/api/routes/settle/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,13 +14,15 @@ pub(in crate::infra::api) fn settle(router: axum::Router<State>) -> axum::Router

async fn route(
state: axum::extract::State<State>,
_: axum::Json<dto::Solution>,
solution: axum::Json<dto::Solution>,
) -> Result<(), (hyper::StatusCode, axum::Json<Error>)> {
let competition = state.competition();
let auction_id = competition.auction_id().map(|id| id.0);
let handle_request = async {
observe::settling();
let result = competition.settle().await;
let result = competition
.settle(solution.submission_deadline_latest_block)
.await;
observe::settled(state.solver().name(), &result);
result.map(|_| ()).map_err(Into::into)
};
Expand Down
1 change: 0 additions & 1 deletion crates/driver/src/infra/config/file/load.rs
Original file line number Diff line number Diff line change
Expand Up @@ -262,7 +262,6 @@ pub async fn load(chain: eth::ChainId, path: &Path) -> infra::Config {
min_priority_fee: config.submission.min_priority_fee,
gas_price_cap: config.submission.gas_price_cap,
target_confirm_time: config.submission.target_confirm_time,
max_confirm_time: config.submission.max_confirm_time,
retry_interval: config.submission.retry_interval,
kind: match mempool {
file::Mempool::Public => {
Expand Down
9 changes: 0 additions & 9 deletions crates/driver/src/infra/config/file/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -82,11 +82,6 @@ struct SubmissionConfig {
#[serde(with = "humantime_serde", default = "default_retry_interval")]
retry_interval: Duration,

/// The maximum time to spend trying to settle a transaction through the
/// Ethereum network before giving up.
#[serde(with = "humantime_serde", default = "default_max_confirm_time")]
max_confirm_time: Duration,

/// The mempools to submit settlement transactions to. Can be the public
/// mempool of a node or the private MEVBlocker mempool.
#[serde(rename = "mempool", default)]
Expand Down Expand Up @@ -167,10 +162,6 @@ fn default_retry_interval() -> Duration {
Duration::from_secs(2)
}

fn default_max_confirm_time() -> Duration {
Duration::from_secs(120)
}

/// 3 gwei
fn default_max_additional_tip() -> eth::U256 {
eth::U256::from(3) * eth::U256::exp10(9)
Expand Down
7 changes: 0 additions & 7 deletions crates/driver/src/infra/mempool/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,17 +12,10 @@ pub struct Config {
pub min_priority_fee: eth::U256,
pub gas_price_cap: eth::U256,
pub target_confirm_time: std::time::Duration,
pub max_confirm_time: std::time::Duration,
pub retry_interval: std::time::Duration,
pub kind: Kind,
}

impl Config {
pub fn deadline(&self) -> tokio::time::Instant {
tokio::time::Instant::now() + self.max_confirm_time
}
}

#[derive(Debug, Clone)]
pub enum Kind {
/// The public mempool of the [`Ethereum`] node.
Expand Down
3 changes: 2 additions & 1 deletion crates/driver/src/tests/setup/driver.rs
Original file line number Diff line number Diff line change
Expand Up @@ -132,9 +132,10 @@ pub fn reveal_req() -> serde_json::Value {
}

/// Create a request for the driver /settle endpoint.
pub fn settle_req() -> serde_json::Value {
pub fn settle_req(submission_deadline_latest_block: u64) -> serde_json::Value {
json!({
"solutionId": "0",
"submissionDeadlineLatestBlock": submission_deadline_latest_block,
})
}

Expand Down
8 changes: 7 additions & 1 deletion crates/driver/src/tests/setup/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -940,14 +940,20 @@ impl Test {
}

pub async fn settle_with_solver(&self, solver_name: &str) -> Settle {
/// The maximum number of blocks to wait for a settlement to appear on
/// chain.
const SUBMISSION_DEADLINE: u64 = 3;
let submission_deadline_latest_block: u64 =
u64::try_from(self.web3().eth().block_number().await.unwrap()).unwrap()
+ SUBMISSION_DEADLINE;
let old_balances = self.balances().await;
let res = self
.client
.post(format!(
"http://{}/{}/settle",
self.driver.addr, solver_name
))
.json(&driver::settle_req())
.json(&driver::settle_req(submission_deadline_latest_block))
.send()
.await
.unwrap();
Expand Down
1 change: 0 additions & 1 deletion crates/driver/src/tests/setup/solver.rs
Original file line number Diff line number Diff line change
Expand Up @@ -291,7 +291,6 @@ impl Solver {
min_priority_fee: Default::default(),
gas_price_cap: eth::U256::MAX,
target_confirm_time: Default::default(),
max_confirm_time: Default::default(),
retry_interval: Default::default(),
kind: infra::mempool::Kind::Public(infra::mempool::RevertProtection::Disabled),
}],
Expand Down
1 change: 0 additions & 1 deletion crates/e2e/src/setup/colocation.rs
Original file line number Diff line number Diff line change
Expand Up @@ -145,7 +145,6 @@ base-tokens = []
[submission]
gas-price-cap = "1000000000000"
max-confirm-time= "2s"
[[submission.mempool]]
mempool = "public"
Expand Down

0 comments on commit 4b3964c

Please sign in to comment.