Compare commits

..

2 commits
v0.0.1 ... main

Author SHA1 Message Date
b74af2628e fix(nostrrelay): populate size field for event storage accounting
Some checks failed
CI / lint (push) Has been cancelled
CI / tests (push) Has been cancelled
Changes:
- relay/event.py: Add `size: int = 0` field to NostrEvent model
- relay/client_connection.py: Set `event.size = event.size_bytes` when creating events from WebSocket messages

The size field has existed in the database schema since migration m001 but was never populated, causing:
  - Incorrect storage accounting (always 0)
  - Broken storage quota enforcement
  - Failed event pruning when storage limits reached

The size field is internal relay metadata and is excluded from the nostr_dict() output, maintaining NIP-01 compliance. The size_bytes property calculates the actual byte size of the event's JSON representation.

Fixes: Database constraint violation when inserting events without the required size column value.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-06 23:53:37 +01:00
8d6f482de0 Fix critical filter logic bugs preventing event propagation in Nostr relay
Some checks failed
CI / lint (push) Waiting to run
CI / tests (push) Blocked by required conditions
/ release (push) Has been cancelled
/ pullrequest (push) Has been cancelled
- Fix inverted logic in _can_add_filter() method that was preventing new
subscription filters from being added
  - Fix REQ message handling to properly clear existing filters before
adding new ones
  - Fix inverted condition check when validating filter addition
capacity
  - Add debug logging to track filter matching and broadcast failures

  These bugs were causing customer order events (NIP-15) to be received
by the relay but not
  forwarded to nostrclient/nostrmarket, requiring server restarts or
manual refresh to process orders.
  The fix ensures proper event propagation: Customer → Relay →
nostrclient → nostrmarket → Invoice.

  Root cause: The _can_add_filter() method returned true when filters >=
max instead of when
  filters < max, and the validation check used the wrong conditional,
effectively blocking all
  new filter subscriptions after initial connection.
2026-01-06 23:05:22 +01:00
2 changed files with 16 additions and 4 deletions

View file

@ -77,6 +77,10 @@ class NostrClientConnection:
resp = event.serialize_response(nostr_filter.subscription_id)
await self._send_msg(resp)
return True
else:
logger.info(
f"[NOSTRRELAY CLIENT] ❌ Filter didn't match for event {event.id}"
)
return False
def _is_direct_message_for_other(self, event: NostrEvent) -> bool:
@ -98,6 +102,10 @@ class NostrClientConnection:
async def _broadcast_event(self, e: NostrEvent):
if self.broadcast_event:
await self.broadcast_event(self, e)
else:
logger.warning(
f"[NOSTRRELAY CLIENT] ❌ No broadcast_event callback available for event {e.id}"
)
async def _handle_message(self, data: list) -> list:
if len(data) < 2:
@ -113,6 +121,8 @@ class NostrClientConnection:
}
event = NostrEvent(**event_dict)
# Set the size field from the size_bytes property
event.size = event.size_bytes
await self._handle_event(event)
return []
if message_type == NostrEventType.REQ:
@ -120,6 +130,8 @@ class NostrClientConnection:
return []
subscription_id = data[1]
# Handle multiple filters in REQ message
# First remove existing filters for this subscription_id
self._remove_filter(subscription_id)
responses = []
for filter_data in data[2:]:
response = await self._handle_request(
@ -293,8 +305,7 @@ class NostrClientConnection:
return [["NOTICE", f"This is a paid relay: '{self.relay_id}'"]]
nostr_filter.subscription_id = subscription_id
self._remove_filter(subscription_id)
if self._can_add_filter():
if not self._can_add_filter():
max_filters = self.config.max_client_filters
return [
[
@ -325,8 +336,8 @@ class NostrClientConnection:
def _can_add_filter(self) -> bool:
return (
self.config.max_client_filters != 0
and len(self.filters) >= self.config.max_client_filters
self.config.max_client_filters == 0
or len(self.filters) < self.config.max_client_filters
)
def _auth_challenge_expired(self):

View file

@ -23,6 +23,7 @@ class NostrEvent(BaseModel):
tags: list[list[str]] = Field(default=[], no_database=True)
content: str = ""
sig: str
size: int = 0
def nostr_dict(self) -> dict:
_nostr_dict = dict(self)