Skip to content

Commit

Permalink
Implement Gelato Settlement Submission Strategy (#841)
Browse files Browse the repository at this point in the history
Succeeds #599 

This PR adds the Gelato settlement submission strategy and is based on #839 and #840. 

With this new strategy, transaction are submitted over the Gelato relay network, so no transaction execution/submission needs to be handled by the driver 🎉.

Note that there is a bit of a code-smell in the `SettlementSubmitter` component. I didn't want to address that in this PR, but I left a clarifying comment to try and explain the issue. Perhaps this is something we can refactor in the near future.

### Test Plan

Added a new manual test for submitting a [settlement](https://goerli.etherscan.io/tx/0x9b3d0a61d2862f98651cf7b8e8d7edccc3f7aaa3621dd9a8e26576c9a6cc4e5e):
```
% cargo test -p solver -- gelato::tests::execute_relayed_settlement --ignored --nocapture
   Compiling solver v0.1.0 (/Users/nlordell/Developer/cowprotocol/services/crates/solver)
    Finished test [unoptimized + debuginfo] target(s) in 5.97s
     Running unittests src/lib.rs (target/debug/deps/solver-4e294b2061fada4d)

running 1 test
executed transaction 0x9b3d0a61d2862f98651cf7b8e8d7edccc3f7aaa3621dd9a8e26576c9a6cc4e5e
test settlement_submission::gelato::tests::execute_relayed_settlement ... ok

test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 196 filtered out; finished in 27.26s
```
  • Loading branch information
Nicholas Rodrigues Lordello authored Nov 28, 2022
1 parent 704fb32 commit 8b875ed
Show file tree
Hide file tree
Showing 7 changed files with 247 additions and 42 deletions.
20 changes: 20 additions & 0 deletions crates/driver/src/arguments.rs
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,20 @@ pub struct Arguments {
)]
pub transaction_strategy: Vec<TransactionStrategyArg>,

/// The API key to use for the Gelato submission strategy.
#[clap(long, env)]
pub gelato_api_key: Option<String>,

/// The poll interval for checking status of Gelato tasks when using it as a
/// transaction submission strategy.
#[clap(
long,
env,
default_value = "5",
value_parser = shared::arguments::duration_from_seconds,
)]
pub gelato_submission_poll_interval: Duration,

/// The API endpoint of the Eden network for transaction submission.
#[clap(long, env, default_value = "https://api.edennetwork.io/v1/rpc")]
pub eden_api_url: Url,
Expand Down Expand Up @@ -305,6 +319,12 @@ impl std::fmt::Display for Arguments {
)?;
writeln!(f, "min_order_age: {:?}", self.min_order_age,)?;
writeln!(f, "transaction_strategy: {:?}", self.transaction_strategy)?;
display_secret_option(f, "gelato_api_key", &self.gelato_api_key)?;
writeln!(
f,
"gelato_submission_poll_interval: {:?}",
&self.gelato_submission_poll_interval
)?;
writeln!(f, "eden_api_url: {}", self.eden_api_url)?;
writeln!(
f,
Expand Down
17 changes: 17 additions & 0 deletions crates/driver/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ use shared::{
baseline_solver::BaseTokens,
current_block::{BlockRetrieving, CurrentBlockStream},
ethrpc::{self, Web3},
gelato_api::GelatoClient,
http_client::HttpClientFactory,
http_solver::{DefaultHttpSolverApi, SolverConfig},
maintenance::{Maintaining, ServiceMaintenance},
Expand Down Expand Up @@ -39,6 +40,7 @@ use solver::{
settlement_ranker::SettlementRanker,
settlement_rater::SettlementRater,
settlement_submission::{
gelato::GelatoSubmitter,
submitter::{
eden_api::EdenApi, flashbots_api::FlashbotsApi, public_mempool_api::PublicMempoolApi,
Strategy,
Expand Down Expand Up @@ -256,6 +258,21 @@ async fn build_submitter(common: &CommonComponents, args: &Arguments) -> Arc<Sol
sub_tx_pool: submitted_transactions.add_sub_pool(Strategy::PublicMempool),
}))
}
TransactionStrategyArg::Gelato => {
transaction_strategies.push(TransactionStrategy::Gelato(Arc::new(
GelatoSubmitter::new(
web3.clone(),
common.settlement_contract.clone(),
GelatoClient::new(
&common.http_factory,
args.gelato_api_key.clone().unwrap(),
),
args.gelato_submission_poll_interval,
)
.await
.unwrap(),
)))
}
TransactionStrategyArg::DryRun => {
transaction_strategies.push(TransactionStrategy::DryRun)
}
Expand Down
1 change: 0 additions & 1 deletion crates/shared/src/gelato_api.rs
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,6 @@ impl GelatoClient {
}
}

