Skip to main content

hashtree_network/
mesh_store_core.rs

1//! Shared routed mesh store core.
2//!
3//! This module provides a concrete store wrapper that works with any local storage
4//! backend plus any signaling transport and peer-link factory. Both production
5//! and simulation (mocks) use this same code.
6
7use async_trait::async_trait;
8use futures::{stream::FuturesUnordered, FutureExt, StreamExt};
9use std::collections::hash_map::DefaultHasher;
10use std::collections::{HashMap, HashSet, VecDeque};
11use std::future::Future;
12use std::hash::{Hash as _, Hasher};
13use std::ops::Range;
14use std::sync::atomic::{AtomicBool, AtomicU64, Ordering};
15use std::sync::{Arc, Mutex as StdMutex};
16use std::time::Duration;
17use tokio::sync::{oneshot, Mutex, Notify, RwLock};
18use tokio::time::Instant;
19
20use hashtree_core::{BlobReply, BlobRequest, BlobRoute, Hash, Store, StoreError, BLOB_MAX_BYTES};
21
22use crate::peer_selector::{PeerMetadataSnapshot, PeerSelector, SelectionStrategy};
23use crate::protocol::{
24    create_pubsub_frame, create_pubsub_interest, create_pubsub_inventory, create_pubsub_want,
25    create_quote_request, create_quote_response_available, create_quote_response_unavailable,
26    create_request, create_request_with_quote, create_response, encode_pubsub_frame,
27    encode_pubsub_interest, encode_pubsub_inventory, encode_pubsub_want, encode_quote_request,
28    encode_quote_response, encode_request, encode_response, hash_to_key, parse_message,
29    DataMessage, DataQuoteRequest, DataQuoteResponse, PubsubFrame, PubsubInterest, PubsubInventory,
30    PubsubWant,
31};
32use crate::pubsub_strategy::{
33    reciprocal_virtual_finish, select_reciprocal_outbound_job, OutboundJobCandidate,
34    PeerTrafficSnapshot, PubsubCandidate, PubsubSchedulerConfig,
35};
36use crate::signaling::MeshRouter;
37use crate::transport::{PeerLinkFactory, SignalingTransport, TransportError};
38use crate::types::{
39    should_forward_htl, PeerHTLConfig, SignalingMessage, TimedSeenSet, MAX_HTL, MESH_EVENT_POLICY,
40};
41
42// Keep the on-disk namespace stable across the crate rename so existing peer
43// metadata does not disappear for users upgrading from the old package name.
44const PEER_METADATA_POINTER_SLOT_KEY: &[u8] = b"hashtree-mesh/peer-metadata/latest/v1";
45const PUBSUB_SEEN_CAPACITY: usize = 16_384;
46const PUBSUB_INBOX_CAPACITY: usize = 4_096;
47const PUBSUB_FRAME_CACHE_CAPACITY: usize = 4_096;
48const VERIFIED_BLOCK_DELIVERY_CAPACITY: usize = 4_096;
49const PUBSUB_SEEN_TTL: Duration = Duration::from_secs(120);
50
51/// Pending request awaiting response
52struct PendingRequest {
53    owner: Arc<()>,
54    response_tx: oneshot::Sender<Option<Vec<u8>>>,
55    started_at: Instant,
56    queried_peers: Vec<String>,
57}
58
59#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
60struct PendingRequestKey {
61    hash: Hash,
62    htl: u8,
63}
64
65impl PendingRequestKey {
66    fn new(hash: Hash, htl: u8) -> Self {
67        Self { hash, htl }
68    }
69}
70
71struct PendingQuoteRequest {
72    response_tx: oneshot::Sender<Option<NegotiatedQuote>>,
73    preferred_mint_url: Option<String>,
74    offered_payment_sat: u64,
75}
76
77struct PendingForwardRequest {
78    requester_ids: HashSet<String>,
79}
80
81type PeerWireStats = PeerTrafficSnapshot;
82
83struct PendingResponseSend {
84    job_id: u64,
85    peer_id: String,
86    bytes: Vec<u8>,
87    ready_at: Instant,
88    queue_sequence: u64,
89}
90
91#[derive(Clone, Copy, Debug, PartialEq, Eq)]
92pub enum BlobRouteKind {
93    Terminal,
94    MeshPeer,
95}
96
97pub trait MeshReadSource: BlobRoute {
98    fn id(&self) -> &str;
99
100    fn kind(&self) -> BlobRouteKind {
101        BlobRouteKind::Terminal
102    }
103
104    fn is_available(&self) -> bool {
105        true
106    }
107}
108
109pub struct NamedBlobRoute {
110    id: String,
111    kind: BlobRouteKind,
112    route: Arc<dyn BlobRoute>,
113}
114
115impl NamedBlobRoute {
116    pub fn terminal(id: impl Into<String>, route: Arc<dyn BlobRoute>) -> Self {
117        Self {
118            id: id.into(),
119            kind: BlobRouteKind::Terminal,
120            route,
121        }
122    }
123
124    pub fn mesh_peer(id: impl Into<String>, route: Arc<dyn BlobRoute>) -> Self {
125        Self {
126            id: id.into(),
127            kind: BlobRouteKind::MeshPeer,
128            route,
129        }
130    }
131}
132
133#[async_trait]
134impl BlobRoute for NamedBlobRoute {
135    async fn route(&self, request: BlobRequest) -> Result<BlobReply, StoreError> {
136        self.route.route(request).await
137    }
138}
139
140impl MeshReadSource for NamedBlobRoute {
141    fn id(&self) -> &str {
142        &self.id
143    }
144
145    fn kind(&self) -> BlobRouteKind {
146        self.kind
147    }
148}
149
150#[derive(Debug, Clone)]
151struct NegotiatedQuote {
152    peer_id: String,
153    quote_id: u64,
154    #[allow(dead_code)]
155    mint_url: Option<String>,
156}
157
158struct IssuedQuote {
159    expires_at: Instant,
160    #[allow(dead_code)]
161    payment_sat: u64,
162    #[allow(dead_code)]
163    mint_url: Option<String>,
164}
165
166#[derive(Debug, Clone, Default)]
167struct AdaptiveSourceStats {
168    requests: u64,
169    successes: u64,
170    misses: u64,
171    failures: u64,
172    timeouts: u64,
173    srtt_ms: f64,
174    rttvar_ms: f64,
175    backoff_level: u32,
176    backed_off_until: Option<Instant>,
177    last_success_at: Option<Instant>,
178    last_failure_at: Option<Instant>,
179}
180
181#[derive(Debug, Clone)]
182enum RouteFetchOutcome {
183    Hit(Vec<u8>),
184    Miss,
185    Timeout,
186    Failure,
187}
188
189impl RouteFetchOutcome {
190    fn combine_without_hit(self, other: Self) -> Self {
191        match (self, other) {
192            (Self::Hit(data), _) | (_, Self::Hit(data)) => Self::Hit(data),
193            (Self::Failure, _) | (_, Self::Failure) => Self::Failure,
194            (Self::Timeout, _) | (_, Self::Timeout) => Self::Timeout,
195            (Self::Miss, Self::Miss) => Self::Miss,
196        }
197    }
198}
199
200struct InflightSourceFetch {
201    owner: Arc<()>,
202    waiters: Vec<oneshot::Sender<RouteFetchOutcome>>,
203}
204
205fn complete_inflight_source_fetch(
206    inflight_source_fetches: &StdMutex<HashMap<String, InflightSourceFetch>>,
207    hash_key: &str,
208    expected_owner: &Arc<()>,
209    result: RouteFetchOutcome,
210) {
211    let mut inflight = inflight_source_fetches
212        .lock()
213        .unwrap_or_else(|poisoned| poisoned.into_inner());
214    let owns_entry = inflight
215        .get(hash_key)
216        .is_some_and(|entry| Arc::ptr_eq(&entry.owner, expected_owner));
217    let waiters = owns_entry
218        .then(|| inflight.remove(hash_key))
219        .flatten()
220        .map(|inflight| inflight.waiters)
221        .unwrap_or_default();
222    drop(inflight);
223    for waiter in waiters {
224        let _ = waiter.send(result.clone());
225    }
226}
227
228/// Owns one shared source fetch until it completes or its caller is cancelled.
229///
230/// Tokio futures have no asynchronous drop hook, so this deliberately uses a
231/// synchronous mutex around the tiny inflight registry. Dropping an aborted
232/// owner removes the entry and wakes coalesced callers with a non-miss outcome.
233struct InflightSourceFetchGuard<'a> {
234    inflight_source_fetches: &'a StdMutex<HashMap<String, InflightSourceFetch>>,
235    hash_key: String,
236    owner: Arc<()>,
237}
238
239impl InflightSourceFetchGuard<'_> {
240    fn complete(self, result: RouteFetchOutcome) {
241        complete_inflight_source_fetch(
242            self.inflight_source_fetches,
243            &self.hash_key,
244            &self.owner,
245            result,
246        );
247    }
248}
249
250impl Drop for InflightSourceFetchGuard<'_> {
251    fn drop(&mut self) {
252        complete_inflight_source_fetch(
253            self.inflight_source_fetches,
254            &self.hash_key,
255            &self.owner,
256            RouteFetchOutcome::Timeout,
257        );
258    }
259}
260
261enum SourceFetchOutcome {
262    Hit {
263        source_id: String,
264        data: Vec<u8>,
265        elapsed_ms: u64,
266    },
267    Miss {
268        source_id: String,
269    },
270    Failure {
271        source_id: String,
272    },
273}
274
275const INITIAL_SOURCE_BACKOFF_MS: u64 = 250;
276const MAX_SOURCE_BACKOFF_MS: u64 = 10_000;
277const SOURCE_SCORE_TIE_DELTA: f64 = 0.15;
278const RECENT_SOURCE_SUCCESS_WINDOW: Duration = Duration::from_secs(60);
279const ACTIVE_PEER_REQUEST_RANK_PENALTY: usize = 3;
280
281fn source_reliability_score(stats: &AdaptiveSourceStats) -> f64 {
282    (stats.successes as f64 + 1.0) / (stats.requests as f64 + 2.0)
283}
284
285fn source_latency_score(stats: &AdaptiveSourceStats) -> f64 {
286    if stats.srtt_ms <= 0.0 {
287        return 0.5;
288    }
289    (500.0 / (stats.srtt_ms + 50.0)).min(1.0)
290}
291
292fn source_has_history(stats: &AdaptiveSourceStats) -> bool {
293    stats.requests > 0
294        || stats.successes > 0
295        || stats.misses > 0
296        || stats.failures > 0
297        || stats.timeouts > 0
298}
299
300fn adaptive_source_score(stats: &AdaptiveSourceStats, now: Instant) -> f64 {
301    if let Some(backed_off_until) = stats.backed_off_until {
302        if backed_off_until > now {
303            return f64::NEG_INFINITY;
304        }
305    }
306
307    let miss_penalty = if stats.requests > 0 {
308        (stats.misses as f64 / stats.requests as f64) * 0.15
309    } else {
310        0.0
311    };
312    let failure_penalty = if stats.requests > 0 {
313        ((stats.failures + stats.timeouts) as f64 / stats.requests as f64) * 0.3
314    } else {
315        0.0
316    };
317    let recency_bonus = if stats
318        .last_success_at
319        .is_some_and(|last| now.duration_since(last) < RECENT_SOURCE_SUCCESS_WINDOW)
320    {
321        0.1
322    } else {
323        0.0
324    };
325
326    0.6 * source_reliability_score(stats) + 0.3 * source_latency_score(stats) + recency_bonus
327        - miss_penalty
328        - failure_penalty
329}
330
331fn peer_endpoint_has_history(stats: &crate::peer_selector::PeerStats) -> bool {
332    stats.requests_sent > 0 || stats.successes > 0 || stats.failures > 0 || stats.timeouts > 0
333}
334
335fn peer_endpoint_score(stats: &crate::peer_selector::PeerStats, now: Instant) -> f64 {
336    if stats.backed_off_until.is_some_and(|until| until > now) {
337        return f64::NEG_INFINITY;
338    }
339
340    let miss_penalty = 0.0;
341    let failure_penalty = if stats.requests_sent > 0 {
342        ((stats.failures + stats.timeouts) as f64 / stats.requests_sent as f64) * 0.3
343    } else {
344        0.0
345    };
346    let recency_bonus = if stats
347        .last_success
348        .is_some_and(|last| now.duration_since(last) < RECENT_SOURCE_SUCCESS_WINDOW)
349    {
350        0.1
351    } else {
352        0.0
353    };
354
355    0.6 * stats.success_rate()
356        + 0.3
357            * source_latency_score(&AdaptiveSourceStats {
358                srtt_ms: stats.srtt_ms,
359                ..AdaptiveSourceStats::default()
360            })
361        + recency_bonus
362        - miss_penalty
363        - failure_penalty
364}
365
366#[derive(Clone)]
367enum ReadRoute {
368    Peers(Vec<String>),
369    Sources,
370}
371
372impl ReadRoute {
373    fn id(&self) -> &'static str {
374        match self {
375            Self::Peers(_) => "peers",
376            Self::Sources => "sources",
377        }
378    }
379}
380
381struct RankedReadRoute {
382    route: ReadRoute,
383    best_endpoint_id: String,
384    score: f64,
385    has_history: bool,
386}
387
388fn ranked_route_kind(route: &ReadRoute) -> u8 {
389    match route {
390        ReadRoute::Sources => 0,
391        ReadRoute::Peers(_) => 1,
392    }
393}
394
395#[derive(Debug, Clone)]
396struct MeshReadContext {
397    exclude_peer_id: Option<String>,
398    request_htl: u8,
399}
400
401impl Default for MeshReadContext {
402    fn default() -> Self {
403        Self {
404            exclude_peer_id: None,
405            request_htl: MAX_HTL,
406        }
407    }
408}
409
410/// Aggregate stats from draining currently available peer-link messages.
411#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
412pub struct DataPumpStats {
413    pub processed: usize,
414    pub request_messages: usize,
415    pub response_messages: usize,
416    pub quote_request_messages: u64,
417    pub quote_response_messages: u64,
418    pub pubsub_interest_messages: u64,
419    pub pubsub_frame_messages: u64,
420    pub pubsub_inventory_messages: u64,
421    pub pubsub_want_messages: u64,
422    pub processed_bytes: u64,
423}
424
425/// Pubsub data delivered to a local subscription.
426#[derive(Debug, Clone, PartialEq, Eq)]
427pub struct PubsubEvent {
428    pub stream_id: String,
429    pub seq: u64,
430    pub origin_peer_id: String,
431    pub from_peer_id: String,
432    pub payload: Vec<u8>,
433}
434
435/// Evidence that this peer won an outstanding, hash-verified block request.
436#[derive(Debug, Clone, PartialEq, Eq)]
437pub struct VerifiedBlockDelivery {
438    pub hash: Hash,
439    pub provider_peer_id: String,
440    pub payload_bytes: u64,
441}
442
443/// One atomic drain of verified delivery evidence and any overflow since the prior drain.
444///
445/// Dropped evidence is intentionally not recoverable or billable through this API. An
446/// application adapter must surface a non-zero count and must not infer a payment claim.
447#[derive(Debug, Clone, Default, PartialEq, Eq)]
448pub struct VerifiedBlockDeliveryBatch {
449    pub deliveries: Vec<VerifiedBlockDelivery>,
450    pub dropped_since_last_drain: u64,
451}
452
453#[derive(Default)]
454struct VerifiedBlockDeliveryBuffer {
455    deliveries: VecDeque<VerifiedBlockDelivery>,
456    dropped_since_last_drain: u64,
457}
458
459/// Send-side accounting from a pubsub publish or forwarded pubsub message.
460#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
461pub struct PubsubPublishStats {
462    pub selected_peers: usize,
463    pub sent_peers: usize,
464    pub sent_bytes: u64,
465    pub deferred_peers: usize,
466}
467
468/// Production pubsub delivery strategy.
469#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
470pub enum PubsubDeliveryMode {
471    /// Push full frames only along advertised interest routes.
472    InterestPush,
473    /// Route small inventories along advertised interest paths and pull payloads back along want paths.
474    #[default]
475    HtlInvWant,
476}
477
478/// Request dispatch strategy for peer queries.
479///
480/// Requests use bounded staged hedging by default. Callers may explicitly
481/// raise the cap for controlled simulations, but production never needs an
482/// unbounded sentinel.
483#[derive(Debug, Clone, Copy)]
484pub struct RequestDispatchConfig {
485    /// Number of peers queried immediately.
486    pub initial_fanout: usize,
487    /// Number of additional peers to query on each hedge step.
488    pub hedge_fanout: usize,
489    /// Total peers allowed for this request.
490    pub max_fanout: usize,
491    /// Delay between hedge waves (ms). `0` means send all waves immediately.
492    pub hedge_interval_ms: u64,
493}
494
495impl Default for RequestDispatchConfig {
496    fn default() -> Self {
497        Self {
498            initial_fanout: 2,
499            hedge_fanout: 1,
500            max_fanout: 4,
501            hedge_interval_ms: 50,
502        }
503    }
504}
505
506/// Normalize fanout config against current peer availability.
507pub fn normalize_dispatch_config(
508    dispatch: RequestDispatchConfig,
509    available_peers: usize,
510) -> RequestDispatchConfig {
511    let mut cfg = dispatch;
512    let cap = if cfg.max_fanout == 0 {
513        available_peers
514    } else {
515        cfg.max_fanout.min(available_peers)
516    };
517    cfg.max_fanout = cap;
518    cfg.initial_fanout = if cfg.initial_fanout == 0 {
519        1
520    } else {
521        cfg.initial_fanout.min(cap.max(1))
522    };
523    cfg.hedge_fanout = if cfg.hedge_fanout == 0 {
524        1
525    } else {
526        cfg.hedge_fanout.min(cap.max(1))
527    };
528    cfg
529}
530
531/// Build wave sizes for staged hedged dispatch.
532pub fn build_hedged_wave_plan(peer_count: usize, dispatch: RequestDispatchConfig) -> Vec<usize> {
533    if peer_count == 0 {
534        return Vec::new();
535    }
536    let cap = dispatch.max_fanout.min(peer_count);
537    if cap == 0 {
538        return Vec::new();
539    }
540
541    let mut plan = Vec::new();
542    let mut sent = 0usize;
543    let first = dispatch.initial_fanout.min(cap).max(1);
544    plan.push(first);
545    sent += first;
546
547    while sent < cap {
548        let next = dispatch.hedge_fanout.min(cap - sent).max(1);
549        plan.push(next);
550        sent += next;
551    }
552    plan
553}
554
555/// Outcome returned after waiting on a hedged dispatch wave.
556#[derive(Debug)]
557pub enum HedgedWaveAction<T> {
558    Continue,
559    Success(T),
560    Abort,
561}
562
563/// Run a staged hedged dispatch over peer index ranges.
564///
565/// This scheduler is shared by the reusable `MeshStoreCore` and the native
566/// `hashtree-cli` mesh path so tests and production use the same wave timing.
567pub async fn run_hedged_waves<T, SendWave, SendWaveFut, WaitWave, WaitWaveFut>(
568    peer_count: usize,
569    dispatch: RequestDispatchConfig,
570    request_timeout: Duration,
571    mut send_wave: SendWave,
572    mut wait_wave: WaitWave,
573) -> Option<T>
574where
575    SendWave: FnMut(Range<usize>) -> SendWaveFut,
576    SendWaveFut: Future<Output = usize>,
577    WaitWave: FnMut(Duration) -> WaitWaveFut,
578    WaitWaveFut: Future<Output = HedgedWaveAction<T>>,
579{
580    let dispatch = normalize_dispatch_config(dispatch, peer_count);
581    let wave_plan = build_hedged_wave_plan(peer_count, dispatch);
582    if wave_plan.is_empty() {
583        return None;
584    }
585
586    let deadline = Instant::now() + request_timeout;
587    let mut sent_total = 0usize;
588    let mut next_peer_idx = 0usize;
589
590    for (wave_idx, wave_size) in wave_plan.iter().copied().enumerate() {
591        let from = next_peer_idx;
592        let to = (next_peer_idx + wave_size).min(peer_count);
593        next_peer_idx = to;
594
595        if from == to {
596            continue;
597        }
598
599        sent_total += send_wave(from..to).await;
600        if sent_total == 0 {
601            if next_peer_idx >= peer_count {
602                break;
603            }
604            continue;
605        }
606
607        let now = Instant::now();
608        if now >= deadline {
609            break;
610        }
611        let remaining = deadline.saturating_duration_since(now);
612        let is_last_wave = wave_idx + 1 == wave_plan.len() || next_peer_idx >= peer_count;
613        let wait = if is_last_wave {
614            remaining
615        } else if dispatch.hedge_interval_ms == 0 {
616            Duration::ZERO
617        } else {
618            Duration::from_millis(dispatch.hedge_interval_ms).min(remaining)
619        };
620
621        if wait.is_zero() {
622            continue;
623        }
624
625        match wait_wave(wait).await {
626            HedgedWaveAction::Continue => {}
627            HedgedWaveAction::Success(value) => return Some(value),
628            HedgedWaveAction::Abort => break,
629        }
630    }
631
632    None
633}
634
635/// Keep selector membership aligned with currently connected peer IDs.
636pub async fn sync_selector_peers(selector: &RwLock<PeerSelector>, current_peer_ids: &[String]) {
637    let mut selector = selector.write().await;
638    let current: HashSet<&str> = current_peer_ids.iter().map(String::as_str).collect();
639    let known: Vec<String> = selector.all_stats().map(|s| s.peer_id.clone()).collect();
640    for peer_id in known {
641        if !current.contains(peer_id.as_str()) {
642            selector.remove_peer(&peer_id);
643        }
644    }
645    for peer_id in current_peer_ids {
646        selector.add_peer(peer_id.clone());
647    }
648}
649
650/// Response behavior profile for simulation/game-theory actors.
651///
652/// Defaults to honest behavior (always respond correctly, no extra delay).
653#[derive(Debug, Clone, Copy)]
654pub struct ResponseBehaviorConfig {
655    /// Probability that a node drops a response even when it has data.
656    pub drop_response_prob: f64,
657    /// Probability that a node responds with corrupted payload.
658    pub corrupt_response_prob: f64,
659    /// Baseline response delay before a peer starts sending any data.
660    pub extra_delay_ms: u64,
661    /// Additional delay before the first response byte becomes available.
662    pub first_byte_delay_ms: u64,
663    /// Sustained throughput for delivering large payloads. `0` disables size-based slowdown.
664    pub bytes_per_second: u64,
665    /// Probability that an otherwise honest response experiences an extra stall.
666    pub stall_response_prob: f64,
667    /// Extra delay injected when a stall event happens.
668    pub stall_delay_ms: u64,
669}
670
671impl Default for ResponseBehaviorConfig {
672    fn default() -> Self {
673        Self {
674            drop_response_prob: 0.0,
675            corrupt_response_prob: 0.0,
676            extra_delay_ms: 0,
677            first_byte_delay_ms: 0,
678            bytes_per_second: 0,
679            stall_response_prob: 0.0,
680            stall_delay_ms: 0,
681        }
682    }
683}
684
685impl ResponseBehaviorConfig {
686    fn normalized(self) -> Self {
687        Self {
688            drop_response_prob: self.drop_response_prob.clamp(0.0, 1.0),
689            corrupt_response_prob: self.corrupt_response_prob.clamp(0.0, 1.0),
690            extra_delay_ms: self.extra_delay_ms,
691            first_byte_delay_ms: self.first_byte_delay_ms,
692            bytes_per_second: self.bytes_per_second,
693            stall_response_prob: self.stall_response_prob.clamp(0.0, 1.0),
694            stall_delay_ms: self.stall_delay_ms,
695        }
696    }
697}
698
699/// Routing policy for request ordering + dispatch fanout.
700#[derive(Debug, Clone)]
701pub struct MeshRoutingConfig {
702    pub selection_strategy: SelectionStrategy,
703    pub fairness_enabled: bool,
704    /// Blend weight for payment-priority ranking in selector (`0.0` disables).
705    pub cashu_payment_weight: f64,
706    /// Refuse serving peers that have reached this many unpaid post-delivery settlements.
707    /// `0` disables refusal and only keeps metadata/downranking.
708    pub cashu_payment_default_block_threshold: u64,
709    /// Cashu mint URLs this node is willing to use for settlement.
710    pub cashu_accepted_mints: Vec<String>,
711    /// Preferred Cashu mint URL when initiating paid retrieval.
712    pub cashu_default_mint: Option<String>,
713    /// Baseline cap for accepting a peer-suggested mint outside the trusted set.
714    pub cashu_peer_suggested_mint_base_cap_sat: u64,
715    /// Additional sats allowed per successful delivery from that peer.
716    pub cashu_peer_suggested_mint_success_step_sat: u64,
717    /// Additional sats allowed per successful post-delivery payment received from that peer.
718    pub cashu_peer_suggested_mint_receipt_step_sat: u64,
719    /// Hard upper bound for any single peer-suggested mint quote we accept.
720    pub cashu_peer_suggested_mint_max_cap_sat: u64,
721    pub dispatch: RequestDispatchConfig,
722    pub response_behavior: ResponseBehaviorConfig,
723    pub pubsub_scheduler: PubsubSchedulerConfig,
724    pub pubsub_delivery_mode: PubsubDeliveryMode,
725    /// Forward peer pubsub interests, inventories, and payloads for downstream peers.
726    pub pubsub_forwarding: bool,
727    /// Initial hops-to-live for locally originated pubsub interest/inventory frames.
728    pub pubsub_max_htl: u8,
729}
730
731impl Default for MeshRoutingConfig {
732    fn default() -> Self {
733        Self {
734            selection_strategy: SelectionStrategy::Weighted,
735            fairness_enabled: true,
736            cashu_payment_weight: 0.0,
737            cashu_payment_default_block_threshold: 0,
738            cashu_accepted_mints: Vec::new(),
739            cashu_default_mint: None,
740            cashu_peer_suggested_mint_base_cap_sat: 0,
741            cashu_peer_suggested_mint_success_step_sat: 0,
742            cashu_peer_suggested_mint_receipt_step_sat: 0,
743            cashu_peer_suggested_mint_max_cap_sat: 0,
744            dispatch: RequestDispatchConfig::default(),
745            response_behavior: ResponseBehaviorConfig::default(),
746            pubsub_scheduler: PubsubSchedulerConfig::default(),
747            pubsub_delivery_mode: PubsubDeliveryMode::HtlInvWant,
748            pubsub_forwarding: true,
749            pubsub_max_htl: MESH_EVENT_POLICY.max_htl,
750        }
751    }
752}
753
754impl MeshRoutingConfig {
755    fn pubsub_initial_htl(&self) -> u8 {
756        self.pubsub_max_htl.clamp(1, MAX_HTL)
757    }
758}
759
760/// Routed mesh store core that works with any storage backend and transport
761/// implementation.
762///
763/// This is the shared code between production and simulation.
764/// - Production: transport-specific crates compose `MeshStoreCore` with their links
765/// - Simulation: `MeshStoreCore<MemoryStore, MockRelayTransport, MockConnectionFactory>`
766pub struct MeshStoreCore<S, R, F>
767where
768    S: Store + Send + Sync + 'static,
769    R: SignalingTransport + Send + Sync + 'static,
770    F: PeerLinkFactory + Send + Sync + 'static,
771{
772    /// Local backing store
773    local_store: Arc<S>,
774    /// Mesh router (handles peer discovery and connection)
775    signaling: Arc<MeshRouter<R, F>>,
776    /// Per-peer HTL config
777    htl_configs: RwLock<HashMap<String, PeerHTLConfig>>,
778    /// Pending requests we sent
779    pending_requests: RwLock<HashMap<PendingRequestKey, Vec<PendingRequest>>>,
780    /// Pending quote negotiations keyed by requested hash.
781    pending_quotes: RwLock<HashMap<String, PendingQuoteRequest>>,
782    /// Forwarded peer requests currently being resolved through the mesh/upstream.
783    pending_forward_requests: RwLock<HashMap<PendingRequestKey, PendingForwardRequest>>,
784    /// Bounded negative cache for recently forwarded misses/timeouts.
785    /// Quotes we issued to peers and will accept exactly once until expiry.
786    issued_quotes: RwLock<HashMap<(String, String, u64), IssuedQuote>>,
787    /// Monotonic quote identifier generator.
788    next_quote_id: RwLock<u64>,
789    /// Non-peer read sources such as upstream Blossom servers.
790    read_sources: RwLock<HashMap<String, Arc<dyn MeshReadSource>>>,
791    /// Adaptive health stats for non-peer read sources.
792    read_source_stats: RwLock<HashMap<String, AdaptiveSourceStats>>,
793    /// Shared in-flight upstream reads keyed by hash.
794    inflight_source_fetches: StdMutex<HashMap<String, InflightSourceFetch>>,
795    /// Adaptive selector for peer ordering.
796    peer_selector: RwLock<PeerSelector>,
797    /// Active per-peer in-flight reads so concurrent block fetches spread across peers.
798    peer_active_requests: RwLock<HashMap<String, usize>>,
799    /// Actual wire traffic stats used for upload-side reciprocity scheduling.
800    peer_wire_stats: RwLock<HashMap<String, PeerWireStats>>,
801    /// Streams this node wants delivered locally.
802    pubsub_local_interests: RwLock<HashSet<String>>,
803    /// Current sequence per local stream interest.
804    pubsub_local_interest_versions: RwLock<HashMap<String, u64>>,
805    /// Reverse pubsub routes: stream id -> peers with local/downstream interest.
806    pubsub_peer_interests: RwLock<HashMap<String, HashSet<String>>>,
807    /// Route owner for each downstream subscriber interest.
808    pubsub_interest_routes: RwLock<HashMap<(String, String), String>>,
809    /// Latest interest sequence observed per subscriber/stream.
810    pubsub_interest_versions: RwLock<HashMap<(String, String), u64>>,
811    /// Bounded dedupe for pubsub interest floods.
812    pubsub_seen_interests: Mutex<TimedSeenSet>,
813    /// Bounded dedupe for pubsub data frames.
814    pubsub_seen_frames: Mutex<TimedSeenSet>,
815    /// Bounded dedupe for pubsub inventory floods.
816    pubsub_seen_inventories: Mutex<TimedSeenSet>,
817    /// Bounded dedupe for pubsub wants by requesting peer.
818    pubsub_seen_wants: Mutex<TimedSeenSet>,
819    /// First upstream peer that announced each inventory key.
820    pubsub_inventory_routes: RwLock<HashMap<String, String>>,
821    /// Downstream peers waiting for a payload after sending a want.
822    pubsub_want_routes: RwLock<HashMap<String, HashSet<String>>>,
823    /// Dedupe for wants this node already sent upstream.
824    pubsub_upstream_wants: Mutex<TimedSeenSet>,
825    /// Small payload cache for serving wants after inventory-first announcements.
826    pubsub_frame_cache: Mutex<VecDeque<(String, PubsubFrame)>>,
827    /// Local pubsub delivery inbox.
828    pubsub_inbox: Mutex<VecDeque<PubsubEvent>>,
829    /// Bounded application-facing evidence for first-winner block deliveries.
830    verified_block_deliveries: Mutex<VerifiedBlockDeliveryBuffer>,
831    /// Wakes consumers waiting for local pubsub deliveries.
832    pubsub_notify: Notify,
833    /// Per stream/peer deferred counts for aging pubsub strategies.
834    pubsub_deferred_counts: RwLock<HashMap<(String, String), u64>>,
835    /// Monotonic sequence for locally originated pubsub interest updates.
836    next_pubsub_interest_seq: AtomicU64,
837    /// Pending content responses waiting for upload arbitration.
838    pending_response_sends: Mutex<Vec<PendingResponseSend>>,
839    /// Upload response scheduler state.
840    response_scheduler_running: AtomicBool,
841    /// Monotonic id for queued response sends.
842    next_response_job_id: AtomicU64,
843    /// Routing/dispatch configuration.
844    routing: MeshRoutingConfig,
845    /// Request timeout
846    request_timeout: Duration,
847    /// Debug mode
848    debug: bool,
849    /// Running flag
850    running: RwLock<bool>,
851}
852
853impl<S, R, F> MeshStoreCore<S, R, F>
854where
855    S: Store + Send + Sync + 'static,
856    R: SignalingTransport + Send + Sync + 'static,
857    F: PeerLinkFactory + Send + Sync + 'static,
858{
859    /// Create a new routed mesh store core.
860    pub fn new(
861        local_store: Arc<S>,
862        signaling: Arc<MeshRouter<R, F>>,
863        request_timeout: Duration,
864        debug: bool,
865    ) -> Self {
866        Self::new_with_routing(
867            local_store,
868            signaling,
869            request_timeout,
870            debug,
871            Default::default(),
872        )
873    }
874
875    /// Create a new routed mesh store core with explicit routing configuration.
876    pub fn new_with_routing(
877        local_store: Arc<S>,
878        signaling: Arc<MeshRouter<R, F>>,
879        request_timeout: Duration,
880        debug: bool,
881        routing: MeshRoutingConfig,
882    ) -> Self {
883        let mut selector = PeerSelector::with_strategy(routing.selection_strategy);
884        selector.set_fairness(routing.fairness_enabled);
885        selector.set_cashu_payment_weight(routing.cashu_payment_weight);
886        Self {
887            local_store,
888            signaling,
889            htl_configs: RwLock::new(HashMap::new()),
890            pending_requests: RwLock::new(HashMap::new()),
891            pending_quotes: RwLock::new(HashMap::new()),
892            pending_forward_requests: RwLock::new(HashMap::new()),
893            issued_quotes: RwLock::new(HashMap::new()),
894            next_quote_id: RwLock::new(1),
895            read_sources: RwLock::new(HashMap::new()),
896            read_source_stats: RwLock::new(HashMap::new()),
897            inflight_source_fetches: StdMutex::new(HashMap::new()),
898            peer_selector: RwLock::new(selector),
899            peer_active_requests: RwLock::new(HashMap::new()),
900            peer_wire_stats: RwLock::new(HashMap::new()),
901            pubsub_local_interests: RwLock::new(HashSet::new()),
902            pubsub_local_interest_versions: RwLock::new(HashMap::new()),
903            pubsub_peer_interests: RwLock::new(HashMap::new()),
904            pubsub_interest_routes: RwLock::new(HashMap::new()),
905            pubsub_interest_versions: RwLock::new(HashMap::new()),
906            pubsub_seen_interests: Mutex::new(TimedSeenSet::new(
907                PUBSUB_SEEN_CAPACITY,
908                PUBSUB_SEEN_TTL,
909            )),
910            pubsub_seen_frames: Mutex::new(TimedSeenSet::new(
911                PUBSUB_SEEN_CAPACITY,
912                PUBSUB_SEEN_TTL,
913            )),
914            pubsub_seen_inventories: Mutex::new(TimedSeenSet::new(
915                PUBSUB_SEEN_CAPACITY,
916                PUBSUB_SEEN_TTL,
917            )),
918            pubsub_seen_wants: Mutex::new(TimedSeenSet::new(PUBSUB_SEEN_CAPACITY, PUBSUB_SEEN_TTL)),
919            pubsub_inventory_routes: RwLock::new(HashMap::new()),
920            pubsub_want_routes: RwLock::new(HashMap::new()),
921            pubsub_upstream_wants: Mutex::new(TimedSeenSet::new(
922                PUBSUB_SEEN_CAPACITY,
923                PUBSUB_SEEN_TTL,
924            )),
925            pubsub_frame_cache: Mutex::new(VecDeque::new()),
926            pubsub_inbox: Mutex::new(VecDeque::new()),
927            verified_block_deliveries: Mutex::new(VerifiedBlockDeliveryBuffer::default()),
928            pubsub_notify: Notify::new(),
929            pubsub_deferred_counts: RwLock::new(HashMap::new()),
930            next_pubsub_interest_seq: AtomicU64::new(1),
931            pending_response_sends: Mutex::new(Vec::new()),
932            response_scheduler_running: AtomicBool::new(false),
933            next_response_job_id: AtomicU64::new(1),
934            routing,
935            request_timeout,
936            debug,
937            running: RwLock::new(false),
938        }
939    }
940
941    /// Start the store (begin listening for messages)
942    pub async fn start(&self) -> Result<(), TransportError> {
943        *self.running.write().await = true;
944
945        // Send initial hello
946        self.signaling.send_hello(vec![]).await?;
947
948        Ok(())
949    }
950
951    /// Stop the store
952    pub async fn stop(&self) {
953        *self.running.write().await = false;
954    }
955
956    /// Process incoming signaling message
957    pub async fn process_signaling(&self, msg: SignalingMessage) -> Result<(), TransportError> {
958        // When a new peer connects, initialize their HTL config
959        let peer_id = msg.peer_id().to_string();
960        {
961            let mut configs = self.htl_configs.write().await;
962            if !configs.contains_key(&peer_id) {
963                configs.insert(peer_id.clone(), PeerHTLConfig::random());
964            }
965        }
966        self.peer_selector.write().await.add_peer(peer_id.clone());
967
968        let result = self.signaling.handle_message(msg).await;
969        if result.is_ok() {
970            self.announce_pubsub_interests_to_peer(&peer_id).await;
971        }
972        result
973    }
974
975    /// Get signaling manager reference
976    pub fn signaling(&self) -> &Arc<MeshRouter<R, F>> {
977        &self.signaling
978    }
979
980    fn response_behavior(&self) -> ResponseBehaviorConfig {
981        self.routing.response_behavior.normalized()
982    }
983
984    async fn record_peer_wire_sent(&self, peer_id: &str, bytes: u64) {
985        if bytes == 0 {
986            return;
987        }
988        let mut stats = self.peer_wire_stats.write().await;
989        let entry = stats.entry(peer_id.to_string()).or_default();
990        entry.bytes_sent = entry.bytes_sent.saturating_add(bytes);
991    }
992
993    async fn record_peer_wire_received(&self, peer_id: &str, bytes: u64) {
994        if bytes == 0 {
995            return;
996        }
997        let mut stats = self.peer_wire_stats.write().await;
998        let entry = stats.entry(peer_id.to_string()).or_default();
999        entry.bytes_received = entry.bytes_received.saturating_add(bytes);
1000    }
1001
1002    /// Record ingress from a peer that matched local or downstream demand.
1003    ///
1004    /// Raw bytes are tracked separately in `record_peer_wire_received`; this
1005    /// counter is the reciprocity signal used by shared outbound scheduling.
1006    pub async fn record_useful_bytes_received_from_peer(&self, peer_id: &str, bytes: u64) {
1007        if bytes == 0 {
1008            return;
1009        }
1010        let mut stats = self.peer_wire_stats.write().await;
1011        let entry = stats.entry(peer_id.to_string()).or_default();
1012        entry.useful_bytes_received = entry.useful_bytes_received.saturating_add(bytes);
1013    }
1014
1015    /// Snapshot peer traffic for production pubsub scheduling or diagnostics.
1016    pub async fn peer_traffic_snapshot(&self, peer_id: &str) -> PeerTrafficSnapshot {
1017        self.peer_wire_stats
1018            .read()
1019            .await
1020            .get(peer_id)
1021            .copied()
1022            .unwrap_or_default()
1023    }
1024
1025    /// Snapshot all known peer traffic for production pubsub scheduling.
1026    pub async fn peer_traffic_snapshots(&self) -> HashMap<String, PeerTrafficSnapshot> {
1027        self.peer_wire_stats.read().await.clone()
1028    }
1029
1030    fn pubsub_key(origin_peer_id: &str, stream_id: &str, seq: u64) -> String {
1031        format!("{origin_peer_id}:{stream_id}:{seq}")
1032    }
1033
1034    fn pubsub_frame_key(frame: &PubsubFrame) -> String {
1035        Self::pubsub_key(&frame.origin_peer_id, &frame.stream_id, frame.seq)
1036    }
1037
1038    fn pubsub_interest_key(interest: &PubsubInterest) -> String {
1039        format!(
1040            "{}:{}:{}:{}",
1041            interest.subscriber_peer_id, interest.stream_id, interest.seq, interest.active
1042        )
1043    }
1044
1045    fn next_pubsub_interest_seq(&self) -> u64 {
1046        self.next_pubsub_interest_seq
1047            .fetch_add(1, Ordering::Relaxed)
1048    }
1049
1050    async fn record_peer_pubsub_wire_sent(&self, peer_id: &str, bytes: u64, bandwidth_debt: f64) {
1051        if bytes == 0 {
1052            return;
1053        }
1054        let mut stats = self.peer_wire_stats.write().await;
1055        let entry = stats.entry(peer_id.to_string()).or_default();
1056        entry.bytes_sent = entry.bytes_sent.saturating_add(bytes);
1057        entry.bandwidth_debt = bandwidth_debt;
1058    }
1059
1060    async fn send_pubsub_interest_to_peers(
1061        &self,
1062        interest: &PubsubInterest,
1063        exclude_peer_id: Option<&str>,
1064    ) -> PubsubPublishStats {
1065        if !should_forward_htl(interest.htl) {
1066            return PubsubPublishStats::default();
1067        }
1068
1069        let mut peer_ids = self.signaling.peer_ids().await;
1070        peer_ids.sort();
1071        peer_ids.retain(|peer_id| exclude_peer_id.is_none_or(|exclude| peer_id != exclude));
1072
1073        let bytes = encode_pubsub_interest(interest);
1074        let mut stats = PubsubPublishStats {
1075            selected_peers: peer_ids.len(),
1076            ..Default::default()
1077        };
1078        for peer_id in peer_ids {
1079            let Some(channel) = self.signaling.get_channel(&peer_id).await else {
1080                continue;
1081            };
1082            if channel.send(bytes.clone()).await.is_ok() {
1083                stats.sent_peers += 1;
1084                stats.sent_bytes = stats.sent_bytes.saturating_add(bytes.len() as u64);
1085                self.record_peer_wire_sent(&peer_id, bytes.len() as u64)
1086                    .await;
1087            }
1088        }
1089        stats
1090    }
1091
1092    async fn announce_pubsub_interests_to_peer(&self, peer_id: &str) {
1093        let mut interests = self
1094            .pubsub_local_interests
1095            .read()
1096            .await
1097            .iter()
1098            .cloned()
1099            .collect::<Vec<_>>();
1100        interests.sort();
1101        if interests.is_empty() {
1102            return;
1103        }
1104
1105        let interests = {
1106            let versions = self.pubsub_local_interest_versions.read().await;
1107            interests
1108                .into_iter()
1109                .filter_map(|stream_id| {
1110                    versions
1111                        .get(&stream_id)
1112                        .copied()
1113                        .map(|seq| (stream_id, seq))
1114                })
1115                .collect::<Vec<_>>()
1116        };
1117
1118        for (stream_id, seq) in interests {
1119            let interest = create_pubsub_interest(
1120                stream_id,
1121                self.signaling.peer_id().to_string(),
1122                seq,
1123                true,
1124                MAX_HTL,
1125            );
1126            let Some(channel) = self.signaling.get_channel(peer_id).await else {
1127                continue;
1128            };
1129            let bytes = encode_pubsub_interest(&interest);
1130            if channel.send(bytes.clone()).await.is_ok() {
1131                self.record_peer_wire_sent(peer_id, bytes.len() as u64)
1132                    .await;
1133            }
1134        }
1135    }
1136
1137    fn remove_pubsub_peer_interest(
1138        peer_interests: &mut HashMap<String, HashSet<String>>,
1139        routes: &HashMap<(String, String), String>,
1140        stream_id: &str,
1141        peer_id: &str,
1142    ) {
1143        let still_has_route = routes
1144            .iter()
1145            .any(|((stream, _subscriber), peer)| stream == stream_id && peer == peer_id);
1146        if still_has_route {
1147            return;
1148        }
1149        if let Some(peers) = peer_interests.get_mut(stream_id) {
1150            peers.remove(peer_id);
1151            if peers.is_empty() {
1152                peer_interests.remove(stream_id);
1153            }
1154        }
1155    }
1156
1157    async fn apply_pubsub_interest_route(
1158        &self,
1159        from_peer: &str,
1160        interest: &PubsubInterest,
1161    ) -> bool {
1162        if interest.stream_id.is_empty() || interest.subscriber_peer_id.is_empty() {
1163            return false;
1164        }
1165        if interest.subscriber_peer_id == self.signaling.peer_id() {
1166            return false;
1167        }
1168
1169        let interest_key = Self::pubsub_interest_key(interest);
1170        if !self
1171            .pubsub_seen_interests
1172            .lock()
1173            .await
1174            .insert_if_new(interest_key)
1175        {
1176            return false;
1177        }
1178
1179        let route_key = (
1180            interest.stream_id.clone(),
1181            interest.subscriber_peer_id.clone(),
1182        );
1183        {
1184            let mut versions = self.pubsub_interest_versions.write().await;
1185            if versions
1186                .get(&route_key)
1187                .is_some_and(|latest| *latest >= interest.seq)
1188            {
1189                return false;
1190            }
1191            versions.insert(route_key.clone(), interest.seq);
1192        }
1193
1194        let mut peer_interests = self.pubsub_peer_interests.write().await;
1195        let mut routes = self.pubsub_interest_routes.write().await;
1196        if interest.active {
1197            if let Some(previous_peer) = routes.insert(route_key, from_peer.to_string()) {
1198                if previous_peer != from_peer {
1199                    Self::remove_pubsub_peer_interest(
1200                        &mut peer_interests,
1201                        &routes,
1202                        &interest.stream_id,
1203                        &previous_peer,
1204                    );
1205                }
1206            }
1207            peer_interests
1208                .entry(interest.stream_id.clone())
1209                .or_default()
1210                .insert(from_peer.to_string());
1211        } else if let Some(previous_peer) = routes.remove(&route_key) {
1212            Self::remove_pubsub_peer_interest(
1213                &mut peer_interests,
1214                &routes,
1215                &interest.stream_id,
1216                &previous_peer,
1217            );
1218        } else {
1219            Self::remove_pubsub_peer_interest(
1220                &mut peer_interests,
1221                &routes,
1222                &interest.stream_id,
1223                from_peer,
1224            );
1225        }
1226
1227        true
1228    }
1229
1230    async fn interested_pubsub_peers(
1231        &self,
1232        stream_id: &str,
1233        exclude_peer_id: Option<&str>,
1234    ) -> Vec<String> {
1235        let connected = self
1236            .signaling
1237            .peer_ids()
1238            .await
1239            .into_iter()
1240            .collect::<HashSet<_>>();
1241        let mut peers = self
1242            .pubsub_peer_interests
1243            .read()
1244            .await
1245            .get(stream_id)
1246            .map(|peers| peers.iter().cloned().collect::<Vec<_>>())
1247            .unwrap_or_default();
1248        peers.retain(|peer_id| {
1249            connected.contains(peer_id) && exclude_peer_id.is_none_or(|exclude| peer_id != exclude)
1250        });
1251        peers.sort();
1252        peers
1253    }
1254
1255    async fn decrement_pubsub_htl_for_peer(&self, peer_id: &str, htl: u8) -> u8 {
1256        let htl_config = {
1257            let configs = self.htl_configs.read().await;
1258            configs
1259                .get(peer_id)
1260                .cloned()
1261                .unwrap_or_else(PeerHTLConfig::random)
1262        };
1263        htl_config.decrement_with_policy(htl, &MESH_EVENT_POLICY)
1264    }
1265
1266    async fn send_pubsub_inventory_to_peers(
1267        &self,
1268        inv: &PubsubInventory,
1269        peer_ids: &[String],
1270    ) -> PubsubPublishStats {
1271        if peer_ids.is_empty() || !should_forward_htl(inv.htl) {
1272            return PubsubPublishStats::default();
1273        }
1274
1275        let mut stats = PubsubPublishStats {
1276            selected_peers: peer_ids.len(),
1277            ..Default::default()
1278        };
1279        for peer_id in peer_ids {
1280            let send_htl = self.decrement_pubsub_htl_for_peer(peer_id, inv.htl).await;
1281            if !should_forward_htl(send_htl) {
1282                continue;
1283            }
1284            let Some(channel) = self.signaling.get_channel(peer_id).await else {
1285                continue;
1286            };
1287            let mut outgoing = inv.clone();
1288            outgoing.htl = send_htl;
1289            let bytes = encode_pubsub_inventory(&outgoing);
1290            let message_bytes = bytes.len() as u64;
1291            if channel.send(bytes).await.is_ok() {
1292                stats.sent_peers += 1;
1293                stats.sent_bytes = stats.sent_bytes.saturating_add(message_bytes);
1294                self.record_peer_wire_sent(peer_id, message_bytes).await;
1295            }
1296        }
1297        stats
1298    }
1299
1300    async fn send_pubsub_want_to_peer(&self, want: &PubsubWant, peer_id: &str) -> bool {
1301        let Some(channel) = self.signaling.get_channel(peer_id).await else {
1302            return false;
1303        };
1304        let bytes = encode_pubsub_want(want);
1305        let message_bytes = bytes.len() as u64;
1306        match channel.send(bytes).await {
1307            Ok(()) => {
1308                self.record_peer_wire_sent(peer_id, message_bytes).await;
1309                true
1310            }
1311            Err(_) => false,
1312        }
1313    }
1314
1315    async fn send_pubsub_want_upstream(
1316        &self,
1317        key: &str,
1318        want: &PubsubWant,
1319        exclude_peer_id: Option<&str>,
1320    ) -> bool {
1321        let upstream = {
1322            let routes = self.pubsub_inventory_routes.read().await;
1323            routes.get(key).cloned()
1324        };
1325        let Some(upstream) = upstream else {
1326            return false;
1327        };
1328        if exclude_peer_id.is_some_and(|exclude| exclude == upstream) {
1329            return false;
1330        }
1331        let want_key = format!("{key}:{upstream}");
1332        if !self
1333            .pubsub_upstream_wants
1334            .lock()
1335            .await
1336            .insert_if_new(want_key)
1337        {
1338            return false;
1339        }
1340        self.send_pubsub_want_to_peer(want, &upstream).await
1341    }
1342
1343    async fn cache_pubsub_frame(&self, key: String, frame: PubsubFrame) {
1344        let mut cache = self.pubsub_frame_cache.lock().await;
1345        if let Some(index) = cache.iter().position(|(cached_key, _)| cached_key == &key) {
1346            cache.remove(index);
1347        }
1348        cache.push_back((key, frame));
1349        while cache.len() > PUBSUB_FRAME_CACHE_CAPACITY {
1350            cache.pop_front();
1351        }
1352    }
1353
1354    async fn cached_pubsub_frame(&self, key: &str) -> Option<PubsubFrame> {
1355        self.pubsub_frame_cache
1356            .lock()
1357            .await
1358            .iter()
1359            .find_map(|(cached_key, frame)| {
1360                if cached_key == key {
1361                    Some(frame.clone())
1362                } else {
1363                    None
1364                }
1365            })
1366    }
1367
1368    async fn remember_pubsub_want_peer(&self, key: String, from_peer: &str) -> bool {
1369        let mut routes = self.pubsub_want_routes.write().await;
1370        routes.entry(key).or_default().insert(from_peer.to_string())
1371    }
1372
1373    async fn take_pubsub_want_peers(
1374        &self,
1375        key: &str,
1376        exclude_peer_id: Option<&str>,
1377    ) -> Vec<String> {
1378        let connected = self
1379            .signaling
1380            .peer_ids()
1381            .await
1382            .into_iter()
1383            .collect::<HashSet<_>>();
1384        let mut peers = self
1385            .pubsub_want_routes
1386            .write()
1387            .await
1388            .remove(key)
1389            .map(|peers| peers.into_iter().collect::<Vec<_>>())
1390            .unwrap_or_default();
1391        peers.retain(|peer_id| {
1392            connected.contains(peer_id) && exclude_peer_id.is_none_or(|exclude| peer_id != exclude)
1393        });
1394        peers.sort();
1395        peers
1396    }
1397
1398    async fn select_pubsub_peers(
1399        &self,
1400        stream_id: &str,
1401        seq: u64,
1402        message_bytes: u64,
1403        peer_ids: &[String],
1404    ) -> (Vec<String>, Vec<String>) {
1405        let traffic = self.peer_wire_stats.read().await;
1406        let deferred_counts = self.pubsub_deferred_counts.read().await;
1407        let candidates = peer_ids
1408            .iter()
1409            .map(|peer_id| PubsubCandidate {
1410                peer_id: peer_id.clone(),
1411                traffic: traffic.get(peer_id).copied().unwrap_or_default(),
1412                deferred_count: deferred_counts
1413                    .get(&(stream_id.to_string(), peer_id.clone()))
1414                    .copied()
1415                    .unwrap_or_default(),
1416            })
1417            .collect::<Vec<_>>();
1418        drop(deferred_counts);
1419        drop(traffic);
1420
1421        let selection = self.routing.pubsub_scheduler.select(
1422            stream_id,
1423            seq,
1424            self.signaling.peer_id(),
1425            message_bytes,
1426            &candidates,
1427        );
1428
1429        {
1430            let mut deferred_counts = self.pubsub_deferred_counts.write().await;
1431            for peer_id in &selection.deferred {
1432                *deferred_counts
1433                    .entry((stream_id.to_string(), peer_id.clone()))
1434                    .or_insert(0) += 1;
1435            }
1436            for peer_id in &selection.selected {
1437                deferred_counts.remove(&(stream_id.to_string(), peer_id.clone()));
1438            }
1439        }
1440
1441        (selection.selected, selection.deferred)
1442    }
1443
1444    async fn send_pubsub_frame_to_peers(
1445        &self,
1446        frame: &PubsubFrame,
1447        peer_ids: &[String],
1448    ) -> PubsubPublishStats {
1449        if peer_ids.is_empty() || !should_forward_htl(frame.htl) {
1450            return PubsubPublishStats::default();
1451        }
1452
1453        let bytes = encode_pubsub_frame(frame);
1454        let message_bytes = bytes.len() as u64;
1455        let (selected, deferred) = self
1456            .select_pubsub_peers(&frame.stream_id, frame.seq, message_bytes, peer_ids)
1457            .await;
1458        let mut stats = PubsubPublishStats {
1459            selected_peers: selected.len(),
1460            deferred_peers: deferred.len(),
1461            ..Default::default()
1462        };
1463
1464        for peer_id in selected {
1465            let Some(channel) = self.signaling.get_channel(&peer_id).await else {
1466                continue;
1467            };
1468            let snapshot = self.peer_traffic_snapshot(&peer_id).await;
1469            let bandwidth_debt = reciprocal_virtual_finish(snapshot, message_bytes);
1470            if channel.send(bytes.clone()).await.is_ok() {
1471                stats.sent_peers += 1;
1472                stats.sent_bytes = stats.sent_bytes.saturating_add(message_bytes);
1473                self.record_peer_pubsub_wire_sent(&peer_id, message_bytes, bandwidth_debt)
1474                    .await;
1475            }
1476        }
1477
1478        stats
1479    }
1480
1481    async fn enqueue_pubsub_event(&self, event: PubsubEvent) {
1482        let mut inbox = self.pubsub_inbox.lock().await;
1483        inbox.push_back(event);
1484        while inbox.len() > PUBSUB_INBOX_CAPACITY {
1485            inbox.pop_front();
1486        }
1487        self.pubsub_notify.notify_one();
1488    }
1489
1490    /// Subscribe this node to a pubsub stream and advertise that interest.
1491    pub async fn subscribe_pubsub(
1492        self: &Arc<Self>,
1493        stream_id: impl Into<String>,
1494    ) -> PubsubPublishStats {
1495        let stream_id = stream_id.into();
1496        if stream_id.is_empty() {
1497            return PubsubPublishStats::default();
1498        }
1499        self.pubsub_local_interests
1500            .write()
1501            .await
1502            .insert(stream_id.clone());
1503        let seq = {
1504            let mut versions = self.pubsub_local_interest_versions.write().await;
1505            match versions.get(&stream_id).copied() {
1506                Some(seq) => seq,
1507                None => {
1508                    let seq = self.next_pubsub_interest_seq();
1509                    versions.insert(stream_id.clone(), seq);
1510                    seq
1511                }
1512            }
1513        };
1514        let interest = create_pubsub_interest(
1515            stream_id,
1516            self.signaling.peer_id().to_string(),
1517            seq,
1518            true,
1519            self.routing.pubsub_initial_htl(),
1520        );
1521        self.send_pubsub_interest_to_peers(&interest, None).await
1522    }
1523
1524    /// Stop local delivery for a pubsub stream and advertise the withdrawn interest.
1525    pub async fn unsubscribe_pubsub(
1526        self: &Arc<Self>,
1527        stream_id: impl Into<String>,
1528    ) -> PubsubPublishStats {
1529        let stream_id = stream_id.into();
1530        if stream_id.is_empty() {
1531            return PubsubPublishStats::default();
1532        }
1533        self.pubsub_local_interests.write().await.remove(&stream_id);
1534        self.pubsub_local_interest_versions
1535            .write()
1536            .await
1537            .remove(&stream_id);
1538        let interest = create_pubsub_interest(
1539            stream_id,
1540            self.signaling.peer_id().to_string(),
1541            self.next_pubsub_interest_seq(),
1542            false,
1543            self.routing.pubsub_initial_htl(),
1544        );
1545        self.send_pubsub_interest_to_peers(&interest, None).await
1546    }
1547
1548    /// Publish bytes on a pubsub stream through the configured mesh delivery mode.
1549    pub async fn publish_pubsub(
1550        self: &Arc<Self>,
1551        stream_id: impl Into<String>,
1552        seq: u64,
1553        payload: Vec<u8>,
1554    ) -> PubsubPublishStats {
1555        let stream_id = stream_id.into();
1556        if stream_id.is_empty() {
1557            return PubsubPublishStats::default();
1558        }
1559        let payload_bytes = payload.len() as u64;
1560        let frame = create_pubsub_frame(
1561            stream_id.clone(),
1562            seq,
1563            self.signaling.peer_id().to_string(),
1564            payload.clone(),
1565            self.routing.pubsub_initial_htl(),
1566        );
1567        let frame_key = Self::pubsub_frame_key(&frame);
1568        self.pubsub_seen_frames
1569            .lock()
1570            .await
1571            .insert_if_new(frame_key.clone());
1572        self.cache_pubsub_frame(frame_key, frame.clone()).await;
1573
1574        if self
1575            .pubsub_local_interests
1576            .read()
1577            .await
1578            .contains(&stream_id)
1579        {
1580            self.enqueue_pubsub_event(PubsubEvent {
1581                stream_id: stream_id.clone(),
1582                seq,
1583                origin_peer_id: self.signaling.peer_id().to_string(),
1584                from_peer_id: self.signaling.peer_id().to_string(),
1585                payload,
1586            })
1587            .await;
1588        }
1589
1590        match self.routing.pubsub_delivery_mode {
1591            PubsubDeliveryMode::InterestPush => {
1592                let peers = self.interested_pubsub_peers(&stream_id, None).await;
1593                self.send_pubsub_frame_to_peers(&frame, &peers).await
1594            }
1595            PubsubDeliveryMode::HtlInvWant => {
1596                let inv = create_pubsub_inventory(
1597                    stream_id,
1598                    seq,
1599                    self.signaling.peer_id().to_string(),
1600                    payload_bytes,
1601                    self.routing.pubsub_initial_htl(),
1602                );
1603                let peers = self.interested_pubsub_peers(&inv.stream_id, None).await;
1604                self.send_pubsub_inventory_to_peers(&inv, &peers).await
1605            }
1606        }
1607    }
1608
1609    /// Drain locally delivered pubsub events.
1610    pub async fn drain_pubsub_events(&self) -> Vec<PubsubEvent> {
1611        self.pubsub_inbox.lock().await.drain(..).collect()
1612    }
1613
1614    /// Drain verified first-winner block deliveries for an application adapter.
1615    pub async fn drain_verified_block_deliveries(&self) -> VerifiedBlockDeliveryBatch {
1616        let mut buffer = self.verified_block_deliveries.lock().await;
1617        VerifiedBlockDeliveryBatch {
1618            deliveries: buffer.deliveries.drain(..).collect(),
1619            dropped_since_last_drain: std::mem::take(&mut buffer.dropped_since_last_drain),
1620        }
1621    }
1622
1623    /// Wait until a locally delivered pubsub event is available, then return it.
1624    pub async fn recv_pubsub_event(&self) -> PubsubEvent {
1625        loop {
1626            if let Some(event) = self.pubsub_inbox.lock().await.pop_front() {
1627                return event;
1628            }
1629            self.pubsub_notify.notified().await;
1630        }
1631    }
1632
1633    /// Connected peers that currently have local or downstream interest in a stream.
1634    pub async fn pubsub_interest_peers(&self, stream_id: &str) -> Vec<String> {
1635        self.interested_pubsub_peers(stream_id, None).await
1636    }
1637
1638    fn choose_ready_response_job(
1639        ready_jobs: &[(u64, String, usize, Instant, u64)],
1640        stats: &HashMap<String, PeerWireStats>,
1641    ) -> Option<(u64, f64)> {
1642        let jobs = ready_jobs
1643            .iter()
1644            .map(|job| OutboundJobCandidate {
1645                job_id: job.0,
1646                peer_id: job.1.clone(),
1647                message_bytes: job.2 as u64,
1648                queue_sequence: job.4,
1649            })
1650            .collect::<Vec<_>>();
1651        select_reciprocal_outbound_job(&jobs, |peer_id| {
1652            stats.get(peer_id).copied().unwrap_or_default()
1653        })
1654        .map(|choice| (choice.job_id, choice.virtual_finish))
1655    }
1656
1657    async fn enqueue_response_send(
1658        self: &Arc<Self>,
1659        peer_id: String,
1660        bytes: Vec<u8>,
1661        ready_at: Instant,
1662    ) {
1663        let job_id = self.next_response_job_id.fetch_add(1, Ordering::Relaxed);
1664        {
1665            let mut queue = self.pending_response_sends.lock().await;
1666            queue.push(PendingResponseSend {
1667                job_id,
1668                peer_id,
1669                bytes,
1670                ready_at,
1671                queue_sequence: job_id,
1672            });
1673        }
1674
1675        if self
1676            .response_scheduler_running
1677            .compare_exchange(false, true, Ordering::AcqRel, Ordering::Acquire)
1678            .is_ok()
1679        {
1680            let this = Arc::clone(self);
1681            tokio::spawn(async move {
1682                this.run_response_scheduler().await;
1683            });
1684        }
1685    }
1686
1687    async fn run_response_scheduler(self: Arc<Self>) {
1688        loop {
1689            let snapshot = {
1690                let queue = self.pending_response_sends.lock().await;
1691                if queue.is_empty() {
1692                    self.response_scheduler_running
1693                        .store(false, Ordering::Release);
1694                    return;
1695                }
1696                queue
1697                    .iter()
1698                    .map(|job| {
1699                        (
1700                            job.job_id,
1701                            job.peer_id.clone(),
1702                            job.bytes.len(),
1703                            job.ready_at,
1704                            job.queue_sequence,
1705                        )
1706                    })
1707                    .collect::<Vec<_>>()
1708            };
1709
1710            let now = Instant::now();
1711            let mut earliest_ready_at: Option<Instant> = None;
1712            let mut ready_jobs = Vec::new();
1713            for job in &snapshot {
1714                if job.3 <= now {
1715                    ready_jobs.push(job.clone());
1716                } else {
1717                    earliest_ready_at = Some(match earliest_ready_at {
1718                        Some(current) => current.min(job.3),
1719                        None => job.3,
1720                    });
1721                }
1722            }
1723
1724            if ready_jobs.is_empty() {
1725                if let Some(ready_at) = earliest_ready_at {
1726                    tokio::time::sleep(ready_at.saturating_duration_since(Instant::now())).await;
1727                    continue;
1728                }
1729                self.response_scheduler_running
1730                    .store(false, Ordering::Release);
1731                return;
1732            }
1733
1734            let (selected_job_id, selected_finish) = {
1735                let stats = self.peer_wire_stats.read().await;
1736                Self::choose_ready_response_job(&ready_jobs, &stats).expect("ready response job")
1737            };
1738
1739            let selected = {
1740                let mut queue = self.pending_response_sends.lock().await;
1741                let Some(index) = queue.iter().position(|job| job.job_id == selected_job_id) else {
1742                    continue;
1743                };
1744                queue.swap_remove(index)
1745            };
1746
1747            let sent = if let Some(channel) = self.signaling.get_channel(&selected.peer_id).await {
1748                channel.send(selected.bytes.clone()).await.is_ok()
1749            } else {
1750                false
1751            };
1752
1753            let queued_peers = {
1754                let queue = self.pending_response_sends.lock().await;
1755                queue
1756                    .iter()
1757                    .map(|job| job.peer_id.clone())
1758                    .collect::<HashSet<_>>()
1759            };
1760            let mut stats = self.peer_wire_stats.write().await;
1761            let entry = stats.entry(selected.peer_id.clone()).or_default();
1762            if sent {
1763                entry.bytes_sent = entry.bytes_sent.saturating_add(selected.bytes.len() as u64);
1764                entry.bandwidth_debt = selected_finish;
1765            }
1766            if queued_peers.is_empty() {
1767                for peer_stats in stats.values_mut() {
1768                    peer_stats.bandwidth_debt = 0.0;
1769                }
1770            } else {
1771                let floor = queued_peers
1772                    .iter()
1773                    .filter_map(|peer_id| stats.get(peer_id).map(|peer| peer.bandwidth_debt))
1774                    .fold(f64::INFINITY, f64::min);
1775                if floor.is_finite() && floor > 0.0 {
1776                    for peer_id in queued_peers {
1777                        if let Some(peer_stats) = stats.get_mut(&peer_id) {
1778                            peer_stats.bandwidth_debt =
1779                                (peer_stats.bandwidth_debt - floor).max(0.0);
1780                        }
1781                    }
1782                }
1783            }
1784        }
1785    }
1786
1787    fn deterministic_actor_draw_for(peer_id: &str, hash: &Hash, salt: u64) -> f64 {
1788        let mut hasher = DefaultHasher::new();
1789        peer_id.hash(&mut hasher);
1790        hash.hash(&mut hasher);
1791        salt.hash(&mut hasher);
1792        let v = hasher.finish();
1793        (v as f64) / (u64::MAX as f64)
1794    }
1795
1796    fn deterministic_actor_draw(&self, hash: &Hash, salt: u64) -> f64 {
1797        Self::deterministic_actor_draw_for(self.signaling.peer_id(), hash, salt)
1798    }
1799
1800    fn peer_metadata_pointer_slot_hash() -> Hash {
1801        hashtree_core::sha256(PEER_METADATA_POINTER_SLOT_KEY)
1802    }
1803
1804    fn decode_hash_hex(hash_hex: &str) -> Result<Hash, StoreError> {
1805        let bytes = hex::decode(hash_hex)
1806            .map_err(|e| StoreError::Other(format!("Invalid hash hex: {e}")))?;
1807        if bytes.len() != 32 {
1808            return Err(StoreError::Other(format!(
1809                "Invalid hash length {}, expected 32 bytes",
1810                bytes.len()
1811            )));
1812        }
1813        let mut hash = [0u8; 32];
1814        hash.copy_from_slice(&bytes);
1815        Ok(hash)
1816    }
1817
1818    fn should_drop_response(&self, hash: &Hash) -> bool {
1819        let p = self.response_behavior().drop_response_prob;
1820        if p <= 0.0 {
1821            return false;
1822        }
1823        self.deterministic_actor_draw(hash, 0xD0_D0_D0_D0_D0_D0_D0_D0) < p
1824    }
1825
1826    fn should_corrupt_response(&self, hash: &Hash) -> bool {
1827        let p = self.response_behavior().corrupt_response_prob;
1828        if p <= 0.0 {
1829            return false;
1830        }
1831        self.deterministic_actor_draw(hash, 0xC0_C0_C0_C0_C0_C0_C0_C0) < p
1832    }
1833
1834    fn should_stall_response(&self, hash: &Hash) -> bool {
1835        let p = self.response_behavior().stall_response_prob;
1836        if p <= 0.0 {
1837            return false;
1838        }
1839        self.deterministic_actor_draw(hash, 0x5A_11_5A_11_5A_11_5A_11) < p
1840    }
1841
1842    fn response_send_delay(&self, hash: &Hash, payload_len: usize) -> Duration {
1843        let behavior = self.response_behavior();
1844        let mut total_ms = behavior
1845            .extra_delay_ms
1846            .saturating_add(behavior.first_byte_delay_ms);
1847
1848        if behavior.bytes_per_second > 0 && payload_len > 0 {
1849            let throughput_ms = ((payload_len as u128) * 1000)
1850                .div_ceil(behavior.bytes_per_second as u128)
1851                .min(u64::MAX as u128) as u64;
1852            total_ms = total_ms.saturating_add(throughput_ms);
1853        }
1854
1855        if behavior.stall_delay_ms > 0 && self.should_stall_response(hash) {
1856            total_ms = total_ms.saturating_add(behavior.stall_delay_ms);
1857        }
1858
1859        Duration::from_millis(total_ms)
1860    }
1861
1862    async fn ordered_connected_peers(&self, exclude_peer_id: Option<&str>) -> Vec<String> {
1863        let current_peer_ids = self.signaling.peer_ids().await;
1864        if current_peer_ids.is_empty() {
1865            return Vec::new();
1866        }
1867
1868        sync_selector_peers(&self.peer_selector, &current_peer_ids).await;
1869        let hash_get_peer_ids: HashSet<String> = self
1870            .signaling
1871            .hash_get_peer_ids()
1872            .await
1873            .into_iter()
1874            .collect();
1875        let mut candidate_peer_ids: Vec<String> = current_peer_ids
1876            .into_iter()
1877            .filter(|peer_id| hash_get_peer_ids.contains(peer_id))
1878            .filter(|peer_id| exclude_peer_id.is_none_or(|exclude| peer_id != exclude))
1879            .collect();
1880        if candidate_peer_ids.is_empty() {
1881            return Vec::new();
1882        }
1883
1884        let current_set: HashSet<&str> = candidate_peer_ids.iter().map(String::as_str).collect();
1885        let mut selector = self.peer_selector.write().await;
1886        let mut selector_order = selector.select_peers();
1887        selector_order.retain(|peer_id| current_set.contains(peer_id.as_str()));
1888        if selector_order.is_empty() {
1889            let mut fallback = candidate_peer_ids;
1890            fallback.sort();
1891            return fallback;
1892        }
1893        let backed_off: HashMap<String, bool> = candidate_peer_ids
1894            .iter()
1895            .map(|peer_id| (peer_id.clone(), selector.is_peer_backed_off(peer_id)))
1896            .collect();
1897        drop(selector);
1898
1899        let rank: HashMap<&str, usize> = selector_order
1900            .iter()
1901            .enumerate()
1902            .map(|(idx, peer_id)| (peer_id.as_str(), idx))
1903            .collect();
1904        let active = self.peer_active_requests.read().await;
1905        candidate_peer_ids.sort_by(|left, right| {
1906            let left_backed_off = backed_off.get(left).copied().unwrap_or(false);
1907            let right_backed_off = backed_off.get(right).copied().unwrap_or(false);
1908            if left_backed_off != right_backed_off {
1909                return if left_backed_off {
1910                    std::cmp::Ordering::Greater
1911                } else {
1912                    std::cmp::Ordering::Less
1913                };
1914            }
1915            let left_rank = rank.get(left.as_str()).copied().unwrap_or(usize::MAX / 2);
1916            let right_rank = rank.get(right.as_str()).copied().unwrap_or(usize::MAX / 2);
1917            let left_load = active.get(left).copied().unwrap_or(0);
1918            let right_load = active.get(right).copied().unwrap_or(0);
1919            (left_rank + left_load.saturating_mul(ACTIVE_PEER_REQUEST_RANK_PENALTY))
1920                .cmp(&(right_rank + right_load.saturating_mul(ACTIVE_PEER_REQUEST_RANK_PENALTY)))
1921                .then_with(|| left.cmp(right))
1922        });
1923        candidate_peer_ids
1924    }
1925
1926    async fn reserve_peer_request(&self, peer_id: &str) {
1927        let mut active = self.peer_active_requests.write().await;
1928        *active.entry(peer_id.to_string()).or_insert(0) += 1;
1929    }
1930
1931    async fn release_peer_request(&self, peer_id: &str) {
1932        let mut active = self.peer_active_requests.write().await;
1933        let Some(count) = active.get_mut(peer_id) else {
1934            return;
1935        };
1936        if *count <= 1 {
1937            active.remove(peer_id);
1938        } else {
1939            *count -= 1;
1940        }
1941    }
1942
1943    async fn release_queried_peer_requests(&self, peer_ids: &[String]) {
1944        for peer_id in peer_ids {
1945            self.release_peer_request(peer_id).await;
1946        }
1947    }
1948
1949    fn requested_quote_mint(&self) -> Option<&str> {
1950        if let Some(default_mint) = self.routing.cashu_default_mint.as_deref() {
1951            if self.routing.cashu_accepted_mints.is_empty()
1952                || self
1953                    .routing
1954                    .cashu_accepted_mints
1955                    .iter()
1956                    .any(|mint| mint == default_mint)
1957            {
1958                return Some(default_mint);
1959            }
1960        }
1961
1962        self.routing
1963            .cashu_accepted_mints
1964            .first()
1965            .map(String::as_str)
1966    }
1967
1968    fn choose_quote_mint(&self, requested_mint: Option<&str>) -> Option<String> {
1969        if let Some(requested_mint) = requested_mint {
1970            if self.accepts_quote_mint(Some(requested_mint)) {
1971                return Some(requested_mint.to_string());
1972            }
1973        }
1974        if let Some(default_mint) = self.routing.cashu_default_mint.as_ref() {
1975            return Some(default_mint.clone());
1976        }
1977        if let Some(first_mint) = self.routing.cashu_accepted_mints.first() {
1978            return Some(first_mint.clone());
1979        }
1980        requested_mint.map(str::to_string)
1981    }
1982
1983    fn accepts_quote_mint(&self, mint_url: Option<&str>) -> bool {
1984        if self.routing.cashu_accepted_mints.is_empty() {
1985            return true;
1986        }
1987
1988        let Some(mint_url) = mint_url else {
1989            return false;
1990        };
1991        self.routing
1992            .cashu_accepted_mints
1993            .iter()
1994            .any(|mint| mint == mint_url)
1995    }
1996
1997    fn trusts_quote_mint(&self, mint_url: Option<&str>) -> bool {
1998        let Some(mint_url) = mint_url else {
1999            return self.routing.cashu_default_mint.is_none()
2000                && self.routing.cashu_accepted_mints.is_empty();
2001        };
2002        self.routing.cashu_default_mint.as_deref() == Some(mint_url)
2003            || self
2004                .routing
2005                .cashu_accepted_mints
2006                .iter()
2007                .any(|mint| mint == mint_url)
2008    }
2009
2010    async fn peer_suggested_mint_cap_sat(&self, peer_id: &str) -> u64 {
2011        let base = self.routing.cashu_peer_suggested_mint_base_cap_sat;
2012        if base == 0 {
2013            return 0;
2014        }
2015
2016        let selector = self.peer_selector.read().await;
2017        let Some(stats) = selector.get_stats(peer_id) else {
2018            let max_cap = self.routing.cashu_peer_suggested_mint_max_cap_sat;
2019            return if max_cap > 0 { base.min(max_cap) } else { base };
2020        };
2021
2022        if stats.cashu_payment_defaults > 0
2023            && stats.cashu_payment_defaults >= stats.cashu_payment_receipts
2024        {
2025            return 0;
2026        }
2027
2028        let success_bonus = stats
2029            .successes
2030            .saturating_mul(self.routing.cashu_peer_suggested_mint_success_step_sat);
2031        let receipt_bonus = stats
2032            .cashu_payment_receipts
2033            .saturating_mul(self.routing.cashu_peer_suggested_mint_receipt_step_sat);
2034        let mut cap = base
2035            .saturating_add(success_bonus)
2036            .saturating_add(receipt_bonus);
2037        let max_cap = self.routing.cashu_peer_suggested_mint_max_cap_sat;
2038        if max_cap > 0 {
2039            cap = cap.min(max_cap);
2040        }
2041        cap
2042    }
2043
2044    async fn should_accept_quote_response(
2045        &self,
2046        from_peer: &str,
2047        preferred_mint_url: Option<&str>,
2048        offered_payment_sat: u64,
2049        res: &DataQuoteResponse,
2050    ) -> bool {
2051        let Some(payment_sat) = res.p else {
2052            return false;
2053        };
2054        if payment_sat > offered_payment_sat {
2055            return false;
2056        }
2057
2058        let response_mint = res.m.as_deref();
2059        if response_mint == preferred_mint_url {
2060            return true;
2061        }
2062        if self.trusts_quote_mint(response_mint) {
2063            return true;
2064        }
2065        if response_mint.is_none() {
2066            return false;
2067        }
2068
2069        payment_sat <= self.peer_suggested_mint_cap_sat(from_peer).await
2070    }
2071
2072    async fn issue_quote(
2073        &self,
2074        peer_id: &str,
2075        hash_key: &str,
2076        payment_sat: u64,
2077        ttl_ms: u32,
2078        mint_url: Option<&str>,
2079    ) -> u64 {
2080        let quote_id = {
2081            let mut next = self.next_quote_id.write().await;
2082            let quote_id = *next;
2083            *next = next.saturating_add(1);
2084            quote_id
2085        };
2086
2087        let expires_at = Instant::now() + Duration::from_millis(ttl_ms as u64);
2088        self.issued_quotes.write().await.insert(
2089            (peer_id.to_string(), hash_key.to_string(), quote_id),
2090            IssuedQuote {
2091                expires_at,
2092                payment_sat,
2093                mint_url: mint_url.map(str::to_string),
2094            },
2095        );
2096        quote_id
2097    }
2098
2099    async fn take_valid_quote(&self, peer_id: &str, hash_key: &str, quote_id: u64) -> bool {
2100        let key = (peer_id.to_string(), hash_key.to_string(), quote_id);
2101        let Some(quote) = self.issued_quotes.write().await.remove(&key) else {
2102            return false;
2103        };
2104        quote.expires_at > Instant::now()
2105    }
2106
2107    async fn send_request_to_peer(
2108        &self,
2109        peer_id: &str,
2110        hash: &Hash,
2111        request_htl: u8,
2112        quote_id: Option<u64>,
2113    ) -> bool {
2114        if !should_forward_htl(request_htl) {
2115            return false;
2116        }
2117
2118        let channel = match self.signaling.get_channel(peer_id).await {
2119            Some(c) => c,
2120            None => return false,
2121        };
2122
2123        // Hashtree owns HTL and consumes exactly one unit when forwarding a
2124        // blob request to another mesh peer. Transport/routing hops below this
2125        // layer must not alter it.
2126        let send_htl = request_htl.saturating_sub(1);
2127        let req = match quote_id {
2128            Some(quote_id) => create_request_with_quote(hash, send_htl, quote_id),
2129            None => create_request(hash, send_htl),
2130        };
2131        let request_bytes = encode_request(&req);
2132        let request_len = request_bytes.len() as u64;
2133
2134        {
2135            let mut selector = self.peer_selector.write().await;
2136            selector.record_request(peer_id, request_len);
2137        }
2138
2139        match channel.send(request_bytes).await {
2140            Ok(()) => {
2141                self.record_peer_wire_sent(peer_id, request_len).await;
2142                true
2143            }
2144            Err(_) => {
2145                self.peer_selector.write().await.record_failure(peer_id);
2146                false
2147            }
2148        }
2149    }
2150
2151    async fn send_quote_request_to_peer(
2152        &self,
2153        peer_id: &str,
2154        hash: &Hash,
2155        payment_sat: u64,
2156        ttl_ms: u32,
2157        mint_url: Option<&str>,
2158    ) -> bool {
2159        let channel = match self.signaling.get_channel(peer_id).await {
2160            Some(c) => c,
2161            None => return false,
2162        };
2163
2164        let req = create_quote_request(hash, ttl_ms, payment_sat, mint_url);
2165        let request_bytes = encode_quote_request(&req);
2166        let request_len = request_bytes.len() as u64;
2167
2168        match channel.send(request_bytes).await {
2169            Ok(()) => {
2170                self.record_peer_wire_sent(peer_id, request_len).await;
2171                true
2172            }
2173            Err(_) => false,
2174        }
2175    }
2176
2177    pub async fn set_read_sources(&self, sources: Vec<Arc<dyn MeshReadSource>>) {
2178        let mut by_id = HashMap::new();
2179        let mut stats = self.read_source_stats.write().await;
2180        for source in sources {
2181            let source_id = source.id().to_string();
2182            by_id.insert(source_id.clone(), source);
2183            stats
2184                .entry(source_id)
2185                .or_insert_with(AdaptiveSourceStats::default);
2186        }
2187        *self.read_sources.write().await = by_id;
2188    }
2189
2190    async fn record_read_source_request(&self, source_id: &str) {
2191        let mut stats = self.read_source_stats.write().await;
2192        stats
2193            .entry(source_id.to_string())
2194            .or_insert_with(AdaptiveSourceStats::default)
2195            .requests += 1;
2196    }
2197
2198    async fn record_read_source_miss(&self, source_id: &str) {
2199        let mut stats = self.read_source_stats.write().await;
2200        stats
2201            .entry(source_id.to_string())
2202            .or_insert_with(AdaptiveSourceStats::default)
2203            .misses += 1;
2204    }
2205
2206    async fn record_read_source_success(&self, source_id: &str, elapsed_ms: u64) {
2207        let now = Instant::now();
2208        let mut stats = self.read_source_stats.write().await;
2209        let stats = stats
2210            .entry(source_id.to_string())
2211            .or_insert_with(AdaptiveSourceStats::default);
2212        stats.successes += 1;
2213        stats.last_success_at = Some(now);
2214        stats.backoff_level = 0;
2215        stats.backed_off_until = None;
2216        if stats.srtt_ms <= 0.0 {
2217            stats.srtt_ms = elapsed_ms as f64;
2218            stats.rttvar_ms = elapsed_ms as f64 / 2.0;
2219            return;
2220        }
2221        let elapsed = elapsed_ms as f64;
2222        stats.rttvar_ms = 0.75 * stats.rttvar_ms + 0.25 * (stats.srtt_ms - elapsed).abs();
2223        stats.srtt_ms = 0.875 * stats.srtt_ms + 0.125 * elapsed;
2224    }
2225
2226    async fn record_read_source_failure(&self, source_id: &str) {
2227        let now = Instant::now();
2228        let mut stats = self.read_source_stats.write().await;
2229        let stats = stats
2230            .entry(source_id.to_string())
2231            .or_insert_with(AdaptiveSourceStats::default);
2232        stats.failures += 1;
2233        stats.last_failure_at = Some(now);
2234        Self::apply_source_backoff(stats, now);
2235    }
2236
2237    async fn record_read_source_timeout(&self, source_id: &str) {
2238        let now = Instant::now();
2239        let mut stats = self.read_source_stats.write().await;
2240        let stats = stats
2241            .entry(source_id.to_string())
2242            .or_insert_with(AdaptiveSourceStats::default);
2243        stats.timeouts += 1;
2244        stats.last_failure_at = Some(now);
2245        Self::apply_source_backoff(stats, now);
2246    }
2247
2248    fn apply_source_backoff(stats: &mut AdaptiveSourceStats, now: Instant) {
2249        stats.backoff_level = stats.backoff_level.saturating_add(1);
2250        let backoff_ms = (INITIAL_SOURCE_BACKOFF_MS
2251            .saturating_mul(2u64.saturating_pow(stats.backoff_level.saturating_sub(1))))
2252        .min(MAX_SOURCE_BACKOFF_MS);
2253        stats.backed_off_until = Some(now + Duration::from_millis(backoff_ms));
2254    }
2255
2256    async fn ordered_read_sources(&self) -> Vec<Arc<dyn MeshReadSource>> {
2257        let sources = self.read_sources.read().await;
2258        if sources.is_empty() {
2259            return Vec::new();
2260        }
2261
2262        let mut available: Vec<Arc<dyn MeshReadSource>> = sources
2263            .values()
2264            .filter(|source| source.is_available())
2265            .cloned()
2266            .collect();
2267        if available.is_empty() {
2268            return Vec::new();
2269        }
2270
2271        let now = Instant::now();
2272        let stats = self.read_source_stats.read().await;
2273        let mut healthy: Vec<Arc<dyn MeshReadSource>> = available
2274            .iter()
2275            .filter(|source| {
2276                stats
2277                    .get(source.id())
2278                    .and_then(|s| s.backed_off_until)
2279                    .is_none_or(|until| until <= now)
2280            })
2281            .cloned()
2282            .collect();
2283        if !healthy.is_empty() {
2284            available = std::mem::take(&mut healthy);
2285        }
2286
2287        available.sort_by(|left, right| {
2288            let left_stats = stats.get(left.id()).cloned().unwrap_or_default();
2289            let right_stats = stats.get(right.id()).cloned().unwrap_or_default();
2290            adaptive_source_score(&right_stats, now)
2291                .partial_cmp(&adaptive_source_score(&left_stats, now))
2292                .unwrap_or(std::cmp::Ordering::Equal)
2293                .then_with(|| left.id().cmp(right.id()))
2294        });
2295        available
2296    }
2297
2298    async fn should_probe_multiple_read_sources(
2299        &self,
2300        ordered_sources: &[Arc<dyn MeshReadSource>],
2301    ) -> bool {
2302        if ordered_sources.len() <= 1 {
2303            return false;
2304        }
2305        let stats = self.read_source_stats.read().await;
2306        let best = stats
2307            .get(ordered_sources[0].id())
2308            .cloned()
2309            .unwrap_or_default();
2310        let second = stats
2311            .get(ordered_sources[1].id())
2312            .cloned()
2313            .unwrap_or_default();
2314        if !source_has_history(&best) || !source_has_history(&second) {
2315            return false;
2316        }
2317        let now = Instant::now();
2318        adaptive_source_score(&best, now) - adaptive_source_score(&second, now)
2319            < SOURCE_SCORE_TIE_DELTA
2320    }
2321
2322    async fn source_dispatch_for(&self, source_count: usize) -> RequestDispatchConfig {
2323        if source_count == 0 {
2324            return self.routing.dispatch;
2325        }
2326        let ordered_sources = self.ordered_read_sources().await;
2327        let probe_multiple = self
2328            .should_probe_multiple_read_sources(&ordered_sources)
2329            .await;
2330        let initial_fanout = if probe_multiple {
2331            source_count.min(2)
2332        } else {
2333            1
2334        };
2335        RequestDispatchConfig {
2336            initial_fanout,
2337            hedge_fanout: self.routing.dispatch.hedge_fanout,
2338            max_fanout: self.routing.dispatch.max_fanout.min(source_count),
2339            hedge_interval_ms: self.routing.dispatch.hedge_interval_ms,
2340        }
2341    }
2342
2343    /// Get peer count
2344    pub async fn peer_count(&self) -> usize {
2345        self.signaling.peer_count().await
2346    }
2347
2348    /// Get connected mesh peer IDs.
2349    pub async fn peer_ids(&self) -> Vec<String> {
2350        self.signaling.peer_ids().await
2351    }
2352
2353    /// Check if we need more peers
2354    pub async fn needs_peers(&self) -> bool {
2355        self.signaling.needs_peers().await
2356    }
2357
2358    /// Re-broadcast hello to refresh discovery as topology changes.
2359    pub async fn send_hello(&self) -> Result<(), TransportError> {
2360        self.signaling.send_hello(vec![]).await
2361    }
2362
2363    /// Drain all currently available peer-link messages and handle them.
2364    ///
2365    /// This keeps the message pump logic shared between simulation and the
2366    /// default production wrapper instead of duplicating per-channel loops.
2367    pub async fn drain_available_data_messages(self: &Arc<Self>) -> DataPumpStats {
2368        let mut stats = DataPumpStats::default();
2369        let peer_ids = self.signaling.peer_ids().await;
2370        for peer_id in peer_ids {
2371            let Some(channel) = self.signaling.get_channel(&peer_id).await else {
2372                continue;
2373            };
2374
2375            while let Some(data) = channel.try_recv() {
2376                stats.processed += 1;
2377                stats.processed_bytes += data.len() as u64;
2378                if let Some(msg) = parse_message(&data) {
2379                    match msg {
2380                        DataMessage::Request(_) => stats.request_messages += 1,
2381                        DataMessage::Response(_) => stats.response_messages += 1,
2382                        DataMessage::QuoteRequest(_) => stats.quote_request_messages += 1,
2383                        DataMessage::QuoteResponse(_) => stats.quote_response_messages += 1,
2384                        DataMessage::PubsubInterest(_) => stats.pubsub_interest_messages += 1,
2385                        DataMessage::PubsubFrame(_) => stats.pubsub_frame_messages += 1,
2386                        DataMessage::PubsubInventory(_) => stats.pubsub_inventory_messages += 1,
2387                        DataMessage::PubsubWant(_) => stats.pubsub_want_messages += 1,
2388                        DataMessage::Payment(_)
2389                        | DataMessage::PaymentAck(_)
2390                        | DataMessage::Chunk(_)
2391                        | DataMessage::PeerHints(_) => {}
2392                    }
2393                }
2394                self.handle_data_message(&peer_id, &data).await;
2395            }
2396        }
2397        stats
2398    }
2399
2400    /// Apply an out-of-band payment credit to a peer's routing priority.
2401    pub async fn record_cashu_payment_for_peer(&self, peer_id: &str, amount_sat: u64) {
2402        self.peer_selector
2403            .write()
2404            .await
2405            .record_cashu_payment(peer_id, amount_sat);
2406    }
2407
2408    /// Record a post-delivery payment we received from a peer.
2409    pub async fn record_cashu_receipt_from_peer(&self, peer_id: &str, amount_sat: u64) {
2410        self.peer_selector
2411            .write()
2412            .await
2413            .record_cashu_receipt(peer_id, amount_sat);
2414    }
2415
2416    /// Record that a peer failed to pay after we delivered successfully.
2417    pub async fn record_cashu_payment_default_from_peer(&self, peer_id: &str) {
2418        self.peer_selector
2419            .write()
2420            .await
2421            .record_cashu_payment_default(peer_id);
2422    }
2423
2424    /// Snapshot routing/selection summary for inspection/debugging.
2425    pub async fn selector_summary(&self) -> crate::peer_selector::SelectorSummary {
2426        self.peer_selector.read().await.summary()
2427    }
2428
2429    fn should_refuse_requests_from_peer(&self, selector: &PeerSelector, peer_id: &str) -> bool {
2430        selector.is_peer_blocked_for_payment_defaults(
2431            peer_id,
2432            self.routing.cashu_payment_default_block_threshold,
2433        )
2434    }
2435
2436    /// Export live peer metadata for inspection/debugging.
2437    pub async fn peer_metadata_snapshot(&self) -> PeerMetadataSnapshot {
2438        self.peer_selector
2439            .read()
2440            .await
2441            .export_peer_metadata_snapshot()
2442    }
2443
2444    /// Snapshot current peer metadata and persist it into `local_store`.
2445    ///
2446    /// Uses content-addressed storage for the snapshot body and a reserved
2447    /// mutable pointer slot for the "latest snapshot hash".
2448    pub async fn persist_peer_metadata(&self) -> Result<Hash, StoreError> {
2449        let snapshot = self
2450            .peer_selector
2451            .read()
2452            .await
2453            .export_peer_metadata_snapshot();
2454        let bytes = serde_json::to_vec(&snapshot).map_err(|e| {
2455            StoreError::Other(format!("Failed to encode peer metadata snapshot: {e}"))
2456        })?;
2457        let snapshot_hash = hashtree_core::sha256(&bytes);
2458        let _ = self.local_store.put(snapshot_hash, bytes).await?;
2459
2460        let pointer_slot = Self::peer_metadata_pointer_slot_hash();
2461        let pointer_bytes = hex::encode(snapshot_hash).into_bytes();
2462        let _ = self.local_store.delete(&pointer_slot).await?;
2463        let _ = self.local_store.put(pointer_slot, pointer_bytes).await?;
2464
2465        Ok(snapshot_hash)
2466    }
2467
2468    /// Load persisted peer metadata from `local_store` if available.
2469    pub async fn load_peer_metadata(&self) -> Result<bool, StoreError> {
2470        let pointer_slot = Self::peer_metadata_pointer_slot_hash();
2471        let Some(pointer_bytes) = self.local_store.get(&pointer_slot).await? else {
2472            return Ok(false);
2473        };
2474        let pointer_hex = std::str::from_utf8(&pointer_bytes).map_err(|e| {
2475            StoreError::Other(format!("Peer metadata pointer is not valid UTF-8: {e}"))
2476        })?;
2477        let snapshot_hash = Self::decode_hash_hex(pointer_hex.trim())?;
2478
2479        let Some(snapshot_bytes) = self.local_store.get(&snapshot_hash).await? else {
2480            return Ok(false);
2481        };
2482        let snapshot: PeerMetadataSnapshot =
2483            serde_json::from_slice(&snapshot_bytes).map_err(|e| {
2484                StoreError::Other(format!("Failed to decode peer metadata snapshot: {e}"))
2485            })?;
2486        self.peer_selector
2487            .write()
2488            .await
2489            .import_peer_metadata_snapshot(&snapshot);
2490        Ok(true)
2491    }
2492
2493    /// Request data from peers after negotiating a paid quote.
2494    ///
2495    /// If quote negotiation fails or the quoted peer does not deliver, the store
2496    /// falls back to the normal unpaid retrieval path to preserve liveness.
2497    pub async fn get_with_quote(
2498        &self,
2499        hash: &Hash,
2500        payment_sat: u64,
2501        quote_ttl: Duration,
2502    ) -> Result<Option<Vec<u8>>, StoreError> {
2503        if let Some(data) = self.local_store.get(hash).await? {
2504            if hashtree_core::sha256(&data) != *hash {
2505                return Err(StoreError::Other(
2506                    "local store returned corrupt content".to_string(),
2507                ));
2508            }
2509            return Ok(Some(data));
2510        }
2511        self.request_from_peers_with_quote(hash, payment_sat, quote_ttl)
2512            .await
2513    }
2514
2515    async fn request_from_peers_with_quote(
2516        &self,
2517        hash: &Hash,
2518        payment_sat: u64,
2519        quote_ttl: Duration,
2520    ) -> Result<Option<Vec<u8>>, StoreError> {
2521        let ordered_peer_ids = self.ordered_connected_peers(None).await;
2522        if ordered_peer_ids.is_empty() {
2523            return Ok(None);
2524        }
2525
2526        if let Some(quote) = self
2527            .request_quote_from_peers(hash, payment_sat, quote_ttl, &ordered_peer_ids)
2528            .await
2529        {
2530            if let Some(data) = self
2531                .request_from_single_peer(hash, &quote.peer_id, MAX_HTL, Some(quote.quote_id))
2532                .await
2533            {
2534                return Ok(Some(data));
2535            }
2536        }
2537
2538        match self
2539            .request_from_mesh_with_context(hash, &MeshReadContext::default())
2540            .await
2541        {
2542            RouteFetchOutcome::Hit(data) => Ok(Some(data)),
2543            RouteFetchOutcome::Miss => Ok(None),
2544            RouteFetchOutcome::Timeout => Err(StoreError::Other(
2545                "blob retrieval deadline expired before the search completed".to_string(),
2546            )),
2547            RouteFetchOutcome::Failure => Err(StoreError::Other(
2548                "blob retrieval failed before the search completed".to_string(),
2549            )),
2550        }
2551    }
2552
2553    async fn request_quote_from_peers(
2554        &self,
2555        hash: &Hash,
2556        payment_sat: u64,
2557        quote_ttl: Duration,
2558        ordered_peer_ids: &[String],
2559    ) -> Option<NegotiatedQuote> {
2560        if ordered_peer_ids.is_empty() {
2561            return None;
2562        }
2563        let ttl_ms = quote_ttl.as_millis().min(u32::MAX as u128) as u32;
2564        if ttl_ms == 0 {
2565            return None;
2566        }
2567        let requested_mint = self.requested_quote_mint().map(str::to_string);
2568
2569        let hash_key = hash_to_key(hash);
2570        let (tx, rx) = oneshot::channel();
2571        self.pending_quotes.write().await.insert(
2572            hash_key.clone(),
2573            PendingQuoteRequest {
2574                response_tx: tx,
2575                preferred_mint_url: requested_mint.clone(),
2576                offered_payment_sat: payment_sat,
2577            },
2578        );
2579
2580        let rx = Arc::new(Mutex::new(rx));
2581        let result = run_hedged_waves(
2582            ordered_peer_ids.len(),
2583            self.routing.dispatch,
2584            self.request_timeout,
2585            |range| {
2586                let wave_peer_ids = ordered_peer_ids[range].to_vec();
2587                let requested_mint = requested_mint.clone();
2588                let hash = *hash;
2589                async move {
2590                    let mut sent = 0usize;
2591                    for peer_id in wave_peer_ids {
2592                        if self
2593                            .send_quote_request_to_peer(
2594                                &peer_id,
2595                                &hash,
2596                                payment_sat,
2597                                ttl_ms,
2598                                requested_mint.as_deref(),
2599                            )
2600                            .await
2601                        {
2602                            sent += 1;
2603                        }
2604                    }
2605                    sent
2606                }
2607            },
2608            |wait| {
2609                let rx = rx.clone();
2610                async move {
2611                    let mut rx = rx.lock().await;
2612                    match tokio::time::timeout(wait, &mut *rx).await {
2613                        Ok(Ok(Some(quote))) => HedgedWaveAction::Success(quote),
2614                        Ok(Ok(None)) | Ok(Err(_)) => HedgedWaveAction::Abort,
2615                        Err(_) => HedgedWaveAction::Continue,
2616                    }
2617                }
2618            },
2619        )
2620        .await;
2621        let _ = self.pending_quotes.write().await.remove(&hash_key);
2622        result
2623    }
2624
2625    async fn register_pending_request(
2626        &self,
2627        request_key: PendingRequestKey,
2628        queried_peers: Vec<String>,
2629    ) -> (Arc<()>, oneshot::Receiver<Option<Vec<u8>>>) {
2630        let owner = Arc::new(());
2631        let (response_tx, response_rx) = oneshot::channel();
2632        self.pending_requests
2633            .write()
2634            .await
2635            .entry(request_key)
2636            .or_default()
2637            .push(PendingRequest {
2638                owner: owner.clone(),
2639                response_tx,
2640                started_at: Instant::now(),
2641                queried_peers,
2642            });
2643        (owner, response_rx)
2644    }
2645
2646    async fn take_pending_request(
2647        &self,
2648        request_key: PendingRequestKey,
2649        owner: &Arc<()>,
2650    ) -> Option<(PendingRequest, bool)> {
2651        let mut pending = self.pending_requests.write().await;
2652        let (request, remove_key) = {
2653            let requests = pending.get_mut(&request_key)?;
2654            let index = requests
2655                .iter()
2656                .position(|request| Arc::ptr_eq(&request.owner, owner))?;
2657            let request = requests.swap_remove(index);
2658            (request, requests.is_empty())
2659        };
2660        if remove_key {
2661            pending.remove(&request_key);
2662        }
2663        Some((request, remove_key))
2664    }
2665
2666    async fn request_from_single_peer(
2667        &self,
2668        hash: &Hash,
2669        peer_id: &str,
2670        request_htl: u8,
2671        quote_id: Option<u64>,
2672    ) -> Option<Vec<u8>> {
2673        let request_key = PendingRequestKey::new(*hash, request_htl);
2674        let (owner, rx) = self
2675            .register_pending_request(request_key, vec![peer_id.to_string()])
2676            .await;
2677
2678        let mut rx = rx;
2679        if !self
2680            .send_request_to_peer(peer_id, hash, request_htl, quote_id)
2681            .await
2682        {
2683            if self
2684                .take_pending_request(request_key, &owner)
2685                .await
2686                .is_some_and(|(_, last)| last)
2687            {
2688                let _ = self.take_forward_requesters(request_key).await;
2689            }
2690            return None;
2691        }
2692        self.reserve_peer_request(peer_id).await;
2693
2694        if let Ok(Ok(Some(data))) = tokio::time::timeout(self.request_timeout, &mut rx).await {
2695            if data.len() <= BLOB_MAX_BYTES && hashtree_core::sha256(&data) == *hash {
2696                let _ = self.local_store.put(*hash, data.clone()).await;
2697                return Some(data);
2698            }
2699        }
2700
2701        if let Some((pending, last)) = self.take_pending_request(request_key, &owner).await {
2702            self.release_queried_peer_requests(&pending.queried_peers)
2703                .await;
2704            for peer_id in pending.queried_peers {
2705                self.peer_selector.write().await.record_timeout(&peer_id);
2706            }
2707            if last {
2708                let _ = self.take_forward_requesters(request_key).await;
2709            }
2710        }
2711        None
2712    }
2713
2714    async fn request_from_ordered_peers(
2715        &self,
2716        hash: &Hash,
2717        ordered_peer_ids: &[String],
2718        request_htl: u8,
2719    ) -> RouteFetchOutcome {
2720        let request_key = PendingRequestKey::new(*hash, request_htl);
2721        let (owner, rx) = self.register_pending_request(request_key, Vec::new()).await;
2722
2723        let rx = Arc::new(Mutex::new(rx));
2724        let result = run_hedged_waves(
2725            ordered_peer_ids.len(),
2726            self.routing.dispatch,
2727            self.request_timeout,
2728            |range| {
2729                let wave_peer_ids = ordered_peer_ids[range].to_vec();
2730                let hash = *hash;
2731                let owner = owner.clone();
2732                async move {
2733                    let mut sent = 0usize;
2734                    for peer_id in wave_peer_ids {
2735                        if self
2736                            .send_request_to_peer(&peer_id, &hash, request_htl, None)
2737                            .await
2738                        {
2739                            sent += 1;
2740                            self.reserve_peer_request(&peer_id).await;
2741                            if let Some(pending) = self
2742                                .pending_requests
2743                                .write()
2744                                .await
2745                                .get_mut(&request_key)
2746                                .and_then(|requests| {
2747                                    requests
2748                                        .iter_mut()
2749                                        .find(|request| Arc::ptr_eq(&request.owner, &owner))
2750                                })
2751                            {
2752                                pending.queried_peers.push(peer_id);
2753                            }
2754                        }
2755                    }
2756                    sent
2757                }
2758            },
2759            |wait| {
2760                let rx = rx.clone();
2761                async move {
2762                    let mut rx = rx.lock().await;
2763                    match tokio::time::timeout(wait, &mut *rx).await {
2764                        Ok(Ok(Some(data)))
2765                            if data.len() <= BLOB_MAX_BYTES
2766                                && hashtree_core::sha256(&data) == *hash =>
2767                        {
2768                            HedgedWaveAction::Success(data)
2769                        }
2770                        Ok(Ok(Some(_))) => HedgedWaveAction::Continue,
2771                        Ok(Ok(None)) | Ok(Err(_)) => HedgedWaveAction::Abort,
2772                        Err(_) => HedgedWaveAction::Continue,
2773                    }
2774                }
2775            },
2776        )
2777        .await;
2778
2779        let Some(data) = result else {
2780            if let Some((pending, last)) = self.take_pending_request(request_key, &owner).await {
2781                self.release_queried_peer_requests(&pending.queried_peers)
2782                    .await;
2783                for peer_id in pending.queried_peers {
2784                    self.peer_selector.write().await.record_timeout(&peer_id);
2785                }
2786                if last {
2787                    let _ = self.take_forward_requesters(request_key).await;
2788                }
2789            }
2790            return RouteFetchOutcome::Timeout;
2791        };
2792
2793        let _ = self.local_store.put(*hash, data.clone()).await;
2794        RouteFetchOutcome::Hit(data)
2795    }
2796
2797    async fn request_from_read_sources_inner(&self, request: BlobRequest) -> RouteFetchOutcome {
2798        if request.htl == 0 {
2799            return RouteFetchOutcome::Miss;
2800        }
2801        let ordered_sources = self.ordered_read_sources().await;
2802        if ordered_sources.is_empty() {
2803            return RouteFetchOutcome::Miss;
2804        }
2805
2806        let dispatch = normalize_dispatch_config(
2807            self.source_dispatch_for(ordered_sources.len()).await,
2808            ordered_sources.len(),
2809        );
2810        let wave_plan = build_hedged_wave_plan(ordered_sources.len(), dispatch);
2811        if wave_plan.is_empty() {
2812            return RouteFetchOutcome::Miss;
2813        }
2814
2815        let deadline = Instant::now() + self.request_timeout;
2816        let mut pending = FuturesUnordered::new();
2817        let mut pending_source_ids = HashSet::new();
2818        let mut saw_timeout = false;
2819        let mut saw_failure = false;
2820        let mut next_source_idx = 0usize;
2821
2822        for (wave_idx, wave_size) in wave_plan.iter().copied().enumerate() {
2823            let from = next_source_idx;
2824            let to = (next_source_idx + wave_size).min(ordered_sources.len());
2825            next_source_idx = to;
2826
2827            for source in &ordered_sources[from..to] {
2828                let source = Arc::clone(source);
2829                let source_id = source.id().to_string();
2830                let source_request = BlobRequest {
2831                    hash: request.hash,
2832                    htl: match source.kind() {
2833                        BlobRouteKind::Terminal => request.htl,
2834                        BlobRouteKind::MeshPeer => request.htl.saturating_sub(1),
2835                    },
2836                };
2837                self.record_read_source_request(&source_id).await;
2838                pending_source_ids.insert(source_id.clone());
2839                pending.push(async move {
2840                    let started_at = Instant::now();
2841                    let result = std::panic::AssertUnwindSafe(source.route(source_request))
2842                        .catch_unwind()
2843                        .await;
2844                    match result {
2845                        Ok(Ok(BlobReply::Data(data)))
2846                            if data.len() <= BLOB_MAX_BYTES
2847                                && hashtree_core::sha256(&data) == source_request.hash =>
2848                        {
2849                            SourceFetchOutcome::Hit {
2850                                source_id,
2851                                data,
2852                                elapsed_ms: started_at.elapsed().as_millis().max(1) as u64,
2853                            }
2854                        }
2855                        Ok(Ok(BlobReply::NoResult)) => SourceFetchOutcome::Miss { source_id },
2856                        Ok(Ok(BlobReply::Data(_)) | Err(_)) | Err(_) => {
2857                            SourceFetchOutcome::Failure { source_id }
2858                        }
2859                    }
2860                });
2861            }
2862
2863            let is_last_wave =
2864                wave_idx + 1 == wave_plan.len() || next_source_idx >= ordered_sources.len();
2865            let window_end = if is_last_wave {
2866                deadline
2867            } else {
2868                (Instant::now() + Duration::from_millis(dispatch.hedge_interval_ms)).min(deadline)
2869            };
2870
2871            while Instant::now() < window_end {
2872                let remaining = window_end.saturating_duration_since(Instant::now());
2873                let Some(outcome) = tokio::time::timeout(remaining, pending.next())
2874                    .await
2875                    .ok()
2876                    .flatten()
2877                else {
2878                    break;
2879                };
2880                match outcome {
2881                    SourceFetchOutcome::Hit {
2882                        source_id,
2883                        data,
2884                        elapsed_ms,
2885                    } => {
2886                        pending_source_ids.remove(&source_id);
2887                        self.record_read_source_success(&source_id, elapsed_ms)
2888                            .await;
2889                        return RouteFetchOutcome::Hit(data);
2890                    }
2891                    SourceFetchOutcome::Miss { source_id } => {
2892                        pending_source_ids.remove(&source_id);
2893                        self.record_read_source_miss(&source_id).await;
2894                    }
2895                    SourceFetchOutcome::Failure { source_id } => {
2896                        pending_source_ids.remove(&source_id);
2897                        saw_failure = true;
2898                        self.record_read_source_failure(&source_id).await;
2899                    }
2900                }
2901            }
2902
2903            if Instant::now() >= deadline {
2904                break;
2905            }
2906        }
2907
2908        for source_id in pending_source_ids {
2909            saw_timeout = true;
2910            self.record_read_source_timeout(&source_id).await;
2911        }
2912        if saw_timeout {
2913            RouteFetchOutcome::Timeout
2914        } else if saw_failure {
2915            RouteFetchOutcome::Failure
2916        } else {
2917            RouteFetchOutcome::Miss
2918        }
2919    }
2920
2921    async fn request_from_read_sources(&self, request: BlobRequest) -> RouteFetchOutcome {
2922        let hash_key = format!("{}:{}", hash_to_key(&request.hash), request.htl);
2923        let (existing_wait, owner_guard) = {
2924            let mut inflight = self
2925                .inflight_source_fetches
2926                .lock()
2927                .unwrap_or_else(|poisoned| poisoned.into_inner());
2928            if let Some(existing) = inflight.get_mut(&hash_key) {
2929                let (tx, rx) = oneshot::channel();
2930                existing.waiters.push(tx);
2931                (Some(rx), None)
2932            } else {
2933                let owner = Arc::new(());
2934                inflight.insert(
2935                    hash_key.clone(),
2936                    InflightSourceFetch {
2937                        owner: owner.clone(),
2938                        waiters: Vec::new(),
2939                    },
2940                );
2941                (
2942                    None,
2943                    Some(InflightSourceFetchGuard {
2944                        inflight_source_fetches: &self.inflight_source_fetches,
2945                        hash_key: hash_key.clone(),
2946                        owner,
2947                    }),
2948                )
2949            }
2950        };
2951
2952        if let Some(wait) = existing_wait {
2953            return match tokio::time::timeout(self.request_timeout, wait).await {
2954                Ok(Ok(result)) => result,
2955                Ok(Err(_)) | Err(_) => RouteFetchOutcome::Timeout,
2956            };
2957        }
2958
2959        let owner_guard = owner_guard.expect("new source fetch owns its inflight entry");
2960        let result = self.request_from_read_sources_inner(request).await;
2961        if let RouteFetchOutcome::Hit(hit) = &result {
2962            let _ = self.local_store.put(request.hash, hit.clone()).await;
2963        }
2964        owner_guard.complete(result.clone());
2965
2966        result
2967    }
2968
2969    async fn cancel_pending_peer_route(&self, request_key: PendingRequestKey) {
2970        if let Some(pending) = self.pending_requests.write().await.remove(&request_key) {
2971            let queried_peers: Vec<_> = pending
2972                .iter()
2973                .flat_map(|request| request.queried_peers.iter().cloned())
2974                .collect();
2975            self.release_queried_peer_requests(&queried_peers).await;
2976        }
2977    }
2978
2979    async fn cancel_losing_route(&self, request: BlobRequest, route: &ReadRoute) {
2980        if matches!(route, ReadRoute::Peers(_)) {
2981            self.cancel_pending_peer_route(PendingRequestKey::new(request.hash, request.htl))
2982                .await;
2983        }
2984    }
2985
2986    async fn ranked_read_routes(&self, context: &MeshReadContext) -> Vec<RankedReadRoute> {
2987        let mut routes = Vec::new();
2988        let ordered_peers = if should_forward_htl(context.request_htl) {
2989            self.ordered_connected_peers(context.exclude_peer_id.as_deref())
2990                .await
2991        } else {
2992            Vec::new()
2993        };
2994        if !ordered_peers.is_empty() {
2995            let best_peer_id = ordered_peers[0].clone();
2996            let selector = self.peer_selector.read().await;
2997            let best_peer = selector.get_stats(&best_peer_id).cloned();
2998            let now = Instant::now();
2999            let (score, has_history) = match best_peer.as_ref() {
3000                Some(stats) => (
3001                    peer_endpoint_score(stats, now),
3002                    peer_endpoint_has_history(stats),
3003                ),
3004                None => (0.0, false),
3005            };
3006            routes.push(RankedReadRoute {
3007                route: ReadRoute::Peers(ordered_peers),
3008                best_endpoint_id: format!("peer:{best_peer_id}"),
3009                score,
3010                has_history,
3011            });
3012        }
3013        let ordered_sources = self.ordered_read_sources().await;
3014        if let Some(best_source) = ordered_sources.first() {
3015            let stats = self.read_source_stats.read().await;
3016            let best_source_stats = stats.get(best_source.id()).cloned().unwrap_or_default();
3017            let now = Instant::now();
3018            routes.push(RankedReadRoute {
3019                route: ReadRoute::Sources,
3020                best_endpoint_id: format!("source:{}", best_source.id()),
3021                score: adaptive_source_score(&best_source_stats, now),
3022                has_history: source_has_history(&best_source_stats),
3023            });
3024        }
3025        if routes.len() <= 1 {
3026            return routes;
3027        }
3028
3029        routes.sort_by(|left, right| {
3030            right
3031                .score
3032                .partial_cmp(&left.score)
3033                .unwrap_or(std::cmp::Ordering::Equal)
3034                .then_with(|| ranked_route_kind(&left.route).cmp(&ranked_route_kind(&right.route)))
3035                .then_with(|| left.best_endpoint_id.cmp(&right.best_endpoint_id))
3036                .then_with(|| left.route.id().cmp(right.route.id()))
3037        });
3038        routes
3039    }
3040
3041    fn should_probe_multiple_routes(&self, routes: &[RankedReadRoute]) -> bool {
3042        if routes.len() <= 1 {
3043            return false;
3044        }
3045        if !routes[0].has_history || !routes[1].has_history {
3046            return false;
3047        }
3048        (routes[0].score - routes[1].score) < SOURCE_SCORE_TIE_DELTA
3049    }
3050
3051    async fn run_read_route(
3052        &self,
3053        hash: &Hash,
3054        route: &ReadRoute,
3055        context: &MeshReadContext,
3056    ) -> RouteFetchOutcome {
3057        match route {
3058            ReadRoute::Peers(peer_ids) => {
3059                self.request_from_ordered_peers(hash, peer_ids, context.request_htl)
3060                    .await
3061            }
3062            ReadRoute::Sources => {
3063                self.request_from_read_sources(BlobRequest {
3064                    hash: *hash,
3065                    htl: context.request_htl,
3066                })
3067                .await
3068            }
3069        }
3070    }
3071
3072    async fn request_from_mesh_with_context(
3073        &self,
3074        hash: &Hash,
3075        context: &MeshReadContext,
3076    ) -> RouteFetchOutcome {
3077        let routes = self.ranked_read_routes(context).await;
3078        match routes.as_slice() {
3079            [] => RouteFetchOutcome::Miss,
3080            [ranked] => self.run_read_route(hash, &ranked.route, context).await,
3081            [first, second, ..] => {
3082                if self.should_probe_multiple_routes(&routes) {
3083                    let first_fut = self.run_read_route(hash, &first.route, context);
3084                    let second_fut = self.run_read_route(hash, &second.route, context);
3085                    tokio::pin!(first_fut);
3086                    tokio::pin!(second_fut);
3087                    let mut first_done = false;
3088                    let mut second_done = false;
3089                    let mut combined = RouteFetchOutcome::Miss;
3090                    loop {
3091                        tokio::select! {
3092                            result = &mut first_fut, if !first_done => {
3093                                first_done = true;
3094                                if matches!(result, RouteFetchOutcome::Hit(_)) {
3095                                    if !second_done {
3096                                        self.cancel_losing_route(
3097                                            BlobRequest {
3098                                                hash: *hash,
3099                                                htl: context.request_htl,
3100                                            },
3101                                            &second.route,
3102                                        ).await;
3103                                    }
3104                                    return result;
3105                                }
3106                                combined = combined.combine_without_hit(result);
3107                            }
3108                            result = &mut second_fut, if !second_done => {
3109                                second_done = true;
3110                                if matches!(result, RouteFetchOutcome::Hit(_)) {
3111                                    if !first_done {
3112                                        self.cancel_losing_route(
3113                                            BlobRequest {
3114                                                hash: *hash,
3115                                                htl: context.request_htl,
3116                                            },
3117                                            &first.route,
3118                                        ).await;
3119                                    }
3120                                    return result;
3121                                }
3122                                combined = combined.combine_without_hit(result);
3123                            }
3124                            else => break,
3125                        }
3126                        if first_done && second_done {
3127                            break;
3128                        }
3129                    }
3130                    combined
3131                } else {
3132                    let mut combined = self.run_read_route(hash, &first.route, context).await;
3133                    if matches!(combined, RouteFetchOutcome::Hit(_)) {
3134                        return combined;
3135                    }
3136                    for ranked in routes.iter().skip(1) {
3137                        let result = self.run_read_route(hash, &ranked.route, context).await;
3138                        if matches!(result, RouteFetchOutcome::Hit(_)) {
3139                            return result;
3140                        }
3141                        combined = combined.combine_without_hit(result);
3142                    }
3143                    combined
3144                }
3145            }
3146        }
3147    }
3148
3149    async fn begin_forward_request(
3150        &self,
3151        request_key: PendingRequestKey,
3152        requester_id: &str,
3153    ) -> bool {
3154        let mut pending = self.pending_forward_requests.write().await;
3155        if let Some(existing) = pending.get_mut(&request_key) {
3156            existing.requester_ids.insert(requester_id.to_string());
3157            return false;
3158        }
3159
3160        let mut requester_ids = HashSet::new();
3161        requester_ids.insert(requester_id.to_string());
3162        pending.insert(request_key, PendingForwardRequest { requester_ids });
3163        true
3164    }
3165
3166    async fn take_forward_requesters(&self, request_key: PendingRequestKey) -> Vec<String> {
3167        self.pending_forward_requests
3168            .write()
3169            .await
3170            .remove(&request_key)
3171            .map(|pending| pending.requester_ids.into_iter().collect())
3172            .unwrap_or_default()
3173    }
3174
3175    async fn complete_pending_response(
3176        self: &Arc<Self>,
3177        from_peer: &str,
3178        hash: &Hash,
3179        payload: Vec<u8>,
3180    ) {
3181        let pending = {
3182            let mut requests = self.pending_requests.write().await;
3183            let matching_keys: Vec<_> = requests
3184                .keys()
3185                .filter(|key| key.hash == *hash)
3186                .copied()
3187                .collect();
3188            matching_keys
3189                .into_iter()
3190                .flat_map(|key| {
3191                    requests
3192                        .remove(&key)
3193                        .into_iter()
3194                        .flatten()
3195                        .map(move |request| (key, request))
3196                })
3197                .collect::<Vec<_>>()
3198        };
3199        if pending.is_empty() {
3200            return;
3201        }
3202
3203        let payload_bytes = payload.len() as u64;
3204        self.record_useful_bytes_received_from_peer(from_peer, payload_bytes)
3205            .await;
3206        {
3207            let mut deliveries = self.verified_block_deliveries.lock().await;
3208            deliveries.deliveries.push_back(VerifiedBlockDelivery {
3209                hash: *hash,
3210                provider_peer_id: from_peer.to_string(),
3211                payload_bytes,
3212            });
3213            while deliveries.deliveries.len() > VERIFIED_BLOCK_DELIVERY_CAPACITY {
3214                deliveries.deliveries.pop_front();
3215                deliveries.dropped_since_last_drain =
3216                    deliveries.dropped_since_last_drain.saturating_add(1);
3217            }
3218        }
3219
3220        let rtt_ms = pending
3221            .iter()
3222            .map(|(_, request)| request.started_at.elapsed().as_millis() as u64)
3223            .max()
3224            .unwrap_or_default();
3225        let queried_peers: Vec<_> = pending
3226            .iter()
3227            .flat_map(|(_, request)| request.queried_peers.iter().cloned())
3228            .collect();
3229        self.release_queried_peer_requests(&queried_peers).await;
3230        self.peer_selector
3231            .write()
3232            .await
3233            .record_success(from_peer, rtt_ms, payload_bytes);
3234
3235        let mut forward_requesters = HashSet::new();
3236        for (request_key, pending) in pending {
3237            forward_requesters.extend(self.take_forward_requesters(request_key).await);
3238            let _ = pending.response_tx.send(Some(payload.clone()));
3239        }
3240        if !forward_requesters.is_empty() {
3241            let response_bytes = encode_response(&create_response(hash, payload));
3242            for requester_id in forward_requesters {
3243                Arc::clone(self)
3244                    .enqueue_response_send(requester_id, response_bytes.clone(), Instant::now())
3245                    .await;
3246            }
3247        }
3248    }
3249
3250    async fn handle_quote_response_message(&self, from_peer: &str, res: DataQuoteResponse) {
3251        if !res.a {
3252            return;
3253        }
3254
3255        let Some(quote_id) = res.q else {
3256            return;
3257        };
3258
3259        let hash_key = hash_to_key(&res.h);
3260        let (preferred_mint_url, offered_payment_sat) = {
3261            let pending_quotes = self.pending_quotes.read().await;
3262            let Some(pending) = pending_quotes.get(&hash_key) else {
3263                return;
3264            };
3265            (
3266                pending.preferred_mint_url.clone(),
3267                pending.offered_payment_sat,
3268            )
3269        };
3270        if !self
3271            .should_accept_quote_response(
3272                from_peer,
3273                preferred_mint_url.as_deref(),
3274                offered_payment_sat,
3275                &res,
3276            )
3277            .await
3278        {
3279            return;
3280        }
3281        let mut pending_quotes = self.pending_quotes.write().await;
3282        if let Some(pending) = pending_quotes.remove(&hash_key) {
3283            let _ = pending.response_tx.send(Some(NegotiatedQuote {
3284                peer_id: from_peer.to_string(),
3285                quote_id,
3286                mint_url: res.m,
3287            }));
3288        }
3289    }
3290
3291    async fn handle_response_message(
3292        self: &Arc<Self>,
3293        from_peer: &str,
3294        res: crate::protocol::DataResponse,
3295    ) {
3296        let hash_key = hash_to_key(&res.h);
3297        let hash = match crate::protocol::bytes_to_hash(&res.h) {
3298            Some(h) => h,
3299            None => return,
3300        };
3301
3302        // Ignore malformed/corrupt payload and keep waiting for a valid response.
3303        if hashtree_core::sha256(&res.d) != hash {
3304            self.peer_selector.write().await.record_failure(from_peer);
3305            if self.debug {
3306                println!("[MeshStoreCore] Ignoring invalid response payload for {hash_key}");
3307            }
3308            return;
3309        }
3310
3311        self.complete_pending_response(from_peer, &hash, res.d)
3312            .await;
3313    }
3314
3315    async fn handle_quote_request_message(&self, from_peer: &str, req: DataQuoteRequest) {
3316        let hash = match crate::protocol::bytes_to_hash(&req.h) {
3317            Some(h) => h,
3318            None => return,
3319        };
3320        let hash_key = hash_to_key(&hash);
3321
3322        {
3323            let selector = self.peer_selector.read().await;
3324            if self.should_refuse_requests_from_peer(&selector, from_peer) {
3325                if self.debug {
3326                    println!(
3327                        "[MeshStoreCore] Refusing quote request from delinquent peer {}",
3328                        from_peer
3329                    );
3330                }
3331                return;
3332            }
3333        }
3334
3335        let chosen_mint = self.choose_quote_mint(req.m.as_deref());
3336        let can_serve = self.local_store.has(&hash).await.ok().unwrap_or(false)
3337            && !self.should_drop_response(&hash)
3338            && !self.should_corrupt_response(&hash);
3339
3340        let res = if can_serve {
3341            let quote_id = self
3342                .issue_quote(from_peer, &hash_key, req.p, req.t, chosen_mint.as_deref())
3343                .await;
3344            create_quote_response_available(&hash, quote_id, req.p, req.t, chosen_mint.as_deref())
3345        } else {
3346            create_quote_response_unavailable(&hash)
3347        };
3348        let response_bytes = encode_quote_response(&res);
3349        if let Some(channel) = self.signaling.get_channel(from_peer).await {
3350            if channel.send(response_bytes.clone()).await.is_ok() {
3351                self.record_peer_wire_sent(from_peer, response_bytes.len() as u64)
3352                    .await;
3353            }
3354        }
3355    }
3356
3357    async fn handle_request_message(
3358        self: &Arc<Self>,
3359        from_peer: &str,
3360        req: crate::protocol::DataRequest,
3361    ) {
3362        if req.htl > MAX_HTL {
3363            return;
3364        }
3365        let hash = match crate::protocol::bytes_to_hash(&req.h) {
3366            Some(h) => h,
3367            None => return,
3368        };
3369        let hash_key = hash_to_key(&hash);
3370        let request_key = PendingRequestKey::new(hash, req.htl);
3371
3372        if let Some(quote_id) = req.q {
3373            if !self.take_valid_quote(from_peer, &hash_key, quote_id).await {
3374                if self.debug {
3375                    println!(
3376                        "[MeshStoreCore] Refusing request with invalid or expired quote {} from {}",
3377                        quote_id, from_peer
3378                    );
3379                }
3380                return;
3381            }
3382        }
3383
3384        let allow_peer_forwarding = {
3385            let selector = self.peer_selector.read().await;
3386            !self.should_refuse_requests_from_peer(&selector, from_peer)
3387        };
3388
3389        // Check local store
3390        if let Ok(Some(mut data)) = self.local_store.get(&hash).await {
3391            if data.len() <= BLOB_MAX_BYTES && hashtree_core::sha256(&data) == hash {
3392                if self.should_drop_response(&hash) {
3393                    if self.debug {
3394                        println!(
3395                            "[MeshStoreCore] Dropping response for {} due to actor profile",
3396                            hash_to_key(&hash)
3397                        );
3398                    }
3399                    return;
3400                }
3401
3402                let response_delay = self.response_send_delay(&hash, data.len());
3403                if self.should_corrupt_response(&hash) {
3404                    if data.is_empty() {
3405                        data.push(0x80);
3406                    } else {
3407                        data[0] ^= 0x80;
3408                    }
3409                }
3410
3411                let res = create_response(&hash, data);
3412                let response_bytes = encode_response(&res);
3413                let ready_at = Instant::now() + response_delay;
3414                Arc::clone(self)
3415                    .enqueue_response_send(from_peer.to_string(), response_bytes, ready_at)
3416                    .await;
3417                return;
3418            }
3419        }
3420
3421        if self
3422            .pending_requests
3423            .read()
3424            .await
3425            .contains_key(&request_key)
3426        {
3427            let _ = self.begin_forward_request(request_key, from_peer).await;
3428            return;
3429        }
3430
3431        if !self.begin_forward_request(request_key, from_peer).await {
3432            return;
3433        }
3434
3435        let from_peer = from_peer.to_string();
3436        let this = Arc::clone(self);
3437        let request_htl = req.htl;
3438        tokio::spawn(async move {
3439            let result = if allow_peer_forwarding {
3440                let context = MeshReadContext {
3441                    exclude_peer_id: Some(from_peer.clone()),
3442                    request_htl,
3443                };
3444                this.request_from_mesh_with_context(&hash, &context).await
3445            } else {
3446                if this.debug {
3447                    println!(
3448                        "[MeshStoreCore] Serving request from delinquent peer {} via read sources only",
3449                        from_peer
3450                    );
3451                }
3452                this.request_from_read_sources(BlobRequest {
3453                    hash,
3454                    htl: request_htl,
3455                })
3456                .await
3457            };
3458            let requester_ids = this.take_forward_requesters(request_key).await;
3459            match result {
3460                RouteFetchOutcome::Hit(data) => {
3461                    let ready_at = Instant::now() + this.response_send_delay(&hash, data.len());
3462                    let res = create_response(&hash, data);
3463                    let response_bytes = encode_response(&res);
3464                    for requester_id in requester_ids {
3465                        Arc::clone(&this)
3466                            .enqueue_response_send(requester_id, response_bytes.clone(), ready_at)
3467                            .await;
3468                    }
3469                }
3470                RouteFetchOutcome::Miss
3471                | RouteFetchOutcome::Timeout
3472                | RouteFetchOutcome::Failure => {}
3473            }
3474        });
3475    }
3476
3477    async fn handle_pubsub_interest_message(
3478        self: &Arc<Self>,
3479        from_peer: &str,
3480        mut interest: PubsubInterest,
3481    ) {
3482        if !self.apply_pubsub_interest_route(from_peer, &interest).await {
3483            return;
3484        }
3485
3486        if !self.routing.pubsub_forwarding || interest.htl <= 1 {
3487            return;
3488        }
3489        interest.htl = interest.htl.saturating_sub(1);
3490        let _ = self
3491            .send_pubsub_interest_to_peers(&interest, Some(from_peer))
3492            .await;
3493    }
3494
3495    async fn handle_pubsub_frame_message(
3496        self: &Arc<Self>,
3497        from_peer: &str,
3498        mut frame: PubsubFrame,
3499        wire_bytes: usize,
3500    ) {
3501        if frame.stream_id.is_empty() || frame.origin_peer_id.is_empty() {
3502            return;
3503        }
3504        if frame.origin_peer_id == self.signaling.peer_id() {
3505            return;
3506        }
3507
3508        let frame_key = Self::pubsub_frame_key(&frame);
3509        if !self
3510            .pubsub_seen_frames
3511            .lock()
3512            .await
3513            .insert_if_new(frame_key.clone())
3514        {
3515            return;
3516        }
3517        self.cache_pubsub_frame(frame_key.clone(), frame.clone())
3518            .await;
3519
3520        let local_interested = self
3521            .pubsub_local_interests
3522            .read()
3523            .await
3524            .contains(&frame.stream_id);
3525        let mut downstream_peers = if self.routing.pubsub_forwarding && frame.htl > 1 {
3526            match self.routing.pubsub_delivery_mode {
3527                PubsubDeliveryMode::InterestPush => {
3528                    let mut peers = self
3529                        .interested_pubsub_peers(&frame.stream_id, Some(from_peer))
3530                        .await;
3531                    peers.extend(
3532                        self.take_pubsub_want_peers(&frame_key, Some(from_peer))
3533                            .await,
3534                    );
3535                    peers.sort();
3536                    peers.dedup();
3537                    peers
3538                }
3539                PubsubDeliveryMode::HtlInvWant => {
3540                    self.take_pubsub_want_peers(&frame_key, Some(from_peer))
3541                        .await
3542                }
3543            }
3544        } else {
3545            Vec::new()
3546        };
3547        downstream_peers.retain(|peer_id| peer_id != from_peer);
3548
3549        if local_interested || !downstream_peers.is_empty() {
3550            self.record_useful_bytes_received_from_peer(from_peer, wire_bytes as u64)
3551                .await;
3552        }
3553
3554        if local_interested {
3555            self.enqueue_pubsub_event(PubsubEvent {
3556                stream_id: frame.stream_id.clone(),
3557                seq: frame.seq,
3558                origin_peer_id: frame.origin_peer_id.clone(),
3559                from_peer_id: from_peer.to_string(),
3560                payload: frame.payload.clone(),
3561            })
3562            .await;
3563        }
3564
3565        if downstream_peers.is_empty() {
3566            return;
3567        }
3568
3569        frame.htl = frame.htl.saturating_sub(1);
3570        let _ = self
3571            .send_pubsub_frame_to_peers(&frame, &downstream_peers)
3572            .await;
3573    }
3574
3575    async fn handle_pubsub_inventory_message(
3576        self: &Arc<Self>,
3577        from_peer: &str,
3578        inv: PubsubInventory,
3579        wire_bytes: usize,
3580    ) {
3581        if inv.stream_id.is_empty() || inv.origin_peer_id.is_empty() {
3582            return;
3583        }
3584        if inv.origin_peer_id == self.signaling.peer_id() {
3585            return;
3586        }
3587
3588        let key = Self::pubsub_key(&inv.origin_peer_id, &inv.stream_id, inv.seq);
3589        if !self
3590            .pubsub_seen_inventories
3591            .lock()
3592            .await
3593            .insert_if_new(key.clone())
3594        {
3595            return;
3596        }
3597        {
3598            let mut routes = self.pubsub_inventory_routes.write().await;
3599            routes
3600                .entry(key.clone())
3601                .or_insert_with(|| from_peer.to_string());
3602        }
3603
3604        let local_interested = self
3605            .pubsub_local_interests
3606            .read()
3607            .await
3608            .contains(&inv.stream_id);
3609        let downstream_peers = if self.routing.pubsub_forwarding {
3610            self.interested_pubsub_peers(&inv.stream_id, Some(from_peer))
3611                .await
3612        } else {
3613            Vec::new()
3614        };
3615        if local_interested || !downstream_peers.is_empty() {
3616            self.record_useful_bytes_received_from_peer(from_peer, wire_bytes as u64)
3617                .await;
3618            let want =
3619                create_pubsub_want(inv.stream_id.clone(), inv.seq, inv.origin_peer_id.clone());
3620            let _ = self.send_pubsub_want_upstream(&key, &want, None).await;
3621        }
3622
3623        if !self.routing.pubsub_forwarding
3624            || downstream_peers.is_empty()
3625            || !should_forward_htl(inv.htl)
3626        {
3627            return;
3628        }
3629        let _ = self
3630            .send_pubsub_inventory_to_peers(&inv, &downstream_peers)
3631            .await;
3632    }
3633
3634    async fn handle_pubsub_want_message(
3635        self: &Arc<Self>,
3636        from_peer: &str,
3637        want: PubsubWant,
3638        wire_bytes: usize,
3639    ) {
3640        if want.stream_id.is_empty() || want.origin_peer_id.is_empty() {
3641            return;
3642        }
3643        if want.origin_peer_id == from_peer {
3644            return;
3645        }
3646
3647        let key = Self::pubsub_key(&want.origin_peer_id, &want.stream_id, want.seq);
3648        let want_key = format!("{from_peer}:{key}");
3649        if !self.pubsub_seen_wants.lock().await.insert_if_new(want_key) {
3650            return;
3651        }
3652
3653        if let Some(frame) = self.cached_pubsub_frame(&key).await {
3654            self.record_useful_bytes_received_from_peer(from_peer, wire_bytes as u64)
3655                .await;
3656            let peers = vec![from_peer.to_string()];
3657            let _ = self.send_pubsub_frame_to_peers(&frame, &peers).await;
3658            return;
3659        }
3660
3661        let has_upstream_route = self.pubsub_inventory_routes.read().await.contains_key(&key);
3662        if !has_upstream_route {
3663            return;
3664        }
3665
3666        if self.remember_pubsub_want_peer(key.clone(), from_peer).await {
3667            self.record_useful_bytes_received_from_peer(from_peer, wire_bytes as u64)
3668                .await;
3669        }
3670        let _ = self
3671            .send_pubsub_want_upstream(&key, &want, Some(from_peer))
3672            .await;
3673    }
3674
3675    /// Handle incoming data message
3676    pub async fn handle_data_message(self: &Arc<Self>, from_peer: &str, data: &[u8]) {
3677        self.record_peer_wire_received(from_peer, data.len() as u64)
3678            .await;
3679        let parsed = match parse_message(data) {
3680            Some(m) => m,
3681            None => return,
3682        };
3683
3684        match parsed {
3685            DataMessage::Request(req) => {
3686                self.handle_request_message(from_peer, req).await;
3687            }
3688            DataMessage::Response(res) => {
3689                self.handle_response_message(from_peer, res).await;
3690            }
3691            DataMessage::QuoteRequest(req) => {
3692                self.handle_quote_request_message(from_peer, req).await;
3693            }
3694            DataMessage::QuoteResponse(res) => {
3695                self.handle_quote_response_message(from_peer, res).await;
3696            }
3697            DataMessage::PubsubInterest(interest) => {
3698                self.handle_pubsub_interest_message(from_peer, interest)
3699                    .await;
3700            }
3701            DataMessage::PubsubFrame(frame) => {
3702                self.handle_pubsub_frame_message(from_peer, frame, data.len())
3703                    .await;
3704            }
3705            DataMessage::PubsubInventory(inv) => {
3706                self.handle_pubsub_inventory_message(from_peer, inv, data.len())
3707                    .await;
3708            }
3709            DataMessage::PubsubWant(want) => {
3710                self.handle_pubsub_want_message(from_peer, want, data.len())
3711                    .await;
3712            }
3713            DataMessage::Payment(_)
3714            | DataMessage::PaymentAck(_)
3715            | DataMessage::Chunk(_)
3716            | DataMessage::PeerHints(_) => {}
3717        }
3718    }
3719}
3720
3721#[async_trait]
3722impl<S, R, F> BlobRoute for MeshStoreCore<S, R, F>
3723where
3724    S: Store + Send + Sync + 'static,
3725    R: SignalingTransport + Send + Sync + 'static,
3726    F: PeerLinkFactory + Send + Sync + 'static,
3727{
3728    async fn route(&self, request: BlobRequest) -> Result<BlobReply, StoreError> {
3729        if request.htl > MAX_HTL {
3730            return Err(StoreError::Other(format!(
3731                "Hashtree blob HTL {} exceeds the maximum of {MAX_HTL}",
3732                request.htl
3733            )));
3734        }
3735        if let Some(data) = self.local_store.get(&request.hash).await? {
3736            if data.len() > BLOB_MAX_BYTES {
3737                return Err(StoreError::Other(format!(
3738                    "local store returned {} bytes, exceeding the {BLOB_MAX_BYTES}-byte limit",
3739                    data.len()
3740                )));
3741            }
3742            if hashtree_core::sha256(&data) != request.hash {
3743                return Err(StoreError::Other(
3744                    "local store returned corrupt content".to_string(),
3745                ));
3746            }
3747            return Ok(BlobReply::Data(data));
3748        }
3749
3750        if request.htl == 0 {
3751            return Ok(BlobReply::NoResult);
3752        }
3753
3754        let context = MeshReadContext {
3755            exclude_peer_id: None,
3756            request_htl: request.htl,
3757        };
3758        match self
3759            .request_from_mesh_with_context(&request.hash, &context)
3760            .await
3761        {
3762            RouteFetchOutcome::Hit(data) => {
3763                if data.len() > BLOB_MAX_BYTES {
3764                    return Err(StoreError::Other(format!(
3765                        "blob route returned {} bytes, exceeding the {BLOB_MAX_BYTES}-byte limit",
3766                        data.len()
3767                    )));
3768                }
3769                if hashtree_core::sha256(&data) != request.hash {
3770                    return Err(StoreError::Other(
3771                        "blob route returned corrupt content".to_string(),
3772                    ));
3773                }
3774                Ok(BlobReply::Data(data))
3775            }
3776            RouteFetchOutcome::Miss => Ok(BlobReply::NoResult),
3777            RouteFetchOutcome::Timeout => Err(StoreError::Other(
3778                "blob retrieval deadline expired before the search completed".to_string(),
3779            )),
3780            RouteFetchOutcome::Failure => Err(StoreError::Other(
3781                "blob retrieval failed before the search completed".to_string(),
3782            )),
3783        }
3784    }
3785}
3786
3787#[async_trait]
3788impl<S, R, F> Store for MeshStoreCore<S, R, F>
3789where
3790    S: Store + Send + Sync + 'static,
3791    R: SignalingTransport + Send + Sync + 'static,
3792    F: PeerLinkFactory + Send + Sync + 'static,
3793{
3794    async fn put(&self, hash: Hash, data: Vec<u8>) -> Result<bool, StoreError> {
3795        self.local_store.put(hash, data).await
3796    }
3797
3798    async fn get(&self, hash: &Hash) -> Result<Option<Vec<u8>>, StoreError> {
3799        Ok(
3800            match self
3801                .route(BlobRequest {
3802                    hash: *hash,
3803                    htl: MAX_HTL,
3804                })
3805                .await?
3806            {
3807                BlobReply::Data(data) => Some(data),
3808                BlobReply::NoResult => None,
3809            },
3810        )
3811    }
3812
3813    async fn has(&self, hash: &Hash) -> Result<bool, StoreError> {
3814        self.local_store.has(hash).await
3815    }
3816
3817    async fn delete(&self, hash: &Hash) -> Result<bool, StoreError> {
3818        self.local_store.delete(hash).await
3819    }
3820}
3821
3822#[cfg(test)]
3823mod delivery_tests;
3824
3825#[cfg(test)]
3826mod tests;
3827
3828/// Type alias for simulation store.
3829pub type SimMeshStore<S> =
3830    MeshStoreCore<S, crate::mock::MockRelayTransport, crate::mock::MockConnectionFactory>;