Summary
initial_subscription_since sets the startup subscription since to now - NIP59_TIMESTAMP_TWEAK_WINDOW_SECS only. It never factors in max_notification_age_secs or max_notification_future_skew_secs, even though (a) the reconnect path does, and (b) the event processor will accept rumors up to max_notification_age_secs old. Because a kind:1059 gift wrap's created_at is randomized up to the tweak window into the past of the rumor's real creation time, the startup subscription requests a strictly narrower window than the server is willing to accept, so a fraction of otherwise-valid events published during downtime are never fetched after a restart.
Location
src/nostr/client.rs:156-158 — initial_subscription_since (used at src/nostr/client.rs:590 in subscribe)
- Contrast with
src/nostr/client.rs:160-176 — reconnect_subscription_since, which floors the lookback at now - max_notification_age_secs
- Acceptance side:
src/nostr/events/processor.rs (validate_notification_freshness, max_notification_age)
Details
To be guaranteed to receive a rumor created at time T, the subscription since must be <= T - tweak_window (the wrap timestamp can be back-dated by up to the tweak window). The server accepts rumors as fresh while rumor_created_at >= now - max_notification_age_secs. So to catch every still-acceptable event, since must be:
now - max_notification_age_secs - tweak_window - future_skew
The reconnect path computes exactly this (disconnect.max(now - age) - tweak - skew). The initial path computes only now - tweak, dropping the - max_notification_age_secs term. Two consequences:
- At defaults (
max_notification_age_secs = 3600, tweak ≈ 2 days): an event created shortly before startup whose wrap timestamp was maximally back-dated has a wrap created_at below now - tweak and is never requested. This is a deterministic ~age/tweak (≈2%) band of loss for events published just before a restart — a small but real gap on every restart.
- If
max_notification_age_secs > NIP59_TIMESTAMP_TWEAK_WINDOW_SECS (a supported config): the startup catch-up window becomes strictly smaller than both the acceptance window and the reconnect lookback, so a large fraction of fresh events published during downtime are silently never fetched after a restart.
The internal inconsistency is the crux: validate_notification_freshness explicitly accepts events the initial subscription never asks the relays for.
Why this is not a duplicate
None note that initial_subscription_since requests a narrower window than the server's acceptance bound.
Recommendation
Make initial_subscription_since take the same SubscriptionLookbackConfig and floor its lookback at now - max_notification_age_secs (then subtract tweak + skew), matching reconnect_subscription_since, so the initial fetch window is never narrower than what the processor will accept.
Impact
Deterministic small loss (~2% of just-before-restart events) at defaults; potentially large loss under max_notification_age_secs > tweak_window. LOW at default config.
Summary
initial_subscription_sincesets the startup subscriptionsincetonow - NIP59_TIMESTAMP_TWEAK_WINDOW_SECSonly. It never factors inmax_notification_age_secsormax_notification_future_skew_secs, even though (a) the reconnect path does, and (b) the event processor will accept rumors up tomax_notification_age_secsold. Because a kind:1059 gift wrap'screated_atis randomized up to the tweak window into the past of the rumor's real creation time, the startup subscription requests a strictly narrower window than the server is willing to accept, so a fraction of otherwise-valid events published during downtime are never fetched after a restart.Location
src/nostr/client.rs:156-158—initial_subscription_since(used atsrc/nostr/client.rs:590insubscribe)src/nostr/client.rs:160-176—reconnect_subscription_since, which floors the lookback atnow - max_notification_age_secssrc/nostr/events/processor.rs(validate_notification_freshness,max_notification_age)Details
To be guaranteed to receive a rumor created at time
T, the subscriptionsincemust be<= T - tweak_window(the wrap timestamp can be back-dated by up to the tweak window). The server accepts rumors as fresh whilerumor_created_at >= now - max_notification_age_secs. So to catch every still-acceptable event,sincemust be:The reconnect path computes exactly this (
disconnect.max(now - age) - tweak - skew). The initial path computes onlynow - tweak, dropping the- max_notification_age_secsterm. Two consequences:max_notification_age_secs = 3600, tweak ≈ 2 days): an event created shortly before startup whose wrap timestamp was maximally back-dated has a wrapcreated_atbelownow - tweakand is never requested. This is a deterministic ~age/tweak(≈2%) band of loss for events published just before a restart — a small but real gap on every restart.max_notification_age_secs > NIP59_TIMESTAMP_TWEAK_WINDOW_SECS(a supported config): the startup catch-up window becomes strictly smaller than both the acceptance window and the reconnect lookback, so a large fraction of fresh events published during downtime are silently never fetched after a restart.The internal inconsistency is the crux:
validate_notification_freshnessexplicitly accepts events the initial subscription never asks the relays for.Why this is not a duplicate
sinceis frozen at process startup, so every relay reconnect re-streams up to 2 days of gift-wrap backlog #258 was about the frozensinceover-fetching (re-streaming 2 days on every reconnect); this is the mirror-image under-fetch on the initial subscription.sincefrom only the reconnecting relay's disconnect time, truncating an in-progress catch-up on another relay #296 is reconnect-pathsincetruncation across relays.None note that
initial_subscription_sincerequests a narrower window than the server's acceptance bound.Recommendation
Make
initial_subscription_sincetake the sameSubscriptionLookbackConfigand floor its lookback atnow - max_notification_age_secs(then subtract tweak + skew), matchingreconnect_subscription_since, so the initial fetch window is never narrower than what the processor will accept.Impact
Deterministic small loss (~2% of just-before-restart events) at defaults; potentially large loss under
max_notification_age_secs > tweak_window. LOW at default config.