Skip to content

Publish says what it published - #22

Merged
sepehr-safari merged 2 commits into
mainfrom
publish-says-what-it-published
Sep 23, 2026
Merged

sepehr-safari merged 2 commits into
mainfrom
publish-says-what-it-published

Conversation

@sepehr-safari

@sepehr-safari sepehr-safari commented Sep 23, 2026 •

Copy link
Copy Markdown
Contributor

Closes #19. Closes #21.

publish printed nothing on stdout and exited 0 when any relay accepted any event anywhere in the run, so a script publishing several events could not tell which went out. It also waited on each relay in turn for 8 seconds of silence that any message restarted, and sent an event without checking it. This changes what it returns and how it waits.

What it returns

  • stdout: each event that at least one relay accepted, as one line of JSON, once the relays have answered or its deadline has passed. What publish writes out is what was published.
  • stderr: every answer with the event's id (<id>: <url>: accepted, accepted (duplicate: ...), refused: <reason>, no answer within <n> ms, closed before answering), notices with the relay's URL, and a k of n published line when something was not.
  • exit: 0 only when every event was accepted by at least one relay; 1 when any was not, or there was nothing to publish; 2 when the command made no sense, including more than 32 relays.

How it waits

  • Events are checked before they are sent; a bad signature is never offered.
  • Relays are dialled at once, under the bound req and fetch now use.
  • Each event is sent to every relay at once, and the relays share one absolute deadline to take it and answer: 10 s, --timeout to change it. A send still going at the deadline means the relay stopped reading, so it is dropped. Its reader is stopped first, since the send can be waiting for the write lock behind that reader's pong.
  • Each relay has its own reader for the whole run. It answers pings, passes answers and notices over a queue, and reports a lost connection at once. An answer counts as soon as it arrives, a relay stalled mid message holds up only its own reader, and a relay that closed while publish waited on input is dialled again before the next event. An event sent down a connection that closes before answering is offered once more on a fresh one, when there is time left.
  • Relay text is shown with control characters escaped, C1 included. Notices are shown up to eight per relay.

req and fetch now say when they leave relays past 32 out. The help for all three says the name lookup is the one step that cannot be cut short.

The nostr pin moves to 0.14.5 (pings and pongs arriving back to back no longer hold a read past its deadline).

Tests

Twenty-two tests drive the real command against relays on loopback, one mode per behaviour in testrelay.zig. Each of these faults, put in on its own, fails at least one of them: no signature check, printing unaccepted events, exiting 0 on any acceptance, unescaped relay text (C0, DEL and C1 separately), no resend, no redial, a stale OK counted, the notice count reset per connection. Sending to relays one after another without a bound, and joining a stuck send without stopping its reader first, each hang the suite instead of failing it; CI jobs now time out at 20 minutes so that shows up as a failure. The suite passed 15 direct runs of the test binary with no failure, the slowest 7 s.

Checked live with the release build: an event nos.lol already had came back accepted (duplicate: have this event), on stdout exactly as offered, with a silent local relay beside it dropped at 5 s.

Release

The last commit bumps the version to 0.3.0 and adds its release notes.

Each event that at least one relay accepted is printed on stdout once the relays have answered or its deadline has passed, so what publish writes out is what was published. Every answer on stderr carries the event's id, and an acceptance shows the relay's message, so a relay that already had the event says so.

The exit code covers every event: 0 only when every event was accepted by at least one relay, 1 when any was not, and the rest of the stream still goes out. Before, one acceptance anywhere in the run was enough to exit 0.

Events are checked before they are sent, so one whose signature does not match its content is never offered. The relays are dialled at once under the same bound as req and fetch, sent each event at once, and given one absolute deadline per event to take it and answer, 10 seconds by default and --timeout to change it. A relay still being sent to at the deadline has stopped reading and is dropped; its reader is stopped first, because a send can be waiting for the write lock behind that reader's pong rather than for the socket.

Each relay has its own reader for the whole run. It answers pings, hands answers and notices over through a queue, and reports the moment its connection goes away. So an answer is counted as soon as it arrives whichever relay sends it, a relay that stalls in the middle of a message holds up only its own reader, and a relay that closed while publish waited for input is dialled again before the next event. An event sent down a connection that then closes before answering is offered once more on a fresh one, when there is time left to do it.

Control characters in text a relay sends are shown as escapes, C1 included, and notices are shown up to eight per relay across its connections. More than 32 relays is refused, and req and fetch now say when they leave relays out. The help for all three says that looking up a relay's name is the one step that cannot be cut short.

The nostr pin moves to 0.14.5, where pings and pongs arriving back to back no longer hold a read past its deadline.

Tests drive the command against real relays on loopback: accepting, refusing, already having the event, never answering the dial, never reading, pinging while never reading, closing after an answer, pinging and then closing, closing on the next event, answering about another event, answering behind a ping or seventy notices beside twenty silent relays, sending notices or pongs without answering, notices across reconnections, and trying to forge output. CI jobs now time out at 20 minutes, so a regression that brings back an unbounded wait fails the job rather than holding a runner.

Closes #19.
Closes #21.
Publish says what it published.
@sepehr-safari sepehr-safari added this to the v0.3.0 milestone Sep 23, 2026
@sepehr-safari
sepehr-safari merged commit 869a5f5 into main Sep 23, 2026
2 checks passed
@sepehr-safari
sepehr-safari deleted the publish-says-what-it-published branch September 23, 2026 12:27
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.

publish does not say which events it published A relay that never answers the dial holds the run forever

1 participant