Consume NIP-19 relay hints for event fetching

Extract and use relay hints from bech32 entities (nevent, nprofile, naddr)
and event tag references (e, q tags) to fetch events from hinted relays
not in the user's relay pool.

Changes:
- Parse relay hints from bech32 TLV data in URLHandler
- Pass relay hints through SearchType and NoteReference enums
- Add ensureConnected() to RelayPool for ephemeral relay connections
- Implement ephemeral relay lease management with race condition protection
- Add repostTarget() helper to extract relay hints from repost e tags
- Add QuoteRef struct to preserve relay hints from q tags (NIP-10/NIP-18)
- Support relay hints in replies with author pubkey in e-tags (NIP-10)
- Implement fallback broadcast when hinted relays don't respond
- Add comprehensive test coverage for relay hint functionality
- Add DEBUG logging for relay hint tracing during development

Implementation details:
- Connect to hinted relays as ephemeral, returning early when first connects
- Use total deadline to prevent timeout accumulation across hint attempts
- Decrement lease count before suspension points to ensure atomicity
- Fall back to broadcast if hints don't resolve or respond

Closes: https://github.com/damus-io/damus/issues/1147
Changelog-Added: Added relay hint support for nevent, nprofile, naddr links and event tag references (reposts, quotes, replies)
Signed-off-by: alltheseas
Signed-off-by: Daniel D'Aquino <daniel@daquino.me>
Co-authored-by: alltheseas <alltheseas@users.noreply.github.com>
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
Co-authored-by: Daniel D'Aquino <daniel@daquino.me
This commit is contained in:
alltheseas
2026-02-02 20:52:41 -06:00
committed by GitHub
parent 6f8e2d3064
commit 9a1ae6f9b5
27 changed files with 1522 additions and 128 deletions

View File

@@ -32,4 +32,26 @@ extension NdbNote {
}
return self.parse_inner_event()
}
/// Returns the target event ID and relay hints for a repost (kind 6) event.
///
/// Per NIP-18, reposts MUST include an `e` tag with the reposted event's ID,
/// and the tag MUST include a relay URL as its third entry.
///
/// - Returns: A tuple of (noteId, relayHints) if this is a repost with a valid e tag, nil otherwise.
func repostTarget() -> (noteId: NoteId, relayHints: [RelayURL])? {
guard self.known_kind == .boost else { return nil }
for tag in self.tags {
guard tag.count >= 2 else { continue }
guard tag[0].matches_char("e") else { continue }
guard let noteIdData = tag[1].id() else { continue }
let noteId = NoteId(noteIdData)
let relayHints = tag.relayHints
return (noteId, relayHints)
}
return nil
}
}