diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index a9459f04..3c26f278 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -75,8 +75,22 @@ jobs: - uses: actions/checkout@v4 - uses: dtolnay/rust-toolchain@stable - uses: Swatinem/rust-cache@v2 - - name: Build release - run: cargo build --release + - name: Build release (no logging) + run: cargo build --release --no-default-features + + test-no-logging: + name: Test (no logging) + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: dtolnay/rust-toolchain@stable + - uses: Swatinem/rust-cache@v2 + - name: Install Foundry + uses: foundry-rs/foundry-toolchain@v1 + with: + version: nightly + - name: Run unit tests without logging + run: cargo test --lib --no-default-features security: name: Security Audit diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 25301234..ce8bf4e4 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -144,6 +144,20 @@ jobs: steps: - uses: actions/checkout@v4 + - name: Determine build profile + id: profile + shell: bash + run: | + if [[ "${{ needs.validate.outputs.is_prerelease }}" == "true" ]]; then + echo "build_flags=" >> $GITHUB_OUTPUT + echo "profile_dir=debug" >> $GITHUB_OUTPUT + echo "Building DEBUG (RC pre-release: logging enabled)" + else + echo "build_flags=--release --no-default-features" >> $GITHUB_OUTPUT + echo "profile_dir=release" >> $GITHUB_OUTPUT + echo "Building RELEASE (logging stripped)" + fi + - name: Install Rust toolchain uses: dtolnay/rust-toolchain@stable with: @@ -159,17 +173,17 @@ jobs: - name: Build (cross) if: matrix.cross - run: cross build --release --target ${{ matrix.target }} + run: cross build ${{ steps.profile.outputs.build_flags }} --target ${{ matrix.target }} - name: Build (native) if: ${{ !matrix.cross }} - run: cargo build --release --target ${{ matrix.target }} + run: cargo build ${{ steps.profile.outputs.build_flags }} --target ${{ matrix.target }} - name: Create archive (Unix) if: matrix.archive == 'tar.gz' run: | - cp config/bootstrap_peers.toml target/${{ matrix.target }}/release/ - cd target/${{ matrix.target }}/release + cp config/bootstrap_peers.toml target/${{ matrix.target }}/${{ steps.profile.outputs.profile_dir }}/ + cd target/${{ matrix.target }}/${{ steps.profile.outputs.profile_dir }} tar -czvf ../../../ant-node-cli-${{ matrix.friendly_name }}.tar.gz ${{ matrix.binary }} bootstrap_peers.toml cd ../../.. @@ -177,8 +191,8 @@ jobs: if: matrix.archive == 'zip' shell: pwsh run: | - Copy-Item "config/bootstrap_peers.toml" "target/${{ matrix.target }}/release/bootstrap_peers.toml" - Push-Location "target/${{ matrix.target }}/release" + Copy-Item "config/bootstrap_peers.toml" "target/${{ matrix.target }}/${{ steps.profile.outputs.profile_dir }}/bootstrap_peers.toml" + Push-Location "target/${{ matrix.target }}/${{ steps.profile.outputs.profile_dir }}" Compress-Archive -Path "${{ matrix.binary }}", "bootstrap_peers.toml" -DestinationPath "../../../ant-node-cli-${{ matrix.friendly_name }}.zip" Pop-Location diff --git a/Cargo.toml b/Cargo.toml index 203d89e9..85b08c54 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -62,10 +62,10 @@ directories = "5" reqwest = { version = "0.13", features = ["json", "rustls"], default-features = false } semver = "1" -# Logging -tracing = "0.1" -tracing-subscriber = { version = "0.3", features = ["env-filter", "json"] } -tracing-appender = "0.2" +# Logging (optional — behind `logging` feature flag) +tracing = { version = "0.1", optional = true } +tracing-subscriber = { version = "0.3", features = ["env-filter", "json"], optional = true } +tracing-appender = { version = "0.2", optional = true } # Error handling thiserror = "2" @@ -119,7 +119,12 @@ path = "tests/e2e/mod.rs" required-features = ["test-utils"] [features] -default = [] +default = ["logging"] +# Enable tracing/logging infrastructure. +# Included in `default` so dev builds (`cargo build`, `cargo test`) get logging +# automatically. Release builds strip it: +# cargo build --release --no-default-features +logging = ["tracing", "tracing-subscriber", "tracing-appender"] # Enable additional diagnostics for development dev = [] # Expose test helpers (cache_insert, payment_verifier accessor) for diff --git a/scripts/testnet/build-and-deploy.sh b/scripts/testnet/build-and-deploy.sh index bf3ac2b0..f1b4be66 100755 --- a/scripts/testnet/build-and-deploy.sh +++ b/scripts/testnet/build-and-deploy.sh @@ -54,8 +54,8 @@ ssh -o StrictHostKeyChecking=no "root@${BUILD_HOST}" " cd /root/ant-node fi - # Build release binary - cargo build --release + # Build release binary (no logging) + cargo build --release --no-default-features # Verify binary ls -la target/release/ant-node diff --git a/scripts/testnet/testnet-endurance.sh b/scripts/testnet/testnet-endurance.sh index 0e318c63..88e5bda5 100755 --- a/scripts/testnet/testnet-endurance.sh +++ b/scripts/testnet/testnet-endurance.sh @@ -30,9 +30,9 @@ echo "Log file: ${LOG_FILE}" | tee -a "$LOG_FILE" echo "Start time: $(date)" | tee -a "$LOG_FILE" echo "" | tee -a "$LOG_FILE" -# Build the test binary if needed -echo "Building test binary..." | tee -a "$LOG_FILE" -cargo build --release 2>&1 | tee -a "$LOG_FILE" +# Build the binary (no logging) +echo "Building binary..." | tee -a "$LOG_FILE" +cargo build --release --no-default-features 2>&1 | tee -a "$LOG_FILE" # Export test configuration export ANT_TEST_BOOTSTRAP="$BOOTSTRAP" diff --git a/src/bin/ant-devnet/main.rs b/src/bin/ant-devnet/main.rs index 3103902b..d8c9fc87 100644 --- a/src/bin/ant-devnet/main.rs +++ b/src/bin/ant-devnet/main.rs @@ -1,12 +1,12 @@ //! ant-devnet CLI entry point. +#![cfg_attr(not(feature = "logging"), allow(unused_variables))] + mod cli; use ant_node::devnet::{Devnet, DevnetConfig, DevnetEvmInfo, DevnetManifest}; use clap::Parser; use cli::Cli; -use tracing::info; -use tracing_subscriber::{fmt, prelude::*, EnvFilter}; #[tokio::main] async fn main() -> color_eyre::Result<()> { @@ -14,15 +14,20 @@ async fn main() -> color_eyre::Result<()> { let cli = Cli::parse(); - let filter = - EnvFilter::try_from_default_env().unwrap_or_else(|_| EnvFilter::new(&cli.log_level)); + #[cfg(feature = "logging")] + { + use tracing_subscriber::{fmt, prelude::*, EnvFilter}; + + let filter = + EnvFilter::try_from_default_env().unwrap_or_else(|_| EnvFilter::new(&cli.log_level)); - tracing_subscriber::registry() - .with(fmt::layer()) - .with(filter) - .init(); + tracing_subscriber::registry() + .with(fmt::layer()) + .with(filter) + .init(); + } - info!("ant-devnet v{}", env!("CARGO_PKG_VERSION")); + ant_node::logging::info!("ant-devnet v{}", env!("CARGO_PKG_VERSION")); let mut config = cli.preset @@ -55,7 +60,7 @@ async fn main() -> color_eyre::Result<()> { // Start Anvil and deploy contracts if EVM is enabled let evm_info = if cli.enable_evm { - info!("Starting local Anvil blockchain for EVM payment enforcement..."); + ant_node::logging::info!("Starting local Anvil blockchain for EVM payment enforcement..."); let testnet = evmlib::testnet::Testnet::new() .await .map_err(|e| color_eyre::eyre::eyre!("Failed to start Anvil testnet: {e}"))?; @@ -79,8 +84,8 @@ async fn main() -> color_eyre::Result<()> { config.evm_network = Some(network); - info!("Anvil blockchain running at {rpc_url}"); - info!("Funded wallet private key: {wallet_key}"); + ant_node::logging::info!("Anvil blockchain running at {rpc_url}"); + ant_node::logging::info!("Funded wallet private key: {wallet_key}"); // Keep testnet alive by leaking it (it will be cleaned up on process exit) // This is necessary because AnvilInstance stops Anvil when dropped @@ -111,12 +116,12 @@ async fn main() -> color_eyre::Result<()> { let json = serde_json::to_string_pretty(&manifest)?; if let Some(path) = cli.manifest { tokio::fs::write(&path, &json).await?; - info!("Wrote manifest to {}", path.display()); + ant_node::logging::info!("Wrote manifest to {}", path.display()); } else { println!("{json}"); } - info!("Devnet running. Press Ctrl+C to stop."); + ant_node::logging::info!("Devnet running. Press Ctrl+C to stop."); tokio::signal::ctrl_c().await?; devnet.shutdown().await?; diff --git a/src/bin/ant-node/cli.rs b/src/bin/ant-node/cli.rs index 0d6509cc..ddbd00b4 100644 --- a/src/bin/ant-node/cli.rs +++ b/src/bin/ant-node/cli.rs @@ -62,21 +62,25 @@ pub struct Cli { pub metrics_port: u16, /// Log level. + #[cfg(feature = "logging")] #[arg(long, value_enum, default_value = "info", env = "RUST_LOG")] pub log_level: CliLogLevel, /// Log output format. + #[cfg(feature = "logging")] #[arg(long, value_enum, default_value = "text", env = "ANT_LOG_FORMAT")] pub log_format: CliLogFormat, /// Directory for log file output. /// When set, logs are written to files in this directory instead of stdout. /// Files rotate daily and are named ant-node.YYYY-MM-DD.log. + #[cfg(feature = "logging")] #[arg(long, env = "ANT_LOG_DIR")] pub log_dir: Option, /// Maximum number of rotated log files to retain (only used with --log-dir). /// Oldest files are deleted when this limit is reached. Rotation is daily. + #[cfg(feature = "logging")] #[arg(long, default_value = "7", env = "ANT_LOG_MAX_FILES")] pub log_max_files: usize, @@ -136,6 +140,7 @@ pub enum CliEvmNetwork { } /// Log level CLI enum. +#[cfg(feature = "logging")] #[derive(Debug, Clone, Copy, ValueEnum, Default)] pub enum CliLogLevel { /// Error messages only. @@ -152,6 +157,7 @@ pub enum CliLogLevel { } /// Log format CLI enum. +#[cfg(feature = "logging")] #[derive(Debug, Clone, Copy, ValueEnum, Default)] pub enum CliLogFormat { /// Plain text output (default). @@ -207,7 +213,10 @@ impl Cli { config.port = self.port; config.ipv4_only = self.ipv4_only; - config.log_level = self.log_level.into(); + #[cfg(feature = "logging")] + { + config.log_level = self.log_level.into(); + } config.network_mode = self.network_mode.into(); // Apply CLI bootstrap peers if provided; otherwise keep config file value. @@ -275,6 +284,7 @@ impl From for EvmNetworkConfig { } } +#[cfg(feature = "logging")] impl From for String { fn from(level: CliLogLevel) -> Self { match level { diff --git a/src/bin/ant-node/main.rs b/src/bin/ant-node/main.rs index c7670496..30560e50 100644 --- a/src/bin/ant-node/main.rs +++ b/src/bin/ant-node/main.rs @@ -1,52 +1,42 @@ //! ant-node CLI entry point. +#![cfg_attr(not(feature = "logging"), allow(unused_variables))] + mod cli; mod platform; use ant_node::config::BootstrapSource; use ant_node::NodeBuilder; use clap::Parser; -use cli::{Cli, CliLogFormat}; -use tracing::{info, warn}; -use tracing_subscriber::prelude::*; -use tracing_subscriber::{fmt, EnvFilter, Layer}; +use cli::Cli; -/// Force at least 4 worker threads regardless of CPU count. +/// Initialize the tracing subscriber when the `logging` feature is active. /// -/// On small VMs (1-2 vCPU), the default `num_cpus` gives only 1-2 worker -/// threads. The NAT traversal `poll()` function does synchronous work -/// (`parking_lot` locks, `DashMap` iteration) that blocks its worker thread. -/// With only 1 worker, this freezes the entire runtime — timers stop, -/// keepalives can't fire, and connections die silently. -#[tokio::main(flavor = "multi_thread", worker_threads = 4)] -async fn main() -> color_eyre::Result<()> { - // Initialize error handling - color_eyre::install()?; - - // Parse CLI arguments - let cli = Cli::parse(); +/// Returns a guard that must be held for the lifetime of the process to ensure +/// buffered logs are flushed on shutdown. +#[cfg(feature = "logging")] +fn init_logging( + cli: &Cli, +) -> color_eyre::Result> { + use cli::CliLogFormat; + use tracing_subscriber::prelude::*; + use tracing_subscriber::{fmt, EnvFilter, Layer}; - // Extract logging options before consuming the CLI struct let log_format = cli.log_format; let log_dir = cli.log_dir.clone(); let log_max_files = cli.log_max_files; - - // Initialize tracing let log_level: String = cli.log_level.into(); let filter = EnvFilter::try_from_default_env().unwrap_or_else(|_| EnvFilter::new(&log_level)); - // _guard must live for the duration of main() to ensure log flushing. - // The guard's Drop impl flushes buffered logs — it is intentionally held, not read. - #[allow(clippy::collection_is_never_read)] - let _guard: Option; + let guard: Option; let layer: Box + Send + Sync> = match (log_format, log_dir) { (CliLogFormat::Text, None) => { - _guard = None; + guard = None; Box::new(fmt::layer()) } (CliLogFormat::Json, None) => { - _guard = None; + guard = None; Box::new(fmt::layer().json().flatten_event(true)) } (CliLogFormat::Text, Some(dir)) => { @@ -56,8 +46,8 @@ async fn main() -> color_eyre::Result<()> { .filename_prefix("ant-node") .filename_suffix("log") .build(dir)?; - let (non_blocking, guard) = tracing_appender::non_blocking(file_appender); - _guard = Some(guard); + let (non_blocking, g) = tracing_appender::non_blocking(file_appender); + guard = Some(g); Box::new(fmt::layer().with_writer(non_blocking).with_ansi(false)) } (CliLogFormat::Json, Some(dir)) => { @@ -67,8 +57,8 @@ async fn main() -> color_eyre::Result<()> { .filename_prefix("ant-node") .filename_suffix("log") .build(dir)?; - let (non_blocking, guard) = tracing_appender::non_blocking(file_appender); - _guard = Some(guard); + let (non_blocking, g) = tracing_appender::non_blocking(file_appender); + guard = Some(g); Box::new( fmt::layer() .json() @@ -84,7 +74,27 @@ async fn main() -> color_eyre::Result<()> { .with(filter) .init(); - info!( + Ok(guard) +} + +/// Force at least 4 worker threads regardless of CPU count. +/// +/// On small VMs (1-2 vCPU), the default `num_cpus` gives only 1-2 worker +/// threads. The NAT traversal `poll()` function does synchronous work +/// (`parking_lot` locks, `DashMap` iteration) that blocks its worker thread. +/// With only 1 worker, this freezes the entire runtime — timers stop, +/// keepalives can't fire, and connections die silently. +#[tokio::main(flavor = "multi_thread", worker_threads = 4)] +async fn main() -> color_eyre::Result<()> { + color_eyre::install()?; + + let cli = Cli::parse(); + + // _guard must live for the duration of main() to ensure log flushing. + #[cfg(feature = "logging")] + let _logging_guard = init_logging(&cli)?; + + ant_node::logging::info!( version = env!("CARGO_PKG_VERSION"), commit = env!("ANT_GIT_COMMIT"), "ant-node starting" @@ -95,11 +105,11 @@ async fn main() -> color_eyre::Result<()> { #[allow(clippy::collection_is_never_read)] let _activity = match platform::disable_app_nap() { Ok(activity) => { - info!("App Nap prevention enabled"); + ant_node::logging::info!("App Nap prevention enabled"); Some(activity) } Err(e) => { - warn!("Failed to disable App Nap: {e}"); + ant_node::logging::warn!("Failed to disable App Nap: {e}"); None } }; @@ -109,37 +119,34 @@ async fn main() -> color_eyre::Result<()> { match &bootstrap_source { BootstrapSource::Cli => { - info!( + ant_node::logging::info!( count = config.bootstrap.len(), "Bootstrap peers provided via CLI" ); } BootstrapSource::ConfigFile => { - info!( + ant_node::logging::info!( count = config.bootstrap.len(), "Bootstrap peers loaded from config file" ); } BootstrapSource::AutoDiscovered(path) => { - info!( + ant_node::logging::info!( count = config.bootstrap.len(), path = %path.display(), "Bootstrap peers loaded from discovered config" ); } BootstrapSource::None => { - warn!( + ant_node::logging::warn!( "No bootstrap peers configured — node will not be able to join an existing network" ); } } - // Build and run the node let mut node = NodeBuilder::new(config).build().await?; - - // Run until shutdown node.run().await?; - info!("Goodbye!"); + ant_node::logging::info!("Goodbye!"); Ok(()) } diff --git a/src/client/chunk_protocol.rs b/src/client/chunk_protocol.rs index 9ce0603d..ea9e50c4 100644 --- a/src/client/chunk_protocol.rs +++ b/src/client/chunk_protocol.rs @@ -4,12 +4,12 @@ //! generic function used by both [`super::QuantumClient`] and E2E test helpers. use crate::ant_protocol::{ChunkMessage, ChunkMessageBody, CHUNK_PROTOCOL_ID}; +use crate::logging::{debug, warn}; use saorsa_core::identity::PeerId; use saorsa_core::{MultiAddr, P2PEvent, P2PNode}; use std::time::Duration; use tokio::sync::broadcast::error::RecvError; use tokio::time::Instant; -use tracing::{debug, warn}; /// Send a chunk-protocol message to `target_peer` and await a matching response. /// diff --git a/src/config.rs b/src/config.rs index b99e3d25..a9f569c3 100644 --- a/src/config.rs +++ b/src/config.rs @@ -556,8 +556,8 @@ impl BootstrapPeersConfig { Ok(config) if !config.peers.is_empty() => return Some((config, path)), Ok(_) => {} Err(err) => { - eprintln!( - "Warning: failed to load bootstrap peers from {}: {err}", + crate::logging::warn!( + "Failed to load bootstrap peers from {}: {err}", path.display(), ); } diff --git a/src/devnet.rs b/src/devnet.rs index a702ce56..a049be6c 100644 --- a/src/devnet.rs +++ b/src/devnet.rs @@ -5,6 +5,7 @@ use crate::ant_protocol::CHUNK_PROTOCOL_ID; use crate::config::{default_root_dir, NODES_SUBDIR, NODE_IDENTITY_FILENAME}; +use crate::logging::{debug, info, warn}; use crate::payment::{ EvmVerifierConfig, PaymentVerifier, PaymentVerifierConfig, QuoteGenerator, QuotingMetricsTracker, @@ -26,7 +27,6 @@ use tokio::sync::RwLock; use tokio::task::JoinHandle; use tokio::time::Instant; use tokio_util::sync::CancellationToken; -use tracing::{debug, info, warn}; // ============================================================================= // Devnet Constants diff --git a/src/lib.rs b/src/lib.rs index 91234ebc..bcad4850 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -38,6 +38,9 @@ #![warn(missing_docs)] #![warn(clippy::all)] #![warn(clippy::pedantic)] +// When the `logging` feature is off, variables used only in log macros become +// unused. These are expected and harmless — the compiler optimises them away. +#![cfg_attr(not(feature = "logging"), allow(unused_variables, unused_assignments))] pub mod ant_protocol; pub mod client; @@ -45,6 +48,7 @@ pub mod config; pub mod devnet; pub mod error; pub mod event; +pub mod logging; pub mod node; pub mod payment; pub mod replication; diff --git a/src/logging.rs b/src/logging.rs new file mode 100644 index 00000000..d3cd9e36 --- /dev/null +++ b/src/logging.rs @@ -0,0 +1,113 @@ +//! Logging facade that compiles to no-ops when the `logging` feature is disabled. +//! +//! When the `logging` feature is enabled, this module re-exports the +//! [`tracing`] macros (`info!`, `warn!`, `debug!`, `error!`, `trace!`). +//! +//! When disabled, all macros expand to `()` — zero binary size overhead, +//! zero runtime cost. Argument expressions are **not evaluated**, so +//! `info!("{}", expensive_call())` costs nothing in release builds. +//! +//! ## Unused variables with `--no-default-features` +//! +//! Variables that exist only for logging (e.g. `let addr_hex = hex::encode(...)`) +//! become unused when macros are compiled out. The crate-level attribute +//! `#![cfg_attr(not(feature = "logging"), allow(unused_variables, unused_assignments))]` +//! in `lib.rs` (and the binary entry points) suppresses these expected warnings. + +// ---- Feature enabled: re-export tracing ---- +#[cfg(feature = "logging")] +pub use tracing::{debug, enabled, error, info, trace, warn, Level}; + +// ---- Feature disabled: no-op macros ---- +// +// `#[macro_export]` places macros at the crate root. We use prefixed names +// to avoid clashing with built-in attributes (e.g. `warn`), then re-export +// them here under the expected names. + +#[cfg(not(feature = "logging"))] +#[macro_export] +#[doc(hidden)] +macro_rules! __log_noop_info { + ($($arg:tt)*) => { + () + }; +} + +#[cfg(not(feature = "logging"))] +#[macro_export] +#[doc(hidden)] +macro_rules! __log_noop_warn { + ($($arg:tt)*) => { + () + }; +} + +#[cfg(not(feature = "logging"))] +#[macro_export] +#[doc(hidden)] +macro_rules! __log_noop_debug { + ($($arg:tt)*) => { + () + }; +} + +#[cfg(not(feature = "logging"))] +#[macro_export] +#[doc(hidden)] +macro_rules! __log_noop_error { + ($($arg:tt)*) => { + () + }; +} + +#[cfg(not(feature = "logging"))] +#[macro_export] +#[doc(hidden)] +macro_rules! __log_noop_trace { + ($($arg:tt)*) => { + () + }; +} + +#[cfg(not(feature = "logging"))] +#[macro_export] +#[doc(hidden)] +macro_rules! __log_noop_enabled { + ($($arg:tt)*) => { + false + }; +} + +// Re-export under short names so `use crate::logging::info;` works. +#[cfg(not(feature = "logging"))] +pub use __log_noop_debug as debug; +#[cfg(not(feature = "logging"))] +pub use __log_noop_enabled as enabled; +#[cfg(not(feature = "logging"))] +pub use __log_noop_error as error; +#[cfg(not(feature = "logging"))] +pub use __log_noop_info as info; +#[cfg(not(feature = "logging"))] +pub use __log_noop_trace as trace; +#[cfg(not(feature = "logging"))] +pub use __log_noop_warn as warn; + +/// Stub for `tracing::Level` when logging is disabled. +#[cfg(not(feature = "logging"))] +#[allow(dead_code)] +pub struct Level; + +#[cfg(not(feature = "logging"))] +#[allow(dead_code)] +impl Level { + /// Debug level stub. + pub const DEBUG: Self = Self; + /// Info level stub. + pub const INFO: Self = Self; + /// Warn level stub. + pub const WARN: Self = Self; + /// Error level stub. + pub const ERROR: Self = Self; + /// Trace level stub. + pub const TRACE: Self = Self; +} diff --git a/src/node.rs b/src/node.rs index 378474a4..4dc2a6d8 100644 --- a/src/node.rs +++ b/src/node.rs @@ -7,6 +7,7 @@ use crate::config::{ }; use crate::error::{Error, Result}; use crate::event::{create_event_channel, NodeEvent, NodeEventsChannel, NodeEventsSender}; +use crate::logging::{debug, error, info, warn}; use crate::payment::metrics::QuotingMetricsTracker; use crate::payment::wallet::parse_rewards_address; use crate::payment::{EvmVerifierConfig, PaymentVerifier, PaymentVerifierConfig, QuoteGenerator}; @@ -31,7 +32,6 @@ use std::sync::Arc; use tokio::sync::Semaphore; use tokio::task::JoinHandle; use tokio_util::sync::CancellationToken; -use tracing::{debug, error, info, warn}; #[cfg(unix)] use tokio::signal::unix::{signal, SignalKind}; diff --git a/src/payment/metrics.rs b/src/payment/metrics.rs index d2f65281..9678790a 100644 --- a/src/payment/metrics.rs +++ b/src/payment/metrics.rs @@ -5,12 +5,12 @@ //! - Storage capacity and usage //! - Network liveness information +use crate::logging::{debug, info, warn}; use evmlib::quoting_metrics::QuotingMetrics; use parking_lot::RwLock; use std::path::PathBuf; use std::sync::atomic::{AtomicU64, AtomicUsize, Ordering}; use std::time::Instant; -use tracing::{debug, info, warn}; /// Number of operations between disk persists (debounce). const PERSIST_INTERVAL: usize = 10; diff --git a/src/payment/quote.rs b/src/payment/quote.rs index 4d6e616f..d3dd9f64 100644 --- a/src/payment/quote.rs +++ b/src/payment/quote.rs @@ -8,6 +8,7 @@ //! and will be fully integrated when the node is initialized. use crate::error::{Error, Result}; +use crate::logging::debug; use crate::payment::metrics::QuotingMetricsTracker; use crate::payment::pricing::calculate_price; use evmlib::merkle_payments::MerklePaymentCandidateNode; @@ -17,7 +18,6 @@ use saorsa_core::MlDsa65; use saorsa_pqc::pqc::types::{MlDsaPublicKey, MlDsaSecretKey, MlDsaSignature}; use saorsa_pqc::pqc::MlDsaOperations; use std::time::SystemTime; -use tracing::debug; /// Content address type (32-byte `XorName`). pub type XorName = [u8; 32]; @@ -155,7 +155,7 @@ impl QuoteGenerator { signature, }; - if tracing::enabled!(tracing::Level::DEBUG) { + if crate::logging::enabled!(crate::logging::Level::DEBUG) { let content_hex = hex::encode(content); debug!("Generated quote for {content_hex} (size: {data_size}, type: {data_type})"); } @@ -235,7 +235,7 @@ impl QuoteGenerator { signature, }; - if tracing::enabled!(tracing::Level::DEBUG) { + if crate::logging::enabled!(crate::logging::Level::DEBUG) { debug!( "Generated ML-DSA-65 merkle candidate quote (size: {data_size}, type: {data_type}, ts: {merkle_payment_timestamp})" ); @@ -259,7 +259,7 @@ impl QuoteGenerator { pub fn verify_quote_content(quote: &PaymentQuote, expected_content: &XorName) -> bool { // Check content matches if quote.content.0 != *expected_content { - if tracing::enabled!(tracing::Level::DEBUG) { + if crate::logging::enabled!(crate::logging::Level::DEBUG) { debug!( "Quote content mismatch: expected {}, got {}", hex::encode(expected_content), @@ -397,7 +397,7 @@ pub fn wire_ml_dsa_signer( generator.set_signer(pub_key_bytes, move |msg| match ml_dsa.sign(&sk, msg) { Ok(sig) => sig.as_bytes().to_vec(), Err(e) => { - tracing::error!("ML-DSA-65 signing failed: {e}"); + crate::logging::error!("ML-DSA-65 signing failed: {e}"); vec![] } }); diff --git a/src/payment/single_node.rs b/src/payment/single_node.rs index 433ecb5f..32a93c80 100644 --- a/src/payment/single_node.rs +++ b/src/payment/single_node.rs @@ -12,12 +12,12 @@ use crate::ant_protocol::CLOSE_GROUP_SIZE; use crate::error::{Error, Result}; +use crate::logging::info; use evmlib::common::{Amount, QuoteHash}; use evmlib::wallet::Wallet; use evmlib::Network as EvmNetwork; use evmlib::PaymentQuote; use evmlib::RewardsAddress; -use tracing::info; /// Index of the median-priced node after sorting, derived from `CLOSE_GROUP_SIZE`. const MEDIAN_INDEX: usize = CLOSE_GROUP_SIZE / 2; diff --git a/src/payment/verifier.rs b/src/payment/verifier.rs index f8c50395..324ff3f0 100644 --- a/src/payment/verifier.rs +++ b/src/payment/verifier.rs @@ -5,6 +5,7 @@ use crate::ant_protocol::CLOSE_GROUP_SIZE; use crate::error::{Error, Result}; +use crate::logging::{debug, info}; use crate::payment::cache::{CacheStats, VerifiedCache, XorName}; use crate::payment::proof::{ deserialize_merkle_proof, deserialize_proof, detect_proof_type, ProofType, @@ -22,7 +23,6 @@ use parking_lot::Mutex; use saorsa_core::identity::node_identity::peer_id_from_public_key_bytes; use std::num::NonZeroUsize; use std::time::SystemTime; -use tracing::{debug, info}; /// Minimum allowed size for a payment proof in bytes. /// @@ -162,14 +162,14 @@ impl PaymentVerifier { pub fn check_payment_required(&self, xorname: &XorName) -> PaymentStatus { // Check LRU cache (fast path) if self.cache.contains(xorname) { - if tracing::enabled!(tracing::Level::DEBUG) { + if crate::logging::enabled!(crate::logging::Level::DEBUG) { debug!("Data {} found in verified cache", hex::encode(xorname)); } return PaymentStatus::CachedAsVerified; } // Not in cache - payment required - if tracing::enabled!(tracing::Level::DEBUG) { + if crate::logging::enabled!(crate::logging::Level::DEBUG) { debug!( "Data {} not in cache - payment required", hex::encode(xorname) @@ -305,7 +305,7 @@ impl PaymentVerifier { /// the cache so `verify_payment` returns `CachedAsVerified` before /// reaching this method. async fn verify_evm_payment(&self, xorname: &XorName, payment: &ProofOfPayment) -> Result<()> { - if tracing::enabled!(tracing::Level::DEBUG) { + if crate::logging::enabled!(crate::logging::Level::DEBUG) { let xorname_hex = hex::encode(xorname); let quote_count = payment.peer_quotes.len(); debug!("Verifying EVM payment for {xorname_hex} with {quote_count} quotes"); @@ -357,7 +357,7 @@ impl PaymentVerifier { )) })?; - if tracing::enabled!(tracing::Level::INFO) { + if crate::logging::enabled!(crate::logging::Level::INFO) { let xorname_hex = hex::encode(xorname); info!("EVM payment verified for {xorname_hex} (median paid {verified_amount} atto)"); } @@ -465,7 +465,7 @@ impl PaymentVerifier { /// 5. Cache the pool hash for subsequent chunk verifications in the same batch #[allow(clippy::too_many_lines)] async fn verify_merkle_payment(&self, xorname: &XorName, proof_bytes: &[u8]) -> Result<()> { - if tracing::enabled!(tracing::Level::DEBUG) { + if crate::logging::enabled!(crate::logging::Level::DEBUG) { debug!("Verifying merkle payment for {}", hex::encode(xorname)); } @@ -599,8 +599,14 @@ impl PaymentVerifier { .collect(); candidate_prices.sort_unstable(); // ascending // Upper median (index 8 of 16) — matches Solidity's median16 (k = 8) - let median_price = candidate_prices[candidate_prices.len() / 2]; - let total_amount = median_price * Amount::from(1u64 << payment_info.depth); + let median_price = *candidate_prices + .get(candidate_prices.len() / 2) + .ok_or_else(|| Error::Payment("empty candidate pool in merkle proof".into()))?; + let shift = u32::from(payment_info.depth); + let multiplier = 1u64 + .checked_shl(shift) + .ok_or_else(|| Error::Payment("merkle proof depth too large".into()))?; + let total_amount = median_price * Amount::from(multiplier); total_amount / Amount::from(u64::from(payment_info.depth)) } else { Amount::ZERO @@ -648,7 +654,7 @@ impl PaymentVerifier { } } - if tracing::enabled!(tracing::Level::INFO) { + if crate::logging::enabled!(crate::logging::Level::INFO) { info!( "Merkle payment verified for {} (pool: {})", hex::encode(xorname), diff --git a/src/replication/audit.rs b/src/replication/audit.rs index 283deae4..eb09a315 100644 --- a/src/replication/audit.rs +++ b/src/replication/audit.rs @@ -6,9 +6,9 @@ use std::collections::HashMap; use std::sync::Arc; use std::time::Instant; +use crate::logging::{debug, info, warn}; use rand::seq::SliceRandom; use rand::Rng; -use tracing::{debug, info, warn}; use crate::ant_protocol::XorName; use crate::replication::config::{ReplicationConfig, REPLICATION_PROTOCOL_ID}; diff --git a/src/replication/bootstrap.rs b/src/replication/bootstrap.rs index 9ddcfbed..b4d66f9d 100644 --- a/src/replication/bootstrap.rs +++ b/src/replication/bootstrap.rs @@ -7,9 +7,9 @@ use std::collections::HashSet; use std::sync::Arc; use std::time::Duration; +use crate::logging::{debug, info, warn}; use tokio::sync::RwLock; use tokio_util::sync::CancellationToken; -use tracing::{debug, info, warn}; use saorsa_core::DhtNetworkEvent; diff --git a/src/replication/fresh.rs b/src/replication/fresh.rs index 0bd2fb0e..5bc47896 100644 --- a/src/replication/fresh.rs +++ b/src/replication/fresh.rs @@ -7,10 +7,10 @@ use std::sync::Arc; +use crate::logging::{debug, warn}; use rand::Rng; use saorsa_core::identity::PeerId; use saorsa_core::P2PNode; -use tracing::{debug, warn}; use crate::ant_protocol::XorName; use crate::replication::config::{ReplicationConfig, REPLICATION_PROTOCOL_ID}; diff --git a/src/replication/mod.rs b/src/replication/mod.rs index 853362b6..d956e00b 100644 --- a/src/replication/mod.rs +++ b/src/replication/mod.rs @@ -34,13 +34,13 @@ use std::time::{Duration, Instant}; use std::pin::Pin; +use crate::logging::{debug, error, info, warn}; use futures::stream::FuturesUnordered; use futures::{Future, StreamExt}; use rand::Rng; use tokio::sync::{mpsc, Notify, RwLock}; use tokio::task::JoinHandle; use tokio_util::sync::CancellationToken; -use tracing::{debug, error, info, warn}; use crate::ant_protocol::XorName; use crate::error::{Error, Result}; diff --git a/src/replication/neighbor_sync.rs b/src/replication/neighbor_sync.rs index da857e27..578c0cad 100644 --- a/src/replication/neighbor_sync.rs +++ b/src/replication/neighbor_sync.rs @@ -6,10 +6,10 @@ use std::sync::Arc; use std::time::{Duration, Instant}; +use crate::logging::{debug, warn}; use rand::Rng; use saorsa_core::identity::PeerId; use saorsa_core::P2PNode; -use tracing::{debug, warn}; use crate::ant_protocol::XorName; use crate::replication::config::{ReplicationConfig, REPLICATION_PROTOCOL_ID}; diff --git a/src/replication/paid_list.rs b/src/replication/paid_list.rs index 427ac33a..dd8673f6 100644 --- a/src/replication/paid_list.rs +++ b/src/replication/paid_list.rs @@ -22,6 +22,7 @@ use crate::ant_protocol::XorName; use crate::error::{Error, Result}; +use crate::logging::{debug, trace, warn}; use heed::types::Bytes; use heed::{Database, Env, EnvOpenOptions}; use parking_lot::RwLock; @@ -29,7 +30,6 @@ use std::collections::HashMap; use std::path::Path; use std::time::Instant; use tokio::task::spawn_blocking; -use tracing::{debug, trace, warn}; use crate::ant_protocol::XORNAME_LEN; diff --git a/src/replication/pruning.rs b/src/replication/pruning.rs index bda4fc39..07db6dd5 100644 --- a/src/replication/pruning.rs +++ b/src/replication/pruning.rs @@ -7,7 +7,7 @@ use std::sync::Arc; use std::time::{Duration, Instant}; -use tracing::{debug, info, warn}; +use crate::logging::{debug, info, warn}; use saorsa_core::identity::PeerId; use saorsa_core::{DHTNode, P2PNode}; diff --git a/src/replication/quorum.rs b/src/replication/quorum.rs index 8dbcf56a..b1348750 100644 --- a/src/replication/quorum.rs +++ b/src/replication/quorum.rs @@ -6,9 +6,9 @@ use std::collections::{HashMap, HashSet}; use std::sync::Arc; +use crate::logging::{debug, warn}; use saorsa_core::identity::PeerId; use saorsa_core::P2PNode; -use tracing::{debug, warn}; use crate::ant_protocol::XorName; use crate::replication::config::{ReplicationConfig, REPLICATION_PROTOCOL_ID}; diff --git a/src/replication/scheduling.rs b/src/replication/scheduling.rs index 1582fd3b..2240b286 100644 --- a/src/replication/scheduling.rs +++ b/src/replication/scheduling.rs @@ -7,7 +7,7 @@ use std::collections::{BinaryHeap, HashMap, HashSet}; use std::time::{Duration, Instant}; -use tracing::debug; +use crate::logging::debug; use crate::ant_protocol::XorName; use crate::replication::types::{FetchCandidate, VerificationEntry}; diff --git a/src/storage/handler.rs b/src/storage/handler.rs index 5eab7094..5d6604fe 100644 --- a/src/storage/handler.rs +++ b/src/storage/handler.rs @@ -35,13 +35,13 @@ use crate::ant_protocol::{ }; use crate::client::compute_address; use crate::error::{Error, Result}; +use crate::logging::{debug, info, warn}; use crate::payment::{PaymentVerifier, QuoteGenerator}; use crate::replication::fresh::FreshWriteEvent; use crate::storage::lmdb::LmdbStorage; use bytes::Bytes; use std::sync::Arc; use tokio::sync::mpsc; -use tracing::{debug, info, warn}; /// ANT protocol handler. /// @@ -280,6 +280,10 @@ impl AntProtocol { // Check if the chunk is already stored so we can tell the client // to skip payment (already_stored = true). + // The match intentionally logs the error when the `logging` feature is + // active. Clippy suggests `unwrap_or_default()` when logging is compiled + // out, but keeping the explicit match preserves the diagnostic intent. + #[allow(clippy::manual_unwrap_or_default)] let already_stored = match self.storage.exists(&request.address) { Ok(exists) => exists, Err(e) => { diff --git a/src/storage/lmdb.rs b/src/storage/lmdb.rs index e5d178ad..3b21d66a 100644 --- a/src/storage/lmdb.rs +++ b/src/storage/lmdb.rs @@ -9,13 +9,13 @@ use crate::ant_protocol::XorName; use crate::error::{Error, Result}; +use crate::logging::{debug, info, trace, warn}; use heed::types::Bytes; use heed::{Database, Env, EnvOpenOptions, MdbError}; use std::path::{Path, PathBuf}; use std::sync::Arc; use std::time::Instant; use tokio::task::spawn_blocking; -use tracing::{debug, info, trace, warn}; use crate::ant_protocol::XORNAME_LEN; @@ -541,7 +541,7 @@ impl LmdbStorage { key.copy_from_slice(key_bytes); keys.push(key); } else { - tracing::warn!( + crate::logging::warn!( "LmdbStorage: skipping entry with unexpected key length {} (expected {XORNAME_LEN})", key_bytes.len() ); diff --git a/src/upgrade/apply.rs b/src/upgrade/apply.rs index 27d9ba0c..9d19870a 100644 --- a/src/upgrade/apply.rs +++ b/src/upgrade/apply.rs @@ -8,6 +8,7 @@ //! 5. Restart the node process use crate::error::{Error, Result}; +use crate::logging::{debug, error, info, warn}; use crate::upgrade::binary_cache::BinaryCache; use crate::upgrade::{signature, UpgradeInfo, UpgradeResult}; use flate2::read::GzDecoder; @@ -17,7 +18,6 @@ use std::fs::{self, File}; use std::io::Read; use std::path::{Path, PathBuf}; use tar::Archive; -use tracing::{debug, error, info, warn}; /// Maximum allowed upgrade archive size (200 MiB). const MAX_ARCHIVE_SIZE_BYTES: usize = 200 * 1024 * 1024; diff --git a/src/upgrade/binary_cache.rs b/src/upgrade/binary_cache.rs index 897b0b5e..43aeb2ff 100644 --- a/src/upgrade/binary_cache.rs +++ b/src/upgrade/binary_cache.rs @@ -10,13 +10,13 @@ //! ML-DSA-65 signature verification performed during the initial download. use crate::error::{Error, Result}; +use crate::logging::{debug, warn}; use fs2::FileExt; use serde::{Deserialize, Serialize}; use sha2::{Digest, Sha256}; use std::fs::{self, File}; use std::io::{Read, Write}; use std::path::PathBuf; -use tracing::{debug, warn}; /// On-disk cache for downloaded upgrade binaries. #[derive(Clone)] diff --git a/src/upgrade/mod.rs b/src/upgrade/mod.rs index ab332186..5502ac43 100644 --- a/src/upgrade/mod.rs +++ b/src/upgrade/mod.rs @@ -27,10 +27,10 @@ pub use signature::{ }; use crate::error::{Error, Result}; +use crate::logging::{debug, info, warn}; use semver::Version; use std::fs; use std::path::Path; -use tracing::{debug, info, warn}; /// Maximum allowed upgrade binary size (200 MiB). /// diff --git a/src/upgrade/monitor.rs b/src/upgrade/monitor.rs index 1c61fae5..587443e4 100644 --- a/src/upgrade/monitor.rs +++ b/src/upgrade/monitor.rs @@ -9,13 +9,13 @@ use crate::config::UpgradeChannel; use crate::error::{Error, Result}; +use crate::logging::{debug, info, warn}; use crate::upgrade::release_cache::ReleaseCache; use crate::upgrade::rollout::StagedRollout; use crate::upgrade::UpgradeInfo; use semver::Version; use serde::Deserialize; use std::time::{Duration, Instant}; -use tracing::{debug, info, warn}; /// GitHub release API response. #[derive(Debug, Deserialize)] diff --git a/src/upgrade/release_cache.rs b/src/upgrade/release_cache.rs index 959de33c..af1496bc 100644 --- a/src/upgrade/release_cache.rs +++ b/src/upgrade/release_cache.rs @@ -6,6 +6,7 @@ //! first node to hit a stale cache actually contacts GitHub. use crate::error::{Error, Result}; +use crate::logging::debug; use crate::upgrade::monitor::{Asset, GitHubRelease}; use fs2::FileExt; use serde::{Deserialize, Serialize}; @@ -13,7 +14,6 @@ use std::fs::{self, File}; use std::io::Write; use std::path::PathBuf; use std::time::{Duration, SystemTime, UNIX_EPOCH}; -use tracing::debug; /// On-disk cache for GitHub release metadata. #[derive(Clone)] diff --git a/src/upgrade/rollout.rs b/src/upgrade/rollout.rs index ef680fd0..0a391863 100644 --- a/src/upgrade/rollout.rs +++ b/src/upgrade/rollout.rs @@ -23,8 +23,8 @@ //! - Nodes are evenly distributed across the rollout window //! - The same node always upgrades at the same point in the window +use crate::logging::debug; use std::time::Duration; -use tracing::debug; /// Staged rollout configuration and delay calculation. #[derive(Debug, Clone)] diff --git a/src/upgrade/signature.rs b/src/upgrade/signature.rs index af71ef79..009aae14 100644 --- a/src/upgrade/signature.rs +++ b/src/upgrade/signature.rs @@ -18,10 +18,10 @@ //! is accepted as a reasonable trade-off for post-quantum security. use crate::error::{Error, Result}; +use crate::logging::debug; use saorsa_pqc::api::sig::{ml_dsa_65, MlDsaPublicKey, MlDsaSignature, MlDsaVariant}; use std::fs; use std::path::Path; -use tracing::debug; /// Signing context for domain separation (prevents cross-protocol attacks). pub const SIGNING_CONTEXT: &[u8] = b"ant-node-release-v1";