Nostr Reliability: Live Relay Delivery and NIP-46 Reply Correlation

Current position

The recent Nostr work addresses two concrete sources of misleading live-test results: missing a fast bunker response because its subscription starts too late, and retaining an old response after a later request times out. The supplied reply-race patch fixes both in steps_nsecbunker_live. The supplied relay adapter also owns connection workers and dispatches inbound requests asynchronously.

Release-build run 20260925132037 subsequently records all 12 live bunker scenarios passing. That is evidence of the tested deployment at that time. It is not an availability guarantee for the listed public relays or every Nostr feature in the application.

Why subscription order matters

NIP-46 request and response traffic uses kind 24133. NIP-01 places that kind in the ephemeral range, whose events are not expected to be retained. A client which publishes a request and only then queries for the reply can miss the response permanently. Moving the query's since value backwards cannot recover an event the relay never stored. See NIP-01.

The corrected test path starts a reply-listener worker for each configured relay. Each worker opens a WebSocket connection and sends a reply filter with limit set to zero. It reports readiness when the matching EOSE arrives. The parent publishes only after readiness has been established for at least one listener. Listeners remain open while publication and bunker ingress are checked, allowing a fast matching reply to wait in the parent's mailbox.

This supplied patch uses dedicated listener connections and the separate black-box publication path. It does not implement the earlier suggested design of putting every request and reply on one connection. The important established ordering is a ready reply subscription before publication, followed by continued listening until the matching reply or a bounded failure.

Matching the current request

The response filter narrows traffic by kind, expected bunker author and the client's recipient tag. After decryption, the response ID must match the current request ID. The later assertion checks that correlation again.

Before creating each request, the patched function removes last_nip46_reply_event, last_nip46_response and last_nip46_reply_error from its live-test namespace. It replaces that namespace with maps:put/3. The normal put_live/2 helper merges maps and therefore cannot be used to remove these fields.

This prevents a particularly confusing failure: an earlier sign_event result being mistaken for the answer to a later ping. A timeout now remains a timeout instead of passing a reply-exists check against old context.

Relay adapter responsibilities

damage_nsecbunker_relay owns the public-relay connections for the bunker bridge. It subscribes to requests addressed to the bunker and publishes signed response events returned by the bunker path. The module does not itself load vault secrets or perform signing.

Its supplied implementation keeps connector workers alive as Gun connection owners, tracks recently observed event IDs and exposes operational counters. Inbound dispatch is asynchronous. This avoids a call cycle in which the relay adapter waits synchronously for the client bridge while that bridge tries to publish a response back through the same adapter.

The test's readiness step examines adapter status instead of treating the initial subscribe/0 return as proof that the subscription is active. Listener workers monitor the parent and close their connection on completion, stop or parent death. These controls make lifecycle failures visible, but they do not turn a remote relay into a durable request queue.

Distinguish each layer of evidence

Observation What it establishes
TCP and TLS complete A transport path to the endpoint exists
HTTP 101 The endpoint accepted the WebSocket upgrade
Matching subscription readiness The test has established its listener before sending
Relay accepts the request event At least one relay accepted that event
Adapter counters and event ID advance The bunker-side adapter observed and dispatched it
Correlated decrypted reply arrives The request completed the reply path
Expected result and author checks pass The particular response satisfies the scenario

Request ingress alone does not show that response publication succeeded. A WebSocket upgrade alone does not establish kind-24133 delivery. These distinctions are why the live suite retains all of its separate assertions.

What the relay investigation actually found

The earlier nos.lol investigation produced changing outcomes: successful upgrades, zero-response timeouts and a trace reaching nginx before HTTP 502. The original fixed WebSocket key also failed in the repeated comparison. Consequently the observations did not establish key-dependent behaviour or a Gun handshake defect.

The practical diagnostic sequence is to identify the failing layer and repeat a controlled test from the actual node. Public relay selection should be based on present kind-24133 publication and receipt, using separate connections and the intended identities. A historical candidate list is not a health check. The current article deliberately makes no claim that a particular public relay is healthy today.

Evidence and remaining limits

The supplied release log records relay canary ingress, black-box ping and signing loops, ordinary live round trips, and policy-rejection responses as successful. The tests use the node's existing damage_nostr identity. A separate local policy probe verifies the requested external client's allowlist entry; it does not replace that client's end-to-end smoke test.

The listener readiness loop can spend time waiting for other configured relays even when one becomes ready earlier. The report includes heartbeat delays, and no latency target is established by a green result. A bounded negative query for a signed article also establishes only that it was not observed on the checked relay path during the check.