aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--PLAN.md110
1 files changed, 92 insertions, 18 deletions
diff --git a/PLAN.md b/PLAN.md
index ab277d9..b4e3b9a 100644
--- a/PLAN.md
+++ b/PLAN.md
@@ -34,24 +34,98 @@ These vectors must be committed to the repo and validated by the JS client in Ph
34 34
35## Phase 2: Relay 35## Phase 2: Relay
36 36
37- WebSocket server (stdlib `net/http` + `golang.org/x/net/websocket` or `nhooyr.io/websocket`) 37The relay lives in `relay/` as a separate Go module (`axon/relay`). It references the core package via a local `replace` directive during development. The muxstr project (a Nostr relay) provides several patterns we can lift directly: custom WebSocket framing, non-blocking fanout, SQLite setup, and filter query building.
38- Challenge/Auth handshake on connect 38
39- Allowlist: authorized pubkeys in a flat config file or SQLite table 39### Module structure
40- Ingest pipeline: 40
41 1. Unmarshal msgpack envelope 41```
42 2. Verify signature using Phase 1 core 42relay/
43 3. Reject duplicates (id PRIMARY KEY) 43 go.mod — module "axon/relay"; replace axon => ../
44 4. Reject expired job requests (check `expires_at` tag) 44 go.sum
45 5. Reject events exceeding 64KB content limit 45 main.go — entry point, config, wiring, graceful shutdown
46 6. Write to index + store 46 config.go — YAML config: listen addr, db path, allowlist
47 7. Fanout to matching subscribers 47 server.go — HTTP server, WebSocket upgrade, connection lifecycle
48- SQLite storage via `database/sql` + `mattn/go-sqlite3` or `modernc.org/sqlite` (pure Go, no CGo) 48 handler.go — per-connection message dispatch
49- Subscription management: filter matching, per-connection subscription map 49 websocket/
50- Ephemeral events (kind 3000–3999): fanout only, skip storage 50 websocket.go — raw WebSocket framing (lifted from muxstr, zero deps)
51- Error responses with HTTP-borrowed codes 51 subscription/
52- WebSocket keepalive: ping every 30s, close after two missed 52 manager.go — in-memory subscriptions, filter matching, fanout
53 53 storage/
54**Go dependencies:** sqlite driver, websocket library. Both should be pure Go where possible. 54 storage.go — SQLite init, schema, WAL config
55 events.go — store, query, dedup
56```
57
58### Components
59
60**WebSocket layer** (`websocket/websocket.go`)
61- Lifted from muxstr with minimal changes — handles RFC 6455 framing, ping/pong, TLS
62- No external WebSocket dependency
63- Relay sends ping every 30s; connections missing two consecutive pings are closed
64
65**Connection auth** (`handler.go`)
66- On connect: relay sends `Challenge { nonce: 32 random bytes }`
67- Client responds: `Auth { pubkey, sig }` where `sig = ed25519.Sign(privkey, SHA256(nonce || relay_url))`
68- Relay verifies sig, checks pubkey against allowlist, responds `Ok` or `Error { 401 }` + close
69- All subsequent messages rejected until auth completes
70
71**Ingest pipeline** (`handler.go` → `storage/events.go`)
721. Unmarshal msgpack `[msg_type, payload]` frame
732. Dispatch by `msg_type` (uint16)
743. For `Publish`: verify sig via axon core, reject if content > 64KB, reject duplicates
754. For job requests (kind 5000–5999): check `expires_at` tag, reject if expired
765. Ephemeral events (kind 3000–3999): fanout only, skip storage
776. Persist to SQLite, fanout to matching subscribers
78
79**Subscription manager** (`subscription/manager.go`)
80- Lifted from muxstr: buffered channel per subscription (cap 100), non-blocking sends (drop-on-full)
81- Per-subscription goroutine streams events to the client connection
82- Filter matching: ids (prefix), authors (prefix), kinds, since/until, tag filters
83- Multiple filters per subscription are OR'd
84
85**Storage** (`storage/`)
86- `modernc.org/sqlite` — pure Go, no CGo
87- WAL mode, 40MB cache, memory-mapped I/O
88- Schema:
89
90```sql
91CREATE TABLE events (
92 id BLOB PRIMARY KEY,
93 pubkey BLOB NOT NULL,
94 created_at INTEGER NOT NULL,
95 kind INTEGER NOT NULL,
96 envelope_bytes BLOB NOT NULL
97) STRICT;
98
99CREATE TABLE tags (
100 event_id BLOB NOT NULL REFERENCES events(id),
101 name TEXT NOT NULL,
102 value TEXT NOT NULL
103) STRICT;
104
105CREATE INDEX idx_events_pubkey ON events(pubkey, created_at DESC);
106CREATE INDEX idx_events_kind ON events(kind, created_at DESC);
107CREATE INDEX idx_events_created_at ON events(created_at DESC);
108CREATE INDEX idx_tags_name_value ON tags(name, value);
109CREATE INDEX idx_tags_event_id ON tags(event_id);
110```
111
112**Config** (`config.go`)
113```yaml
114addr: ":8080"
115db: "axon.db"
116relay_url: "ws://localhost:8080"
117allowlist:
118 - "<hex pubkey>"
119 - "<hex pubkey>"
120```
121
122### Dependencies
123
124- `modernc.org/sqlite` — pure Go SQLite driver
125- Custom WebSocket (no external lib)
126- Core `axon` package via local replace
127
128No other external dependencies.
55 129
56--- 130---
57 131