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.
