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 @@ -141,35 +140,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,
})
}
9 changes: 9 additions & 0 deletions crates/autopilot/src/domain/eth/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,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
44 changes: 36 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: &infra::blockchain::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,25 @@ impl Settlement {
}
}

const MAINNET_BLOCK_TIME: u64 = 13_000; // ms
const GNOSIS_BLOCK_TIME: u64 = 5_000; // ms
const SEPOLIA_BLOCK_TIME: u64 = 13_000; // ms
const ARBITRUM_ONE_BLOCK_TIME: u64 = 100; // ms

/// 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: &infra::blockchain::Id) -> u64 {
const TARGET_AGE: u64 = 6 * 60 * 60 * 1000; // 6h in ms
match chain {
m-lord-renkse marked this conversation as resolved.
Show resolved Hide resolved
infra::blockchain::Id::Mainnet => TARGET_AGE / MAINNET_BLOCK_TIME,
infra::blockchain::Id::Gnosis => TARGET_AGE / GNOSIS_BLOCK_TIME,
infra::blockchain::Id::Sepolia => TARGET_AGE / SEPOLIA_BLOCK_TIME,
infra::blockchain::Id::ArbitrumOne => TARGET_AGE / ARBITRUM_ONE_BLOCK_TIME,
}
}

#[derive(Debug, thiserror::Error)]
pub enum Error {
#[error("failed communication with the database: {0}")]
Expand Down Expand Up @@ -286,6 +310,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 +461,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 +625,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 +804,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.chain(),
)
.await
{
Ok(settlement) => Some(settlement),
Err(err) if retryable(&err) => return Err(err.into()),
Expand Down
4 changes: 2 additions & 2 deletions crates/autopilot/src/infra/blockchain/authenticator.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,8 @@ use {
crate::{
domain::{self, eth},
infra::blockchain::{
self,
contracts::{deployment_address, Contracts},
ChainId,
},
},
ethcontract::{dyns::DynWeb3, GasPrice},
Expand All @@ -25,7 +25,7 @@ impl Manager {
/// Creates an authenticator which can remove solvers from the allow-list
pub async fn new(
web3: DynWeb3,
chain: ChainId,
chain: blockchain::Id,
contracts: Contracts,
authenticator_pk: eth::H256,
) -> Self {
Expand Down
18 changes: 14 additions & 4 deletions crates/autopilot/src/infra/blockchain/contracts.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,8 @@
use {super::ChainId, crate::domain, ethcontract::dyns::DynWeb3, primitive_types::H160};
use {
crate::{domain, infra::blockchain},
ethcontract::dyns::DynWeb3,
primitive_types::H160,
};

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

impl Contracts {
pub async fn new(web3: &DynWeb3, chain: &ChainId, addresses: Addresses) -> Self {
pub async fn new(web3: &DynWeb3, chain: &blockchain::Id, 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 +96,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: &blockchain::Id,
) -> Option<H160> {
contract
.networks
.get(chain.network_id())
.map(|network| network.address)
}
43 changes: 43 additions & 0 deletions crates/autopilot/src/infra/blockchain/id.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;
33 changes: 10 additions & 23 deletions crates/autopilot/src/infra/blockchain/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,29 +11,14 @@ use {

pub mod authenticator;
pub mod contracts;
pub mod id;

/// 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)
}
}
pub use id::Id;

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

Expand All @@ -45,7 +30,7 @@ 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 = Id::new(web3.eth().chain_id().await?).map_err(|_| Error::UnsupportedChain)?;

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

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

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

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

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