From 21f3a46ca44cdab0ea958d5e1e4b14ebd57b4f1e Mon Sep 17 00:00:00 2001 From: DanConwayDev Date: Sat, 8 Aug 2026 08:55:02 +0000 Subject: [PATCH] chore(relay): relax temporary query allowance rust-nostr 0.45 newly enables a 120-query-per-minute LocalRelay default. Production sync exhausted it because each SDK-managed NIP-77 NEG-MSG continuation consumes the same per-connection bucket, while the small per-connection quota alone is weak DoS protection because clients can multiply connections. Override the embedded relay allowance by 10x to 1,200 queries per minute while retaining a finite safety backstop. Keep the value internal rather than expanding the operator configuration surface, and document the effective limit and its rationale. Correctness assumes the existing subscription, message, write, event-size, and connection-admission controls remain the primary resource bounds. Per-IP or global rate limiting and proactive outbound historic pacing are deliberately excluded. This override is temporary: review its value after rust-nostr separates or otherwise revises NIP-77 continuation accounting. Validation: nix develop -c cargo check --lib. --- CHANGELOG.md | 5 +++++ docs/explanation/defensive-measures.md | 6 +++++- docs/explanation/sync-scaling-constraints.md | 5 ++++- docs/reference/relay-limits.md | 10 +++++++++- src/nostr/builder.rs | 11 ++++++++++- 5 files changed, 33 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 530675b..2457d15 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,11 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Fixed +- Temporarily override rust-nostr 0.45's newly introduced LocalRelay query + allowance from 120 to 1,200 messages per minute per connection. This retains + a finite DoS backstop while avoiding an unusually restrictive default that + also charges every SDK-managed NIP-77 `NEG-MSG` continuation. Re-evaluate + the 10× allowance after upstream revises that NIP-77 accounting. - Pace query starts after a relay reports its per-connection query-rate budget is exhausted, preventing fixed cooldown recovery from replaying the same fast historic-sync burst indefinitely. If an SDK-managed NIP-77 exchange diff --git a/docs/explanation/defensive-measures.md b/docs/explanation/defensive-measures.md index 3756232..5b4a455 100644 --- a/docs/explanation/defensive-measures.md +++ b/docs/explanation/defensive-measures.md @@ -31,11 +31,15 @@ operator configurable; other dependency hardening is pinned in the builder. These limits prevent individual connections from overwhelming the relay. The relay advertises the standard `max_subscriptions`, `max_limit`, `default_limit`, `max_message_length`, and `max_subid_length` NIP-11 fields. -rust-nostr 0.45 also enforces fixed ngit-grasp-selected defaults of 120 queries, +rust-nostr 0.45 also enforces fixed ngit-grasp-selected defaults of 1,200 queries, 30 authentication events, and 6,000 WebSocket messages per minute; 20 filters per REQ; 1 MiB subscription state; 10 active negentropy sessions and 50,000 negentropy items per connection; a 5 MiB WebSocket message; and a 10-second handshake deadline. NIP-11 has no standard fields for most of those controls. +The query allowance is a temporary 10× override of rust-nostr's newly added +120/minute default because NIP-77 currently charges each SDK-managed `NEG-MSG` +continuation separately; it must be reviewed when upstream revises that +accounting. ### Per-IP Connection Monitoring diff --git a/docs/explanation/sync-scaling-constraints.md b/docs/explanation/sync-scaling-constraints.md index 0163242..793fe09 100644 --- a/docs/explanation/sync-scaling-constraints.md +++ b/docs/explanation/sync-scaling-constraints.md @@ -170,7 +170,7 @@ though they advertise the larger accepted `max_limit`. #### Admission and rate limits (condensed) Native rate limiting varies wildly and is invisible to clients. Our own -embedded relay enforces per-connection per-minute quotas (120 queries, 6,000 +embedded relay enforces per-connection per-minute quotas (1,200 queries, 6,000 WebSocket messages, 60 event writes); nostream ships per-IP connection-attempt and kind-specific event quotas with EWMA decay; khatru and haven offer discrete leaky counters that drain over minutes; nostr-rs-relay, relayer, @@ -190,6 +190,9 @@ itself because a hostile client can multiply connections; per-IP admission and global resource bounds address that threat more directly. rust-nostr also charges SDK-managed NIP-77 `NEG-MSG` continuation frames to this same bucket, so one application-started reconciliation can consume multiple query tokens. +ngit-grasp temporarily overrides that default to 1,200 queries/minute while +retaining a finite per-connection backstop. Re-evaluate the 10× value after +upstream separates or otherwise revises NIP-77 continuation accounting. NIP-11 describes hard relay limitations, not rate-limit algorithms. The standard fields relevant here are `max_limit` and diff --git a/docs/reference/relay-limits.md b/docs/reference/relay-limits.md index 5ea68de..563e387 100644 --- a/docs/reference/relay-limits.md +++ b/docs/reference/relay-limits.md @@ -13,7 +13,7 @@ production admission policy. | Results per filter | 500 | `NGIT_RELAY_FILTER_LIMIT` | `max_limit`, `default_limit` | | Serialized event size | 192 KiB | `NGIT_RELAY_MAX_EVENT_SIZE_BYTES` | No equivalent field | | Event writes per minute | 60 | Fixed | No standard field | -| Queries per minute | 120 | Fixed | No standard field | +| Queries per minute | 1,200 | Fixed temporary override | No standard field | | Authentication events per minute | 30 | Fixed | No standard field | | WebSocket messages per minute | 6,000 | Fixed | No standard field | | WebSocket message size | 5 MiB | Fixed | `max_message_length` | @@ -35,6 +35,14 @@ Production history contains a valid NIP-34 patch event of about 149 KiB, so 64 KiB is incompatible with ngit-grasp's purpose. The raised limit remains bounded and below the 5 MiB WebSocket message ceiling. +The 1,200-query allowance temporarily overrides rust-nostr 0.45's newly +introduced 120/minute default. A finite per-connection bound remains one useful +DoS layer, but the upstream default is unusually restrictive for sync clients +and currently counts every SDK-managed NIP-77 `NEG-MSG` continuation as another +query. Review this 10× override after upstream changes that accounting; a small +per-connection quota is not a substitute for per-IP admission or global +resource bounds because clients can multiply connections. + ## Client adaptation ngit-grasp refetches NIP-11 per connection session. Its outbound sync ledger diff --git a/src/nostr/builder.rs b/src/nostr/builder.rs index 0714b71..dc1c92a 100644 --- a/src/nostr/builder.rs +++ b/src/nostr/builder.rs @@ -43,6 +43,15 @@ use crate::sync::rejected_index::RejectedEventsIndex; /// tighter operation quotas in charge of valid protocol work. const CLIENT_MESSAGES_PER_MINUTE: u32 = 6_000; +/// Temporary compatibility override for rust-nostr 0.45's newly introduced +/// 120-query-per-minute default. +/// +/// A finite per-connection bound remains useful as one DoS layer, but 120 is +/// too restrictive for legitimate sync and NIP-77 currently charges every +/// SDK-managed `NEG-MSG` continuation against the same allowance. Re-evaluate +/// this 10× value after upstream separates or otherwise revises that accounting. +const CLIENT_QUERIES_PER_MINUTE: u32 = 1_200; + /// NIP-34 Write Policy — admission and routing for GRASP-01 events /// /// Acts as the top-level admission gate and router. Each incoming event is: @@ -1021,7 +1030,7 @@ pub async fn create_relay( max_reqs: config.relay_max_subscriptions, notes_per_minute: 60, }) - .queries_per_minute(120) + .queries_per_minute(CLIENT_QUERIES_PER_MINUTE) .auth_events_per_minute(30) .messages_per_minute(CLIENT_MESSAGES_PER_MINUTE) .max_websocket_message_size(5 * 1024 * 1024)