Phase 3 wiring (task #12):
- NostrSeedDiscovery: async ProviderDiscovery that queries relays for signed
seed adverts and parses endpoint ids (swarm/iroh_provider.rs, seed_advert.rs).
- seed_and_advertise publish path; dep-free fetch/publish helpers reuse the
node's Nostr identity (build_nostr_client/load_or_create_nostr_keys made
pub(crate)).
- swarm::init builds the IrohProvider once into a OnceLock runtime; providers()
returns it; announce_held_blob() is called from update.rs after a release
component passes both hash gates.
- config swarm_enabled (ARCHIPELAGO_SWARM_ENABLED, default off); server.rs init.
Paid swarm serving (Phase 4 step F):
- swarm/paid.rs gates the iroh-blobs provider through streaming::gate,
intercepting connect + GET (peer push hard-disabled). Free by default
(content-download service disabled); denies unpaid peers when enabled;
fails open on internal error so a payment fault never blocks distribution.
Wired into IrohProvider::new.
All iroh code behind the iroh-swarm feature; the default build is inert.
Default build clean; --features iroh-swarm: 11/11 swarm tests pass.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The discovery wire format that feeds the swarm's ProviderDiscovery seam: a
node announces 'I seed blake3 H from iroh endpoint E' as a signed NIP-33
addressable Nostr event. Scope is releases/catalog content ONLY (decided
2026-06-16) — never private user blobs.
- swarm/seed_advert.rs: kind 30081, d-tag = blake3 hex (one current advert
per author+hash, latest-replaces), content {"v":1,"endpoint_id":...}.
advertisement_builder / advertisement_filter / parse_endpoint_id /
endpoint_ids_from_events (dedup). Endpoint ids stay opaque strings so the
protocol is dep-light + unit-testable on the default build.
4/4 tests pass (sign->parse roundtrip, filter targeting, reject wrong-kind/
empty, dedup across nodes).
Next (task #12): gated NostrSeedDiscovery glue (query relays, parse ids ->
iroh::EndpointId), publish path, wire swarm::providers().
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Pulls iroh 1.0 + iroh-blobs 0.103 as OPTIONAL deps under the iroh-swarm
feature and implements a real BlobProvider over them. Verified: the full
iroh QUIC dep tree (260 pkgs) resolves and compiles against the pinned
bitcoin/nostr-sdk/reqwest-rustls stack; the provider compiles against the
0.103/1.0 API.
- swarm/iroh_provider.rs: IrohProvider::new binds a QUIC Endpoint, opens a
persistent FsStore (data_dir/iroh-blobs), and serves blobs via the
iroh-blobs protocol/Router — a node that fetches also SEEDS. try_fetch
maps ContentDigest -> iroh Hash, asks discovery for seed EndpointIds, then
downloader.download(hash, providers) (range-verified) + export to staging.
- ProviderDiscovery trait: the seam Phase 3 (signed Nostr advertisement
events) fills. discovery=None -> no seeds -> origin-only, so enabling the
feature is never worse than today.
- Default build untouched: iroh is optional, the module is cfg-gated, and
providers() stays empty until Phase 3 wires discovery in.
Build: cargo build --features iroh-swarm succeeds (dev). Default build +
44 swarm/update/content_hash/blobs tests unchanged.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Lands the transport/swarm orchestration layer (the iroh engine attaches
later, behind a flag). The seam is fully exercised today with the origin
HTTP path; with no swarm providers registered the behaviour is byte-for-byte
identical to before.
- swarm/mod.rs: BlobProvider trait + fetch_content_addressed() — tries each
provider in order, VERIFIES peer-sourced bytes against the content digest
before accepting (untrusted seeds can't inject tampered bytes), falls back
to the origin closure if none serve. Returns Swarm|Origin.
- Cargo: iroh-swarm feature (off by default; heavy QUIC dep tree attaches
here). providers() is empty until enabled → every fetch hits origin.
- update.rs: components with a BLAKE3 digest route through the seam, using
the existing resumable HTTP downloader as the origin fallback; a swarm hit
is re-checked against the mandatory SHA-256 manifest gate (re-fetch from
origin on any disagreement). Components without blake3 take the original
path untouched.
44/44 swarm/update/content_hash/blobs tests pass (incl. swarm hit/miss,
tampered-bytes-rejected→origin, fall-through ordering).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>