#[cfg(test)]
pub fn test_from_env() -> Result<Self> {
Ok(Self::new(
&HttpClientFactory::default(),
Expand Down
23 changes: 22 additions & 1 deletion crates/solver/src/arguments.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ use crate::{
use primitive_types::H160;
use reqwest::Url;
use shared::{
arguments::{display_list, display_option},
arguments::{display_list, display_option, display_secret_option},
http_client,
};
use std::time::Duration;
Expand Down Expand Up @@ -165,6 +165,20 @@ pub struct Arguments {
)]
pub transaction_strategy: Vec<TransactionStrategyArg>,

/// The API key to use for the Gelato submission strategy.
#[clap(long, env)]
pub gelato_api_key: Option<String>,

/// The poll interval for checking status of Gelato tasks when using it as a
/// transaction submission strategy.
#[clap(
long,
env,
default_value = "5",
value_parser = shared::arguments::duration_from_seconds,
)]
pub gelato_submission_poll_interval: Duration,

/// Which access list estimators to use. Multiple estimators are used in sequence if a previous one
/// fails. Individual estimators might support different networks.
/// `Tenderly`: supports every network.
Expand Down Expand Up @@ -329,6 +343,12 @@ impl std::fmt::Display for Arguments {
)?;
writeln!(f, "gas_price_cap: {}", self.gas_price_cap)?;
writeln!(f, "transaction_strategy: {:?}", self.transaction_strategy)?;
display_secret_option(f, "gelato_api_key", &self.gelato_api_key)?;
writeln!(
f,
"gelato_submission_poll_interval: {:?}",
&self.gelato_submission_poll_interval
)?;
writeln!(
f,
"access_list_estimators: {:?}",
Expand Down Expand Up @@ -403,5 +423,6 @@ pub enum TransactionStrategyArg {
PublicMempool,
Eden,
Flashbots,
Gelato,
DryRun,
}
14 changes: 14 additions & 0 deletions crates/solver/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ use num::rational::Ratio;
use shared::{
baseline_solver::BaseTokens,
ethrpc::{self, Web3},
gelato_api::GelatoClient,
http_client::HttpClientFactory,
maintenance::{Maintaining, ServiceMaintenance},
metrics::serve_metrics,
Expand Down Expand Up @@ -33,6 +34,7 @@ use solver::{
orderbook::OrderBookApi,
settlement_post_processing::PostProcessingPipeline,
settlement_submission::{
gelato::GelatoSubmitter,
submitter::{
eden_api::EdenApi, flashbots_api::FlashbotsApi, public_mempool_api::PublicMempoolApi,
Strategy,
Expand Down Expand Up @@ -402,6 +404,18 @@ async fn main() -> ! {
sub_tx_pool: submitted_transactions.add_sub_pool(Strategy::PublicMempool),
}))
}
TransactionStrategyArg::Gelato => {
transaction_strategies.push(TransactionStrategy::Gelato(Arc::new(
GelatoSubmitter::new(
web3.clone(),
settlement_contract.clone(),
GelatoClient::new(&http_factory, args.gelato_api_key.clone().unwrap()),
args.gelato_submission_poll_interval,
)
.await
.unwrap(),
)))
}
TransactionStrategyArg::DryRun => {
transaction_strategies.push(TransactionStrategy::DryRun)
}
Expand Down
95 changes: 56 additions & 39 deletions crates/solver/src/settlement_submission.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
mod dry_run;
mod gelato;
pub mod gelato;
pub mod submitter;

use crate::{
Expand Down Expand Up @@ -28,6 +28,8 @@ use submitter::{
use tracing::Instrument;
use web3::types::TransactionReceipt;

use self::gelato::GelatoSubmitter;

const ESTIMATE_GAS_LIMIT_FACTOR: f64 = 1.2;

pub struct SubTxPool {
Expand Down Expand Up @@ -121,6 +123,7 @@ pub enum TransactionStrategy {
Eden(StrategyArgs),
Flashbots(StrategyArgs),
PublicMempool(StrategyArgs),
Gelato(Arc<GelatoSubmitter>),
DryRun,
}

Expand All @@ -130,7 +133,7 @@ impl TransactionStrategy {
TransactionStrategy::Eden(args) => Some(args),
TransactionStrategy::Flashbots(args) => Some(args),
TransactionStrategy::PublicMempool(args) => Some(args),
TransactionStrategy::DryRun => None,
TransactionStrategy::Gelato(_) | TransactionStrategy::DryRun => None,
}
}
}
Expand All @@ -148,44 +151,58 @@ impl SolutionSubmitter {
account: Account,
nonce: U256,
) -> Result<TransactionReceipt, SubmissionError> {
let is_dry_run: bool = self
.transaction_strategies
.iter()
.any(|strategy| matches!(strategy, TransactionStrategy::DryRun));
// Other transaction strategies than the ones below, depend on an
// account signing a raw transaction for a nonce, and waiting until that
// nonce increases to detect that it actually mined. However, the
// strategies below are **not** compatible with this. So if one of them
// is specified, use it exclusively for submitting and exit the loop.
// TODO(nlordell): We can refactor the `SolutionSubmitter` interface to
// better reflect configuration incompatibilities like this.
for strategy in &self.transaction_strategies {
match strategy {
TransactionStrategy::DryRun => {
return Ok(dry_run::log_settlement(account, &self.contract, settlement).await?);
}
TransactionStrategy::Gelato(gelato) => {
return tokio::time::timeout(
self.max_confirm_time,
gelato.relay_settlement(account, settlement),
)
.await
.map_err(|_| SubmissionError::Timeout)?;
}
_ => {}
}
}

let network_id = self.web3.net().version().await?;

if is_dry_run {
Ok(dry_run::log_settlement(account, &self.contract, settlement).await?)
} else {
let mut futures = self
.transaction_strategies
.iter()
.enumerate()
.map(|(i, strategy)| {
self.settle_with_strategy(
strategy,
&account,
nonce,
gas_estimate,
network_id.clone(),
settlement.clone(),
i,
)
.boxed()
})
.collect::<Vec<_>>();

loop {
let (result, _index, rest) = futures::future::select_all(futures).await;
match result {
Ok(receipt) => return Ok(receipt),
Err(err) if rest.is_empty() || err.is_transaction_mined() => {
return Err(err);
}
Err(_) => {
futures = rest;
}
let mut futures = self
.transaction_strategies
.iter()
.enumerate()
.map(|(i, strategy)| {
self.settle_with_strategy(
strategy,
&account,
nonce,
gas_estimate,
network_id.clone(),
settlement.clone(),
i,
)
.boxed()
})
.collect::<Vec<_>>();

loop {
let (result, _index, rest) = futures::future::select_all(futures).await;
match result {
Ok(receipt) => return Ok(receipt),
Err(err) if rest.is_empty() || err.is_transaction_mined() => {
return Err(err);
}
Err(_) => {
futures = rest;
}
}
}
Expand All @@ -211,7 +228,7 @@ impl SolutionSubmitter {
}
}
TransactionStrategy::PublicMempool(_) => {}
TransactionStrategy::DryRun => unreachable!(),
_ => unreachable!(),
};

let strategy_args = strategy.strategy_args().expect("unreachable code executed");
Expand Down
Loading

0 comments on commit 8b875ed

Please sign in to comment.