feat: back off pending payment polling

This commit is contained in:
Riccardo Balbo 2026-08-26 16:15:51 +02:00
commit e9d3c48aec
2 changed files with 16 additions and 8 deletions

View file

@ -34,7 +34,9 @@ from .paranoia import (
) )
from .permission import nwc_permissions 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: 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 True # currently required by nip 47 specs, might change in future
) )
payment_status: PaymentStatus | None = None payment_status: PaymentStatus | None = None
poll_interval = PAYMENT_STATUS_POLL_INITIAL_INTERVAL_SECONDS
while wait_for_preimage: while wait_for_preimage:
payment_status = await check_transaction_status(wallet_id, payment_hash) payment_status = await check_transaction_status(wallet_id, payment_hash)
if payment_status.success: if payment_status.success:
@ -124,7 +127,11 @@ async def _process_invoice(
}, },
"in_budget": in_budget, "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: if not payment_status:
raise Exception("Payment status not found") raise Exception("Payment status not found")
return { return {

View file

@ -32,29 +32,30 @@ async def test_process_invoice_returns_payment_failed_on_failed_status(monkeypat
@pytest.mark.asyncio @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, monkeypatch,
): ):
async def fake_tracked_spend_nwc(*args, **kwargs): async def fake_tracked_spend_nwc(*args, **kwargs):
return True, "a" * 64 return True, "a" * 64
pending = SimpleNamespace(success=False, failed=False)
statuses = iter( statuses = iter(
[ [pending] * 8
SimpleNamespace(success=False, failed=False), + [
SimpleNamespace( SimpleNamespace(
success=True, success=True,
failed=False, failed=False,
preimage="b" * 64, preimage="b" * 64,
fee_msat=10, fee_msat=10,
paid=True, paid=True,
), )
] ]
) )
async def fake_check_transaction_status(wallet_id: str, payment_hash: str): async def fake_check_transaction_status(wallet_id: str, payment_hash: str):
return next(statuses) return next(statuses)
sleep_calls = [] sleep_calls: list[float] = []
async def fake_sleep(delay: float): async def fake_sleep(delay: float):
sleep_calls.append(delay) sleep_calls.append(delay)
@ -73,7 +74,7 @@ async def test_process_invoice_polls_pending_payment_at_sustainable_interval(
description="test", 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["preimage"] == "b" * 64
assert result["fee_msats"] == 10 assert result["fee_msats"] == 10
assert result["paid"] is True assert result["paid"] is True