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

Use blocks to determine other environment settlements #3053

Merged
merged 11 commits into from
Oct 17, 2024
1 change: 1 addition & 0 deletions crates/autopilot/src/boundary/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ pub use {
SellTokenSource,
},
signature::{EcdsaSignature, Signature, SigningScheme},
solver_competition::SolverCompetitionDB,
DomainSeparator,
},
shared::order_validation::{is_order_outside_market_price, Amounts},
Expand Down
36 changes: 2 additions & 34 deletions crates/autopilot/src/database/competition.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,10 +10,9 @@ use {
Address,
},
derivative::Derivative,
model::solver_competition::{SolverCompetitionAPI, SolverCompetitionDB},
model::solver_competition::SolverCompetitionDB,
number::conversions::u256_to_big_decimal,
primitive_types::{H160, H256, U256},
sqlx::{types::JsonValue, PgConnection},
primitive_types::{H160, U256},
std::collections::{BTreeMap, HashSet},
};

Expand Down Expand Up @@ -137,35 +136,4 @@ impl super::Postgres {

Ok(())
}

pub async fn find_competition(
auction_id: AuctionId,
ex: &mut PgConnection,
) -> anyhow::Result<Option<SolverCompetitionAPI>> {
database::solver_competition::load_by_id(ex, auction_id)
.await
.context("solver_competition::load_by_id")?
.map(|row| {
deserialize_solver_competition(
row.json,
row.id,
row.tx_hash.map(|hash| H256(hash.0)),
)
})
.transpose()
}
}

fn deserialize_solver_competition(
json: JsonValue,
auction_id: model::auction::AuctionId,
transaction_hash: Option<H256>,
) -> anyhow::Result<SolverCompetitionAPI> {
let common: SolverCompetitionDB =
serde_json::from_value(json).context("deserialize SolverCompetitionDB")?;
Ok(SolverCompetitionAPI {
auction_id,
transaction_hash,
common,
})
}
43 changes: 43 additions & 0 deletions crates/autopilot/src/domain/eth/chain.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
use primitive_types::U256;

/// A supported Ethereum Chain ID.
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum ChainId {
Copy link
Contributor

Choose a reason for hiding this comment

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

Maybe not to do in this PR, but we have logic depending on the chain id scattered all over the place. Maybe it could be unified in one place, and implement all functions related to the chain (e.i. default_amount_to_estimate_native_prices_with())

Copy link
Contributor Author

@sunce86 sunce86 Oct 16, 2024

Choose a reason for hiding this comment

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

I am not against it. Although I think we agreed somewhere that we are fine with some level of code duplication to avoid sharing too much code between autopilot, driver, solvers etc, and to support DDD way of organizing code.

Copy link
Contributor

Choose a reason for hiding this comment

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

Yes, but for this particular case I think it comes handy. So, in the future, if we were to support more networks, it is all in one place.

But no need for action in this PR, I just wanted to open the debate. This is something I want to do in the upcoming weeks.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

In that case, I'd suggest opening an issue for this (if we don't have it already). Some people might want to discuss it further. 👍

Mainnet = 1,
Gnosis = 100,
Sepolia = 11155111,
ArbitrumOne = 42161,
}

impl ChainId {
pub fn new(value: U256) -> Result<Self, UnsupportedChain> {
// Check to avoid panics for large `U256` values, as there is no checked
// conversion API available and we don't support chains with IDs greater
// than `u64::MAX` anyway.
if value > U256::from(u64::MAX) {
return Err(UnsupportedChain);
}

match value.as_u64() {
1 => Ok(Self::Mainnet),
100 => Ok(Self::Gnosis),
11155111 => Ok(Self::Sepolia),
42161 => Ok(Self::ArbitrumOne),
_ => Err(UnsupportedChain),
}
}

/// Returns the network ID for the chain.
pub fn network_id(self) -> &'static str {
match self {
ChainId::Mainnet => "1",
ChainId::Gnosis => "100",
ChainId::Sepolia => "11155111",
ChainId::ArbitrumOne => "42161",
}
}
}

#[derive(Debug, thiserror::Error)]
#[error("unsupported chain")]
pub struct UnsupportedChain;
13 changes: 13 additions & 0 deletions crates/autopilot/src/domain/eth/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,10 @@ use {
derive_more::{Display, From, Into},
};

mod chain;

pub use chain::{ChainId, UnsupportedChain};

/// An address. Can be an EOA or a smart contract address.
#[derive(
Debug, Default, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, From, Into, Display,
Expand All @@ -14,6 +18,15 @@ pub struct Address(pub H160);
#[derive(Debug, Copy, Clone, From, PartialEq, PartialOrd, Default)]
pub struct BlockNo(pub u64);

