Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 19 additions & 1 deletion .github/RELEASE_NOTES.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,24 @@

Every artifact below is published with a `.sha256` beside it, so the download can be checked against a digest that was written by the same job that built it.

### What's new in v0.3.0

`publish` says what it published.

**What comes out of `publish` is what was published.** Each event that at least one relay accepted is printed once the relays have answered or the deadline has passed, so `deed publish wss://a wss://b < events.jsonl > sent.jsonl` leaves a record of exactly which events a relay accepted in time. Every relay's answer goes to stderr with the event's id on it, including a relay saying it already had the event.

**The exit code covers every event.** `publish` exits 0 only when every event was accepted by at least one relay. An event no relay accepted, a record that is not an event, or an event whose signature does not check out is reported on stderr and makes the run exit 1, and the rest of the stream still goes out. In v0.2.0 one acceptance anywhere in the run was enough to exit 0.

**Events are checked before they are sent.** An event whose id or signature does not match its content is not offered to any relay.

**One deadline per event.** Every relay is sent the event at once, and the relays have ten seconds from then to take it and answer, `--timeout` to change it. Each relay is read on its own for the whole run, so an answer is counted the moment it arrives, a relay that keeps sending notices, pings or messages that cannot be read does not keep the wait going, and a relay that stalls in the middle of a message holds up nobody else. A relay that stops reading what is sent to it is dropped when the deadline passes.

**A relay that never answers the dial no longer holds a run open.** `req`, `fetch` and `publish` dial the relays at once, and a relay that has not accepted the connection within five seconds is named and left out. The name lookup is the one step that cannot be cut short. Before, a relay that accepted the TCP connection and never answered the websocket upgrade held the whole run, and every relay listed after it, forever. A relay that closes the connection while `publish` waits for more input is dialled again for the next event, and an event sent down a connection that closes before answering is offered once more on a fresh one.

**Text from a relay is shown, not obeyed.** Control characters in a relay's refusal or notice are printed as escapes, so a relay cannot write a line of its own into the output or send control sequences to the terminal. Notices are shown up to eight per relay.

**Two fixes from the nostr library, now at 0.14.5.** An event that carries a key NIP-01 does not name is accepted as the event that was signed, and a message from a relay that cannot be read costs that message only, rather than every message after it on that connection.

### What's new in v0.2.0

deed reaches relays now, and keeps what it finds.
Expand All @@ -15,7 +33,7 @@ deed req -k 1 -l 50 --store ~/.deed/db wss://relay.example
deed req -k 1 -l 50 --store ~/.deed/db --local
```

The second dials nothing. The events are the same events, and they still verify, because what is stored is what was signed. Other nostr command lines do not do this: the one most people use keeps its local database behind a build tag for Linux on x86_64 only, and even there its own query verbs never write to it.
The second dials nothing. The events are the same events, and they still verify, because what is stored is what was signed.

**Events are checked before they are kept or printed.** A relay can send anything. A signature that does not verify is dropped, and so is an event that does not answer the question that was asked. Both are reported rather than silently skipped.

Expand Down
4 changes: 4 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,10 @@ jobs:
matrix:
os: [ubuntu-latest, macos-latest]
runs-on: ${{ matrix.os }}
# The tests talk to relays over real sockets, so a regression that brings
# back an unbounded wait shows up as a hang. It should fail the job in
# minutes, not hold a runner for hours.
timeout-minutes: 20
steps:
- uses: actions/checkout@v4
with:
Expand Down
9 changes: 6 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ macOS and Linux, Intel and ARM. It works out which build this machine wants, che
The script is short and worth reading before you pipe anything into bash. If you would rather do it yourself:

```sh
VERSION=0.2.0
VERSION=0.3.0
PLATFORM=macos-aarch64 # or macos-x86_64, linux-x86_64, linux-aarch64
BASE=https://github.com/zig-nostr/deed/releases/download/v$VERSION

Expand Down Expand Up @@ -53,7 +53,7 @@ deed reaches relays and keeps what it finds. Every verb that does not need a soc
| `verify` | check that events are correctly signed |
| `req` | build a subscription, and run it |
| `fetch` | get the events a code names |
| `publish` | offer signed events to relays |
| `publish` | offer signed events to relays, and print the ones they accepted |

`deed help <command>` explains any of them.

Expand Down Expand Up @@ -85,8 +85,11 @@ export NOSTR_SECRET_KEY=$(deed key generate)

deed event -c "hello" | deed verify # builds one, signs it, checks it
cat drafts.jsonl | deed event - | deed verify
cat drafts.jsonl | deed event - | deed publish wss://relay.example > sent.jsonl
```

`publish` prints each event a relay accepted, once the relays have answered or the deadline has passed, so what it writes out is what was published.

A key can be passed with `--sec`, but a key on a command line lands in your shell history and in the process table, so `$NOSTR_SECRET_KEY` is the better habit.

A bad record fails that record alone. The stream carries on, the reason goes to standard error, and the exit code reports that something in the run failed.
Expand All @@ -98,7 +101,7 @@ Scripts branch on these, so they are part of the interface and not free to drift
| | |
| --- | --- |
| `0` | it worked |
| `1` | the command ran and failed: a bad signature, an unreadable key, a malformed code |
| `1` | the command ran and failed: a bad signature, an unreadable key, a malformed code, an event no relay accepted |
| `2` | the command was not understood: unknown verb, unknown flag, missing argument. Nothing was attempted |
| `141` | the reader on the other end of the pipe went away, as in `deed decode … \| head -1` |

Expand Down
6 changes: 3 additions & 3 deletions build.zig.zon
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
.{
.name = .deed,
.version = "0.2.0",
.version = "0.3.0",
.fingerprint = 0x89498c2094a1a4a3,
.minimum_zig_version = "0.16.0",
.dependencies = .{
Expand All @@ -10,8 +10,8 @@
// themselves later, and "whatever main happened to be that afternoon"
// is not that.
.nostr = .{
.url = "https://github.com/zig-nostr/nostr/archive/refs/tags/v0.14.4.tar.gz",
.hash = "nostr-0.14.4-CMyPze4BCQAUoi8JLNWfaR4ZDF1e4C5dhM2vMwGJ_qIT",
.url = "https://github.com/zig-nostr/nostr/archive/refs/tags/v0.14.5.tar.gz",
.hash = "nostr-0.14.5-CMyPzUgLCQBev0r22ht88wNHfIfc6vajpRq-hylCECfB",
},
},
.paths = .{
Expand Down
1 change: 1 addition & 0 deletions src/cmd_fetch.zig
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ pub const usage =
\\
\\A relay that has not accepted the connection within five seconds, or
\\within --timeout if that is shorter, is named on stderr and left out.
\\Looking up a relay's name is the one step that cannot be cut short.
\\
\\A bare npub fetches that person's profile rather than everything they have
\\ever written, which is what `npub` on its own can sensibly mean.
Expand Down
Loading
Loading