mirror of
https://github.com/vitorpamplona/amethyst.git
synced 2026-08-09 16:14:40 +00:00
Each [[mirror]] entry now takes strfry-router's dir: - down (default): pull — subscribe to the upstream and ingest, exactly as before. - up: push — an in-process session on the LOCAL relay subscribes with the same scoped filter (so backfill_seconds and filter behave identically in both directions, and the relay's own policy chain gates what leaves), and every matching event is handed to the client's outbox for the upstream, which owns delivery and re-sends across reconnects. - both: pull and push, with echo suppression: a per-upstream LRU of recently exchanged ids keeps an event pulled down from being pushed straight back (and vice versa when the upstream fans our own publish back). Eviction only costs a duplicate round trip — the stores' unique-id constraints stay the correctness backstop. trusted (verify skip) remains a down-only concept; the up direction never verifies since the upstream does its own gatekeeping. Tests: up pushes both the backfill window and the live tail; both converges two stores with disjoint content and holds exact counts after the echo settles (no ping-pong). Live-published test events are genuinely signed — the local publish path verifies, which is also what the debugging showed: the mechanism was fine, the first version of the tests was pushing forged events into a verifying relay. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01TtDNpayEYvJH7QuPswND3A
162 lines
6.7 KiB
TOML
162 lines
6.7 KiB
TOML
# Example config for geode. Section layout mirrors
|
|
# nostr-rs-relay's config.toml so existing operators can port across.
|
|
#
|
|
# Run with:
|
|
# ./gradlew :geode:run --args="--config /etc/geode.toml"
|
|
#
|
|
# CLI flags override individual values: e.g. `--port 8888` wins over
|
|
# `[network].port`.
|
|
|
|
[info]
|
|
# The wss:// URL clients use to reach this relay (mandatory for NIP-42
|
|
# AUTH challenges). If not set, the relay synthesises one from the
|
|
# [network] section.
|
|
relay_url = "wss://relay.example.com/"
|
|
name = "Example Geode"
|
|
description = "A geode deployment."
|
|
contact = "admin@example.com"
|
|
# Operator pubkey (NIP-11). Optional.
|
|
# pubkey = "..."
|
|
# Override the supported NIPs advertised on the NIP-11 endpoint. If
|
|
# omitted, the relay advertises the NIPs it actually implements.
|
|
# supported_nips = [1, 9, 11, 40, 42, 45, 50, 62]
|
|
|
|
[network]
|
|
host = "0.0.0.0"
|
|
port = 7447
|
|
path = "/"
|
|
# Ktor CIO event-loop pool sizing. Leave commented-out for sensible
|
|
# per-CPU defaults (typical for <2k concurrent connections). Lift on
|
|
# big-VM deployments targeting 10k+ connections — over-threading at
|
|
# low connection counts hurts L1/L2 cache locality, so always
|
|
# benchmark before/after when tuning these.
|
|
#
|
|
# Operators targeting >1k concurrent WebSockets should also raise the
|
|
# OS file-descriptor limit: `ulimit -n 65536` (or higher) before
|
|
# launching, plus a matching `LimitNOFILE=` in any systemd unit. The
|
|
# default of 1024 on most distros caps the relay well below 1k FDs
|
|
# (one per WS plus DB and listening sockets).
|
|
# connection_group_size = 4
|
|
# worker_group_size = 16
|
|
# call_group_size = 64
|
|
|
|
[database]
|
|
# True keeps an in-memory SQLite db (events vanish on restart). Useful
|
|
# for tests; set false + `file = "..."` for persistent storage.
|
|
in_memory = false
|
|
file = "/var/lib/geode/events.db"
|
|
|
|
# NOTE: the four knobs below measured as pure noise in relayBench A/B
|
|
# runs on 4-core container hardware at 50k events (see
|
|
# geode/plans/2026-07-04-sqlite-knobs-ab.md) — their value is
|
|
# hardware-dependent, so benchmark on YOUR box before enabling.
|
|
#
|
|
# Reader-connection pool size (file-backed stores only). Default 4.
|
|
# readers = 4
|
|
|
|
# PRAGMA mmap_size in bytes — maps the db file into memory so reads
|
|
# skip the pread syscall. Off by default (SQLite default).
|
|
# mmap_size = 268435456
|
|
|
|
# PRAGMA temp_store = MEMORY — RAM instead of temp files for large
|
|
# sorts. Off by default.
|
|
# temp_store_memory = true
|
|
|
|
# Refresh query-planner statistics (PRAGMA optimize) every N seconds.
|
|
# Incremental and usually a no-op; keeps the planner from drifting
|
|
# onto the wrong index as the corpus grows. Off by default.
|
|
# optimize_interval_seconds = 3600
|
|
|
|
[options]
|
|
# Drop events whose Schnorr signature does not verify. Strongly
|
|
# recommended for any relay accepting traffic from real clients.
|
|
# Verify Schnorr signatures on every EVENT. Default: true. Disable
|
|
# only for trusted-input scenarios (test fixtures, mirror replays).
|
|
verify_signatures = true
|
|
|
|
# Run signature verification in parallel inside the IngestQueue
|
|
# (across all CPU cores) instead of serially on each connection's
|
|
# WebSocket pump. Default: true. Set false to fall back to the
|
|
# legacy in-policy verify path.
|
|
# parallel_verify = true
|
|
|
|
# NIP-50 full-text search. Default: true. When false no FTS index is
|
|
# built or maintained (inserts skip tokenization — a measurable share
|
|
# of ingest cost), NIP-11 stops advertising NIP-50, and REQ filters
|
|
# with a `search` term match nothing. CLI: --no-search.
|
|
# full_text_search = true
|
|
|
|
# Require clients to NIP-42 AUTH before REQ/EVENT/COUNT.
|
|
require_auth = false
|
|
|
|
# Advertise NIP-42 AUTH without requiring it: the relay sends the
|
|
# challenge and records clients that authenticate (so downstream
|
|
# policies can gate on identity), but REQ/EVENT/COUNT still work for
|
|
# clients that never AUTH. Ignored when require_auth = true.
|
|
# optional_auth = false
|
|
|
|
# Reject events whose `created_at` is more than this many seconds in
|
|
# the future. Enforced by RejectFutureEventsPolicy.
|
|
# reject_future_seconds = 1800
|
|
|
|
[authorization]
|
|
# Allow / deny lists. Allow is a permissive ceiling; deny still
|
|
# removes specific entries inside it. Enforced by Pubkey/KindAllowDenyPolicy.
|
|
# pubkey_whitelist = ["abcdef...64hex..."]
|
|
# pubkey_blacklist = []
|
|
# kind_whitelist = [0, 1, 3, 7, 1059, 30023]
|
|
# kind_blacklist = [4]
|
|
|
|
# Mirror upstream relays (strfry-router style, "down" direction): the
|
|
# relay dials each [[mirror]] url, subscribes to everything newer than
|
|
# now - backfill_seconds, and ingests the stream alongside client
|
|
# publishes. Reconnects and re-subscribes automatically.
|
|
#
|
|
# `trusted = true` is the relay-to-relay trust switch: events from that
|
|
# upstream skip Schnorr signature verification (the upstream already
|
|
# verified its own ingest; re-verifying burns ~8% of ingest CPU). The
|
|
# trusted identity is the URL *this* relay dialed — TLS-authenticated
|
|
# for wss:// — so an inbound client can never claim it. Default false:
|
|
# mirror-but-verify. Only trust relays you operate or whose ingest
|
|
# discipline you'd stake your own db on.
|
|
#
|
|
# `filter` (optional) scopes an upstream, as a NIP-01 filter JSON
|
|
# object — same idea as strfry-router's per-stream filter. It shapes
|
|
# the REQ sent upstream AND every delivered event is re-checked
|
|
# against it before ingest, so even a trusted upstream can only
|
|
# inject events inside the declared scope. `since`/`limit` inside it
|
|
# are ignored (backfill_seconds owns the time window). Omit to mirror
|
|
# everything; for several disjoint scopes, repeat [[mirror]] with the
|
|
# same url.
|
|
#
|
|
# `dir` (strfry-router parity) sets the flow direction: "down" pulls
|
|
# from the upstream (default), "up" pushes this relay's matching
|
|
# events to it, "both" does both with echo suppression so the two
|
|
# directions don't ping-pong the same event.
|
|
#
|
|
# [[mirror]]
|
|
# url = "wss://upstream.example.com/"
|
|
# dir = "both"
|
|
# trusted = true
|
|
# backfill_seconds = 3600
|
|
# filter = '{"kinds":[0,1,3,7],"#t":["nostr"]}'
|
|
|
|
[admin]
|
|
# NIP-86 relay management API. When `pubkeys` is non-empty, the relay
|
|
# accepts HTTP POST application/nostr+json+rpc on the same URL,
|
|
# authenticated with NIP-98 HTTP-Auth. Only events signed by one of
|
|
# the listed pubkeys can run admin RPCs (banpubkey / banevent /
|
|
# changerelayname / …). Empty (the default) disables the endpoint.
|
|
#
|
|
# NIP-98 binds admin tokens to the relay's HTTP URL, which is derived
|
|
# from [info].relay_url with the scheme swapped (ws -> http, wss ->
|
|
# https) per NIP-86. Make sure [info].relay_url is set to the
|
|
# canonical public URL when behind TLS termination or a reverse proxy.
|
|
# pubkeys = ["abcdef...64hex..."]
|
|
|
|
# Path for the JSON snapshot that persists NIP-86 admin state (ban
|
|
# lists + the live NIP-11 doc) across restarts. When unset, admin
|
|
# state is in-memory only and forgotten on every restart. Convention
|
|
# is to place this next to the SQLite event-store file.
|
|
# state_file = "/var/lib/geode/events.db.admin.json"
|