mirror of
https://github.com/aljazceru/nutshell.git
synced 2025-12-20 18:44:20 +01:00
fix: subscription re-init bug (#781)
* fix subscription re-initialization bug * test for regression issue * format * Update cashu/mint/events/client.py Co-authored-by: callebtc <93376500+callebtc@users.noreply.github.com> --------- Co-authored-by: callebtc <93376500+callebtc@users.noreply.github.com>
This commit is contained in:
@@ -184,13 +184,11 @@ class LedgerEventClientManager:
|
|||||||
if len(self.subscriptions[kind]) >= self.max_subscriptions:
|
if len(self.subscriptions[kind]) >= self.max_subscriptions:
|
||||||
raise ValueError("Max subscriptions reached")
|
raise ValueError("Max subscriptions reached")
|
||||||
|
|
||||||
for filter in filters:
|
for f in filters:
|
||||||
if filter not in self.subscriptions:
|
logger.debug(f"Adding subscription {subId} for filter {f}")
|
||||||
self.subscriptions[kind][filter] = []
|
self.subscriptions[kind].setdefault(f, []).append(subId)
|
||||||
logger.debug(f"Adding subscription {subId} for filter {filter}")
|
|
||||||
self.subscriptions[kind][filter].append(subId)
|
|
||||||
# Initialize the subscription
|
# Initialize the subscription
|
||||||
asyncio.create_task(self._init_subscription(subId, filter, kind))
|
asyncio.create_task(self._init_subscription(subId, f, kind))
|
||||||
|
|
||||||
def remove_subscription(self, subId: str) -> None:
|
def remove_subscription(self, subId: str) -> None:
|
||||||
for kind, sub_filters in self.subscriptions.items():
|
for kind, sub_filters in self.subscriptions.items():
|
||||||
|
|||||||
@@ -1,16 +1,16 @@
|
|||||||
import asyncio
|
import asyncio
|
||||||
|
import threading
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
import pytest_asyncio
|
import pytest_asyncio
|
||||||
|
|
||||||
from cashu.core.base import Method, MintQuoteState, ProofState
|
from cashu.core.base import Method, MintQuoteState, ProofState
|
||||||
from cashu.core.json_rpc.base import JSONRPCNotficationParams
|
from cashu.core.json_rpc.base import JSONRPCNotficationParams, JSONRPCSubscriptionKinds
|
||||||
from cashu.core.nuts.nuts import WEBSOCKETS_NUT
|
from cashu.core.nuts.nuts import WEBSOCKETS_NUT
|
||||||
from cashu.core.settings import settings
|
from cashu.core.settings import settings
|
||||||
from cashu.wallet.wallet import Wallet
|
from cashu.wallet.wallet import Wallet
|
||||||
from tests.conftest import SERVER_ENDPOINT
|
from tests.conftest import SERVER_ENDPOINT
|
||||||
from tests.helpers import (
|
from tests.helpers import (
|
||||||
is_fake,
|
|
||||||
pay_if_regtest,
|
pay_if_regtest,
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -27,7 +27,6 @@ async def wallet(mint):
|
|||||||
|
|
||||||
|
|
||||||
@pytest.mark.asyncio
|
@pytest.mark.asyncio
|
||||||
@pytest.mark.skipif(is_fake, reason="only regtest")
|
|
||||||
async def test_wallet_subscription_mint(wallet: Wallet):
|
async def test_wallet_subscription_mint(wallet: Wallet):
|
||||||
if not wallet.mint_info.supports_nut(WEBSOCKETS_NUT):
|
if not wallet.mint_info.supports_nut(WEBSOCKETS_NUT):
|
||||||
pytest.skip("No websocket support")
|
pytest.skip("No websocket support")
|
||||||
@@ -110,3 +109,68 @@ async def test_wallet_subscription_swap(wallet: Wallet):
|
|||||||
for msg in spent_stack:
|
for msg in spent_stack:
|
||||||
proof_state = ProofState.parse_obj(msg.payload)
|
proof_state = ProofState.parse_obj(msg.payload)
|
||||||
assert proof_state.spent
|
assert proof_state.spent
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.asyncio
|
||||||
|
async def test_wallet_subscription_multiple_listeners_receive_updates(wallet: Wallet):
|
||||||
|
"""Regression test: ensure multiple subscriptions for the same quote receive updates.
|
||||||
|
|
||||||
|
We open two websocket subscriptions for the same mint quote and verify that
|
||||||
|
both listeners receive the initial (unpaid) state and the subsequent paid update.
|
||||||
|
"""
|
||||||
|
if not wallet.mint_info.supports_nut(WEBSOCKETS_NUT):
|
||||||
|
pytest.skip("No websocket support")
|
||||||
|
|
||||||
|
if not wallet.mint_info.supports_websocket_mint_quote(
|
||||||
|
Method["bolt11"], wallet.unit
|
||||||
|
):
|
||||||
|
pytest.skip("No websocket support for bolt11_mint_quote")
|
||||||
|
|
||||||
|
# Request a quote without auto-subscribing
|
||||||
|
mint_quote = await wallet.request_mint(123)
|
||||||
|
|
||||||
|
# Manually create a SubscriptionManager and subscribe twice to the same quote
|
||||||
|
from cashu.wallet.subscriptions import SubscriptionManager
|
||||||
|
|
||||||
|
subs = SubscriptionManager(wallet.url)
|
||||||
|
threading.Thread(target=subs.connect, name="SubscriptionManager", daemon=True).start()
|
||||||
|
|
||||||
|
stack1: list[JSONRPCNotficationParams] = []
|
||||||
|
stack2: list[JSONRPCNotficationParams] = []
|
||||||
|
|
||||||
|
def cb1(msg: JSONRPCNotficationParams):
|
||||||
|
stack1.append(msg)
|
||||||
|
|
||||||
|
def cb2(msg: JSONRPCNotficationParams):
|
||||||
|
stack2.append(msg)
|
||||||
|
|
||||||
|
subs.subscribe(
|
||||||
|
kind=JSONRPCSubscriptionKinds.BOLT11_MINT_QUOTE,
|
||||||
|
filters=[mint_quote.quote],
|
||||||
|
callback=cb1,
|
||||||
|
)
|
||||||
|
subs.subscribe(
|
||||||
|
kind=JSONRPCSubscriptionKinds.BOLT11_MINT_QUOTE,
|
||||||
|
filters=[mint_quote.quote],
|
||||||
|
callback=cb2,
|
||||||
|
)
|
||||||
|
|
||||||
|
# Allow time for the initial snapshot to arrive on both subscriptions
|
||||||
|
await asyncio.sleep(0.5)
|
||||||
|
|
||||||
|
assert len(stack1) >= 1 and len(stack2) >= 1
|
||||||
|
assert stack1[0].payload["state"] == MintQuoteState.unpaid.value
|
||||||
|
assert stack2[0].payload["state"] == MintQuoteState.unpaid.value
|
||||||
|
|
||||||
|
# Pay the invoice and wait for the paid update to be pushed to both listeners
|
||||||
|
await pay_if_regtest(mint_quote.request)
|
||||||
|
|
||||||
|
wait = (settings.fakewallet_delay_incoming_payment or 1) + 1
|
||||||
|
await asyncio.sleep(wait)
|
||||||
|
|
||||||
|
# Verify that both listeners received a paid update
|
||||||
|
assert any(m.payload["state"] == MintQuoteState.paid.value for m in stack1)
|
||||||
|
assert any(m.payload["state"] == MintQuoteState.paid.value for m in stack2)
|
||||||
|
|
||||||
|
# Cleanup the websocket
|
||||||
|
subs.close()
|
||||||
|
|||||||
Reference in New Issue
Block a user