From 0331861cc6eb515e3d9811afcb558911ecb97dd8 Mon Sep 17 00:00:00 2001 From: fiatjaf Date: Fri, 12 Nov 2021 13:05:10 -0300 Subject: [PATCH] prevent pay_invoice from locking sqlite for the entire app. --- lnbits/core/services.py | 144 ++++++++++++++++++++-------------------- 1 file changed, 72 insertions(+), 72 deletions(-) diff --git a/lnbits/core/services.py b/lnbits/core/services.py index 08ee6e375..57722dfd5 100644 --- a/lnbits/core/services.py +++ b/lnbits/core/services.py @@ -85,81 +85,81 @@ async def pay_invoice( description: str = "", conn: Optional[Connection] = None, ) -> str: - async with (db.reuse_conn(conn) if conn else db.connect()) as conn: - temp_id = f"temp_{urlsafe_short_hash()}" - internal_id = f"internal_{urlsafe_short_hash()}" + temp_id = f"temp_{urlsafe_short_hash()}" + internal_id = f"internal_{urlsafe_short_hash()}" - invoice = bolt11.decode(payment_request) - if invoice.amount_msat == 0: - raise ValueError("Amountless invoices not supported.") - if max_sat and invoice.amount_msat > max_sat * 1000: - raise ValueError("Amount in invoice is too high.") + invoice = bolt11.decode(payment_request) + if invoice.amount_msat == 0: + raise ValueError("Amountless invoices not supported.") + if max_sat and invoice.amount_msat > max_sat * 1000: + raise ValueError("Amount in invoice is too high.") - # put all parameters that don't change here - PaymentKwargs = TypedDict( - "PaymentKwargs", - { - "wallet_id": str, - "payment_request": str, - "payment_hash": str, - "amount": int, - "memo": str, - "extra": Optional[Dict], - }, + # put all parameters that don't change here + PaymentKwargs = TypedDict( + "PaymentKwargs", + { + "wallet_id": str, + "payment_request": str, + "payment_hash": str, + "amount": int, + "memo": str, + "extra": Optional[Dict], + }, + ) + payment_kwargs: PaymentKwargs = dict( + wallet_id=wallet_id, + payment_request=payment_request, + payment_hash=invoice.payment_hash, + amount=-invoice.amount_msat, + memo=description or invoice.description or "", + extra=extra, + ) + + # check_internal() returns the checking_id of the invoice we're waiting for + internal_checking_id = await check_internal(invoice.payment_hash, conn=conn) + if internal_checking_id: + # create a new payment from this wallet + await create_payment( + checking_id=internal_id, + fee=0, + pending=False, + conn=conn, + **payment_kwargs, ) - payment_kwargs: PaymentKwargs = dict( - wallet_id=wallet_id, - payment_request=payment_request, - payment_hash=invoice.payment_hash, - amount=-invoice.amount_msat, - memo=description or invoice.description or "", - extra=extra, + else: + # create a temporary payment here so we can check if + # the balance is enough in the next step + await create_payment( + checking_id=temp_id, + fee=-fee_reserve(invoice.amount_msat), + conn=conn, + **payment_kwargs, ) - # check_internal() returns the checking_id of the invoice we're waiting for - internal_checking_id = await check_internal(invoice.payment_hash, conn=conn) - if internal_checking_id: - # create a new payment from this wallet - await create_payment( - checking_id=internal_id, - fee=0, - pending=False, - conn=conn, - **payment_kwargs, - ) - else: - # create a temporary payment here so we can check if - # the balance is enough in the next step - await create_payment( - checking_id=temp_id, - fee=-fee_reserve(invoice.amount_msat), - conn=conn, - **payment_kwargs, - ) + # do the balance check + wallet = await get_wallet(wallet_id, conn=conn) + assert wallet + if wallet.balance_msat < 0: + raise PermissionError("Insufficient balance.") - # do the balance check - wallet = await get_wallet(wallet_id, conn=conn) - assert wallet - if wallet.balance_msat < 0: - raise PermissionError("Insufficient balance.") + if internal_checking_id: + # mark the invoice from the other side as not pending anymore + # so the other side only has access to his new money when we are sure + # the payer has enough to deduct from + await update_payment_status( + checking_id=internal_checking_id, pending=False, conn=conn + ) - if internal_checking_id: - # mark the invoice from the other side as not pending anymore - # so the other side only has access to his new money when we are sure - # the payer has enough to deduct from - await update_payment_status( - checking_id=internal_checking_id, pending=False, conn=conn - ) + # notify receiver asynchronously - # notify receiver asynchronously + from lnbits.tasks import internal_invoice_queue - from lnbits.tasks import internal_invoice_queue - - await internal_invoice_queue.put(internal_checking_id) - else: - # actually pay the external invoice - payment: PaymentResponse = await WALLET.pay_invoice(payment_request) - if payment.checking_id: + await internal_invoice_queue.put(internal_checking_id) + else: + # actually pay the external invoice + payment: PaymentResponse = await WALLET.pay_invoice(payment_request) + if payment.checking_id: + async with (db.reuse_conn(conn) if conn else db.connect()) as conn: await create_payment( checking_id=payment.checking_id, fee=payment.fee_msat, @@ -169,13 +169,13 @@ async def pay_invoice( **payment_kwargs, ) await delete_payment(temp_id, conn=conn) - else: - raise PaymentFailure( - payment.error_message - or "Payment failed, but backend didn't give us an error message." - ) + else: + raise PaymentFailure( + payment.error_message + or "Payment failed, but backend didn't give us an error message." + ) - return invoice.payment_hash + return invoice.payment_hash async def redeem_lnurl_withdraw(