From ced6ef15a1e2ff4babd8a291f6d423846d986302 Mon Sep 17 00:00:00 2001 From: DanConwayDev Date: Sat, 10 Jan 2026 00:11:20 +0000 Subject: fix: detect NIP-77 NOTICE immediately during negentropy sync Previously, when a relay didn't support NIP-77, the negentropy_sync_diff function would wait for the full client.sync() timeout even after receiving a NOTICE message that marked the relay as not supporting NIP-77. This change uses tokio::select! to race the sync operation against a polling task that checks the nip77_supported flag every 10ms. When a NOTICE is received (detected in the message handler), the poll task detects the status change and immediately returns an error, allowing quick fallback to REQ+EOSE without waiting for timeouts. Benefits: - Fast failure (within 10ms) when relay sends NIP-77 NOTICE - No artificial timeout reduction that could hurt legitimate operations - Maintains full timeout for relays that actually support NIP-77 --- src/sync/relay_connection.rs | 77 ++++++++++++++++++++++++++++---------------- 1 file changed, 50 insertions(+), 27 deletions(-) (limited to 'src/sync') diff --git a/src/sync/relay_connection.rs b/src/sync/relay_connection.rs index f19abb9..99fc4ea 100644 --- a/src/sync/relay_connection.rs +++ b/src/sync/relay_connection.rs @@ -509,42 +509,65 @@ impl RelayConnection { // Use dry_run to only identify differences without downloading events let sync_opts = SyncOptions::default().dry_run(); - match self.client.sync(filter.clone(), &sync_opts).await { - Ok(output) => { - let reconciliation = output.val; + // Clone the atomic for the polling task + let nip77_status = self.nip77_supported.clone(); + let url = self.url.clone(); + + // Create a polling task that checks if NIP-77 support was detected as unavailable + let poll_task = async move { + loop { + let status = nip77_status.load(std::sync::atomic::Ordering::Relaxed); + if status == 2 { + // Relay confirmed not to support NIP-77 (via NOTICE or other means) + return Err(format!( + "Relay {} does not support NIP-77 (detected via NOTICE)", + url + )); + } + tokio::time::sleep(tokio::time::Duration::from_millis(10)).await; + } + }; + // Race the sync operation against the polling task + let sync_task = self.client.sync(filter.clone(), &sync_opts); + + let result = tokio::select! { + poll_result = poll_task => { + // Polling detected NIP-77 not supported + poll_result + } + sync_result = sync_task => { + // Sync completed (or failed) first + match sync_result { + Ok(output) => { + // Check for any failures + // Note: Timeouts are common for relays without NIP-77 support + if !output.failed.is_empty() { + Err(format!("Negentropy diff had failures: {:?}", output.failed)) + } else { + Ok(output.val) + } + } + Err(e) => Err(format!("Negentropy diff failed: {}", e)) + } + } + }; + + match result { + Ok(reconciliation) => { tracing::debug!( relay = %self.url, local_count = reconciliation.local.len(), remote_count = reconciliation.remote.len(), "Negentropy diff completed (dry run)" ); - - // Check for any failures - // Note: Timeouts are common for relays without NIP-77 support - if !output.failed.is_empty() { - // Mark relay as not supporting NIP-77 (timeout typically means no support) - self.nip77_supported - .store(2, std::sync::atomic::Ordering::Relaxed); - - // Log warning only once per relay to avoid spam - if !self - .nip77_warning_logged - .swap(true, std::sync::atomic::Ordering::Relaxed) - { - tracing::warn!( - relay = %self.url, - failures = ?output.failed, - "Negentropy diff had failures (timeout usually means relay doesn't support NIP-77), will fall back to REQ+EOSE" - ); - } - - return Err(format!("Negentropy diff had failures: {:?}", output.failed)); - } - Ok(reconciliation) } Err(e) => { + // Mark relay as not supporting NIP-77 + self.nip77_supported + .store(2, std::sync::atomic::Ordering::Relaxed); + // Log warning only once per relay to avoid spam if !self .nip77_warning_logged @@ -556,7 +579,7 @@ impl RelayConnection { "Negentropy diff failed, will fall back to REQ+EOSE" ); } - Err(format!("Negentropy diff failed: {}", e)) + Err(e) } } } -- cgit v1.2.3