From 1c2c01ccfa4b6b728c3ac6341584f8c679251200 Mon Sep 17 00:00:00 2001 From: callebtc <93376500+callebtc@users.noreply.github.com> Date: Mon, 19 Feb 2024 19:42:06 +0100 Subject: [PATCH] test blink failed payment (#446) --- cashu/lightning/blink.py | 47 ++++++++++++++++++++---------- cashu/mint/ledger.py | 4 +-- tests/test_mint_lightning_blink.py | 44 ++++++++++++++++++++++++++++ 3 files changed, 77 insertions(+), 18 deletions(-) diff --git a/cashu/lightning/blink.py b/cashu/lightning/blink.py index 83348d5f..625419d8 100644 --- a/cashu/lightning/blink.py +++ b/cashu/lightning/blink.py @@ -2,7 +2,7 @@ import asyncio import json import math -from typing import Dict, Optional +from typing import Dict, Optional, Union import bolt11 import httpx @@ -201,23 +201,36 @@ async def pay_invoice( return PaymentResponse(ok=False, error_message=str(e)) resp: dict = r.json() + + error_message: Union[None, str] = None + fee: Union[None, int] = None + if resp.get("data", {}).get("lnInvoicePaymentSend", {}).get("errors"): + error_message = ( + resp["data"]["lnInvoicePaymentSend"]["errors"][0].get("message") + or "Unknown error" + ) + paid = self.payment_execution_statuses[ resp.get("data", {}).get("lnInvoicePaymentSend", {}).get("status") ] - fee = ( - resp.get("data", {}) - .get("lnInvoicePaymentSend", {}) - .get("transaction", {}) - .get("settlementFee") - ) + if paid is None: + error_message = "Invoice already paid." + + if resp.get("data", {}).get("lnInvoicePaymentSend", {}).get("transaction", {}): + fee = ( + resp.get("data", {}) + .get("lnInvoicePaymentSend", {}) + .get("transaction", {}) + .get("settlementFee") + ) checking_id = quote.request return PaymentResponse( ok=paid, checking_id=checking_id, - fee=Amount(Unit.sat, fee), + fee=Amount(Unit.sat, fee) if fee else None, preimage=None, - error_message="Invoice already paid." if paid is None else None, + error_message=error_message, ) async def get_invoice_status(self, checking_id: str) -> PaymentStatus: @@ -373,15 +386,17 @@ async def get_payment_quote(self, bolt11: str) -> PaymentQuoteResponse: r.raise_for_status() resp: dict = r.json() if resp.get("data", {}).get("lnInvoiceFeeProbe", {}).get("errors"): - raise Exception( - resp["data"]["lnInvoiceFeeProbe"]["errors"][0].get("message") - or "Unknown error" + # if there was an error, we simply ignore the response and decide the fees ourselves + fees_response_msat = 0 + logger.debug( + f"Blink probe error: {resp['data']['lnInvoiceFeeProbe']['errors'][0].get('message')}" ) - fees_response_msat = ( - int(resp.get("data", {}).get("lnInvoiceFeeProbe", {}).get("amount")) - * 1000 - ) + else: + fees_response_msat = ( + int(resp.get("data", {}).get("lnInvoiceFeeProbe", {}).get("amount")) + * 1000 + ) except httpx.ReadTimeout: pass except Exception as e: diff --git a/cashu/mint/ledger.py b/cashu/mint/ledger.py index 81cb5c38..ce1ce7b1 100644 --- a/cashu/mint/ledger.py +++ b/cashu/mint/ledger.py @@ -692,8 +692,8 @@ async def melt( melt_quote, melt_quote.fee_reserve * 1000 ) logger.debug( - f"Melt status: {payment.ok}: preimage: {payment.preimage}," - f" fee: {payment.fee.str() if payment.fee else 0}" + f"Melt – Ok: {payment.ok}: preimage: {payment.preimage}," + f" fee: {payment.fee.str() if payment.fee is not None else 'None'}" ) if not payment.ok: raise LightningError( diff --git a/tests/test_mint_lightning_blink.py b/tests/test_mint_lightning_blink.py index 64777d6e..739af2ee 100644 --- a/tests/test_mint_lightning_blink.py +++ b/tests/test_mint_lightning_blink.py @@ -102,6 +102,38 @@ async def test_blink_pay_invoice(): assert payment.checking_id == payment_request +@respx.mock +@pytest.mark.asyncio +async def test_blink_pay_invoice_failure(): + mock_response = { + "data": { + "lnInvoicePaymentSend": { + "status": "FAILURE", + "errors": [ + {"message": "This is the error", "codee": "ROUTE_FINDING_ERROR"}, + ], + } + } + } + respx.post(blink.endpoint).mock(return_value=Response(200, json=mock_response)) + quote = MeltQuote( + request=payment_request, + quote="asd", + method="bolt11", + checking_id=payment_request, + unit="sat", + amount=100, + fee_reserve=12, + paid=False, + ) + payment = await blink.pay_invoice(quote, 1000) + assert not payment.ok + assert payment.fee is None + assert payment.error_message + assert "This is the error" in payment.error_message + assert payment.checking_id == payment_request + + @respx.mock @pytest.mark.asyncio async def test_blink_get_invoice_status(): @@ -180,3 +212,15 @@ async def test_blink_get_payment_quote(): assert quote.checking_id == payment_request_1 assert quote.amount == Amount(Unit.msat, 1000) # msat assert quote.fee == Amount(Unit.msat, MINIMUM_FEE_MSAT) # msat + + +@respx.mock +@pytest.mark.asyncio +async def test_blink_get_payment_quote_backend_error(): + # response says error but invoice (1000 sat) * 0.5% is 5 sat so we expect 10 sat + mock_response = {"data": {"lnInvoiceFeeProbe": {"errors": [{"message": "error"}]}}} + respx.post(blink.endpoint).mock(return_value=Response(200, json=mock_response)) + quote = await blink.get_payment_quote(payment_request) + assert quote.checking_id == payment_request + assert quote.amount == Amount(Unit.msat, 1000000) # msat + assert quote.fee == Amount(Unit.msat, 5000) # msat