/// Adding blocks to a block number.
impl std::ops::Add<u64> for BlockNo {
type Output = BlockNo;

fn add(self, rhs: u64) -> Self::Output {
Self(self.0 + rhs)
}
}

/// A transaction ID, AKA transaction hash.
#[derive(Debug, Copy, Clone, From, Default)]
pub struct TxId(pub H256);
Expand Down
2 changes: 2 additions & 0 deletions crates/autopilot/src/domain/settlement/auction.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@ use {
#[derive(Debug)]
pub struct Auction {
pub id: domain::auction::Id,
/// The block on top of which the auction was created.
pub block: domain::eth::BlockNo,
/// All orders from a competition auction. Some of them may contain fee
/// policies.
pub orders: HashMap<domain::OrderUid, Vec<domain::fee::Policy>>,
Expand Down
36 changes: 28 additions & 8 deletions crates/autopilot/src/domain/settlement/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,10 @@
//! a form of settlement transaction.

use {
crate::{domain, domain::eth, infra},
crate::{
domain::{self, eth},
infra,
},
std::collections::HashMap,
};

Expand Down Expand Up @@ -103,19 +106,21 @@ impl Settlement {
pub async fn new(
settled: Transaction,
persistence: &infra::Persistence,
chain: &eth::ChainId,
) -> Result<Self, Error> {
if persistence
.auction_has_settlement(settled.auction_id)
.await?
{
// This settlement has already been processed by another environment.
let auction = persistence.get_auction(settled.auction_id).await?;

if settled.block > auction.block + max_settlement_age(chain) {
// A settled transaction references a VERY old auction.
//
// A hacky way to detect processing of production settlements in the staging
// environment, as production is lagging with auction ids by ~270 days on
// Ethereum mainnet.
//
// TODO: remove once https://github.com/cowprotocol/services/issues/2848 is resolved and ~270 days are passed since bumping.
return Err(Error::WrongEnvironment);
}

let auction = persistence.get_auction(settled.auction_id).await?;

let trades = settled
.trades
.into_iter()
Expand All @@ -133,6 +138,17 @@ impl Settlement {
}
}

/// How old (in terms of blocks) a settlement should be, to be considered as a
/// settlement from another environment.
fn max_settlement_age(chain: &eth::ChainId) -> u64 {
match chain {
m-lord-renkse marked this conversation as resolved.
Show resolved Hide resolved
eth::ChainId::Mainnet => 100,
eth::ChainId::Gnosis => 200,
eth::ChainId::Sepolia => 200,
eth::ChainId::ArbitrumOne => 100_000,
}
}

#[derive(Debug, thiserror::Error)]
pub enum Error {
#[error("failed communication with the database: {0}")]
Expand Down Expand Up @@ -286,6 +302,7 @@ mod tests {
let order_uid = transaction.trades[0].uid;

let auction = super::Auction {
block: eth::BlockNo(0),
// prices read from https://solver-instances.s3.eu-central-1.amazonaws.com/prod/mainnet/legacy/8655372.json
prices: auction::Prices::from([
(
Expand Down Expand Up @@ -436,6 +453,7 @@ mod tests {

let order_uid = transaction.trades[0].uid;
let auction = super::Auction {
block: eth::BlockNo(0),
prices,
surplus_capturing_jit_order_owners: Default::default(),
id: 0,
Expand Down Expand Up @@ -599,6 +617,7 @@ mod tests {
]);

let auction = super::Auction {
block: eth::BlockNo(0),
prices,
surplus_capturing_jit_order_owners: HashSet::from([eth::Address(
eth::H160::from_slice(&hex!("f08d4dea369c456d26a3168ff0024b904f2d8b91")),
Expand Down Expand Up @@ -777,6 +796,7 @@ mod tests {
]);

let auction = super::Auction {
block: eth::BlockNo(0),
prices,
surplus_capturing_jit_order_owners: Default::default(),
id: 0,
Expand Down
8 changes: 6 additions & 2 deletions crates/autopilot/src/domain/settlement/observer.rs
Original file line number Diff line number Diff line change
Expand Up @@ -78,8 +78,12 @@ impl Observer {
let (auction_id, settlement) = match transaction {
Ok(transaction) => {
let auction_id = transaction.auction_id;
let settlement = match settlement::Settlement::new(transaction, &self.persistence)
.await
let settlement = match settlement::Settlement::new(
transaction,
&self.persistence,
self.eth.network(),
)
.await
{
Ok(settlement) => Some(settlement),
Err(err) if retryable(&err) => return Err(err.into()),
Expand Down
7 changes: 2 additions & 5 deletions crates/autopilot/src/infra/blockchain/authenticator.rs
Original file line number Diff line number Diff line change
@@ -1,10 +1,7 @@
use {
crate::{
domain::{self, eth},
infra::blockchain::{
contracts::{deployment_address, Contracts},
ChainId,
},
infra::blockchain::contracts::{deployment_address, Contracts},
},
ethcontract::{dyns::DynWeb3, GasPrice},
};
Expand All @@ -25,7 +22,7 @@ impl Manager {
/// Creates an authenticator which can remove solvers from the allow-list
pub async fn new(
web3: DynWeb3,
chain: ChainId,
chain: eth::ChainId,
contracts: Contracts,
authenticator_pk: eth::H256,
) -> Self {
Expand Down
11 changes: 7 additions & 4 deletions crates/autopilot/src/infra/blockchain/contracts.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
use {super::ChainId, crate::domain, ethcontract::dyns::DynWeb3, primitive_types::H160};
use {crate::domain, ethcontract::dyns::DynWeb3, primitive_types::H160};

#[derive(Debug, Clone)]
pub struct Contracts {
Expand All @@ -20,7 +20,7 @@ pub struct Addresses {
}

impl Contracts {
pub async fn new(web3: &DynWeb3, chain: &ChainId, addresses: Addresses) -> Self {
pub async fn new(web3: &DynWeb3, chain: &domain::eth::ChainId, addresses: Addresses) -> Self {
let address_for = |contract: &ethcontract::Contract, address: Option<H160>| {
address
.or_else(|| deployment_address(contract, chain))
Expand Down Expand Up @@ -92,6 +92,9 @@ impl Contracts {

/// Returns the address of a contract for the specified network, or `None` if
/// there is no known deployment for the contract on that network.
pub fn deployment_address(contract: &ethcontract::Contract, chain: &ChainId) -> Option<H160> {
Some(contract.networks.get(&chain.to_string())?.address)
pub fn deployment_address(
contract: &ethcontract::Contract,
chain: &domain::eth::ChainId,
) -> Option<H160> {
Some(contract.networks.get(chain.network_id())?.address)
}
sunce86 marked this conversation as resolved.
Show resolved Hide resolved
33 changes: 9 additions & 24 deletions crates/autopilot/src/infra/blockchain/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,28 +12,10 @@ use {
pub mod authenticator;
pub mod contracts;

/// Chain ID as defined by EIP-155.
///
/// https://eips.ethereum.org/EIPS/eip-155
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub struct ChainId(pub U256);

impl std::fmt::Display for ChainId {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{}", self.0)
}
}

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

/// An Ethereum RPC connection.
pub struct Rpc {
web3: DynWeb3,
chain: ChainId,
chain: eth::ChainId,
url: Url,
}

Expand All @@ -45,7 +27,8 @@ impl Rpc {
ethrpc_args: &shared::ethrpc::Arguments,
) -> Result<Self, Error> {
let web3 = boundary::web3_client(url, ethrpc_args);
let chain = web3.eth().chain_id().await?.into();
let chain =
eth::ChainId::new(web3.eth().chain_id().await?).map_err(|_| Error::UnsupportedChain)?;

Ok(Self {
web3,
Expand All @@ -55,7 +38,7 @@ impl Rpc {
}

/// Returns the chain id for the RPC connection.
pub fn chain(&self) -> ChainId {
pub fn chain(&self) -> eth::ChainId {
self.chain
}

Expand All @@ -74,7 +57,7 @@ impl Rpc {
#[derive(Clone)]
pub struct Ethereum {
web3: DynWeb3,
chain: ChainId,
chain: eth::ChainId,
current_block: CurrentBlockWatcher,
contracts: Contracts,
}
Expand All @@ -88,7 +71,7 @@ impl Ethereum {
/// any initialization error.
pub async fn new(
web3: DynWeb3,
chain: ChainId,
chain: eth::ChainId,
url: Url,
addresses: contracts::Addresses,
poll_interval: Duration,
Expand All @@ -105,7 +88,7 @@ impl Ethereum {
}
}

pub fn network(&self) -> &ChainId {
pub fn network(&self) -> &eth::ChainId {
&self.chain
}

Expand Down Expand Up @@ -179,4 +162,6 @@ pub enum Error {
IncompleteTransactionData(anyhow::Error),
#[error("transaction not found")]
TransactionNotFound,
#[error("unsupported chain")]
UnsupportedChain,
}
Loading
Loading