Expand description
Transport abstraction for Community events.
The protocol’s send/sync logic is written against this trait, not against the
live Nostr client, so it can be exercised end-to-end by multiple emulated
clients sharing an in-memory relay (no network, fully deterministic). Production
provides an adapter over NOSTR_CLIENT; tests use [MemoryRelay].
Structs§
- Live
Transport - Production
Transportover the live Nostr network. - Query
- The slice of a relay query the Community protocol needs: event kinds, the
zpseudonym tag values, and an optionalsincefloor. Production translates this into a NostrFilter; the in-memory relay matches it directly.
Constants§
- CONFIRM_
WINDOW - Hard ceiling on the initial confirmation: at least one relay must ACK within this window or the publish is a failure (we throw rather than spin on a dead/unreachable relay set forever). Once ONE relay accepts, the slow/ratelimited stragglers are threaded in the background (capped at MAX_PUBLISH_ATTEMPTS).
- FETCH_
UNION_ GRACE_ MS - Union grace window: how long past the FIRST response the live transport keeps unioning slower relays before returning. Long enough for a healthy-but-slower relay on a high-latency link; short enough that a dead relay doesn’t stall every sync.
- MAX_
PUBLISH_ ATTEMPTS - Per-relay broadcast cap: retry each relay up to this many times before giving up on it (matching the NIP-17 deletable-DM durability). High enough to ride out a transient relay/local-network blip.
Traits§
- Community
Ingest Sink - Sink for “straggler” events — ones a SLOWER relay returns after a racing
LiveTransport::fetchhas already handed the caller the first relay’s batch. The integrator (src-tauri) registers a handler that feeds them back through the normal Concord ingest path (process_incomingfor content, control/rekey re-fold for authority). The transport stays DUMB: it dedups only by event id (identical bytes) and forwards everything else. Two relays disagreeing on the latest control commit are two editions with DIFFERENT ids, so BOTH reach the ingester, where the deterministic convergence engine (version floors + same-version tiebreakers) decides the winner. The transport never resolves conflicts itself. - Transport
- Publish + fetch over a set of relays. Async to match the live Nostr client (the
whole app is tokio-based and network I/O is async);
async-traitboxes the futures asSendso impls work inside spawned tasks.
Functions§
- durable_
broadcast - The retry engine behind a durable broadcast, factored out so it is unit-testable without a live
client.
send_round(pending)performs ONE broadcast attempt to the given still-pending relays and returns the subset that ACKed; this retries the rest (withbackoffbetween rounds) until every relay has ACKed ormax_attemptsis reached. ReturnsOkif at least one relay ever accepted (the event is durably out there; the fetch-union self-heals the stragglers),Erronly if ZERO relays accepted after exhausting the retries. Dedupsrelaysfirst so a duplicated url isn’t double-counted. - forget_
warmed_ relay - Forget a relay from the warm set — call when a relay is REMOVED from the pool (e.g. pruned after
leaving a community), so a later
warm_clientfor another community that shares it doesn’t fast-path-skip the re-add and target a relay the pool no longer holds. Poison-tolerant: the set is pure optimization state, so a poisoned lock is recovered rather than propagated. - prune_
unneeded_ community_ relays - Shed pooled Community relays from
candidatesthat no JOINED community still needs. Used by both the leave path (relays of a community we left) and the invite-preload TTL cleanup (relays an unsolicited/declined invite warmed but never became a join, #297). Keep rules: a relay is kept if a remaining joined community lists it, OR it carries READ/WRITE (the user’s own chat relays — Community relays are GOSSIP-only, so never READ/WRITE). A pruned relay re-warms automatically if its invite is later accepted (the join’s subscription re-adds it), so pruning a still-pending invite’s relay is safe. - set_
community_ ingest_ sink - Register the straggler ingest sink. Call once during app startup (mirrors
set_event_emitter).