Files
ngit-grasp/docs/reference/relay-limits.md
T
DanConwayDev aa543d8051 feat(relay): make discoverable hardening limits explicit
Motivation: rust-nostr 0.45 introduced a broad local-relay hardening series
whose effective defaults were mostly absent from its changelog and entirely
absent from ngit-grasp's custom NIP-11 response. One new 64 KiB event bound is
incompatible with production history containing a valid roughly 149 KiB
NIP-34 patch event. Leaving other defaults implicit risks another dependency
upgrade silently changing serving policy, while exposing every internal knob
would create configuration that peers cannot usefully negotiate.

Approach: explicitly select every retained rust-nostr hardening value in the
builder. Expose only the subscription and per-filter result limits that peers
can discover and ngit-grasp sync already consumes, plus the Git-specific event
size policy. Apply one filter-limit option consistently to explicit, query,
and omitted-limit caps. Raise the event default from 64 KiB to 192 KiB and
validate it remains beneath the fixed 5 MiB WebSocket message ceiling. Publish
the standard NIP-11 limitation fields and correct the architecture/reference
documentation, including removal of the obsolete max_filters claim.

Correctness: the NIP-11 max_subscriptions value feeds the existing per-session
subscription ledger; default_limit feeds adaptive pagination with its existing
verification-page safeguard. max_limit is advertised truthfully but is not
misused as an omitted-filter promise. The 192 KiB bound clears the observed
patch by about 29% while preserving a finite allocation boundary. All three
new options are synchronized across source, reference docs, NixOS, and the
environment example.

Excluded scope: message-size negotiation, filter-payload limits, per-IP
fairness, and non-standard NIP-11 extensions remain separate work. Rate,
handshake, subscription-memory, filter-count, and negentropy bounds are pinned
but deliberately not operator-configurable because our sync cannot negotiate
them through standard NIP-11 fields.

Validation: focused unit tests passed for explicit configuration defaults,
event/WebSocket size validation, configured NIP-11 advertisement, and the full
http::nip11::tests module (10 tests before adding the focused override case).
The previously validated full library suite and deployment build were not
repeated at the user's request.
2026-08-07 07:44:31 +00:00

50 lines
2.6 KiB
Markdown

# Embedded relay limits
ngit-grasp embeds rust-nostr `LocalRelay` 0.45.0. The application selects every
effective limit explicitly so future dependency defaults cannot silently alter
production admission policy.
## Effective limits
| Limit | ngit-grasp default | Operator configuration | NIP-11 |
| --- | ---: | --- | --- |
| Total inbound connections | Unbounded | `NGIT_MAX_CONNECTIONS` | No standard field |
| Active REQs per connection | 500 | `NGIT_RELAY_MAX_SUBSCRIPTIONS` | `max_subscriptions` |
| 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 |
| Authentication events per minute | 30 | Fixed | No standard field |
| Text messages per minute | 300 | Fixed | No standard field |
| WebSocket message size | 5 MiB | Fixed | `max_message_length` |
| Handshake deadline | 10 seconds | Fixed | No standard field |
| Subscription-ID length | 250 bytes | Fixed | `max_subid_length` |
| Filters per REQ | 20 | Fixed | No standard field |
| Subscription state per connection | 1 MiB | Fixed | No standard field |
| Active negentropy sessions per connection | 10 | Fixed | No standard field |
| Negentropy items per connection | 50,000 | Fixed | No standard field |
| Negentropy frame | 60,000 bytes | Fixed upstream | No standard field |
The filter setting is applied consistently to rust-nostr's explicit filter
cap, per-query result cap, and omitted-limit default. Limits are per filter;
results from multiple filters in one REQ are merged without an aggregate
truncation.
The 192 KiB event default is three times rust-nostr's new 64 KiB default.
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.
## Client adaptation
ngit-grasp refetches NIP-11 per connection session. Its outbound sync ledger
uses `max_subscriptions`, falling back conservatively when absent. Adaptive
historic pagination uses `default_limit` with a verification page and learns
from raw delivered page sizes. `max_limit` describes explicit filter limits;
historic sync currently omits `limit`, so it does not treat `max_limit` as an
omitted-filter page-size promise.
NIP-11 has no standard `max_filters` field in the current schema. Filter-count
and serialized-message budgets therefore remain conservative client-side
constants rather than falsely negotiated capabilities.