diff --git a/cmd/ethrex/ethrex.rs b/cmd/ethrex/ethrex.rs index 15467714127..96851dbe26c 100644 --- a/cmd/ethrex/ethrex.rs +++ b/cmd/ethrex/ethrex.rs @@ -5,11 +5,14 @@ use ethrex::{ utils::{NodeConfigFile, get_client_version, store_node_config_file}, }; use ethrex_p2p::{discv4::peer_table::PeerTable, types::NodeRecord}; +use serde::Deserialize; use std::{path::Path, time::Duration}; use tokio::signal::unix::{SignalKind, signal}; use tokio_util::sync::CancellationToken; use tracing::info; +const LATEST_VERSION_URL: &str = "https://api.github.com/repos/lambdaclass/ethrex/releases/latest"; + #[cfg(all(feature = "jemalloc", not(target_env = "msvc")))] #[global_allocator] static ALLOC: tikv_jemallocator::Jemalloc = tikv_jemallocator::Jemalloc; @@ -45,6 +48,80 @@ async fn server_shutdown( info!("Server shutting down!"); } +/// Fetches the latest release version on github +/// Returns None if there was an error when requesting the latest version +async fn latest_release_version() -> Option { + #[derive(Deserialize)] + struct Release { + tag_name: String, + } + let client = reqwest::Client::new(); + let response = client + .get(LATEST_VERSION_URL) + .header("User-Agent", "ethrex") + .send() + .await + .ok()?; + if !response.status().is_success() { + None + } else { + Some( + response + .json::() + .await + .ok()? + .tag_name + .trim_start_matches("v") + .to_string(), + ) + } +} + +/// Reads current crate version +fn current_version() -> &'static str { + env!("CARGO_PKG_VERSION") +} + +/// Returns true if the received latest version is higher than the current ethrex version +fn is_higher_than_current(latest_version: &str) -> bool { + let current_version_numbers = current_version() + .split(".") + .map(|c| c.parse::().unwrap_or_default()); + let latest_version_numbers = latest_version + .split(".") + .map(|c| c.parse::().unwrap_or_default()); + for (current, latest) in current_version_numbers.zip(latest_version_numbers) { + match current.cmp(&latest) { + std::cmp::Ordering::Less => return true, + std::cmp::Ordering::Equal => {} + std::cmp::Ordering::Greater => return false, + }; + } + false +} + +/// Checks if the latest released version is higher than the current version and emits an info log +/// Won't emit a log line if the current version is newer or equal, or if there was a problem reading either version +async fn check_version_update() { + if let Some(latest_version) = latest_release_version().await + && is_higher_than_current(&latest_version) + { + info!( + "There is a newer ethrex version available, current version: {} vs latest version: {latest_version}", + current_version() + ); + } +} + +/// Checks if there is a newer ethrex verison available every hour +async fn periodically_check_version_update() { + let mut interval = tokio::time::interval(Duration::from_secs(60 * 60)); + loop { + interval.tick().await; + check_version_update().await; + } +} + #[tokio::main] async fn main() -> eyre::Result<()> { let CLI { opts, command } = CLI::parse(); @@ -61,6 +138,7 @@ async fn main() -> eyre::Result<()> { let log_filter_handler = init_tracing(&opts); info!("ethrex version: {}", get_client_version()); + tokio::spawn(periodically_check_version_update()); let (datadir, cancel_token, peer_table, local_node_record) = init_l1(opts, Some(log_filter_handler)).await?;