prevent pay_invoice from locking sqlite for the entire app.

This commit is contained in:
fiatjaf
2021-11-12 13:05:10 -03:00
parent e9b9197641
commit 0331861cc6

View File

@@ -85,81 +85,81 @@ async def pay_invoice(
description: str = "", description: str = "",
conn: Optional[Connection] = None, conn: Optional[Connection] = None,
) -> str: ) -> str:
async with (db.reuse_conn(conn) if conn else db.connect()) as conn: temp_id = f"temp_{urlsafe_short_hash()}"
temp_id = f"temp_{urlsafe_short_hash()}" internal_id = f"internal_{urlsafe_short_hash()}"
internal_id = f"internal_{urlsafe_short_hash()}"
invoice = bolt11.decode(payment_request) invoice = bolt11.decode(payment_request)
if invoice.amount_msat == 0: if invoice.amount_msat == 0:
raise ValueError("Amountless invoices not supported.") raise ValueError("Amountless invoices not supported.")
if max_sat and invoice.amount_msat > max_sat * 1000: if max_sat and invoice.amount_msat > max_sat * 1000:
raise ValueError("Amount in invoice is too high.") raise ValueError("Amount in invoice is too high.")
# put all parameters that don't change here # put all parameters that don't change here
PaymentKwargs = TypedDict( PaymentKwargs = TypedDict(
"PaymentKwargs", "PaymentKwargs",
{ {
"wallet_id": str, "wallet_id": str,
"payment_request": str, "payment_request": str,
"payment_hash": str, "payment_hash": str,
"amount": int, "amount": int,
"memo": str, "memo": str,
"extra": Optional[Dict], "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( else:
wallet_id=wallet_id, # create a temporary payment here so we can check if
payment_request=payment_request, # the balance is enough in the next step
payment_hash=invoice.payment_hash, await create_payment(
amount=-invoice.amount_msat, checking_id=temp_id,
memo=description or invoice.description or "", fee=-fee_reserve(invoice.amount_msat),
extra=extra, conn=conn,
**payment_kwargs,
) )
# check_internal() returns the checking_id of the invoice we're waiting for # do the balance check
internal_checking_id = await check_internal(invoice.payment_hash, conn=conn) wallet = await get_wallet(wallet_id, conn=conn)
if internal_checking_id: assert wallet
# create a new payment from this wallet if wallet.balance_msat < 0:
await create_payment( raise PermissionError("Insufficient balance.")
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 if internal_checking_id:
wallet = await get_wallet(wallet_id, conn=conn) # mark the invoice from the other side as not pending anymore
assert wallet # so the other side only has access to his new money when we are sure
if wallet.balance_msat < 0: # the payer has enough to deduct from
raise PermissionError("Insufficient balance.") await update_payment_status(
checking_id=internal_checking_id, pending=False, conn=conn
)
if internal_checking_id: # notify receiver asynchronously
# 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 from lnbits.tasks import internal_invoice_queue
from lnbits.tasks import internal_invoice_queue await internal_invoice_queue.put(internal_checking_id)
else:
await internal_invoice_queue.put(internal_checking_id) # actually pay the external invoice
else: payment: PaymentResponse = await WALLET.pay_invoice(payment_request)
# actually pay the external invoice if payment.checking_id:
payment: PaymentResponse = await WALLET.pay_invoice(payment_request) async with (db.reuse_conn(conn) if conn else db.connect()) as conn:
if payment.checking_id:
await create_payment( await create_payment(
checking_id=payment.checking_id, checking_id=payment.checking_id,
fee=payment.fee_msat, fee=payment.fee_msat,
@@ -169,13 +169,13 @@ async def pay_invoice(
**payment_kwargs, **payment_kwargs,
) )
await delete_payment(temp_id, conn=conn) await delete_payment(temp_id, conn=conn)
else: else:
raise PaymentFailure( raise PaymentFailure(
payment.error_message payment.error_message
or "Payment failed, but backend didn't give us an 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( async def redeem_lnurl_withdraw(