Skip to main content

Module transport

Module transport 

Source
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§

LiveTransport
Production Transport over the live Nostr network.
Query
The slice of a relay query the Community protocol needs: event kinds, the z pseudonym tag values, and an optional since floor. Production translates this into a Nostr Filter; the in-memory relay matches it directly.

Enums§

Evidence
How much relay coverage a fetch waits to witness before returning.

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).
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.
QUORUM_GRACE_MS
Time-bound on the Quorum majority wait, measured from the FIRST successful EOSE. Without it a 2-relay set with one dead relay would ride the dead relay’s full timeout on every fetch; with it a degraded set costs first-success + this bound, and a healthy set returns at majority (typically far sooner).
RESIDUAL_GRACE_MS
Residual union window past the moment a fetch’s evidence requirement is met: relays finishing within it still merge synchronously; slower ones background- merge via the straggler sink.

Traits§

CommunityIngestSink
Sink for “straggler” events — ones a SLOWER relay returns after a racing LiveTransport::fetch has 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_incoming for 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-trait boxes the futures as Send so impls work inside spawned tasks.

Functions§

clear_plane_pool
Close + drop every pooled plane connection. Call on account swap — the pooled clients are authenticated as the swapped-out account’s community plane keys.
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 (with backoff between rounds) until every relay has ACKed or max_attempts is reached. Returns Ok if at least one relay ever accepted (the event is durably out there; the fetch-union self-heals the stragglers), Err only if ZERO relays accepted after exhausting the retries. Dedups relays first so a duplicated url isn’t double-counted.
fetch_relay_eose
Fetch one relay to GENUINE EOSE, or fail. Client::fetch_events_from (and the whole nostr-sdk 0.44 fetch stack) returns Ok(collected) on timeout, disconnect, and relay-CLOSED alike — success does NOT mean EOSE, which would let a dead relay count as quorum evidence and return confident empties. So the verdict is read from the relay’s own notification stream instead: EOSE = success (empty included — a quiet coordinate is a legitimate answer); CLOSED / shutdown / deadline = failure.
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_client for 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 candidates that 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).