Low-probability edge case: it requires the 64-slot relay monitor broadcast channel to overflow (RecvError::Lagged) and a relay to disconnect+reconnect entirely within the dropped-message window and no other relay to be concurrently down.
Summary
The Lagged arm of run_subscription_refresh_monitor is meant to be a conservative catch-up: when status notifications were dropped, it should re-request the gift-wrap subscription far enough back to cover whatever outage it may have missed. But its current logic assumes "no recorded disconnect ⇒ no gap occurred," which is false precisely in the case the lag handler exists to cover. When a relay disconnected and reconnected during the skipped-notification window, it performs no refresh at all, and gift wraps that relay would have delivered during its brief downtime are never re-requested.
Location
src/nostr/client.rs:256-310 — the Err(broadcast::error::RecvError::Lagged(skipped)) arm of run_subscription_refresh_monitor
Details
On a normal StatusChanged the monitor records each relay's disconnect time in disconnected_since and, on the matching Connected, refreshes since from that recorded time (lines 224-254). The Lagged arm tries to reconstruct an oldest_disconnect from:
- currently-
Connected relays that are still present in disconnected_since (lines 267-273), and
- relays it can currently observe as down (lines 278-286).
Consider a relay that both dropped and came back while the channel was overflowed:
- Its
Disconnected and Connected notifications are both among the skipped (dropped) messages, so it was never inserted into disconnected_since.
- In the
Lagged scan it now reads as plainly Connected, so disconnected_since.remove(relay_url) returns None and it contributes nothing to refresh_from (lines 267-273).
If no other relay happens to be down at that moment, refresh_from stays None, disconnected_since is empty, oldest_disconnect is None (lines 290-293), and the if let Some(...) block (lines 295-309) is skipped entirely — no subscription refresh is issued. The flapped relay's outage window is silently un-caught and its gift wraps for that window are lost (they were only ever offered by that relay while it was reconnecting).
Concrete trigger: sustained relay status churn (many relays reconnecting at once, e.g. after a network blip across a large relay set) overflows the 64-slot RELAY_MONITOR_CHANNEL_SIZE; one relay's down→up pair lands in the dropped set.
A truly conservative handler cannot prove that no gap occurred when it has lost information, so it should refresh from a safe lower bound (process_started_at, or now - max_notification_age) whenever the lag arm fires and it cannot otherwise establish an older disconnect time — rather than doing nothing.
Why this is not a duplicate
Neither addresses the subscription-refresh monitor's Lagged arm producing no refresh at all when the only disconnect it needed to catch was itself among the dropped notifications.
Impact
Silent, unrecoverable loss of a subset of gift wraps (missed silent push notifications) after a burst of relay status churn. Bounded to what a single flapped relay uniquely held during a sub-second-to-seconds reconnect, and gated on the rare Lagged + flap-in-window coincidence — hence LOW.
Low-probability edge case: it requires the 64-slot relay monitor broadcast channel to overflow (
RecvError::Lagged) and a relay to disconnect+reconnect entirely within the dropped-message window and no other relay to be concurrently down.Summary
The
Laggedarm ofrun_subscription_refresh_monitoris meant to be a conservative catch-up: when status notifications were dropped, it should re-request the gift-wrap subscription far enough back to cover whatever outage it may have missed. But its current logic assumes "no recorded disconnect ⇒ no gap occurred," which is false precisely in the case the lag handler exists to cover. When a relay disconnected and reconnected during the skipped-notification window, it performs no refresh at all, and gift wraps that relay would have delivered during its brief downtime are never re-requested.Location
src/nostr/client.rs:256-310— theErr(broadcast::error::RecvError::Lagged(skipped))arm ofrun_subscription_refresh_monitorDetails
On a normal
StatusChangedthe monitor records each relay's disconnect time indisconnected_sinceand, on the matchingConnected, refreshessincefrom that recorded time (lines 224-254). TheLaggedarm tries to reconstruct anoldest_disconnectfrom:Connectedrelays that are still present indisconnected_since(lines 267-273), andConsider a relay that both dropped and came back while the channel was overflowed:
DisconnectedandConnectednotifications are both among theskipped(dropped) messages, so it was never inserted intodisconnected_since.Laggedscan it now reads as plainlyConnected, sodisconnected_since.remove(relay_url)returnsNoneand it contributes nothing torefresh_from(lines 267-273).If no other relay happens to be down at that moment,
refresh_fromstaysNone,disconnected_sinceis empty,oldest_disconnectisNone(lines 290-293), and theif let Some(...)block (lines 295-309) is skipped entirely — no subscription refresh is issued. The flapped relay's outage window is silently un-caught and its gift wraps for that window are lost (they were only ever offered by that relay while it was reconnecting).Concrete trigger: sustained relay status churn (many relays reconnecting at once, e.g. after a network blip across a large relay set) overflows the 64-slot
RELAY_MONITOR_CHANNEL_SIZE; one relay's down→up pair lands in the dropped set.A truly conservative handler cannot prove that no gap occurred when it has lost information, so it should refresh from a safe lower bound (
process_started_at, ornow - max_notification_age) whenever the lag arm fires and it cannot otherwise establish an older disconnect time — rather than doing nothing.Why this is not a duplicate
sincefrom only the reconnecting relay's disconnect time, truncating an in-progress catch-up on another relay #296 is about the normalConnectedarm advancingsincefrom only the reconnecting relay's disconnect time.Connectedresets onLagged.Neither addresses the subscription-refresh monitor's
Laggedarm producing no refresh at all when the only disconnect it needed to catch was itself among the dropped notifications.Impact
Silent, unrecoverable loss of a subset of gift wraps (missed silent push notifications) after a burst of relay status churn. Bounded to what a single flapped relay uniquely held during a sub-second-to-seconds reconnect, and gated on the rare
Lagged+ flap-in-window coincidence — hence LOW.