From 696d767fb469d3a2b249d1231e6e478f12ef5207 Mon Sep 17 00:00:00 2001 From: PeterChrz Date: Wed, 12 Aug 2026 09:43:17 -0400 Subject: [PATCH] fix(channels/buzz): accept subscription without EOSE after auth MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The Buzz relay authenticates via NIP-42 but does not send EndOfStoredEvents after the second subscription. Waiting for EOSE caused a 15s timeout and channel startup failure. After auth, wait a short grace period (3s) for either EOSE or a rejection. If neither arrives, the subscription was accepted — the relay simply doesn not send EOSE, which is valid per NIP-01 (EOSE is optional for live-only subscriptions). --- pkg/channels/buzz/buzz.go | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/pkg/channels/buzz/buzz.go b/pkg/channels/buzz/buzz.go index 4bc4386e..643d70fc 100644 --- a/pkg/channels/buzz/buzz.go +++ b/pkg/channels/buzz/buzz.go @@ -195,6 +195,12 @@ func (c *BuzzChannel) subscribeAuthed( "pubkey": c.publicKey, }) + // postAuthGrace is how long we wait after re-subscribing for either + // EOSE (relay sends end-of-stored-events) or a rejection. If neither + // arrives, the relay accepted the subscription but doesn't send EOSE — + // which is fine, we just start consuming events. + const postAuthGrace = 3 * time.Second + authed, err := c.relay.Subscribe(ctx, filters) if err != nil { return nil, fmt.Errorf("buzz subscribe after auth failed: %w", err) @@ -208,8 +214,14 @@ func (c *BuzzChannel) subscribeAuthed( // A second rejection means the identity is not permitted, not that // the handshake was mistimed — retrying would loop forever. return nil, fmt.Errorf("buzz relay rejected subscription after auth: %s", reason) - case <-time.After(subscribeTimeout): - return nil, fmt.Errorf("buzz timed out waiting for subscription after auth") + case <-time.After(postAuthGrace): + // No EOSE and no rejection within the grace period — the relay + // accepted the subscription. Some relays (including Buzz) don't + // send EOSE after auth, so this is the expected success path. + logger.InfoCF("buzz", "Subscription accepted after auth (no EOSE)", map[string]any{ + "relay": c.config.RelayURL, + }) + return authed, nil } case <-time.After(subscribeTimeout):