diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index f90be8517..22df3520f 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -10,6 +10,7 @@ permissions: env: RUSTFLAGS: -Dwarnings + RUST_LOG_JSON: "0" jobs: test: diff --git a/Cargo.lock b/Cargo.lock index 7f1a59a24..1c336c13e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2981,6 +2981,7 @@ dependencies = [ "tonic", "tonic-prost", "tracing", + "tracing-subscriber", ] [[package]] diff --git a/Cargo.toml b/Cargo.toml index df100c1b2..ca41f1e6b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -43,7 +43,7 @@ tabled = "0.16" anyhow = "1.0.98" toml = "0.9.2" futures = "0.3.31" -tracing-subscriber = { version = "0.3.19", features = ["env-filter"] } +tracing-subscriber = { version = "0.3.19", features = ["env-filter", "json"] } # TODO: Switch back to a crates.io release once age 0.12+ is published. # We pin to this specific commit because age 0.11.x depends on i18n-embed-fl 0.9.x, # which has a non-determinism bug (HashMap iteration in the fl!() proc macro) that diff --git a/crates/e2e-tests/src/main.rs b/crates/e2e-tests/src/main.rs index 8f81443a4..bcddcebd4 100644 --- a/crates/e2e-tests/src/main.rs +++ b/crates/e2e-tests/src/main.rs @@ -24,6 +24,10 @@ use std::path::Path; about = "Manage a local Hashi dev environment" )] struct Cli { + /// Enable verbose tracing output (INFO level). + #[clap(long, short, global = true)] + verbose: bool, + #[command(subcommand)] command: Commands, } @@ -73,10 +77,6 @@ enum Commands { #[clap(long, default_value = "18443")] btc_rpc_port: u16, - /// Enable verbose tracing output - #[clap(long, short)] - verbose: bool, - #[command(flatten)] opts: LocalnetOpts, }, @@ -215,23 +215,24 @@ fn print_warning(msg: &str) { async fn main() -> Result<()> { let cli = Cli::parse(); + let default_level = if cli.verbose { + tracing::level_filters::LevelFilter::INFO + } else { + tracing::level_filters::LevelFilter::OFF + }; + hashi_types::telemetry::TelemetryConfig::new() + .with_default_level(default_level) + .with_target(false) + .with_env() + .init(); + match cli.command { Commands::Start { num_validators, sui_rpc_port, btc_rpc_port, - verbose, opts, - } => { - cmd_start( - num_validators, - sui_rpc_port, - btc_rpc_port, - verbose, - &opts.data_dir, - ) - .await - } + } => cmd_start(num_validators, sui_rpc_port, btc_rpc_port, &opts.data_dir).await, Commands::Stop { opts } => cmd_stop(&opts.data_dir).await, Commands::Status { opts } => cmd_status(&opts.data_dir), Commands::Info { opts } => cmd_info(&opts.data_dir), @@ -259,7 +260,6 @@ async fn cmd_start( num_validators: usize, sui_rpc_port: u16, btc_rpc_port: u16, - verbose: bool, data_dir: &Path, ) -> Result<()> { // Check for existing running instance @@ -273,21 +273,6 @@ async fn cmd_start( print_warning("Found stale state file, cleaning up..."); } - let default_level = if verbose { - tracing::level_filters::LevelFilter::INFO - } else { - tracing::level_filters::LevelFilter::OFF - }; - - tracing_subscriber::fmt() - .with_env_filter( - tracing_subscriber::EnvFilter::builder() - .with_default_directive(default_level.into()) - .from_env_lossy(), - ) - .with_target(false) - .init(); - use std::io::Write; print!( "{} Starting localnet with {} validators...", diff --git a/crates/hashi-guardian/src/main.rs b/crates/hashi-guardian/src/main.rs index 32512a924..f82dd10b8 100644 --- a/crates/hashi-guardian/src/main.rs +++ b/crates/hashi-guardian/src/main.rs @@ -102,7 +102,10 @@ pub struct EphemeralKeyPairs { /// SETUP_MODE=false: all endpoints except setup_new_key are enabled. #[tokio::main] async fn main() -> Result<()> { - init_tracing_subscriber(true); + hashi_types::telemetry::TelemetryConfig::new() + .with_file_line(true) + .with_env() + .init(); // Check if SETUP_MODE is enabled (defaults to false) let setup_mode = std::env::var("SETUP_MODE") @@ -537,27 +540,6 @@ impl Enclave { } } -// --------------------------------- -// Tracing utilities -// --------------------------------- - -/// Initialize tracing subscriber with optional file/line number logging -pub fn init_tracing_subscriber(with_file_line: bool) { - let mut builder = tracing_subscriber::FmtSubscriber::builder().with_env_filter( - tracing_subscriber::EnvFilter::builder() - .with_default_directive(tracing::level_filters::LevelFilter::INFO.into()) - .from_env_lossy(), - ); - - if with_file_line { - builder = builder.with_file(true).with_line_number(true); - } - - let subscriber = builder.finish(); - tracing::subscriber::set_global_default(subscriber) - .expect("unable to initialize tracing subscriber"); -} - // --------------------------------- // Tests and related utilities // --------------------------------- diff --git a/crates/hashi-monitor/src/main.rs b/crates/hashi-monitor/src/main.rs index 3e900d9a9..8db9f2b3a 100644 --- a/crates/hashi-monitor/src/main.rs +++ b/crates/hashi-monitor/src/main.rs @@ -51,7 +51,10 @@ enum Command { #[tokio::main] async fn main() -> anyhow::Result<()> { - init_tracing_subscriber(false); + hashi_types::telemetry::TelemetryConfig::new() + .with_target(false) + .with_env() + .init(); let cli = Cli::parse(); @@ -78,22 +81,3 @@ async fn main() -> anyhow::Result<()> { Ok(()) } - -pub fn init_tracing_subscriber(with_file_line: bool) { - let mut builder = tracing_subscriber::FmtSubscriber::builder().with_env_filter( - tracing_subscriber::EnvFilter::builder() - .with_default_directive(tracing::level_filters::LevelFilter::INFO.into()) - .from_env_lossy(), - ); - - if with_file_line { - builder = builder - .with_file(true) - .with_line_number(true) - .with_target(false); - } - - let subscriber = builder.finish(); - tracing::subscriber::set_global_default(subscriber) - .expect("unable to initialize tracing subscriber"); -} diff --git a/crates/hashi-screener/src/main.rs b/crates/hashi-screener/src/main.rs index fb16c3ab0..8b894a75c 100644 --- a/crates/hashi-screener/src/main.rs +++ b/crates/hashi-screener/src/main.rs @@ -226,20 +226,6 @@ impl ScreenerService for ScreenerServiceImpl { } } -fn init_tracing_subscriber() { - let subscriber = tracing_subscriber::FmtSubscriber::builder() - .with_env_filter( - tracing_subscriber::EnvFilter::builder() - .with_default_directive(tracing::level_filters::LevelFilter::INFO.into()) - .from_env_lossy(), - ) - .with_file(true) - .with_line_number(true) - .finish(); - tracing::subscriber::set_global_default(subscriber) - .expect("unable to initialize tracing subscriber"); -} - fn start_metrics_server(registry: prometheus::Registry) -> sui_http::ServerHandle { let addr: SocketAddr = "0.0.0.0:9184".parse().unwrap(); info!("Prometheus metrics server listening on {}", addr); @@ -266,7 +252,10 @@ async fn metrics_handler( #[tokio::main] async fn main() -> Result<()> { - init_tracing_subscriber(); + hashi_types::telemetry::TelemetryConfig::new() + .with_file_line(true) + .with_env() + .init(); let api_key = env::var("MERKLE_SCIENCE_API_KEY") .map_err(|_| anyhow::anyhow!("MERKLE_SCIENCE_API_KEY environment variable is not set"))?; diff --git a/crates/hashi-types/Cargo.toml b/crates/hashi-types/Cargo.toml index c3a0657e8..9ffafd88c 100644 --- a/crates/hashi-types/Cargo.toml +++ b/crates/hashi-types/Cargo.toml @@ -37,6 +37,7 @@ ed25519-consensus = "2.1" miniscript = "13.0.0" serde_json.workspace = true tracing.workspace = true +tracing-subscriber.workspace = true time = "0.3" hex.workspace = true diff --git a/crates/hashi-types/src/lib.rs b/crates/hashi-types/src/lib.rs index d34fbab8b..01294a36a 100644 --- a/crates/hashi-types/src/lib.rs +++ b/crates/hashi-types/src/lib.rs @@ -6,4 +6,5 @@ pub mod committee; pub mod guardian; pub mod move_types; pub mod proto; +pub mod telemetry; pub mod utils; diff --git a/crates/hashi-types/src/telemetry.rs b/crates/hashi-types/src/telemetry.rs new file mode 100644 index 000000000..10b672721 --- /dev/null +++ b/crates/hashi-types/src/telemetry.rs @@ -0,0 +1,102 @@ +// Copyright (c) Mysten Labs, Inc. +// SPDX-License-Identifier: Apache-2.0 + +//! Shared `tracing` subscriber initialization for all hashi binaries. + +use std::io::IsTerminal; +use std::io::stderr; + +use tracing::level_filters::LevelFilter; +use tracing_subscriber::EnvFilter; +use tracing_subscriber::Layer; +use tracing_subscriber::layer::SubscriberExt; +use tracing_subscriber::util::SubscriberInitExt; + +pub struct TelemetryConfig { + default_level: LevelFilter, + json: Option, + file_line: bool, + target: bool, +} + +impl Default for TelemetryConfig { + fn default() -> Self { + Self::new() + } +} + +impl TelemetryConfig { + pub fn new() -> Self { + Self { + default_level: LevelFilter::INFO, + json: None, + file_line: false, + target: true, + } + } + + pub fn with_default_level(mut self, level: LevelFilter) -> Self { + self.default_level = level; + self + } + + pub fn with_json(mut self, json: bool) -> Self { + self.json = Some(json); + self + } + + pub fn with_file_line(mut self, enabled: bool) -> Self { + self.file_line = enabled; + self + } + + pub fn with_target(mut self, enabled: bool) -> Self { + self.target = enabled; + self + } + + /// `RUST_LOG_JSON=0`/`false`/`no` forces TTY; any other value forces JSON. + pub fn with_env(mut self) -> Self { + if let Ok(value) = std::env::var("RUST_LOG_JSON") { + self.json = match value.trim().to_ascii_lowercase().as_str() { + "0" | "false" | "no" => Some(false), + _ => Some(true), + }; + } + self + } + + pub fn init(self) { + let use_json = match self.json { + Some(true) => true, + Some(false) => false, + None => !stderr().is_terminal(), + }; + + let env_filter = EnvFilter::builder() + .with_default_directive(self.default_level.into()) + .from_env_lossy(); + + if use_json { + let fmt_layer = tracing_subscriber::fmt::layer() + .with_writer(stderr) + .with_file(true) + .with_line_number(true) + .with_target(self.target) + .json() + .with_filter(env_filter); + + tracing_subscriber::registry().with(fmt_layer).init(); + } else { + let fmt_layer = tracing_subscriber::fmt::layer() + .with_writer(stderr) + .with_file(self.file_line) + .with_line_number(self.file_line) + .with_target(self.target) + .with_ansi(stderr().is_terminal()) + .with_filter(env_filter); + + tracing_subscriber::registry().with(fmt_layer).init(); + } + } +} diff --git a/crates/hashi/src/btc_monitor/monitor.rs b/crates/hashi/src/btc_monitor/monitor.rs index a68c93483..0966e74f8 100644 --- a/crates/hashi/src/btc_monitor/monitor.rs +++ b/crates/hashi/src/btc_monitor/monitor.rs @@ -226,6 +226,7 @@ impl Monitor { } /// Run the main event loop, returning the reason it exited. + #[tracing::instrument(name = "btc_monitor", skip_all)] async fn run_event_loop( &mut self, kyoto_client: &mut kyoto::Client, diff --git a/crates/hashi/src/cli/mod.rs b/crates/hashi/src/cli/mod.rs index 9a67df4e1..92259cf5b 100644 --- a/crates/hashi/src/cli/mod.rs +++ b/crates/hashi/src/cli/mod.rs @@ -668,19 +668,16 @@ fn parse_metadata(args: Vec) -> Vec<(String, String)> { } fn init_tracing(verbose: bool) { - let filter = if verbose { - tracing_subscriber::EnvFilter::builder() - .with_default_directive(tracing::level_filters::LevelFilter::DEBUG.into()) - .from_env_lossy() + let level = if verbose { + tracing::level_filters::LevelFilter::DEBUG } else { - tracing_subscriber::EnvFilter::builder() - .with_default_directive(tracing::level_filters::LevelFilter::WARN.into()) - .from_env_lossy() + tracing::level_filters::LevelFilter::WARN }; - tracing_subscriber::fmt() - .with_env_filter(filter) + hashi_types::telemetry::TelemetryConfig::new() + .with_default_level(level) .with_target(false) + .with_env() .init(); } diff --git a/crates/hashi/src/deposits.rs b/crates/hashi/src/deposits.rs index 082c67aeb..2652b2a5c 100644 --- a/crates/hashi/src/deposits.rs +++ b/crates/hashi/src/deposits.rs @@ -46,6 +46,7 @@ pub fn derive_deposit_address( } impl Hashi { + #[tracing::instrument(level = "info", skip_all, fields(deposit_id = %deposit_request.id))] pub async fn validate_and_sign_deposit_confirmation( &self, deposit_request: &DepositRequest, @@ -54,6 +55,7 @@ impl Hashi { self.sign_deposit_confirmation(deposit_request) } + #[tracing::instrument(level = "debug", skip_all, fields(deposit_id = %deposit_request.id))] pub async fn validate_deposit_request( &self, deposit_request: &DepositRequest, @@ -67,6 +69,7 @@ impl Hashi { /// Run AML/Sanctions checks for the deposit request. /// If no screener client is configured, checks are skipped. + #[tracing::instrument(level = "debug", skip_all, fields(deposit_id = %deposit_request.id))] async fn screen_deposit( &self, deposit_request: &DepositRequest, @@ -104,6 +107,7 @@ impl Hashi { } /// Validate that the deposit request exists on Sui + #[tracing::instrument(level = "debug", skip_all, fields(deposit_id = %deposit_request.id))] fn validate_deposit_request_on_sui( &self, deposit_request: &DepositRequest, @@ -143,6 +147,15 @@ impl Hashi { } /// Validate that there is a txout on Bitcoin that matches the deposit request + #[tracing::instrument( + level = "debug", + skip_all, + fields( + deposit_id = %deposit_request.id, + bitcoin_txid = %deposit_request.utxo.id.txid, + vout = deposit_request.utxo.id.vout, + ), + )] async fn validate_deposit_request_on_bitcoin( &self, deposit_request: &DepositRequest, diff --git a/crates/hashi/src/grpc/bridge_service.rs b/crates/hashi/src/grpc/bridge_service.rs index 66a84f3ab..1772682a8 100644 --- a/crates/hashi/src/grpc/bridge_service.rs +++ b/crates/hashi/src/grpc/bridge_service.rs @@ -45,20 +45,26 @@ impl BridgeService for HttpService { } /// Validate and sign a confirmation of a bitcoin deposit request. + #[tracing::instrument( + level = "info", + skip_all, + fields(deposit_id = tracing::field::Empty, caller = tracing::field::Empty), + )] async fn sign_deposit_confirmation( &self, request: Request, ) -> Result, Status> { - authenticate_caller(&request)?; + let caller = authenticate_caller(&request)?; + tracing::Span::current().record("caller", tracing::field::display(&caller)); let deposit_request = parse_deposit_request(request.get_ref()) .map_err(|e| Status::invalid_argument(e.to_string()))?; + tracing::Span::current().record("deposit_id", tracing::field::display(&deposit_request.id)); let member_signature = self .inner .validate_and_sign_deposit_confirmation(&deposit_request) .await .map_err(|e| Status::failed_precondition(e.to_string()))?; tracing::info!( - deposit_request_id = %deposit_request.id, utxo_txid = %deposit_request.utxo.id.txid, utxo_vout = deposit_request.utxo.id.vout, amount = deposit_request.utxo.amount, @@ -70,42 +76,53 @@ impl BridgeService for HttpService { } /// Step 1: Validate and sign approval for a batch of unapproved withdrawal requests. + #[tracing::instrument( + level = "info", + skip_all, + fields(request_id = tracing::field::Empty, caller = tracing::field::Empty), + )] async fn sign_withdrawal_request_approval( &self, request: Request, ) -> Result, Status> { - authenticate_caller(&request)?; + let caller = authenticate_caller(&request)?; + tracing::Span::current().record("caller", tracing::field::display(&caller)); let approval = parse_withdrawal_request_approval(request.get_ref()) .map_err(|e| Status::invalid_argument(e.to_string()))?; + tracing::Span::current() + .record("request_id", tracing::field::display(&approval.request_id)); let member_signature = self .inner .validate_and_sign_withdrawal_request_approval(&approval) .await .map_err(|e| Status::failed_precondition(e.to_string()))?; - tracing::info!( - request_id = %approval.request_id, - "Signed withdrawal request approval", - ); + tracing::info!("Signed withdrawal request approval"); Ok(Response::new(SignWithdrawalRequestApprovalResponse { member_signature: Some(member_signature), })) } /// Step 2: Validate and sign a proposed withdrawal transaction construction. + #[tracing::instrument( + level = "info", + skip_all, + fields(bitcoin_txid = tracing::field::Empty, caller = tracing::field::Empty), + )] async fn sign_withdrawal_tx_construction( &self, request: Request, ) -> Result, Status> { - authenticate_caller(&request)?; + let caller = authenticate_caller(&request)?; + tracing::Span::current().record("caller", tracing::field::display(&caller)); let approval = parse_withdrawal_tx_commitment(request.get_ref()) .map_err(|e| Status::invalid_argument(e.to_string()))?; + tracing::Span::current().record("bitcoin_txid", tracing::field::display(&approval.txid)); let member_signature = self .inner .validate_and_sign_withdrawal_tx_commitment(&approval) .await .map_err(|e| Status::failed_precondition(e.to_string()))?; tracing::info!( - txid = %approval.txid, requests = approval.request_ids.len(), "Signed withdrawal tx construction", ); @@ -114,23 +131,33 @@ impl BridgeService for HttpService { })) } + #[tracing::instrument( + level = "info", + skip_all, + fields(withdrawal_txn_id = tracing::field::Empty, caller = tracing::field::Empty), + )] async fn sign_withdrawal_transaction( &self, request: Request, ) -> Result, Status> { - authenticate_caller(&request)?; + let caller = authenticate_caller(&request)?; + tracing::Span::current().record("caller", tracing::field::display(&caller)); let withdrawal_txn_id = Address::from_bytes(&request.get_ref().withdrawal_txn_id) .map_err(|e| Status::invalid_argument(format!("invalid withdrawal_txn_id: {e}")))?; - tracing::info!(withdrawal_txn_id = %withdrawal_txn_id, "sign_withdrawal_transaction called"); + tracing::Span::current().record( + "withdrawal_txn_id", + tracing::field::display(&withdrawal_txn_id), + ); + tracing::info!("sign_withdrawal_transaction called"); let signatures = self .inner .validate_and_sign_withdrawal_tx(&withdrawal_txn_id) .await .map_err(|e| { - tracing::error!(withdrawal_txn_id = %withdrawal_txn_id, "sign_withdrawal_transaction failed: {e}"); + tracing::error!("sign_withdrawal_transaction failed: {e}"); Status::failed_precondition(e.to_string()) })?; - tracing::info!(withdrawal_txn_id = %withdrawal_txn_id, "sign_withdrawal_transaction succeeded"); + tracing::info!("sign_withdrawal_transaction succeeded"); Ok(Response::new(SignWithdrawalTransactionResponse { signatures_by_input: signatures .iter() @@ -140,41 +167,55 @@ impl BridgeService for HttpService { } /// Step 3: Validate and sign the BLS certificate over witness signatures. + #[tracing::instrument( + level = "info", + skip_all, + fields(withdrawal_id = tracing::field::Empty, caller = tracing::field::Empty), + )] async fn sign_withdrawal_tx_signing( &self, request: Request, ) -> Result, Status> { - authenticate_caller(&request)?; + let caller = authenticate_caller(&request)?; + tracing::Span::current().record("caller", tracing::field::display(&caller)); let message = parse_withdrawal_tx_signing(request.get_ref()) .map_err(|e| Status::invalid_argument(e.to_string()))?; + tracing::Span::current().record( + "withdrawal_id", + tracing::field::display(&message.withdrawal_id), + ); let member_signature = self .inner .validate_and_sign_withdrawal_tx_signing(&message) .map_err(|e| Status::failed_precondition(e.to_string()))?; - tracing::info!( - withdrawal_id = %message.withdrawal_id, - "Signed withdrawal tx signing", - ); + tracing::info!("Signed withdrawal tx signing"); Ok(Response::new(SignWithdrawalTxSigningResponse { member_signature: Some(member_signature), })) } + #[tracing::instrument( + level = "info", + skip_all, + fields(withdrawal_txn_id = tracing::field::Empty, caller = tracing::field::Empty), + )] async fn sign_withdrawal_confirmation( &self, request: Request, ) -> Result, Status> { - authenticate_caller(&request)?; + let caller = authenticate_caller(&request)?; + tracing::Span::current().record("caller", tracing::field::display(&caller)); let withdrawal_txn_id = Address::from_bytes(&request.get_ref().withdrawal_txn_id) .map_err(|e| Status::invalid_argument(format!("invalid withdrawal_txn_id: {e}")))?; + tracing::Span::current().record( + "withdrawal_txn_id", + tracing::field::display(&withdrawal_txn_id), + ); let member_signature = self .inner .sign_withdrawal_confirmation(&withdrawal_txn_id) .map_err(|e| Status::failed_precondition(e.to_string()))?; - tracing::info!( - withdrawal_txn_id = %withdrawal_txn_id, - "Signed withdrawal confirmation", - ); + tracing::info!("Signed withdrawal confirmation"); Ok(Response::new(SignWithdrawalConfirmationResponse { member_signature: Some(member_signature), })) diff --git a/crates/hashi/src/leader/mod.rs b/crates/hashi/src/leader/mod.rs index eb288ffd8..34f1fa00b 100644 --- a/crates/hashi/src/leader/mod.rs +++ b/crates/hashi/src/leader/mod.rs @@ -98,6 +98,7 @@ impl LeaderService { }) } + #[tracing::instrument(name = "leader", skip_all)] async fn run(mut self) { info!("Starting leader service"); @@ -345,22 +346,23 @@ impl LeaderService { self.check_delete_expired_deposit_requests(&deposit_requests, checkpoint_timestamp_ms); } + #[tracing::instrument(level = "info", skip_all, fields(deposit_id = %deposit_request.id))] async fn process_deposit_request( inner: Arc, deposit_request: DepositRequest, ) -> anyhow::Result<()> { - info!(deposit_request_id = %deposit_request.id, "Processing deposit request"); + info!("Processing deposit request"); // Validate deposit_request before asking for signatures inner .validate_deposit_request(&deposit_request) .await .map_err(|e| { - debug!(request_id = ?deposit_request.id, "Deposit validation failed: {e}"); + debug!("Deposit validation failed: {e}"); anyhow::anyhow!(e) })?; - info!(deposit_request_id = %deposit_request.id, "Deposit request validated successfully"); + info!("Deposit request validated successfully"); let proto_request = deposit_request_to_proto(&deposit_request); let members = inner @@ -394,7 +396,7 @@ impl LeaderService { while let Some(result) = sig_tasks.join_next().await { let Ok(Some(sig)) = result else { continue }; if let Err(e) = aggregator.add_signature(sig) { - error!(deposit_request_id = %deposit_request.id, "Failed to add deposit signature: {e}"); + error!("Failed to add deposit signature: {e}"); } if aggregator.weight() >= required_weight { break; @@ -420,10 +422,10 @@ impl LeaderService { .with_label_values(&["confirm_deposit", "success"]) .inc(); inner.metrics.deposits_confirmed_total.inc(); - info!(deposit_request_id = %deposit_request.id, "Successfully submitted deposit confirmation"); + info!("Successfully submitted deposit confirmation"); }) .inspect_err(|e| { - error!(deposit_request_id = %deposit_request.id, "Failed to submit deposit confirmation: {e}"); + error!("Failed to submit deposit confirmation: {e}"); inner .metrics .sui_tx_submissions_total @@ -433,16 +435,14 @@ impl LeaderService { Ok(()) } + #[tracing::instrument(level = "debug", skip_all, fields(validator = %member.validator_address()))] async fn request_deposit_confirmation_signature( inner: &Arc, proto_request: SignDepositConfirmationRequest, member: &CommitteeMember, ) -> Option { let validator_address = member.validator_address(); - trace!( - "Requesting deposit confirmation signature from {}", - validator_address - ); + trace!("Requesting deposit confirmation signature"); let mut rpc_client = inner .onchain_state() @@ -550,6 +550,7 @@ impl LeaderService { })); } + #[tracing::instrument(level = "info", skip_all, fields(batch_size = to_process.len()))] async fn process_unapproved_withdrawal_requests_task( inner: Arc, retry_tracker: RetryTracker, @@ -637,10 +638,7 @@ impl LeaderService { retry_tracker.record_failure(kind, request_id, checkpoint_timestamp_ms); } if let Err(err) = &result { - error!( - withdrawal_request_id = %request_id, - "Withdrawal approval failed: {err:#}" - ); + error!(request_id = %request_id, "Withdrawal approval failed: {err:#}"); } (request_id, result) @@ -666,6 +664,7 @@ impl LeaderService { Ok(()) } + #[tracing::instrument(level = "info", skip_all, fields(request_id = %request.id))] async fn process_unapproved_withdrawal_request( inner: Arc, retry_tracker: RetryTracker, @@ -705,7 +704,7 @@ impl LeaderService { let mut aggregator = BlsSignatureAggregator::new(committee, approval); if let Err(e) = aggregator.add_signature(local_sig) { - error!(withdrawal_request_id = %request.id, "Failed to add local approval signature: {e}"); + error!("Failed to add local approval signature: {e}"); } // Fan out signature requests to remote members in parallel. @@ -726,7 +725,7 @@ impl LeaderService { while let Some(result) = sig_tasks.join_next().await { let Ok(Some(sig)) = result else { continue }; if let Err(e) = aggregator.add_signature(sig) { - error!(withdrawal_request_id = %request.id, "Failed to add approval signature: {e}"); + error!("Failed to add approval signature: {e}"); } if aggregator.weight() >= required_weight { break; @@ -745,14 +744,14 @@ impl LeaderService { request.id, checkpoint_timestamp_ms, ); - error!(withdrawal_request_id = %request.id, "Insufficient approval signatures: weight {weight} < {required_weight}"); + error!("Insufficient approval signatures: weight {weight} < {required_weight}"); return Ok(None); } match aggregator.finish() { Ok(signed) => Ok(Some((request.id, signed.committee_signature().clone()))), Err(e) => { - error!(withdrawal_request_id = %request.id, "Failed to build approval certificate: {e}"); + error!("Failed to build approval certificate: {e}"); Ok(None) } } @@ -917,6 +916,7 @@ impl LeaderService { })); } + #[tracing::instrument(level = "info", skip_all, fields(batch_size = requests.len()))] async fn process_approved_withdrawal_request_batch( inner: Arc, retry_tracker: GlobalRetryTracker, @@ -1102,6 +1102,7 @@ impl LeaderService { } } + #[tracing::instrument(level = "info", skip_all, fields(withdrawal_txn_id = %txn.id))] async fn process_unsigned_withdrawal_txn( inner: Arc, txn: WithdrawalTransaction, @@ -1112,7 +1113,6 @@ impl LeaderService { let current_epoch = inner.onchain_state().epoch(); if txn.epoch != current_epoch { info!( - withdrawal_txn_id = %txn.id, "Withdrawal transaction from epoch {} (current {}), reassigning presig indices", txn.epoch, current_epoch, ); @@ -1120,14 +1120,11 @@ impl LeaderService { executor .execute_allocate_presigs_for_withdrawal_txn(txn.id) .await?; - info!( - withdrawal_txn_id = %txn.id, - "Presig indices reassigned, will sign on next checkpoint" - ); + info!("Presig indices reassigned, will sign on next checkpoint"); // Return and let the next checkpoint iteration pick up the updated state. return Ok(()); } - info!(withdrawal_txn_id = %txn.id, "MPC signing withdrawal transaction"); + info!("MPC signing withdrawal transaction"); let members = inner .onchain_state() @@ -1267,25 +1264,33 @@ impl LeaderService { /// Check BTC tx status, broadcast/re-broadcast if needed, confirm when /// enough BTC confirmations are reached. + #[tracing::instrument(level = "info", skip_all, fields(withdrawal_txn_id = %txn.id, bitcoin_txid))] async fn handle_signed_withdrawal( inner: Arc, txn: WithdrawalTransaction, ) -> anyhow::Result<()> { let confirmation_threshold = inner.onchain_state().bitcoin_confirmation_threshold(); let txid: bitcoin::Txid = txn.txid.into(); + tracing::Span::current().record("bitcoin_txid", tracing::field::display(&txid)); match inner.btc_monitor().get_transaction_status(txid).await { Ok(TxStatus::Confirmed { confirmations }) if confirmations >= confirmation_threshold => { - info!(withdrawal_txn_id = %txn.id, bitcoin_txid = %txid, confirmations, "Withdrawal tx confirmed, proceeding to on-chain confirmation"); + info!( + confirmations, + "Withdrawal tx confirmed, proceeding to on-chain confirmation" + ); Self::confirm_withdrawal_on_sui(&inner, &txn).await?; } Ok(TxStatus::Confirmed { confirmations }) => { - debug!(withdrawal_txn_id = %txn.id, bitcoin_txid = %txid, confirmations, confirmation_threshold, "Withdrawal tx waiting for more confirmations"); + debug!( + confirmations, + confirmation_threshold, "Withdrawal tx waiting for more confirmations" + ); } Ok(TxStatus::InMempool) => { - debug!(withdrawal_txn_id = %txn.id, bitcoin_txid = %txid, "Withdrawal tx in mempool, waiting for confirmations"); + debug!("Withdrawal tx in mempool, waiting for confirmations"); } Ok(TxStatus::NotFound) => { Self::rebuild_and_broadcast_withdrawal_btc_tx(&inner, &txn, txid).await; @@ -1302,27 +1307,28 @@ impl LeaderService { /// Rebuild a fully signed Bitcoin transaction from on-chain WithdrawalTransaction /// data (stored witness signatures) and broadcast it to the Bitcoin network. + #[tracing::instrument(level = "info", skip_all, fields(withdrawal_txn_id = %txn.id, bitcoin_txid = %txid))] async fn rebuild_and_broadcast_withdrawal_btc_tx( inner: &Arc, txn: &WithdrawalTransaction, txid: bitcoin::Txid, ) { - warn!(withdrawal_txn_id = %txn.id, bitcoin_txid = %txid, "Withdrawal tx not found, re-broadcasting from on-chain signatures"); + warn!("Withdrawal tx not found, re-broadcasting from on-chain signatures"); let tx = match Self::rebuild_signed_tx_from_onchain(inner, txn) { Ok(tx) => tx, Err(e) => { - error!(withdrawal_txn_id = %txn.id, bitcoin_txid = %txid, "Failed to rebuild signed withdrawal tx: {e}"); + error!("Failed to rebuild signed withdrawal tx: {e}"); return; } }; match inner.btc_monitor().broadcast_transaction(tx).await { Ok(()) => { - info!(withdrawal_txn_id = %txn.id, bitcoin_txid = %txid, "Re-broadcast withdrawal tx"); + info!("Re-broadcast withdrawal tx"); } Err(e) => { - error!(withdrawal_txn_id = %txn.id, bitcoin_txid = %txid, "Failed to re-broadcast withdrawal tx: {e}"); + error!("Failed to re-broadcast withdrawal tx: {e}"); } } } @@ -1369,6 +1375,7 @@ impl LeaderService { Ok(tx) } + #[tracing::instrument(level = "info", skip_all, fields(withdrawal_txn_id = %txn.id))] async fn confirm_withdrawal_on_sui( inner: &Arc, txn: &WithdrawalTransaction, @@ -1402,6 +1409,7 @@ impl LeaderService { Ok(()) } + #[tracing::instrument(level = "debug", skip_all, fields(withdrawal_txn_id = %withdrawal_txn_id))] async fn collect_withdrawal_confirmation_signature( inner: &Arc, withdrawal_txn_id: Address, @@ -1431,7 +1439,7 @@ impl LeaderService { while let Some(result) = sig_tasks.join_next().await { let Ok(Some(sig)) = result else { continue }; if let Err(e) = aggregator.add_signature(sig) { - error!(withdrawal_txn_id = %withdrawal_txn_id, "Failed to add withdrawal confirmation signature: {e}"); + error!("Failed to add withdrawal confirmation signature: {e}"); } if aggregator.weight() >= required_weight { break; @@ -1449,16 +1457,14 @@ impl LeaderService { Ok(aggregator.finish()?.into_parts().0) } + #[tracing::instrument(level = "debug", skip_all, fields(validator = %member.validator_address()))] async fn request_withdrawal_tx_commitment_signature( inner: &Arc, proto_request: SignWithdrawalTxConstructionRequest, member: &CommitteeMember, ) -> Option { let validator_address = member.validator_address(); - trace!( - "Requesting withdrawal approval signature from {}", - validator_address - ); + trace!("Requesting withdrawal tx commitment signature"); let mut rpc_client = inner .onchain_state() @@ -1501,16 +1507,14 @@ impl LeaderService { .ok() } + #[tracing::instrument(level = "debug", skip_all, fields(validator = %member.validator_address()))] async fn request_withdrawal_approval_signature( inner: &Arc, proto_request: SignWithdrawalRequestApprovalRequest, member: &CommitteeMember, ) -> Option { let validator_address = member.validator_address(); - trace!( - "Requesting withdrawal request approval signature from {}", - validator_address - ); + trace!("Requesting withdrawal request approval signature"); let mut rpc_client = inner .onchain_state() @@ -1553,16 +1557,14 @@ impl LeaderService { .ok() } + #[tracing::instrument(level = "debug", skip_all, fields(validator = %member.validator_address()))] async fn request_withdrawal_tx_signing_signature( inner: &Arc, proto_request: SignWithdrawalTxSigningRequest, member: &CommitteeMember, ) -> Option { let validator_address = member.validator_address(); - trace!( - "Requesting withdrawal tx signing signature from {}", - validator_address - ); + trace!("Requesting withdrawal tx signing signature"); let mut rpc_client = inner .onchain_state() @@ -1605,16 +1607,14 @@ impl LeaderService { .ok() } + #[tracing::instrument(level = "debug", skip_all, fields(validator = %member.validator_address()))] async fn request_withdrawal_tx_signature( inner: &Arc, withdrawal_txn_id: &Address, member: &CommitteeMember, ) -> anyhow::Result> { let validator_address = member.validator_address(); - trace!( - "Requesting withdrawal tx signature from {}", - validator_address - ); + trace!("Requesting withdrawal tx signature"); let mut rpc_client = inner .onchain_state() @@ -1688,16 +1688,14 @@ impl LeaderService { } } + #[tracing::instrument(level = "debug", skip_all, fields(validator = %member.validator_address()))] async fn request_withdrawal_confirmation_signature( inner: &Arc, withdrawal_txn_id: Address, member: &CommitteeMember, ) -> Option { let validator_address = member.validator_address(); - trace!( - "Requesting withdrawal confirmation signature from {}", - validator_address - ); + trace!("Requesting withdrawal confirmation signature"); let mut rpc_client = inner .onchain_state() diff --git a/crates/hashi/src/main.rs b/crates/hashi/src/main.rs index a7badceb9..941c1419f 100644 --- a/crates/hashi/src/main.rs +++ b/crates/hashi/src/main.rs @@ -158,7 +158,10 @@ async fn main() -> anyhow::Result<()> { } async fn run_server(config_path: Option) -> anyhow::Result<()> { - init_tracing_subscriber(); + hashi_types::telemetry::TelemetryConfig::new() + .with_file_line(true) + .with_env() + .init(); tracing::info!("welcome to hashi"); @@ -193,17 +196,3 @@ async fn run_server(config_path: Option) -> anyhow::Result<( tracing::info!("hashi shutting down; goodbye"); Ok(()) } - -fn init_tracing_subscriber() { - let subscriber = ::tracing_subscriber::FmtSubscriber::builder() - .with_env_filter( - tracing_subscriber::EnvFilter::builder() - .with_default_directive(tracing::level_filters::LevelFilter::INFO.into()) - .from_env_lossy(), - ) - .with_file(true) - .with_line_number(true) - .finish(); - ::tracing::subscriber::set_global_default(subscriber) - .expect("unable to initialize tracing subscriber"); -} diff --git a/crates/hashi/src/mpc/service.rs b/crates/hashi/src/mpc/service.rs index 34d853fd1..f2a9e5985 100644 --- a/crates/hashi/src/mpc/service.rs +++ b/crates/hashi/src/mpc/service.rs @@ -102,6 +102,7 @@ impl MpcService { }) } + #[tracing::instrument(name = "mpc_service", skip_all)] async fn run(mut self) { let pending = self.get_pending_epoch_change(); let is_in_committee = self.inner.is_in_current_committee(); @@ -282,6 +283,7 @@ impl MpcService { Ok(output) } + #[tracing::instrument(level = "info", skip_all, fields(target_epoch))] async fn run_dkg(&self, target_epoch: u64) -> anyhow::Result { let onchain_state = self.inner.onchain_state().clone(); let mpc_manager = self @@ -531,6 +533,7 @@ impl MpcService { } } + #[tracing::instrument(level = "info", skip_all, fields(target_epoch))] async fn handle_reconfig(&self, target_epoch: u64) { let run_dkg = self .inner @@ -645,6 +648,7 @@ impl MpcService { Ok(()) } + #[tracing::instrument(level = "info", skip_all, fields(target_epoch))] async fn run_key_rotation(&self, target_epoch: u64) -> anyhow::Result { let onchain_state = self.inner.onchain_state().clone(); let mpc_manager = self diff --git a/crates/hashi/src/mpc/signing.rs b/crates/hashi/src/mpc/signing.rs index 8f3c45c68..eafc2b568 100644 --- a/crates/hashi/src/mpc/signing.rs +++ b/crates/hashi/src/mpc/signing.rs @@ -184,6 +184,11 @@ impl SigningManager { } #[allow(clippy::too_many_arguments)] + #[tracing::instrument( + level = "info", + skip_all, + fields(sui_request_id = %sui_request_id, global_presig_index), + )] pub async fn sign( signing_manager: &Arc>, p2p_channel: &impl P2PChannel, diff --git a/crates/hashi/src/onchain/watcher.rs b/crates/hashi/src/onchain/watcher.rs index 21961041e..3e88eb2fc 100644 --- a/crates/hashi/src/onchain/watcher.rs +++ b/crates/hashi/src/onchain/watcher.rs @@ -27,6 +27,7 @@ use crate::onchain::types::ProposalType; use crate::onchain::types::WithdrawalRequest; use hashi_types::move_types::HashiEvent; +#[tracing::instrument(name = "watcher", skip_all)] pub async fn watcher(mut client: Client, state: OnchainState, metrics: Option>) { let subscription_read_mask = FieldMask::from_paths([ Checkpoint::path_builder().sequence_number(), diff --git a/crates/hashi/src/sui_tx_executor.rs b/crates/hashi/src/sui_tx_executor.rs index 873a5cb6a..4e9775760 100644 --- a/crates/hashi/src/sui_tx_executor.rs +++ b/crates/hashi/src/sui_tx_executor.rs @@ -260,6 +260,11 @@ impl SuiTxExecutor { /// and resolving object versions/digests automatically. /// /// Note: The builder is consumed because `TransactionBuilder::build()` takes ownership. + #[tracing::instrument( + level = "debug", + skip_all, + fields(sui_digest = tracing::field::Empty), + )] pub async fn execute( &mut self, mut builder: TransactionBuilder, @@ -282,6 +287,11 @@ impl SuiTxExecutor { .await? .into_inner(); + tracing::Span::current().record( + "sui_digest", + tracing::field::display(response.transaction().digest()), + ); + Ok(response) } @@ -292,6 +302,11 @@ impl SuiTxExecutor { /// Execute a deposit confirmation transaction. /// /// Passes a `Certificate` (BCS-encoded struct) to `deposit::confirm_deposit`. + #[tracing::instrument( + level = "info", + skip_all, + fields(deposit_id = %deposit_request.id), + )] pub async fn execute_confirm_deposit( &mut self, deposit_request: &DepositRequest, @@ -334,6 +349,11 @@ impl SuiTxExecutor { /// /// This builds and executes a PTB that calls `deposit::delete_expired_deposit` /// for each expired request in the batch. + #[tracing::instrument( + level = "info", + skip_all, + fields(expired_count = expired_requests.len()), + )] pub async fn execute_delete_expired_deposit_requests( &mut self, expired_requests: &[DepositRequest], @@ -384,6 +404,11 @@ impl SuiTxExecutor { /// /// Note: The `txid` parameter should be the Bitcoin transaction ID converted to a Sui Address /// (i.e., the 32-byte txid interpreted as a Sui address). + #[tracing::instrument( + level = "info", + skip_all, + fields(bitcoin_txid = %txid, vout, amount = amount_sats), + )] pub async fn execute_create_deposit_request( &mut self, txid: Address, @@ -471,6 +496,11 @@ impl SuiTxExecutor { /// 2. Calling deposit(hashi, utxo, clock) which creates the DepositRequest on-chain /// /// Returns the deposit request IDs on success. + #[tracing::instrument( + level = "info", + skip_all, + fields(bitcoin_txid = %txid, utxo_count = utxos.len()), + )] pub async fn execute_create_deposit_requests_batch( &mut self, txid: Address, @@ -567,6 +597,11 @@ impl SuiTxExecutor { /// /// Callers must ensure the batch size stays within the PTB command limit /// (roughly 300 deposits per PTB due to the 1024-command cap). + #[tracing::instrument( + level = "info", + skip_all, + fields(deposit_count = deposits.len()), + )] pub async fn execute_create_deposit_requests_multi( &mut self, deposits: &[(Address, u32, u64)], @@ -656,6 +691,11 @@ impl SuiTxExecutor { /// 2. Calling `withdraw::request_withdrawal` /// /// Returns the withdrawal request ID on success. + #[tracing::instrument( + level = "info", + skip_all, + fields(amount = withdrawal_amount_sats, request_id = tracing::field::Empty), + )] pub async fn execute_create_withdrawal_request( &mut self, withdrawal_amount_sats: u64, @@ -710,6 +750,10 @@ impl SuiTxExecutor { for event in response.transaction().events().events() { if event.contents().name().contains("WithdrawalRequestedEvent") { let event_data = WithdrawalRequestedEvent::from_bcs(event.contents().value())?; + tracing::Span::current().record( + "request_id", + tracing::field::display(&event_data.request_id), + ); return Ok(event_data.request_id); } } @@ -717,6 +761,7 @@ impl SuiTxExecutor { anyhow::bail!("WithdrawalRequestedEvent not found in transaction events") } + #[tracing::instrument(level = "info", skip_all)] pub async fn execute_start_reconfig(&mut self) -> anyhow::Result<()> { let mut builder = TransactionBuilder::new(); let hashi_arg = builder.object( @@ -747,6 +792,7 @@ impl SuiTxExecutor { Ok(()) } + #[tracing::instrument(level = "info", skip_all)] pub async fn execute_end_reconfig( &mut self, mpc_public_key: &[u8], @@ -779,6 +825,11 @@ impl SuiTxExecutor { } /// Reassign presig indices for a withdrawal transaction from a previous epoch. + #[tracing::instrument( + level = "info", + skip_all, + fields(withdrawal_txn_id = %withdrawal_id), + )] pub async fn execute_allocate_presigs_for_withdrawal_txn( &mut self, withdrawal_id: Address, @@ -813,6 +864,7 @@ impl SuiTxExecutor { /// Delegates to [`build_register_or_update_validator_tx`] to determine which /// move calls are needed, then signs and executes the resulting transaction. /// Returns `Ok(false)` if nothing needed to be updated. + #[tracing::instrument(level = "info", skip_all)] pub async fn execute_register_or_update_validator( &mut self, config: &Config, @@ -858,6 +910,11 @@ impl SuiTxExecutor { /// This submits a DKG, rotation, or nonce generation certificate to the on-chain /// certificate store. The certificate contains the dealer's message hash and /// committee signature. + #[tracing::instrument( + level = "info", + skip_all, + fields(cert_kind = tracing::field::Empty), + )] pub async fn execute_submit_certificate(&mut self, cert: &CertificateV1) -> anyhow::Result<()> { let (inner_cert, function_name, batch_index) = match cert { CertificateV1::Dkg(c) => (c, "submit_dkg_cert", None), @@ -866,6 +923,7 @@ impl SuiTxExecutor { (cert, "submit_nonce_cert", Some(*batch_index)) } }; + tracing::Span::current().record("cert_kind", function_name); let message = inner_cert.message(); let dealer = message.dealer_address; @@ -911,6 +969,11 @@ impl SuiTxExecutor { } /// Execute `withdraw::approve_request` to approve withdrawal requests on-chain. + #[tracing::instrument( + level = "info", + skip_all, + fields(batch_size = approvals.len()), + )] pub async fn execute_approve_withdrawal_requests( &mut self, approvals: &[(Address, &CommitteeSignature)], @@ -950,6 +1013,14 @@ impl SuiTxExecutor { /// Execute `withdraw::commit_withdrawal_tx` to commit to a withdrawal on-chain. /// - `r: &Random` + #[tracing::instrument( + level = "info", + skip_all, + fields( + bitcoin_txid = %approval.txid, + request_count = approval.request_ids.len(), + ), + )] pub async fn execute_commit_withdrawal_tx( &mut self, approval: &WithdrawalTxCommitment, @@ -1062,6 +1133,15 @@ impl SuiTxExecutor { /// handle this, the signatures are split into chunks that each fit within /// the pure-arg budget and stitched back together via /// `0x1::vector::append` calls in the PTB. + #[tracing::instrument( + level = "info", + skip_all, + fields( + withdrawal_txn_id = %withdrawal_id, + request_count = request_ids.len(), + input_count = signatures.len(), + ), + )] pub async fn execute_sign_withdrawal( &mut self, withdrawal_id: &Address, @@ -1111,6 +1191,11 @@ impl SuiTxExecutor { /// /// The Move function returns a `Balance` which is sent back to the /// sender's address balance. + #[tracing::instrument( + level = "info", + skip_all, + fields(request_id = %withdrawal_id), + )] pub async fn execute_cancel_withdrawal( &mut self, withdrawal_id: &Address, @@ -1172,6 +1257,11 @@ impl SuiTxExecutor { /// The Move function expects: /// - `hashi: &mut Hashi` /// - `withdrawal_id: address` + #[tracing::instrument( + level = "info", + skip_all, + fields(withdrawal_txn_id = %withdrawal_id), + )] pub async fn execute_confirm_withdrawal( &mut self, withdrawal_id: &Address, diff --git a/crates/hashi/src/withdrawals.rs b/crates/hashi/src/withdrawals.rs index 65eca3f31..3ef640a5c 100644 --- a/crates/hashi/src/withdrawals.rs +++ b/crates/hashi/src/withdrawals.rs @@ -104,6 +104,7 @@ pub struct WithdrawalConfirmation { impl Hashi { // --- Step 1: Request approval (lightweight) --- + #[tracing::instrument(level = "info", skip_all, fields(request_id = %approval.request_id))] pub async fn validate_and_sign_withdrawal_request_approval( &self, approval: &WithdrawalRequestApproval, @@ -132,6 +133,7 @@ impl Hashi { // --- Step 2: Construction approval (with UTXO selection) --- + #[tracing::instrument(level = "info", skip_all, fields(bitcoin_txid = %approval.txid))] pub async fn validate_and_sign_withdrawal_tx_commitment( &self, approval: &WithdrawalTxCommitment, @@ -140,6 +142,7 @@ impl Hashi { self.sign_withdrawal_tx_commitment(approval) } + #[tracing::instrument(level = "debug", skip_all, fields(bitcoin_txid = %approval.txid))] pub async fn validate_withdrawal_tx_commitment( &self, approval: &WithdrawalTxCommitment, @@ -396,6 +399,7 @@ impl Hashi { // --- Step 3: Sign withdrawal (store witness signatures on-chain) --- + #[tracing::instrument(level = "info", skip_all, fields(withdrawal_id = %message.withdrawal_id))] pub fn validate_and_sign_withdrawal_tx_signing( &self, message: &WithdrawalTxSigning, @@ -496,6 +500,7 @@ impl Hashi { // --- MPC BTC tx signing --- + #[tracing::instrument(level = "info", skip_all, fields(withdrawal_txn_id = %withdrawal_txn_id))] pub async fn validate_and_sign_withdrawal_tx( &self, withdrawal_txn_id: &Address, @@ -532,6 +537,11 @@ impl Hashi { } /// Produce MPC Schnorr signatures for an unsigned withdrawal transaction. + #[tracing::instrument( + level = "debug", + skip_all, + fields(withdrawal_txn_id = %txn.id, input_count = txn.inputs.len()), + )] async fn mpc_sign_withdrawal_tx( &self, txn: &crate::onchain::types::WithdrawalTransaction, @@ -706,6 +716,7 @@ impl Hashi { /// UTXOs using the batching-aware coin selection algorithm, build the /// unsigned BTC tx, and return a `WithdrawalTxCommitment` covering the /// selected requests. + #[tracing::instrument(level = "debug", skip_all, fields(request_count = requests.len()))] pub async fn build_withdrawal_tx_commitment( &self, requests: &[WithdrawalRequest], @@ -835,6 +846,7 @@ impl Hashi { /// Run AML/Sanctions checks for a withdrawal request. /// If no screener client is configured, checks are skipped. + #[tracing::instrument(level = "debug", skip_all, fields(request_id = %request.id))] pub(crate) async fn screen_withdrawal( &self, request: &WithdrawalRequest,