From e9d3c48aec8797391a14107ae79a4f249ca5b5b5 Mon Sep 17 00:00:00 2001 From: Riccardo Balbo Date: Wed, 26 Aug 2026 16:15:51 +0200 Subject: [PATCH] feat: back off pending payment polling --- tasks.py | 11 +++++++++-- tests/unit/test_tasks.py | 13 +++++++------ 2 files changed, 16 insertions(+), 8 deletions(-) diff --git a/tasks.py b/tasks.py index 5ea3ffc..5540a31 100644 --- a/tasks.py +++ b/tasks.py @@ -34,7 +34,9 @@ from .paranoia import ( ) from .permission import nwc_permissions -PAYMENT_STATUS_POLL_INTERVAL_SECONDS = 1.0 +PAYMENT_STATUS_POLL_INITIAL_INTERVAL_SECONDS = 1.0 +PAYMENT_STATUS_POLL_MAX_INTERVAL_SECONDS = 60.0 +PAYMENT_STATUS_POLL_BACKOFF_MULTIPLIER = 2.0 async def _check(nwc: NWCKey | None, method: str) -> dict | None: @@ -112,6 +114,7 @@ async def _process_invoice( True # currently required by nip 47 specs, might change in future ) payment_status: PaymentStatus | None = None + poll_interval = PAYMENT_STATUS_POLL_INITIAL_INTERVAL_SECONDS while wait_for_preimage: payment_status = await check_transaction_status(wallet_id, payment_hash) if payment_status.success: @@ -124,7 +127,11 @@ async def _process_invoice( }, "in_budget": in_budget, } - await asyncio.sleep(PAYMENT_STATUS_POLL_INTERVAL_SECONDS) + await asyncio.sleep(poll_interval) + poll_interval = min( + poll_interval * PAYMENT_STATUS_POLL_BACKOFF_MULTIPLIER, + PAYMENT_STATUS_POLL_MAX_INTERVAL_SECONDS, + ) if not payment_status: raise Exception("Payment status not found") return { diff --git a/tests/unit/test_tasks.py b/tests/unit/test_tasks.py index 9758954..370daf4 100644 --- a/tests/unit/test_tasks.py +++ b/tests/unit/test_tasks.py @@ -32,29 +32,30 @@ async def test_process_invoice_returns_payment_failed_on_failed_status(monkeypat @pytest.mark.asyncio -async def test_process_invoice_polls_pending_payment_at_sustainable_interval( +async def test_process_invoice_backs_off_pending_payment_polling_to_configured_max( monkeypatch, ): async def fake_tracked_spend_nwc(*args, **kwargs): return True, "a" * 64 + pending = SimpleNamespace(success=False, failed=False) statuses = iter( - [ - SimpleNamespace(success=False, failed=False), + [pending] * 8 + + [ SimpleNamespace( success=True, failed=False, preimage="b" * 64, fee_msat=10, paid=True, - ), + ) ] ) async def fake_check_transaction_status(wallet_id: str, payment_hash: str): return next(statuses) - sleep_calls = [] + sleep_calls: list[float] = [] async def fake_sleep(delay: float): sleep_calls.append(delay) @@ -73,7 +74,7 @@ async def test_process_invoice_polls_pending_payment_at_sustainable_interval( description="test", ) - assert sleep_calls == [tasks.PAYMENT_STATUS_POLL_INTERVAL_SECONDS] + assert sleep_calls == [1.0, 2.0, 4.0, 8.0, 16.0, 32.0, 60.0, 60.0] assert result["preimage"] == "b" * 64 assert result["fee_msats"] == 10 assert result["paid"] is True