Skip to content

feat(nostr): daemon pull-loop — receive over Nostr, completing RFC-007 D3 (#227)#332

Merged
laulpogan merged 1 commit into
mainfrom
feat/nostr-pull-loop-d3
Jun 16, 2026
Merged

feat(nostr): daemon pull-loop — receive over Nostr, completing RFC-007 D3 (#227)#332
laulpogan merged 1 commit into
mainfrom
feat/nostr-pull-loop-d3

Conversation

@laulpogan

Copy link
Copy Markdown
Collaborator

What

The receive half of the Nostr transport — the last RFC-007 D3 piece. Send-path landed in #322; this completes the round-trip.

run_sync_pull now, after the HTTP-slot pull, also pulls Nostr-delivered events:

  • For each relay a peer is reachable on (peers[*].nostr_transport.relay — in the common symmetric pairing, exactly where a peer publishes our inbound), pull events #p-tagged to our npub, kind:1.
  • Each is transport-verified (verify_and_decode — recompute the NIP-01 id + check the schnorr sig); the inner signed wire event is fed through the same process_events path as HTTP-pulled events — so the inner Ed25519 signature, trust pin, and inbox dedup are all reused, not reimplemented. (Transport-verified here, identity-verified there.)
  • Per-relay errors logged + skipped (one dead relay can't black-hole the others). Sync wrapper over async NostrWs via a one-shot runtime block_on (the daemon loop is sync) — same bridge as the send path.

Together with #322, a transport: nostr peer now fully round-trips through the daemon.

Safety

Strictly additive: a no-op when this session isn't wire enroll nostr'd or no peer carries a nostr_transport → the HTTP-slot pull is byte-identical. The security model holds: transport-verified at pull, identity-verified (Ed25519 + trust) in process_events, exactly like HTTP events.

Tests

nostr_relays_from_peers (distinct, skips transportless/empty, empty-state no-op) is pure + unit-tested; the decode + process_events layers are covered by their own tests + the nostr_ws mock-relay tests. 599 lib tests green; clippy -D warnings clean.

Note / follow-up

Pull set = the relays peers are reachable on, which is correct for symmetric same-relay pairing (the wire nostr pair/accept --relay X flow). A fully asymmetric setup (peer publishes to a relay we don't pair them on) would need a persisted "self nostr relay" set — tracked as a follow-up, not needed for the dogfood path. Live public-relay e2e (AC-3) remains a manual wire nostr validation.

🤖 Generated with Claude Code

The receive half of the Nostr transport (send-path landed in #322). The daemon's
`run_sync_pull` now, after the HTTP-slot pull, also pulls Nostr-delivered events:

- For each relay a peer is reachable on (`peers[*].nostr_transport.relay` — in
  the common symmetric pairing, exactly where a peer publishes our inbound), it
  pulls events `#p`-tagged to our npub, `kind:1`.
- Each is transport-verified (`nostr_event::verify_and_decode` — recompute the
  NIP-01 id + check the schnorr sig) and the inner signed wire event is fed
  through the SAME `crate::pull::process_events` path as HTTP-pulled events — so
  the inner Ed25519 signature, trust pin, and inbox dedup are all reused, not
  reimplemented. Transport-verified here, identity-verified there.
- Cursor None: Nostr re-pulls a recent window each cycle; process_events dedups
  by event_id, so repeats are free. Per-relay errors are logged + skipped (one
  dead relay can't black-hole the others).
- Sync wrapper over async NostrWs via a one-shot runtime block_on (the daemon
  loop is sync) — same bridge as the send path.

Together with #322, a `transport: nostr` peer now fully round-trips through the
daemon. Strictly additive: a no-op when not `wire enroll nostr`'d or no peer
carries a nostr transport, so the HTTP-slot pull is byte-identical.

`nostr_relays_from_peers` (distinct, skips transportless/empty) is pure +
unit-tested; the decode + process_events layers are already covered by their own
tests + the nostr_ws mock-relay tests. 599 lib tests green; clippy clean.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@cloudflare-workers-and-pages

Copy link
Copy Markdown

Deploying wireup-landing with  Cloudflare Pages  Cloudflare Pages

Latest commit: 5962c97
Status: ✅  Deploy successful!
Preview URL: https://d6a744ba.wireup-landing.pages.dev
Branch Preview URL: https://feat-nostr-pull-loop-d3.wireup-landing.pages.dev

View logs

@laulpogan laulpogan merged commit fcf2a08 into main Jun 16, 2026
13 checks passed
@laulpogan laulpogan deleted the feat/nostr-pull-loop-d3 branch June 16, 2026 05:57
laulpogan added a commit that referenced this pull request Jun 16, 2026
…en asymmetric (#333)

Follow-up to #332. The pull-loop pulled only from `peers[*].nostr_transport.relay`
(the relays we reach *peers* on), which is correct only when both sides paired
over the same relay. A peer sends to us by publishing to a relay *we're*
reachable on — not necessarily one we reach them on. So record that:

- `endpoints::pin_self_nostr_relay` / `self_nostr_relays` — a deduped
  `self.nostr_relays[]` set (additive on the self block; composes with the
  existing slot fields).
- `wire nostr pair`/`accept`/`fetch --relay X` now record X as a relay we're
  reachable on (accept folds it into its existing relay-state RMW).
- `relay::nostr_relays_from_peers` now unions `self.nostr_relays[]` (the
  authoritative "where peers publish our inbound" set) with the peer-transport
  relays (still covers the symmetric case before a self-relay is recorded).

Net: a `transport: nostr` peer round-trips regardless of pairing symmetry. Still
additive — no nostr relay recorded → empty pull set → HTTP path byte-identical.

Unit tests: `self_nostr_relay_roundtrips_and_dedups` (roundtrip, dedup, empty,
doesn't clobber other self keys) + the relay-helper test now asserts the
self∪peer union + dedup. 600 lib tests green; clippy clean.

Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant