From b5b2a5a9686b65885480e759822dc59492d5f9d8 Mon Sep 17 00:00:00 2001 From: Dzejkop Date: Fri, 22 Dec 2023 12:24:18 +0100 Subject: [PATCH] Use config params & refactor tests --- src/config.rs | 22 ++++++ src/tasks/handle_reorgs.rs | 15 +--- tests/common/anvil_builder.rs | 67 ++++++++++++++++ tests/common/mod.rs | 131 ++++---------------------------- tests/common/service_builder.rs | 96 +++++++++++++++++++++++ tests/create_relayer.rs | 4 +- tests/escalation.rs | 12 +-- tests/reorg.rs | 49 +++++------- tests/rpc_access.rs | 8 +- tests/send_many_txs.rs | 8 +- tests/send_tx.rs | 7 +- 11 files changed, 228 insertions(+), 191 deletions(-) create mode 100644 tests/common/anvil_builder.rs create mode 100644 tests/common/service_builder.rs diff --git a/src/config.rs b/src/config.rs index 7e1e534..2869c1e 100644 --- a/src/config.rs +++ b/src/config.rs @@ -18,6 +18,12 @@ pub struct TxSitterConfig { #[serde(with = "humantime_serde")] pub escalation_interval: Duration, + #[serde(with = "humantime_serde", default = "default_soft_reorg_interval")] + pub soft_reorg_interval: Duration, + + #[serde(with = "humantime_serde", default = "default_hard_reorg_interval")] + pub hard_reorg_interval: Duration, + #[serde(default)] pub datadog_enabled: bool, @@ -28,6 +34,14 @@ pub struct TxSitterConfig { pub predefined: Option, } +const fn default_soft_reorg_interval() -> Duration { + Duration::from_secs(60) +} + +const fn default_hard_reorg_interval() -> Duration { + Duration::from_secs(60 * 60) +} + #[derive(Debug, Clone, Serialize, Deserialize)] #[serde(rename_all = "snake_case")] pub struct Predefined { @@ -148,6 +162,8 @@ mod tests { const WITH_DB_CONNECTION_STRING: &str = indoc! {r#" [service] escalation_interval = "1h" + soft_reorg_interval = "1m" + hard_reorg_interval = "1h" datadog_enabled = false statsd_enabled = false @@ -165,6 +181,8 @@ mod tests { const WITH_DB_PARTS: &str = indoc! {r#" [service] escalation_interval = "1h" + soft_reorg_interval = "1m" + hard_reorg_interval = "1h" datadog_enabled = false statsd_enabled = false @@ -188,6 +206,8 @@ mod tests { let config = Config { service: TxSitterConfig { escalation_interval: Duration::from_secs(60 * 60), + soft_reorg_interval: default_soft_reorg_interval(), + hard_reorg_interval: default_hard_reorg_interval(), datadog_enabled: false, statsd_enabled: false, predefined: None, @@ -214,6 +234,8 @@ mod tests { let config = Config { service: TxSitterConfig { escalation_interval: Duration::from_secs(60 * 60), + soft_reorg_interval: default_soft_reorg_interval(), + hard_reorg_interval: default_hard_reorg_interval(), datadog_enabled: false, statsd_enabled: false, predefined: None, diff --git a/src/tasks/handle_reorgs.rs b/src/tasks/handle_reorgs.rs index 43c5f56..7b9a12d 100644 --- a/src/tasks/handle_reorgs.rs +++ b/src/tasks/handle_reorgs.rs @@ -1,12 +1,7 @@ use std::sync::Arc; -use std::time::Duration; use crate::app::App; -// TODO: Make this configurable -const TIME_BETWEEN_HARD_REORGS_SECONDS: i64 = 60 * 60; // Once every hour -const TIME_BETWEEN_SOFT_REORGS_SECONDS: i64 = 60; // Once every minute - pub async fn handle_hard_reorgs(app: Arc) -> eyre::Result<()> { loop { tracing::info!("Handling hard reorgs"); @@ -17,10 +12,7 @@ pub async fn handle_hard_reorgs(app: Arc) -> eyre::Result<()> { tracing::info!(id = tx, "Tx hard reorged"); } - tokio::time::sleep(Duration::from_secs( - TIME_BETWEEN_HARD_REORGS_SECONDS as u64, - )) - .await; + tokio::time::sleep(app.config.service.hard_reorg_interval).await; } } @@ -34,9 +26,6 @@ pub async fn handle_soft_reorgs(app: Arc) -> eyre::Result<()> { tracing::info!(id = tx, "Tx soft reorged"); } - tokio::time::sleep(Duration::from_secs( - TIME_BETWEEN_SOFT_REORGS_SECONDS as u64, - )) - .await; + tokio::time::sleep(app.config.service.soft_reorg_interval).await; } } diff --git a/tests/common/anvil_builder.rs b/tests/common/anvil_builder.rs new file mode 100644 index 0000000..ac1fd55 --- /dev/null +++ b/tests/common/anvil_builder.rs @@ -0,0 +1,67 @@ +use std::time::Duration; + +use ethers::providers::Middleware; +use ethers::types::{Eip1559TransactionRequest, U256}; +use ethers::utils::{Anvil, AnvilInstance}; + +use super::prelude::{ + setup_middleware, DEFAULT_ANVIL_ACCOUNT, DEFAULT_ANVIL_BLOCK_TIME, + SECONDARY_ANVIL_PRIVATE_KEY, +}; + +#[derive(Debug, Clone, Default)] +pub struct AnvilBuilder { + pub block_time: Option, + pub port: Option, +} + +impl AnvilBuilder { + pub fn block_time(mut self, block_time: u64) -> Self { + self.block_time = Some(block_time); + self + } + + pub fn port(mut self, port: u16) -> Self { + self.port = Some(port); + self + } + + pub async fn spawn(self) -> eyre::Result { + let mut anvil = Anvil::new(); + + let block_time = if let Some(block_time) = self.block_time { + block_time + } else { + DEFAULT_ANVIL_BLOCK_TIME + }; + anvil = anvil.block_time(block_time); + + if let Some(port) = self.port { + anvil = anvil.port(port); + } + + let anvil = anvil.spawn(); + + let middleware = + setup_middleware(anvil.endpoint(), SECONDARY_ANVIL_PRIVATE_KEY) + .await?; + + // Wait for the chain to start and produce at least one block + tokio::time::sleep(Duration::from_secs(block_time)).await; + + // We need to seed some transactions so we can get fee estimates on the first block + middleware + .send_transaction( + Eip1559TransactionRequest { + to: Some(DEFAULT_ANVIL_ACCOUNT.into()), + value: Some(U256::from(100u64)), + ..Default::default() + }, + None, + ) + .await? + .await?; + + Ok(anvil) + } +} diff --git a/tests/common/mod.rs b/tests/common/mod.rs index cccb402..3d5f121 100644 --- a/tests/common/mod.rs +++ b/tests/common/mod.rs @@ -1,40 +1,43 @@ #![allow(dead_code)] // Needed because this module is imported as module by many test crates -use std::net::{Ipv4Addr, SocketAddr, SocketAddrV4}; use std::sync::Arc; -use std::time::Duration; use ethers::core::k256::ecdsa::SigningKey; use ethers::middleware::SignerMiddleware; use ethers::providers::{Http, Middleware, Provider}; use ethers::signers::{LocalWallet, Signer}; -use ethers::types::{Address, Eip1559TransactionRequest, H160, U256}; -use ethers::utils::{Anvil, AnvilInstance}; +use ethers::types::{Address, H160}; use postgres_docker_utils::DockerContainerGuard; use tracing::level_filters::LevelFilter; use tracing_subscriber::layer::SubscriberExt; use tracing_subscriber::util::SubscriberInitExt; use tracing_subscriber::EnvFilter; -use tx_sitter::client::TxSitterClient; -use tx_sitter::config::{ - Config, DatabaseConfig, KeysConfig, LocalKeysConfig, Predefined, - PredefinedNetwork, PredefinedRelayer, ServerConfig, TxSitterConfig, -}; -use tx_sitter::service::Service; pub type AppMiddleware = SignerMiddleware>, LocalWallet>; +mod anvil_builder; +mod service_builder; + +pub use self::anvil_builder::AnvilBuilder; +pub use self::service_builder::ServiceBuilder; + #[allow(unused_imports)] pub mod prelude { pub use std::time::Duration; + pub use ethers::prelude::{Http, Provider}; pub use ethers::providers::Middleware; - pub use ethers::types::{Eip1559TransactionRequest, U256}; + pub use ethers::types::{Eip1559TransactionRequest, H256, U256}; pub use ethers::utils::parse_units; + pub use futures::stream::FuturesUnordered; + pub use futures::StreamExt; + pub use tx_sitter::api_key::ApiKey; + pub use tx_sitter::client::TxSitterClient; pub use tx_sitter::server::routes::relayer::{ - CreateRelayerRequest, CreateRelayerResponse, + CreateApiKeyResponse, CreateRelayerRequest, CreateRelayerResponse, }; pub use tx_sitter::server::routes::transaction::SendTxRequest; + pub use url::Url; pub use super::*; } @@ -60,63 +63,6 @@ pub const DEFAULT_ANVIL_BLOCK_TIME: u64 = 2; pub const DEFAULT_RELAYER_ID: &str = "1b908a34-5dc1-4d2d-a146-5eb46e975830"; -#[derive(Debug, Clone, Default)] -pub struct AnvilBuilder { - pub block_time: Option, - pub port: Option, -} - -impl AnvilBuilder { - pub fn block_time(mut self, block_time: u64) -> Self { - self.block_time = Some(block_time); - self - } - - pub fn port(mut self, port: u16) -> Self { - self.port = Some(port); - self - } - - pub async fn spawn(self) -> eyre::Result { - let mut anvil = Anvil::new(); - - let block_time = if let Some(block_time) = self.block_time { - block_time - } else { - DEFAULT_ANVIL_BLOCK_TIME - }; - anvil = anvil.block_time(block_time); - - if let Some(port) = self.port { - anvil = anvil.port(port); - } - - let anvil = anvil.spawn(); - - let middleware = - setup_middleware(anvil.endpoint(), SECONDARY_ANVIL_PRIVATE_KEY) - .await?; - - // Wait for the chain to start and produce at least one block - tokio::time::sleep(Duration::from_secs(block_time)).await; - - // We need to seed some transactions so we can get fee estimates on the first block - middleware - .send_transaction( - Eip1559TransactionRequest { - to: Some(DEFAULT_ANVIL_ACCOUNT.into()), - value: Some(U256::from(100u64)), - ..Default::default() - }, - None, - ) - .await? - .await?; - - Ok(anvil) - } -} - pub fn setup_tracing() { tracing_subscriber::registry() .with(tracing_subscriber::fmt::layer().pretty().compact()) @@ -137,53 +83,6 @@ pub async fn setup_db() -> eyre::Result<(String, DockerContainerGuard)> { Ok((url, db_container)) } -pub async fn setup_service( - anvil: &AnvilInstance, - db_connection_url: &str, - escalation_interval: Duration, -) -> eyre::Result<(Service, TxSitterClient)> { - let anvil_private_key = hex::encode(DEFAULT_ANVIL_PRIVATE_KEY); - - let config = Config { - service: TxSitterConfig { - escalation_interval, - datadog_enabled: false, - statsd_enabled: false, - predefined: Some(Predefined { - network: PredefinedNetwork { - chain_id: DEFAULT_ANVIL_CHAIN_ID, - name: "Anvil".to_string(), - http_rpc: anvil.endpoint(), - ws_rpc: anvil.ws_endpoint(), - }, - relayer: PredefinedRelayer { - name: "Anvil".to_string(), - id: DEFAULT_RELAYER_ID.to_string(), - key_id: anvil_private_key, - chain_id: DEFAULT_ANVIL_CHAIN_ID, - }, - }), - }, - server: ServerConfig { - host: SocketAddr::V4(SocketAddrV4::new( - Ipv4Addr::new(127, 0, 0, 1), - 0, - )), - username: None, - password: None, - }, - database: DatabaseConfig::connection_string(db_connection_url), - keys: KeysConfig::Local(LocalKeysConfig::default()), - }; - - let service = Service::new(config).await?; - - let client = - TxSitterClient::new(format!("http://{}", service.local_addr())); - - Ok((service, client)) -} - pub async fn setup_middleware( rpc_url: impl AsRef, private_key: &[u8], diff --git a/tests/common/service_builder.rs b/tests/common/service_builder.rs new file mode 100644 index 0000000..492e538 --- /dev/null +++ b/tests/common/service_builder.rs @@ -0,0 +1,96 @@ +use std::net::{Ipv4Addr, SocketAddr, SocketAddrV4}; +use std::time::Duration; + +use ethers::utils::AnvilInstance; +use tx_sitter::client::TxSitterClient; +use tx_sitter::config::{ + Config, DatabaseConfig, KeysConfig, LocalKeysConfig, Predefined, + PredefinedNetwork, PredefinedRelayer, ServerConfig, TxSitterConfig, +}; +use tx_sitter::service::Service; + +use super::prelude::{ + DEFAULT_ANVIL_CHAIN_ID, DEFAULT_ANVIL_PRIVATE_KEY, DEFAULT_RELAYER_ID, +}; + +pub struct ServiceBuilder { + escalation_interval: Duration, + soft_reorg_interval: Duration, + hard_reorg_interval: Duration, +} + +impl Default for ServiceBuilder { + fn default() -> Self { + Self { + escalation_interval: Duration::from_secs(30), + soft_reorg_interval: Duration::from_secs(45), + hard_reorg_interval: Duration::from_secs(60), + } + } +} + +impl ServiceBuilder { + pub fn escalation_interval(mut self, interval: Duration) -> Self { + self.escalation_interval = interval; + self + } + + pub fn soft_reorg_interval(mut self, interval: Duration) -> Self { + self.soft_reorg_interval = interval; + self + } + + pub fn hard_reorg_interval(mut self, interval: Duration) -> Self { + self.hard_reorg_interval = interval; + self + } + + pub async fn build( + self, + anvil: &AnvilInstance, + db_url: &str, + ) -> eyre::Result<(Service, TxSitterClient)> { + let anvil_private_key = hex::encode(DEFAULT_ANVIL_PRIVATE_KEY); + + let config = Config { + service: TxSitterConfig { + escalation_interval: self.escalation_interval, + soft_reorg_interval: self.soft_reorg_interval, + hard_reorg_interval: self.hard_reorg_interval, + datadog_enabled: false, + statsd_enabled: false, + predefined: Some(Predefined { + network: PredefinedNetwork { + chain_id: DEFAULT_ANVIL_CHAIN_ID, + name: "Anvil".to_string(), + http_rpc: anvil.endpoint(), + ws_rpc: anvil.ws_endpoint(), + }, + relayer: PredefinedRelayer { + name: "Anvil".to_string(), + id: DEFAULT_RELAYER_ID.to_string(), + key_id: anvil_private_key, + chain_id: DEFAULT_ANVIL_CHAIN_ID, + }, + }), + }, + server: ServerConfig { + host: SocketAddr::V4(SocketAddrV4::new( + Ipv4Addr::new(127, 0, 0, 1), + 0, + )), + username: None, + password: None, + }, + database: DatabaseConfig::connection_string(db_url), + keys: KeysConfig::Local(LocalKeysConfig::default()), + }; + + let service = Service::new(config).await?; + + let client = + TxSitterClient::new(format!("http://{}", service.local_addr())); + + Ok((service, client)) + } +} diff --git a/tests/create_relayer.rs b/tests/create_relayer.rs index 4be92a3..17a8233 100644 --- a/tests/create_relayer.rs +++ b/tests/create_relayer.rs @@ -2,8 +2,6 @@ mod common; use crate::common::prelude::*; -const ESCALATION_INTERVAL: Duration = Duration::from_secs(30); - #[tokio::test] async fn create_relayer() -> eyre::Result<()> { setup_tracing(); @@ -12,7 +10,7 @@ async fn create_relayer() -> eyre::Result<()> { let anvil = AnvilBuilder::default().spawn().await?; let (_service, client) = - setup_service(&anvil, &db_url, ESCALATION_INTERVAL).await?; + ServiceBuilder::default().build(&anvil, &db_url).await?; let CreateRelayerResponse { .. } = client .create_relayer(&CreateRelayerRequest { diff --git a/tests/escalation.rs b/tests/escalation.rs index 285a1d8..cab10c6 100644 --- a/tests/escalation.rs +++ b/tests/escalation.rs @@ -1,11 +1,5 @@ mod common; -use ethers::prelude::{Http, Provider}; -use ethers::types::H256; -use tx_sitter::api_key::ApiKey; -use tx_sitter::client::TxSitterClient; -use tx_sitter::server::routes::relayer::CreateApiKeyResponse; - use crate::common::prelude::*; const ESCALATION_INTERVAL: Duration = Duration::from_secs(2); @@ -21,8 +15,10 @@ async fn escalation() -> eyre::Result<()> { .spawn() .await?; - let (_service, client) = - setup_service(&anvil, &db_url, ESCALATION_INTERVAL).await?; + let (_service, client) = ServiceBuilder::default() + .escalation_interval(ESCALATION_INTERVAL) + .build(&anvil, &db_url) + .await?; let CreateApiKeyResponse { api_key } = client.create_relayer_api_key(DEFAULT_RELAYER_ID).await?; diff --git a/tests/reorg.rs b/tests/reorg.rs index f50a9f1..a25ae9a 100644 --- a/tests/reorg.rs +++ b/tests/reorg.rs @@ -1,27 +1,19 @@ mod common; -use ethers::prelude::{Http, Provider}; -use ethers::types::H256; -use tx_sitter::api_key::ApiKey; -use tx_sitter::client::TxSitterClient; -use tx_sitter::server::routes::relayer::CreateApiKeyResponse; - use crate::common::prelude::*; -const ESCALATION_INTERVAL: Duration = Duration::from_secs(2); - #[tokio::test] async fn reorg() -> eyre::Result<()> { setup_tracing(); let (db_url, _db_container) = setup_db().await?; - let anvil = AnvilBuilder::default() - .spawn() - .await?; + let anvil = AnvilBuilder::default().spawn().await?; let anvil_port = anvil.port(); - let (_service, client) = - setup_service(&anvil, &db_url, ESCALATION_INTERVAL).await?; + let (_service, client) = ServiceBuilder::default() + .hard_reorg_interval(Duration::from_secs(2)) + .build(&anvil, &db_url) + .await?; let CreateApiKeyResponse { api_key } = client.create_relayer_api_key(DEFAULT_RELAYER_ID).await?; @@ -45,9 +37,12 @@ async fn reorg() -> eyre::Result<()> { await_balance(&provider, value).await?; // Drop anvil to simulate a reorg + tracing::warn!("Dropping anvil & restarting at port {anvil_port}"); drop(anvil); - AnvilBuilder::default().port(anvil_port).spawn().await?; + let anvil = AnvilBuilder::default().port(anvil_port).spawn().await?; + let provider = setup_provider(anvil.endpoint()).await?; + await_balance(&provider, value).await?; Ok(()) @@ -58,7 +53,15 @@ async fn await_balance( value: U256, ) -> eyre::Result<()> { for _ in 0..24 { - let balance = provider.get_balance(ARBITRARY_ADDRESS, None).await?; + let balance = match provider.get_balance(ARBITRARY_ADDRESS, None).await + { + Ok(balance) => balance, + Err(err) => { + tracing::warn!("Error getting balance: {:?}", err); + tokio::time::sleep(Duration::from_secs(3)).await; + continue; + } + }; if balance == value { return Ok(()); @@ -69,19 +72,3 @@ async fn await_balance( eyre::bail!("Balance not updated in time"); } - -async fn get_tx_hash( - client: &TxSitterClient, - api_key: &ApiKey, - tx_id: &str, -) -> eyre::Result { - loop { - let tx = client.get_tx(api_key, tx_id).await?; - - if let Some(tx_hash) = tx.tx_hash { - return Ok(tx_hash); - } else { - tokio::time::sleep(Duration::from_secs(3)).await; - } - } -} diff --git a/tests/rpc_access.rs b/tests/rpc_access.rs index 7038e56..f646210 100644 --- a/tests/rpc_access.rs +++ b/tests/rpc_access.rs @@ -1,13 +1,7 @@ mod common; -use ethers::prelude::*; -use tx_sitter::server::routes::relayer::CreateApiKeyResponse; -use url::Url; - use crate::common::prelude::*; -const ESCALATION_INTERVAL: Duration = Duration::from_secs(30); - #[tokio::test] async fn rpc_access() -> eyre::Result<()> { setup_tracing(); @@ -16,7 +10,7 @@ async fn rpc_access() -> eyre::Result<()> { let anvil = AnvilBuilder::default().spawn().await?; let (service, client) = - setup_service(&anvil, &db_url, ESCALATION_INTERVAL).await?; + ServiceBuilder::default().build(&anvil, &db_url).await?; let CreateApiKeyResponse { api_key } = client.create_relayer_api_key(DEFAULT_RELAYER_ID).await?; diff --git a/tests/send_many_txs.rs b/tests/send_many_txs.rs index 0649326..4325423 100644 --- a/tests/send_many_txs.rs +++ b/tests/send_many_txs.rs @@ -1,13 +1,7 @@ mod common; -use futures::stream::FuturesUnordered; -use futures::StreamExt; -use tx_sitter::server::routes::relayer::CreateApiKeyResponse; - use crate::common::prelude::*; -const ESCALATION_INTERVAL: Duration = Duration::from_secs(30); - #[tokio::test] async fn send_many_txs() -> eyre::Result<()> { setup_tracing(); @@ -16,7 +10,7 @@ async fn send_many_txs() -> eyre::Result<()> { let anvil = AnvilBuilder::default().spawn().await?; let (_service, client) = - setup_service(&anvil, &db_url, ESCALATION_INTERVAL).await?; + ServiceBuilder::default().build(&anvil, &db_url).await?; let CreateApiKeyResponse { api_key } = client.create_relayer_api_key(DEFAULT_RELAYER_ID).await?; diff --git a/tests/send_tx.rs b/tests/send_tx.rs index b7f5f61..1a8fe62 100644 --- a/tests/send_tx.rs +++ b/tests/send_tx.rs @@ -1,11 +1,7 @@ mod common; -use tx_sitter::server::routes::relayer::CreateApiKeyResponse; - use crate::common::prelude::*; -const ESCALATION_INTERVAL: Duration = Duration::from_secs(30); - #[tokio::test] async fn send_tx() -> eyre::Result<()> { setup_tracing(); @@ -14,8 +10,7 @@ async fn send_tx() -> eyre::Result<()> { let anvil = AnvilBuilder::default().spawn().await?; let (_service, client) = - setup_service(&anvil, &db_url, ESCALATION_INTERVAL).await?; - + ServiceBuilder::default().build(&anvil, &db_url).await?; let CreateApiKeyResponse { api_key } = client.create_relayer_api_key(DEFAULT_RELAYER_ID).await?;