From 11fec7a88923ff75820d7b804546207469dbcc62 Mon Sep 17 00:00:00 2001 From: callebtc <93376500+callebtc@users.noreply.github.com> Date: Mon, 24 Jul 2023 12:00:41 +0200 Subject: [PATCH] [Wallets] CLN: fix pending state check (#1770) * better checking * flake8 fix * make format * invoices scope are function for tests * invoice back to sessionbut keep real_invoice for now * make format * comment * get payment by checking id and test --- lnbits/wallets/cln.py | 15 +++-- tests/conftest.py | 2 +- tests/core/views/test_api.py | 124 +++++++++++++++++++++++++++++++++++ 3 files changed, 135 insertions(+), 6 deletions(-) diff --git a/lnbits/wallets/cln.py b/lnbits/wallets/cln.py index 7052df483..0bf46d870 100644 --- a/lnbits/wallets/cln.py +++ b/lnbits/wallets/cln.py @@ -161,15 +161,18 @@ class CoreLightningWallet(Wallet): return PaymentStatus(True) elif invoice_resp["status"] == "unpaid": return PaymentStatus(None) - logger.warning(f"supplied an invalid checking_id: {checking_id}") + elif invoice_resp["status"] == "expired": + return PaymentStatus(False) + else: + logger.warning(f"supplied an invalid checking_id: {checking_id}") return PaymentStatus(None) async def get_payment_status(self, checking_id: str) -> PaymentStatus: try: - r = self.ln.call("listpays", {"payment_hash": checking_id}) + r = self.ln.listpays(payment_hash=checking_id) except: return PaymentStatus(None) - if not r["pays"]: + if "pays" not in r or not r["pays"]: return PaymentStatus(None) payment_resp = r["pays"][-1] @@ -183,8 +186,10 @@ class CoreLightningWallet(Wallet): return PaymentStatus(True, fee_msat, payment_resp["preimage"]) elif status == "failed": return PaymentStatus(False) - return PaymentStatus(None) - logger.warning(f"supplied an invalid checking_id: {checking_id}") + else: + return PaymentStatus(None) + else: + logger.warning(f"supplied an invalid checking_id: {checking_id}") return PaymentStatus(None) async def paid_invoices_stream(self) -> AsyncGenerator[str, None]: diff --git a/tests/conftest.py b/tests/conftest.py index 8ea59577e..48788b671 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -125,7 +125,7 @@ async def invoice(to_wallet): del invoice -@pytest_asyncio.fixture(scope="session") +@pytest_asyncio.fixture(scope="function") async def real_invoice(): invoice = get_real_invoice(100) yield {"bolt11": invoice["payment_request"]} diff --git a/tests/core/views/test_api.py b/tests/core/views/test_api.py index ce5fda2bd..a645a4dba 100644 --- a/tests/core/views/test_api.py +++ b/tests/core/views/test_api.py @@ -5,6 +5,7 @@ from time import time import pytest from lnbits import bolt11 +from lnbits.core.crud import get_standalone_payment, update_payment_details from lnbits.core.models import Payment from lnbits.core.views.admin_api import api_auditor from lnbits.core.views.api import api_payment @@ -387,3 +388,126 @@ async def test_create_real_invoice(client, adminkey_headers_from, inkey_headers_ await asyncio.sleep(0.3) balance = await get_node_balance_sats() assert balance - prev_balance == create_invoice.amount + + +@pytest.mark.asyncio +@pytest.mark.skipif(is_fake, reason="this only works in regtest") +async def test_pay_real_invoice_set_pending_and_check_state( + client, real_invoice, adminkey_headers_from, inkey_headers_from +): + """ + 1. We create an invoice + 2. We pay it + 3. We verify that the inoice was paid + 4. We set the invoice to pending in the database + 5. We recheck the state of the invoice + 6. We verify that the invoice is paid + """ + response = await client.post( + "/api/v1/payments", json=real_invoice, headers=adminkey_headers_from + ) + assert response.status_code < 300 + invoice = response.json() + assert len(invoice["payment_hash"]) == 64 + assert len(invoice["checking_id"]) > 0 + + # check the payment status + response = await api_payment( + invoice["payment_hash"], inkey_headers_from["X-Api-Key"] + ) + assert response["paid"] + + status = await WALLET.get_payment_status(invoice["payment_hash"]) + assert status.paid + + # get the outgoing payment from the db + payment = await get_standalone_payment(invoice["payment_hash"]) + assert payment + assert payment.pending is False + + # set the outgoing invoice to pending + await update_payment_details(payment.checking_id, pending=True) + + payment_pending = await get_standalone_payment(invoice["payment_hash"]) + assert payment_pending + assert payment_pending.pending is True + + # check the outgoing payment status + await payment.check_status() + + payment_not_pending = await get_standalone_payment(invoice["payment_hash"]) + assert payment_not_pending + assert payment_not_pending.pending is False + + +@pytest.mark.asyncio +@pytest.mark.skipif(is_fake, reason="this only works in regtest") +async def test_receive_real_invoice_set_pending_and_check_state( + client, adminkey_headers_from, inkey_headers_from +): + """ + 1. We create a real invoice + 2. We pay it from our wallet + 3. We check that the inoice was paid with the backend + 4. We set the invoice to pending in the database + 5. We recheck the state of the invoice with the backend + 6. We verify that the invoice is now marked as paid in the database + """ + create_invoice = CreateInvoiceData(out=False, amount=1000, memo="test") + response = await client.post( + "/api/v1/payments", + json=create_invoice.dict(), + headers=adminkey_headers_from, + ) + assert response.status_code < 300 + invoice = response.json() + response = await api_payment( + invoice["payment_hash"], inkey_headers_from["X-Api-Key"] + ) + assert not response["paid"] + + async def listen(): + async for payment_hash in get_wallet_class().paid_invoices_stream(): + assert payment_hash == invoice["payment_hash"] + return + + task = asyncio.create_task(listen()) + pay_real_invoice(invoice["payment_request"]) + await asyncio.wait_for(task, timeout=3) + response = await api_payment( + invoice["payment_hash"], inkey_headers_from["X-Api-Key"] + ) + assert response["paid"] + + # get the incoming payment from the db + payment = await get_standalone_payment(invoice["payment_hash"], incoming=True) + assert payment + assert payment.pending is False + + # set the incoming invoice to pending + await update_payment_details(payment.checking_id, pending=True) + + payment_pending = await get_standalone_payment( + invoice["payment_hash"], incoming=True + ) + assert payment_pending + assert payment_pending.pending is True + + # check the incoming payment status + await payment.check_status() + + payment_not_pending = await get_standalone_payment( + invoice["payment_hash"], incoming=True + ) + assert payment_not_pending + assert payment_not_pending.pending is False + + # verify we get the same result if we use the checking_id to look up the payment + payment_by_checking_id = await get_standalone_payment( + payment_not_pending.checking_id, incoming=True + ) + + assert payment_by_checking_id + assert payment_by_checking_id.pending is False + assert payment_by_checking_id.bolt11 == payment_not_pending.bolt11 + assert payment_by_checking_id.payment_hash == payment_not_pending.payment_hash