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_hashes.iter().map(|hash| H256(hash.0)).collect(),
)
})
.transpose()
}
}

fn deserialize_solver_competition(
json: JsonValue,
auction_id: model::auction::AuctionId,
transaction_hashes: Vec<H256>,
) -> anyhow::Result<SolverCompetitionAPI> {
let common: SolverCompetitionDB =
serde_json::from_value(json).context("deserialize SolverCompetitionDB")?;
Ok(SolverCompetitionAPI {
auction_id,
transaction_hashes,
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 Id {
Mainnet = 1,
Gnosis = 100,
Sepolia = 11155111,
ArbitrumOne = 42161,
}

impl Id {
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 {
Id::Mainnet => "1",
Id::Gnosis => "100",
Id::Sepolia => "11155111",
Id::ArbitrumOne => "42161",
}
}
}

#[derive(Debug, thiserror::Error)]
#[error("unsupported chain")]
pub struct UnsupportedChain;
11 changes: 11 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,8 @@ use {
derive_more::{Display, From, Into},
};

pub mod chain;

/// 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 +16,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
38 changes: 30 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::chain::Id,
) -> 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,19 @@ impl Settlement {
}
}

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

#[derive(Debug, thiserror::Error)]
pub enum Error {
#[error("failed communication with the database: {0}")]
Expand Down Expand Up @@ -286,6 +304,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 +455,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 +619,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 +798,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::chain::Id,
contracts: Contracts,
authenticator_pk: eth::H256,
) -> Self {
Expand Down
14 changes: 10 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::chain::Id, addresses: Addresses) -> Self {
let address_for = |contract: &ethcontract::Contract, address: Option<H160>| {
Copy link
Contributor

Choose a reason for hiding this comment

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

nit: domain::eth::chain seems weird.
A structure like this make more sense to me:
chain
----> id
----> mod

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Hm, not following, how would the path go in your case?

Copy link
Contributor

Choose a reason for hiding this comment

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

domain::chain::Id instead of domain::eth::chain::Id.
It doesn't make much sense to me to have the less specific chain as a child of the more specific eth module.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Hm, to me chain is not that important concept to live directly under domain. Previously it lived under infra::blockchain, maybe we should move it there, so we would have infra::blockchain::Id?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Moved to infra, probably better now

address
.or_else(|| deployment_address(contract, chain))
Expand Down Expand Up @@ -92,6 +92,12 @@ 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::chain::Id,
) -> Option<H160> {
contract
.networks
.get(chain.network_id())
.map(|network| network.address)
}
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::chain::Id,
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::chain::Id::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::chain::Id {
self.chain
}

Expand All @@ -74,7 +57,7 @@ impl Rpc {
#[derive(Clone)]
pub struct Ethereum {
web3: DynWeb3,
chain: ChainId,
chain: eth::chain::Id,
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::chain::Id,
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::chain::Id {
&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