diff --git a/config.json b/config.json index 6831512..a837ae0 100644 --- a/config.json +++ b/config.json @@ -1,6 +1,6 @@ { "name": "Nostr Relay", - "version": "1.1.0-aio.3", + "version": "1.1.0", "short_description": "One click launch your own relay!", "tile": "/nostrrelay/static/image/nostrrelay.png", "min_lnbits_version": "1.4.0", diff --git a/docs/upstream-candidates.md b/docs/upstream-candidates.md deleted file mode 100644 index d084044..0000000 --- a/docs/upstream-candidates.md +++ /dev/null @@ -1,8 +0,0 @@ -# Upstream candidates - -Fork changes that `lnbits/nostrrelay` might want, kept in upstream's shape -so a future rebase merges cleanly. Strike a row when the upstream PR merges. - -| Landed | Change | Fork commit | Upstream status | -| --- | --- | --- | --- | -| 2026-09-12 | `notify_event` delivers to every matching subscription on a connection instead of stopping at the first match (breaks any multiplexed client, e.g. nostrclient + nwcprovider) | see `git log -- relay/client_connection.py` | not yet proposed; upstream `a87bc1f` has the same first-match `return True` | diff --git a/relay/client_connection.py b/relay/client_connection.py index fae522e..b9c9708 100644 --- a/relay/client_connection.py +++ b/relay/client_connection.py @@ -72,24 +72,16 @@ class NostrClientConnection: if self._is_private_event_for_other(event): return False - # One connection can hold several subscriptions whose filters all match - # the same event: a multiplexer such as nostrclient funnels every one of - # its clients through a single connection. NIP-01 requires the event to - # reach each matching subscription, so deliver once per subscription id - # rather than stopping at the first hit. - notified: set[str] = set() for nostr_filter in self.filters: - sub_id = nostr_filter.subscription_id - if sub_id in notified: - continue if nostr_filter.matches(event): - notified.add(sub_id) # type: ignore[arg-type] - await self._send_msg(event.serialize_response(sub_id)) + resp = event.serialize_response(nostr_filter.subscription_id) + await self._send_msg(resp) + return True else: - logger.debug( + logger.info( f"[NOSTRRELAY CLIENT] ❌ Filter didn't match for event {event.id}" ) - return len(notified) > 0 + return False def _is_private_event_for_other(self, event: NostrEvent) -> bool: """ diff --git a/tests/test_notify.py b/tests/test_notify.py deleted file mode 100644 index a3de80b..0000000 --- a/tests/test_notify.py +++ /dev/null @@ -1,82 +0,0 @@ -from unittest.mock import AsyncMock, MagicMock - -import pytest - -from ..relay.client_connection import NostrClientConnection -from ..relay.event import NostrEvent -from ..relay.filter import NostrFilter -from ..relay.relay import RelaySpec - -RELAY_ID = "relay_notify" -WALLET = "1111111111111111111111111111111111111111111111111111111111111111" -CLIENT = "2222222222222222222222222222222222222222222222222222222222222222" -REQUEST_ID = "3" * 64 -SIG = "0" * 128 - - -def _connection() -> NostrClientConnection: - conn = NostrClientConnection(relay_id=RELAY_ID, websocket=MagicMock()) - conn.get_client_config = lambda: RelaySpec() - conn._send_msg = AsyncMock() # type: ignore[method-assign] - return conn - - -def _nwc_response() -> NostrEvent: - return NostrEvent( - id="4" * 64, - relay_id=RELAY_ID, - publisher=WALLET, - pubkey=WALLET, - created_at=0, - kind=23195, - tags=[["p", CLIENT], ["e", REQUEST_ID]], - content="ciphertext", - sig=SIG, - ) - - -def _sent_subscription_ids(conn: NostrClientConnection) -> list[str]: - return [call.args[0][1] for call in conn._send_msg.await_args_list] # type: ignore[attr-defined] - - -@pytest.mark.asyncio -async def test_event_reaches_every_matching_subscription(): - """ - Regression: delivery used to stop at the first matching filter, so when a - wallet service (subscribed to its own kind-23195 responses) and a wallet - app shared one connection via nostrclient, the app never got the reply. - """ - conn = _connection() - conn.filters = [ - NostrFilter(subscription_id="wallet-own", kinds=[23195], authors=[WALLET]), - NostrFilter(subscription_id="unrelated", kinds=[1]), - NostrFilter( - subscription_id="app-reply", - kinds=[23195], - **{"#e": [REQUEST_ID], "#p": [CLIENT]}, # type: ignore[arg-type] - ), - ] - - assert await conn.notify_event(_nwc_response()) is True - assert _sent_subscription_ids(conn) == ["wallet-own", "app-reply"] - - -@pytest.mark.asyncio -async def test_subscription_with_several_matching_filters_gets_event_once(): - conn = _connection() - conn.filters = [ - NostrFilter(subscription_id="multi", kinds=[23195]), - NostrFilter(subscription_id="multi", authors=[WALLET]), - ] - - assert await conn.notify_event(_nwc_response()) is True - assert _sent_subscription_ids(conn) == ["multi"] - - -@pytest.mark.asyncio -async def test_no_matching_filter_sends_nothing(): - conn = _connection() - conn.filters = [NostrFilter(subscription_id="other", kinds=[1])] - - assert await conn.notify_event(_nwc_response()) is False - conn._send_msg.assert_not_awaited() # type: ignore[attr-defined]