vector_core/
negentropy.rs1use std::collections::HashSet;
8use std::time::Duration;
9
10use nostr_sdk::prelude::*;
11
12pub async fn reconcile_missing(
24 filter: Filter,
25 local_items: Vec<(EventId, Timestamp)>,
26 timeout: Duration,
27) -> Result<HashSet<EventId>, String> {
28 use futures_util::stream::{FuturesUnordered, StreamExt};
29
30 let client = crate::state::nostr_client().ok_or("Nostr client not initialized")?;
31
32 let opts = SyncOptions::new()
33 .direction(SyncDirection::Down)
34 .initial_timeout(timeout)
35 .dry_run();
36
37 let relay_map = client.relays().await;
39 let trusted = crate::state::active_trusted_relays().await;
40 let relays: Vec<(String, Relay)> = trusted.iter().filter_map(|url| {
41 let normalized = url.trim_end_matches('/');
42 relay_map.iter()
43 .find(|(u, _)| u.as_str().trim_end_matches('/') == normalized)
44 .map(|(_, r)| (url.to_string(), r.clone()))
45 }).collect();
46 drop(relay_map);
47
48 if relays.is_empty() {
49 crate::log_warn!("[Negentropy] No trusted relays available for reconciliation");
50 return Ok(HashSet::new());
51 }
52
53 let mut futs = FuturesUnordered::new();
54 for (url, relay) in &relays {
55 let url = url.clone();
56 let relay = relay.clone();
57 let f = filter.clone();
58 let items = local_items.clone();
59 let o = opts.clone();
60 futs.push(async move {
61 let r = tokio::time::timeout(timeout, relay.sync_with_items(f, items, &o)).await;
62 (url, r)
63 });
64 }
65
66 let mut missing: HashSet<EventId> = HashSet::new();
67 while let Some((url, result)) = futs.next().await {
68 match result {
69 Ok(Ok(recon)) => {
70 let n = recon.remote.len();
71 missing.extend(recon.remote);
72 crate::log_debug!("[Negentropy] {} reconciled: {} missing", url, n);
73 }
74 Ok(Err(e)) => crate::log_warn!("[Negentropy] {} failed: {}", url, e),
75 Err(_) => crate::log_warn!("[Negentropy] {} timed out", url),
76 }
77 }
78
79 Ok(missing)
80}