Documentation
¶
Index ¶
- Variables
- func BroadcastEvent(evt nostr.Event)
- func CheckIPConnectionRate(ip string, limitPerMinute int) bool
- func ClientHandler(ws *websocket.Conn)
- func DeleteEvents(ids []string) error
- func EnforceConnectionRateLimit(w http.ResponseWriter, r *http.Request, limitPerMinute int) bool
- func HandleArgs() bool
- func ImportEvents(filename string) error
- func InitStatsMonitoring(ctx context.Context)
- func PrintStats(ctx context.Context)
- func ReadDeleteFile(path string) ([]string, error)
- func RecordIPViolation(ip string)
- func RecordRejection(category, ip string)
- func Run() error
- func SetVersionInfo(version, buildTime, gitCommit string)
- type Client
- func (c *Client) AllowEvent(kind int, category string) (bool, string)
- func (c *Client) AllowReq() (bool, string)
- func (c *Client) ClientInfo() string
- func (c *Client) CloseClient()
- func (c *Client) DeleteSubscription(subID string)
- func (c *Client) ForEachSubscription(fn func(subID string, filters []nostr.Filter))
- func (c *Client) GetSubscriptions() map[string][]nostr.Filter
- func (c *Client) GetWS() *websocket.Conn
- func (c *Client) IsConnected() bool
- func (c *Client) SendMessage(msg interface{})
- func (c *Client) SendMessageBlocking(msg interface{}) error
- func (c *Client) SetSubscription(subID string, filters []nostr.Filter)
- func (c *Client) SubscriptionCount() int
- type ConnectionManager
Constants ¶
This section is empty.
Variables ¶
var ( Version = "dev" BuildTime = "unknown" GitCommit = "unknown" )
Version information - these will be set during build via main package
Functions ¶
func BroadcastEvent ¶ added in v0.5.0
BroadcastEvent sends an event to all connected clients whose active subscriptions match the event. This is the real-time delivery mechanism required by NIP-01: after EOSE, new matching events are pushed to subscribers.
func CheckIPConnectionRate ¶ added in v0.5.4
CheckIPConnectionRate consumes one attempt from the given IP's bucket. Returns false if the attempt would put the IP over limitPerMinute and must be rejected. limitPerMinute=0 disables the check.
Each rejection here is also reported to RecordIPViolation so #62's auto-escalation has a feed to consume.
func ClientHandler ¶
func DeleteEvents ¶ added in v0.5.0
DeleteEvents is the admin-takedown entry point invoked from `grain --delete <id>` / `grain --delete-file <path>`. It opens nostrdb with the same settings as normal startup, enqueues a physical delete for every supplied hex event id, and waits for the writer queue to drain on Close.
Authorization model: there is none. Shell access to the grain binary and data directory is the authorization boundary — identical to the existing `--import` flow. This is the moderator / legal-takedown path, not a protocol-level feature, and deliberately has no signature check.
Each call logs one line per id: "deleted <id>" on success, "not found" isn't observable from the Go side (the C delete is a no-op on missing ids and returns success), so every well-formed id that enqueues cleanly is reported as deleted. Malformed ids report an error and continue.
func EnforceConnectionRateLimit ¶ added in v0.5.4
EnforceConnectionRateLimit is the HTTP middleware-style guard that runs before the WebSocket upgrade. It enforces, in order:
- IP block list (#62) — both admin-curated and auto-escalated. 403.
- Per-IP attempt rate limit (#61). 429 + Retry-After.
Returns true if the request should be allowed to proceed to the WS upgrade. limitPerMinute=0 disables the rate-limit stage; the block check still runs because there's no equivalent kill switch — admins who want it off should empty their lists.
func HandleArgs ¶ added in v0.4.1
func HandleArgs() bool
HandleArgs processes command-line arguments and returns true if the program should exit
func ImportEvents ¶ added in v0.5.0
ImportEvents reads a JSONL file of Nostr events and stores them in nostrdb. It processes the entire file in a single run with an in-place progress bar.
func InitStatsMonitoring ¶
Start stats monitoring. ctx bounds all the spawned background goroutines to the lifetime of the server instance (#93).
func PrintStats ¶
PrintStats periodically logs messaging and connection statistics. It exits when ctx is cancelled so it doesn't outlive its server instance (#93).
func ReadDeleteFile ¶ added in v0.5.0
ReadDeleteFile loads a newline-delimited file of hex event ids, stripping blank lines and comments (lines beginning with '#'). Used by `grain --delete-file <path>`.
func RecordIPViolation ¶ added in v0.5.4
func RecordIPViolation(ip string)
RecordIPViolation is the per-IP rate-limit hook. It feeds the rejection aggregator and drives #62's auto-escalation state machine (config.RecordIPRateViolation), so a rate-limit hit can graduate to a temp ban and eventually a permanent block. The escalation no-ops if the relevant thresholds in BlacklistConfig are unset.
func RecordRejection ¶ added in v0.5.4
func RecordRejection(category, ip string)
RecordRejection bumps the appropriate counter for a rejected connection attempt. category is one of: "max_conn", "rate_limit", "blocked". ip is the offending IP (may be empty if unknown — in that case the counter still advances but no IP is attributed).
func Run ¶ added in v0.4.1
func Run() error
Run starts the GRAIN relay server with configuration management and graceful shutdown
func SetVersionInfo ¶ added in v0.4.1
func SetVersionInfo(version, buildTime, gitCommit string)
SetVersionInfo allows main package to set version information. The version is also forwarded to server/utils so the NIP-11 relay info document always advertises the running binary's version.
Types ¶
type Client ¶
type Client struct {
// contains filtered or unexported fields
}
Client implements ClientInterface
func (*Client) AllowEvent ¶ added in v0.5.0
AllowEvent checks this client's per-connection event rate limiter.
func (*Client) AllowReq ¶ added in v0.5.0
AllowReq checks this client's per-connection REQ rate limiter.
func (*Client) ClientInfo ¶
func (*Client) CloseClient ¶
func (c *Client) CloseClient()
CloseClient closes the client connection and cleans up resources.
Both this function and the clientReader defer race to clean up a given client — whichever wins clientsMu first performs the decrement and the other observes the missing map entry as a no-op. The exists guard is load-bearing for #67's fix: without it the same connection's lifecycle produced two decrements (once here, once in the reader defer), driving currentConnections deeply negative and silently disabling the max_connections admission gate.
func (*Client) DeleteSubscription ¶ added in v0.5.0
func (*Client) ForEachSubscription ¶ added in v0.5.0
func (*Client) IsConnected ¶
IsConnected returns true if the client connection is still active
func (*Client) SendMessage ¶
func (c *Client) SendMessage(msg interface{})
SendMessage marshals and enqueues a message for delivery to the client. It NEVER blocks on the network — the dedicated writeLoop goroutine drains the queue. If the per-client buffer is full, the consumer is treated as slow/dead: the message is dropped and the connection is closed. This is the load-bearing fix for the broadcaster lockup: a single stuck peer can no longer freeze BroadcastEvent for every other client.
func (*Client) SendMessageBlocking ¶ added in v0.6.0
SendMessageBlocking enqueues a message with backpressure: it blocks until the writeLoop drains a slot or the connection's context is cancelled. Use this from REQ historical-fulfillment loops where the producer and consumer must stay in step — never from BroadcastEvent, where one stuck peer must not freeze fan-out for every other subscriber (BroadcastEvent uses SendMessage, which drops on full).
Returns errClientGone when the connection is already torn down or becomes torn down while we're waiting for buffer space. Callers should treat any error as "abort, this client is done" — there is no useful recovery beyond returning from the fulfillment loop.
This split exists because the prior single-path SendMessage produced spurious "Slow consumer detected" disconnects under REQ load: the REQ loop iterated through up to implicit_req_limit events at memory speed (~10µs per Marshal+enqueue) while writeLoop drained at network speed (~100-500µs per WS write). Producer ran ~10-50× faster than consumer, the 256-slot buffer filled near-instantly, and the 257th SendMessage call hit the default branch and disconnected a healthy client mid-fulfillment. See req.go's historical-event loop for the canonical caller.
func (*Client) SetSubscription ¶ added in v0.5.0
func (*Client) SubscriptionCount ¶ added in v0.5.0
type ConnectionManager ¶
type ConnectionManager struct {
// contains filtered or unexported fields
}
ConnectionManager tracks connections and memory usage
func (*ConnectionManager) GetConnectionCount ¶
func (cm *ConnectionManager) GetConnectionCount() int
GetConnectionCount returns the current number of connections
func (*ConnectionManager) GetMemoryStats ¶
func (cm *ConnectionManager) GetMemoryStats() map[string]interface{}
GetMemoryStats returns memory statistics for monitoring
func (*ConnectionManager) RegisterConnection ¶
func (cm *ConnectionManager) RegisterConnection(client *Client)
RegisterConnection adds a connection to the manager. If the memory threshold is exceeded, the oldest connection is selected for eviction and closed AFTER releasing cm.mu — the close path re-enters this manager via RemoveConnection, which would self-deadlock the goroutine if we were still holding the lock here. That self-deadlock was the production WebSocket lockup symptom: the holding goroutine blocked forever on its own mutex, every subsequent new connection then blocked on cm.mu, and the relay went silent on WebSockets while HTTP kept serving (it never touches cm).
func (*ConnectionManager) RemoveConnection ¶
func (cm *ConnectionManager) RemoveConnection(client *Client)
RemoveConnection removes a connection from tracking
Source Files
¶
Directories
¶
| Path | Synopsis |
|---|---|
|
Package api hosts the relay-management HTTP handlers.
|
Package api hosts the relay-management HTTP handlers. |
|
docs
Package docs serves grain's OpenAPI specification.
|
Package docs serves grain's OpenAPI specification. |
|
db
|
|
|
relayurl
Package relayurl applies NIP-42-flavored URL normalization for the purpose of comparing a client-supplied AUTH `relay` tag against the relay's configured Auth.RelayURL.
|
Package relayurl applies NIP-42-flavored URL normalization for the purpose of comparing a client-supplied AUTH `relay` tag against the relay's configured Auth.RelayURL. |