diff --git a/.github/workflows/pull-request.yaml b/.github/workflows/pull-request.yaml index 372f1670bf..c3613f34b7 100644 --- a/.github/workflows/pull-request.yaml +++ b/.github/workflows/pull-request.yaml @@ -121,7 +121,9 @@ jobs: test-forked-node: # Do not run this job on forks since some secrets are required for it. - if: ${{ github.event.pull_request.head.repo.full_name == github.repository }} + if: | + (github.event_name == 'pull_request' && github.event.pull_request.head.repo.full_name == github.repository) || + (github.event_name == 'push' && github.ref == 'refs/heads/main') timeout-minutes: 60 runs-on: ubuntu-latest env: diff --git a/Cargo.lock b/Cargo.lock index 05cfd11c17..9ce09b15ee 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1966,9 +1966,9 @@ dependencies = [ [[package]] name = "ethcontract" -version = "0.25.7" +version = "0.25.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "de7f5ab3529a815a6f637e6ed6e62335cdeb535dce0dabd805941bfbfbd22e3f" +checksum = "6c83ea43ad75c3c78468966184a489c195822185df81d5fcaba4b1ba87f382c5" dependencies = [ "arrayvec", "aws-config 0.55.3", @@ -1993,9 +1993,9 @@ dependencies = [ [[package]] name = "ethcontract-common" -version = "0.25.7" +version = "0.25.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "acb731ef73bc8837d0002887263f43641786e34853f438779444293813f24992" +checksum = "51c3253a19d093d249024012ed2f4117d819958f79339fd24d8158fb05306747" dependencies = [ "ethabi", "hex", @@ -2009,9 +2009,9 @@ dependencies = [ [[package]] name = "ethcontract-derive" -version = "0.25.7" +version = "0.25.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "21e8f1e55f0f1c341859821a045d33867323fe8b778944bbf60cff883574cca1" +checksum = "d0f2a75ebcd8fe443014a9f1157fab5bb8950cff7c3a62079b748201ca32570c" dependencies = [ "anyhow", "ethcontract-common", @@ -2023,9 +2023,9 @@ dependencies = [ [[package]] name = "ethcontract-generate" -version = "0.25.7" +version = "0.25.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fc0983a5bc8284b4e7ac33a2818d63315cd19b3ca59ce90fea5a6837c1d65081" +checksum = "b0c0c474f16da0667eec38c2fc4c1ee7ad7cdfd879ffabfd57713444d851c6c7" dependencies = [ "Inflector", "anyhow", @@ -2039,9 +2039,9 @@ dependencies = [ [[package]] name = "ethcontract-mock" -version = "0.25.7" +version = "0.25.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6d56f0a4e3aba1a4f4aa06182c3cbe329c0bfecdd3280662abeabbf7620f9569" +checksum = "ff5cf1c4d38e002747e5ecc69fe50cd8d00d4080f1de32acb4cb6373093b8a1e" dependencies = [ "ethcontract", "hex", @@ -2925,12 +2925,6 @@ dependencies = [ "vcpkg", ] -[[package]] -name = "linked-hash-map" -version = "0.5.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0717cef1bc8b636c6e1c1bbdefc09e6322da8a9321966e8928ef80d20f7f770f" - [[package]] name = "linux-raw-sys" version = "0.4.14" @@ -4506,7 +4500,6 @@ dependencies = [ "tokio", "tracing", "tracing-subscriber", - "ttl_cache", "url", "web3", ] @@ -5070,9 +5063,9 @@ dependencies = [ [[package]] name = "time" -version = "0.3.36" +version = "0.3.37" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5dfd88e563464686c916c7e46e623e520ddc6d79fa6641390f2e3fa86e83e885" +checksum = "35e7868883861bd0e56d9ac6efcaaca0d6d5d82a2a7ec8209ff492c07cf37b21" dependencies = [ "deranged", "itoa", @@ -5093,9 +5086,9 @@ checksum = "ef927ca75afb808a4d64dd374f00a2adf8d0fcff8e7b184af886c3c87ec4a3f3" [[package]] name = "time-macros" -version = "0.2.18" +version = "0.2.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3f252a68540fde3a3877aeea552b832b40ab9a69e318efd078774a01ddee1ccf" +checksum = "2834e6017e3e5e4b9834939793b282bc03b37a3336245fa820e35e233e2a85de" dependencies = [ "num-conv", "time-core", @@ -5413,15 +5406,6 @@ version = "0.2.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b" -[[package]] -name = "ttl_cache" -version = "0.5.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4189890526f0168710b6ee65ceaedf1460c48a14318ceec933cb26baa492096a" -dependencies = [ - "linked-hash-map", -] - [[package]] name = "typenum" version = "1.17.0" diff --git a/Cargo.toml b/Cargo.toml index 6c819517e9..ae3831d7e7 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -13,10 +13,10 @@ clap = { version = "4.5.6", features = ["derive", "env"] } dashmap = "6.1.0" derivative = "2.2.0" derive_more = { version = "1.0.0", features = ["full"] } -ethcontract = { version = "0.25.7", default-features = false, features = ["aws-kms"] } +ethcontract = { version = "0.25.8", default-features = false, features = ["aws-kms"] } mimalloc = "0.1.43" -ethcontract-generate = { version = "0.25.7", default-features = false } -ethcontract-mock = { version = "0.25.7", default-features = false } +ethcontract-generate = { version = "0.25.8", default-features = false } +ethcontract-mock = { version = "0.25.8", default-features = false } ethereum-types = "0.14.1" flate2 = "1.0.30" futures = "0.3.30" @@ -44,7 +44,6 @@ serde_with = "3.8.1" sqlx = { version = "0.7", default-features = false, features = ["runtime-tokio", "tls-native-tls", "bigdecimal", "chrono", "postgres", "macros"] } strum = { version = "0.26.2", features = ["derive"] } tempfile = "3.10.1" -time = { version = "0.3.36", features = ["macros"] } thiserror = "1.0.61" toml = "0.8.14" tokio = { version = "1.38.0", features = ["tracing"] } diff --git a/crates/autopilot/src/infra/solvers/dto/reveal.rs b/crates/autopilot/src/infra/solvers/dto/reveal.rs index 41971d28b1..6bda360d77 100644 --- a/crates/autopilot/src/infra/solvers/dto/reveal.rs +++ b/crates/autopilot/src/infra/solvers/dto/reveal.rs @@ -11,8 +11,8 @@ pub struct Request { /// Unique ID of the solution (per driver competition), to reveal. pub solution_id: u64, /// Auction ID in which the specified solution ID is competing. - #[serde_as(as = "Option")] - pub auction_id: Option, + #[serde_as(as = "serde_with::DisplayFromStr")] + pub auction_id: i64, } #[serde_as] diff --git a/crates/autopilot/src/infra/solvers/dto/settle.rs b/crates/autopilot/src/infra/solvers/dto/settle.rs index 5277eef76e..385ad68212 100644 --- a/crates/autopilot/src/infra/solvers/dto/settle.rs +++ b/crates/autopilot/src/infra/solvers/dto/settle.rs @@ -13,6 +13,6 @@ pub struct Request { /// The last block number in which the solution TX can be included pub submission_deadline_latest_block: u64, /// Auction ID in which the specified solution ID is competing. - #[serde_as(as = "Option")] - pub auction_id: Option, + #[serde_as(as = "serde_with::DisplayFromStr")] + pub auction_id: i64, } diff --git a/crates/autopilot/src/run_loop.rs b/crates/autopilot/src/run_loop.rs index 53b70b4100..3b55738364 100644 --- a/crates/autopilot/src/run_loop.rs +++ b/crates/autopilot/src/run_loop.rs @@ -765,7 +765,7 @@ impl RunLoop { let request = settle::Request { solution_id, submission_deadline_latest_block, - auction_id: None, // Requires 2-stage release for API-break change + auction_id, }; driver .settle(&request, self.config.max_settlement_transaction_wait) diff --git a/crates/autopilot/src/shadow.rs b/crates/autopilot/src/shadow.rs index beddd86acc..44db5bd203 100644 --- a/crates/autopilot/src/shadow.rs +++ b/crates/autopilot/src/shadow.rs @@ -276,7 +276,7 @@ impl RunLoop { let revealed = driver .reveal(&reveal::Request { solution_id, - auction_id: None, // Requires 2-stage release for API-break change + auction_id: request.id, }) .await .map_err(Error::Reveal)?; diff --git a/crates/database/src/trades.rs b/crates/database/src/trades.rs index 36c4c07dfd..38d2ca06d0 100644 --- a/crates/database/src/trades.rs +++ b/crates/database/src/trades.rs @@ -106,6 +106,31 @@ AND t.log_index BETWEEN (SELECT * from previous_settlement) AND $2 .await } +pub async fn token_first_trade_block( + ex: &mut PgConnection, + token: Address, +) -> Result, sqlx::Error> { + const QUERY: &str = r#" +SELECT MIN(sub.block_number) AS earliest_block +FROM ( + SELECT MIN(t.block_number) AS block_number + FROM trades t + JOIN orders o ON t.order_uid = o.uid + WHERE o.sell_token = $1 OR o.buy_token = $1 + + UNION ALL + + SELECT MIN(t.block_number) AS block_number + FROM trades t + JOIN jit_orders j ON t.order_uid = j.uid + WHERE j.sell_token = $1 OR j.buy_token = $1 +) AS sub +"#; + + let (block_number,) = sqlx::query_as(QUERY).bind(token).fetch_one(ex).await?; + Ok(block_number) +} + #[cfg(test)] mod tests { use { @@ -579,4 +604,31 @@ mod tests { }] ); } + + #[tokio::test] + #[ignore] + async fn postgres_token_first_trade_block() { + let mut db = PgConnection::connect("postgresql://").await.unwrap(); + let mut db = db.begin().await.unwrap(); + crate::clear_DANGER_(&mut db).await.unwrap(); + + let token = Default::default(); + assert_eq!(token_first_trade_block(&mut db, token).await.unwrap(), None); + + let (owners, order_ids) = generate_owners_and_order_ids(2, 2).await; + let event_index_a = EventIndex { + block_number: 123, + log_index: 0, + }; + let event_index_b = EventIndex { + block_number: 124, + log_index: 0, + }; + add_order_and_trade(&mut db, owners[0], order_ids[0], event_index_a, None, None).await; + add_order_and_trade(&mut db, owners[1], order_ids[1], event_index_b, None, None).await; + assert_eq!( + token_first_trade_block(&mut db, token).await.unwrap(), + Some(123) + ); + } } diff --git a/crates/driver/src/domain/competition/mod.rs b/crates/driver/src/domain/competition/mod.rs index 2ecde0fdc7..e8c8d964ed 100644 --- a/crates/driver/src/domain/competition/mod.rs +++ b/crates/driver/src/domain/competition/mod.rs @@ -318,17 +318,14 @@ impl Competition { pub async fn reveal( &self, solution_id: u64, - auction_id: Option, + auction_id: auction::Id, ) -> Result { let settlement = self .settlements .lock() .unwrap() .iter() - .find(|s| { - s.solution().get() == solution_id - && auction_id.is_none_or(|id| s.auction_id.0 == id) - }) + .find(|s| s.solution().get() == solution_id && s.auction_id == auction_id) .cloned() .ok_or(Error::SolutionNotAvailable)?; Ok(Revealed { @@ -347,7 +344,7 @@ impl Competition { /// [`Competition::solve`] to generate the solution. pub async fn settle( &self, - auction_id: Option, + auction_id: auction::Id, solution_id: u64, submission_deadline: BlockNo, ) -> Result { @@ -413,16 +410,14 @@ impl Competition { tracing::error!(?err, "Failed to send /settle response"); } } - .instrument( - tracing::info_span!("/settle", solver, auction_id = ?auction_id.map(|id| id.0)), - ) + .instrument(tracing::info_span!("/settle", solver, %auction_id)) .await } } async fn process_settle_request( &self, - auction_id: Option, + auction_id: auction::Id, solution_id: u64, submission_deadline: BlockNo, ) -> Result { @@ -430,10 +425,7 @@ impl Competition { let mut lock = self.settlements.lock().unwrap(); let index = lock .iter() - .position(|s| { - s.solution().get() == solution_id - && auction_id.is_none_or(|id| s.auction_id == id) - }) + .position(|s| s.solution().get() == solution_id && s.auction_id == auction_id) .ok_or(Error::SolutionNotAvailable)?; // remove settlement to ensure we can't settle it twice by accident lock.swap_remove_front(index) @@ -537,7 +529,7 @@ fn merge(solutions: impl Iterator, auction: &Auction) -> Vec, + auction_id: auction::Id, solution_id: u64, submission_deadline: BlockNo, response_sender: oneshot::Sender>, diff --git a/crates/driver/src/domain/mempools.rs b/crates/driver/src/domain/mempools.rs index 3d4ed86135..f0cedd26af 100644 --- a/crates/driver/src/domain/mempools.rs +++ b/crates/driver/src/domain/mempools.rs @@ -105,7 +105,7 @@ impl Mempools { // settlement. This way we only run iterations in blocks that can potentially // include the settlement. let mut block_stream = into_stream(self.ethereum.current_block().clone()); - block_stream.next().await; + let block = block_stream.next().await; // The tx is simulated before submitting the solution to the competition, but a // delay between that and the actual execution can cause the simulation to be @@ -116,7 +116,7 @@ impl Mempools { ?err, "settlement tx simulation reverted before submitting to the mempool" ); - return Err(Error::SimulationRevert); + return Err(Error::SimulationRevert(block.map(|block| block.number))); } else { tracing::warn!( ?err, @@ -142,7 +142,12 @@ impl Mempools { }); match receipt { TxStatus::Executed => return Ok(hash.clone()), - TxStatus::Reverted => return Err(Error::Revert(hash.clone())), + TxStatus::Reverted => { + return Err(Error::Revert { + tx_id: hash.clone(), + block_number: block.number, + }) + } TxStatus::Pending => { // Check if the current block reached the submission deadline block number if block.number >= submission_deadline { @@ -172,7 +177,7 @@ impl Mempools { ?err, "tx started failing in mempool, cancelling" ); - return Err(Error::SimulationRevert); + return Err(Error::SimulationRevert(Some(block.number))); } else { tracing::warn!(?hash, ?err, "couldn't re-simulate tx"); } @@ -235,10 +240,13 @@ pub enum RevertProtection { #[derive(Debug, thiserror::Error)] pub enum Error { - #[error("Mined reverted transaction: {0:?}")] - Revert(eth::TxId), - #[error("Simulation started reverting during submission")] - SimulationRevert, + #[error("Mined reverted transaction: {tx_id:?}, block number: {block_number}")] + Revert { + tx_id: eth::TxId, + block_number: BlockNo, + }, + #[error("Simulation started reverting during submission, block number: {0:?}")] + SimulationRevert(Option), #[error("Settlement did not get included in time")] Expired, #[error("Strategy disabled for this tx")] diff --git a/crates/driver/src/infra/api/routes/reveal/dto/reveal_request.rs b/crates/driver/src/infra/api/routes/reveal/dto/reveal_request.rs index c782ed57fc..e0e762dd47 100644 --- a/crates/driver/src/infra/api/routes/reveal/dto/reveal_request.rs +++ b/crates/driver/src/infra/api/routes/reveal/dto/reveal_request.rs @@ -7,6 +7,6 @@ pub struct RevealRequest { /// Unique ID of the solution (per driver competition), to reveal. pub solution_id: u64, /// Auction ID in which the specified solution ID is competing. - #[serde_as(as = "Option")] - pub auction_id: Option, + #[serde_as(as = "serde_with::DisplayFromStr")] + pub auction_id: i64, } diff --git a/crates/driver/src/infra/api/routes/reveal/mod.rs b/crates/driver/src/infra/api/routes/reveal/mod.rs index b96ce290f8..7a2d6081e5 100644 --- a/crates/driver/src/infra/api/routes/reveal/mod.rs +++ b/crates/driver/src/infra/api/routes/reveal/mod.rs @@ -1,9 +1,12 @@ mod dto; use { - crate::infra::{ - api::{Error, State}, - observe, + crate::{ + domain::competition::auction, + infra::{ + api::{self, Error, State}, + observe, + }, }, tracing::Instrument, }; @@ -16,11 +19,13 @@ async fn route( state: axum::extract::State, req: axum::Json, ) -> Result, (hyper::StatusCode, axum::Json)> { + let auction_id = + auction::Id::try_from(req.auction_id).map_err(api::routes::AuctionError::from)?; let handle_request = async { observe::revealing(); let result = state .competition() - .reveal(req.solution_id, req.auction_id) + .reveal(req.solution_id, auction_id) .await; observe::revealed(state.solver().name(), &result); let result = result?; @@ -28,6 +33,6 @@ async fn route( }; handle_request - .instrument(tracing::info_span!("/reveal", solver = %state.solver().name(), req.auction_id)) + .instrument(tracing::info_span!("/reveal", solver = %state.solver().name(), %auction_id)) .await } diff --git a/crates/driver/src/infra/api/routes/settle/dto/settle_request.rs b/crates/driver/src/infra/api/routes/settle/dto/settle_request.rs index 844a716738..6f5735fbce 100644 --- a/crates/driver/src/infra/api/routes/settle/dto/settle_request.rs +++ b/crates/driver/src/infra/api/routes/settle/dto/settle_request.rs @@ -9,6 +9,6 @@ pub struct SettleRequest { /// The last block number in which the solution TX can be included pub submission_deadline_latest_block: u64, /// Auction ID in which this solution is competing. - #[serde_as(as = "Option")] - pub auction_id: Option, + #[serde_as(as = "serde_with::DisplayFromStr")] + pub auction_id: i64, } diff --git a/crates/driver/src/infra/api/routes/settle/mod.rs b/crates/driver/src/infra/api/routes/settle/mod.rs index 516e3efb70..1334693a6e 100644 --- a/crates/driver/src/infra/api/routes/settle/mod.rs +++ b/crates/driver/src/infra/api/routes/settle/mod.rs @@ -19,11 +19,8 @@ async fn route( state: axum::extract::State, req: axum::Json, ) -> Result<(), (hyper::StatusCode, axum::Json)> { - let auction_id = req - .auction_id - .map(auction::Id::try_from) - .transpose() - .map_err(Into::::into)?; + let auction_id = + auction::Id::try_from(req.auction_id).map_err(api::routes::AuctionError::from)?; let solver = state.solver().name().to_string(); let handle_request = async move { @@ -39,7 +36,7 @@ async fn route( observe::settled(state.solver().name(), &result); result.map(|_| ()).map_err(Into::into) } - .instrument(tracing::info_span!("/settle", solver, auction_id = ?auction_id.map(|id| id.0))); + .instrument(tracing::info_span!("/settle", solver, %auction_id)); // Handle `/settle` call in a background task to ensure that we correctly // submit the settlement (or cancellation) on-chain even if the server diff --git a/crates/driver/src/infra/notify/mod.rs b/crates/driver/src/infra/notify/mod.rs index 4f33f3e5e6..edb4c55458 100644 --- a/crates/driver/src/infra/notify/mod.rs +++ b/crates/driver/src/infra/notify/mod.rs @@ -105,8 +105,8 @@ pub fn executed( ) { let kind = match res { Ok(hash) => notification::Settlement::Success(hash.clone()), - Err(Error::Revert(hash)) => notification::Settlement::Revert(hash.clone()), - Err(Error::SimulationRevert) => notification::Settlement::SimulationRevert, + Err(Error::Revert { tx_id: hash, .. }) => notification::Settlement::Revert(hash.clone()), + Err(Error::SimulationRevert { .. }) => notification::Settlement::SimulationRevert, Err(Error::Expired) => notification::Settlement::Expired, Err(Error::Other(_) | Error::Disabled) => notification::Settlement::Fail, }; diff --git a/crates/driver/src/infra/observe/mod.rs b/crates/driver/src/infra/observe/mod.rs index f8c1394e54..0dff50d918 100644 --- a/crates/driver/src/infra/observe/mod.rs +++ b/crates/driver/src/infra/observe/mod.rs @@ -339,7 +339,7 @@ pub fn mempool_executed( } let result = match res { Ok(_) => "Success", - Err(mempools::Error::Revert(_) | mempools::Error::SimulationRevert) => "Revert", + Err(mempools::Error::Revert { .. } | mempools::Error::SimulationRevert { .. }) => "Revert", Err(mempools::Error::Expired) => "Expired", Err(mempools::Error::Other(_)) => "Other", Err(mempools::Error::Disabled) => "Disabled", diff --git a/crates/observe/Cargo.toml b/crates/observe/Cargo.toml index f2b1029ef0..75ebe24279 100644 --- a/crates/observe/Cargo.toml +++ b/crates/observe/Cargo.toml @@ -13,7 +13,7 @@ futures = { workspace = true } pin-project-lite = "0.2.14" prometheus = { workspace = true } prometheus-metric-storage = { workspace = true } -time = { workspace = true } +time = { version = "0.3.37", features = ["macros"] } tokio = { workspace = true, features = [ "fs" ] } tracing = { workspace = true } tracing-subscriber = { workspace = true, features = ["env-filter", "fmt", "time"] } diff --git a/crates/orderbook/src/api.rs b/crates/orderbook/src/api.rs index a272950148..8eb7321f1f 100644 --- a/crates/orderbook/src/api.rs +++ b/crates/orderbook/src/api.rs @@ -23,6 +23,7 @@ mod get_order_by_uid; mod get_order_status; mod get_orders_by_tx; mod get_solver_competition; +mod get_token_metadata; mod get_total_surplus; mod get_trades; mod get_user_orders; @@ -105,7 +106,11 @@ pub fn handle_all_routes( ), ( "v1/get_total_surplus", - box_filter(get_total_surplus::get(database)), + box_filter(get_total_surplus::get(database.clone())), + ), + ( + "v1/get_token_metadata", + box_filter(get_token_metadata::get_token_metadata(database)), ), ]; diff --git a/crates/orderbook/src/api/get_token_metadata.rs b/crates/orderbook/src/api/get_token_metadata.rs new file mode 100644 index 0000000000..a08ec3a09d --- /dev/null +++ b/crates/orderbook/src/api/get_token_metadata.rs @@ -0,0 +1,31 @@ +use { + crate::database::Postgres, + hyper::StatusCode, + primitive_types::H160, + std::convert::Infallible, + warp::{reply, Filter, Rejection}, +}; + +fn get_native_prices_request() -> impl Filter + Clone { + warp::path!("v1" / "token" / H160 / "metadata").and(warp::get()) +} + +pub fn get_token_metadata( + db: Postgres, +) -> impl Filter + Clone { + get_native_prices_request().and_then(move |token: H160| { + let db = db.clone(); + async move { + let result = db.token_metadata(&token).await; + let response = match result { + Ok(metadata) => reply::with_status(reply::json(&metadata), StatusCode::OK), + Err(err) => { + tracing::error!(?err, ?token, "Failed to fetch token's first trade block"); + crate::api::internal_error_reply() + } + }; + + Result::<_, Infallible>::Ok(response) + } + }) +} diff --git a/crates/orderbook/src/database/orders.rs b/crates/orderbook/src/database/orders.rs index d077b1c8a2..51bce8a8c6 100644 --- a/crates/orderbook/src/database/orders.rs +++ b/crates/orderbook/src/database/orders.rs @@ -1,6 +1,6 @@ use { super::Postgres, - crate::orderbook::AddOrderError, + crate::{dto::TokenMetadata, orderbook::AddOrderError}, anyhow::{Context as _, Result}, app_data::AppDataHash, async_trait::async_trait, @@ -492,6 +492,27 @@ impl Postgres { .map(full_order_into_model_order) .collect::>>() } + + pub async fn token_metadata(&self, token: &H160) -> Result { + let timer = super::Metrics::get() + .database_queries + .with_label_values(&["token_first_trade_block"]) + .start_timer(); + + let mut ex = self.pool.acquire().await?; + let block_number = database::trades::token_first_trade_block(&mut ex, ByteArray(token.0)) + .await + .map_err(anyhow::Error::from)? + .map(u32::try_from) + .transpose() + .map_err(anyhow::Error::from)?; + + timer.stop_and_record(); + + Ok(TokenMetadata { + first_trade_block: block_number, + }) + } } #[async_trait] diff --git a/crates/orderbook/src/dto/mod.rs b/crates/orderbook/src/dto/mod.rs index fb3365dd95..b706e79d4d 100644 --- a/crates/orderbook/src/dto/mod.rs +++ b/crates/orderbook/src/dto/mod.rs @@ -5,3 +5,11 @@ pub use { auction::{Auction, AuctionId, AuctionWithId}, order::Order, }; +use {serde::Serialize, serde_with::serde_as}; + +#[serde_as] +#[derive(Serialize)] +#[serde(rename_all = "camelCase")] +pub struct TokenMetadata { + pub first_trade_block: Option, +} diff --git a/crates/shared/Cargo.toml b/crates/shared/Cargo.toml index 87cb190f1f..622f3cf536 100644 --- a/crates/shared/Cargo.toml +++ b/crates/shared/Cargo.toml @@ -22,7 +22,6 @@ contracts = { path = "../contracts" } dashmap = { workspace = true } database = { path = "../database" } derive_more = { workspace = true } -ttl_cache = "0.5" derivative = { workspace = true } ethcontract = { workspace = true } ethrpc = { path = "../ethrpc" } diff --git a/crates/shared/src/sources/uniswap_v2/pool_fetching.rs b/crates/shared/src/sources/uniswap_v2/pool_fetching.rs index b7966663f9..377ecdcc6d 100644 --- a/crates/shared/src/sources/uniswap_v2/pool_fetching.rs +++ b/crates/shared/src/sources/uniswap_v2/pool_fetching.rs @@ -2,6 +2,7 @@ use { super::pair_provider::PairProvider, crate::{baseline_solver::BaselineSolvable, ethrpc::Web3, recent_block_cache::Block}, anyhow::Result, + cached::{Cached, TimedCache}, contracts::{errors::EthcontractErrorType, IUniswapLikePair, ERC20}, ethcontract::{errors::MethodError, BlockId, H160, U256}, futures::{ @@ -15,7 +16,6 @@ use { sync::{LazyLock, RwLock}, time::Duration, }, - ttl_cache::TtlCache, }; const POOL_SWAP_GAS_COST: usize = 60_000; @@ -184,8 +184,7 @@ impl BaselineSolvable for Pool { pub struct PoolFetcher { pub pool_reader: Reader, pub web3: Web3, - pub cache_time: Duration, - pub non_existent_pools: RwLock>, + pub non_existent_pools: RwLock>, } impl PoolFetcher { @@ -193,8 +192,7 @@ impl PoolFetcher { Self { pool_reader: reader, web3, - cache_time, - non_existent_pools: RwLock::new(TtlCache::new(usize::MAX)), + non_existent_pools: RwLock::new(TimedCache::with_lifespan(cache_time.as_secs())), } } } @@ -207,8 +205,8 @@ where async fn fetch(&self, token_pairs: HashSet, at_block: Block) -> Result> { let mut token_pairs: Vec<_> = token_pairs.into_iter().collect(); { - let non_existent_pools = self.non_existent_pools.read().unwrap(); - token_pairs.retain(|pair| !non_existent_pools.contains_key(pair)); + let mut non_existent_pools = self.non_existent_pools.write().unwrap(); + token_pairs.retain(|pair| non_existent_pools.cache_get(pair).is_none()); } let block = BlockId::Number(at_block.into()); let futures = token_pairs @@ -230,7 +228,7 @@ where tracing::debug!(token_pairs = ?new_missing_pairs, "stop indexing liquidity"); let mut non_existent_pools = self.non_existent_pools.write().unwrap(); for pair in new_missing_pairs { - non_existent_pools.insert(pair, (), self.cache_time); + non_existent_pools.cache_set(pair, ()); } } Ok(pools) diff --git a/database/sql/V077__orders_token_indexes.sql b/database/sql/V077__orders_token_indexes.sql new file mode 100644 index 0000000000..d212427efd --- /dev/null +++ b/database/sql/V077__orders_token_indexes.sql @@ -0,0 +1,3 @@ +CREATE INDEX orders_sell_buy_tokens ON orders (sell_token, buy_token); + +CREATE INDEX jit_orders_sell_buy_tokens ON jit_orders (sell_token, buy_token);