overengineered async fix for /lnurlwallet internal hanging.

This commit is contained in:
fiatjaf 2020-09-29 18:24:08 -03:00
parent e0b8470d40
commit 211ac0391b
7 changed files with 92 additions and 76 deletions

View file

@ -20,6 +20,7 @@ quart-cors = "*"
quart-compress = "*"
secure = "*"
typing-extensions = "*"
httpx = "*"
[dev-packages]
black = "==20.8b1"

View file

@ -8,4 +8,6 @@ core_app: Blueprint = Blueprint(
from .views.api import * # noqa
from .views.generic import * # noqa
from .views.lnurl import * # noqa
from .tasks import grab_app_for_later
core_app.record(grab_app_for_later)

View file

@ -1,5 +1,7 @@
import httpx
from typing import Optional, Tuple, Dict
from quart import g
from lnurl import LnurlWithdrawResponse
try:
from typing import TypedDict # type: ignore
@ -94,7 +96,7 @@ def pay_invoice(
# do the balance check
wallet = get_wallet(wallet_id)
assert wallet, "invalid wallet id"
assert wallet
if wallet.balance_msat < 0:
g.db.rollback()
raise PermissionError("Insufficient balance.")
@ -119,6 +121,24 @@ def pay_invoice(
return invoice.payment_hash
async def redeem_lnurl_withdraw(wallet_id: str, res: LnurlWithdrawResponse, memo: Optional[str] = None) -> None:
if not memo:
memo = res.default_description
_, payment_request = create_invoice(
wallet_id=wallet_id,
amount=res.max_sats,
memo=memo,
extra={"tag": "lnurlwallet"},
)
async with httpx.AsyncClient() as client:
await client.get(
res.callback.base,
params={**res.callback.query_params, **{"k1": res.k1, "pr": payment_request}},
)
def check_invoice_status(wallet_id: str, payment_hash: str) -> PaymentStatus:
payment = get_wallet_payment(wallet_id, payment_hash)
if not payment:

33
lnbits/core/tasks.py Normal file
View file

@ -0,0 +1,33 @@
import asyncio
from typing import Optional, Awaitable
from quart import Quart, Request, g
from werkzeug.datastructures import Headers
from lnbits.db import open_db
main_app: Optional[Quart] = None
def grab_app_for_later(state):
global main_app
main_app = state.app
def run_on_pseudo_request(awaitable: Awaitable):
async def run(awaitable):
fk = Request(
"GET",
"http",
"/background/pseudo",
b"",
Headers([("host", "lnbits.background")]),
"",
"1.1",
send_push_promise=lambda x, h: None,
)
async with main_app.request_context(fk):
g.db = open_db()
await awaitable
loop = asyncio.get_event_loop()
loop.create_task(run(awaitable))

View file

@ -1,6 +1,8 @@
from quart import g, abort, redirect, request, render_template, send_from_directory, url_for
from http import HTTPStatus
import httpx
from os import path
from http import HTTPStatus
from quart import g, abort, redirect, request, render_template, send_from_directory, url_for
from lnurl import LnurlResponse, LnurlWithdrawResponse, decode as decode_lnurl # type: ignore
from lnbits.core import core_app
from lnbits.decorators import check_user_exists, validate_uuids
@ -13,6 +15,8 @@ from ..crud import (
create_wallet,
delete_wallet,
)
from ..services import redeem_lnurl_withdraw
from ..tasks import run_on_pseudo_request
@core_app.route("/favicon.ico")
@ -73,12 +77,11 @@ async def wallet():
return redirect(url_for("core.wallet", usr=user.id, wal=wallet.id))
if wallet_id not in user.wallet_ids:
wallet = user.get_wallet(wallet_id)
if not wallet:
abort(HTTPStatus.FORBIDDEN, "Not your wallet.")
return await render_template(
"core/wallet.html", user=user, wallet=user.get_wallet(wallet_id), service_fee=service_fee
)
return await render_template("core/wallet.html", user=user, wallet=wallet, service_fee=service_fee)
@core_app.route("/deletewallet")
@ -98,3 +101,28 @@ async def deletewallet():
return redirect(url_for("core.wallet", usr=g.user.id, wal=user_wallet_ids[0]))
return redirect(url_for("core.home"))
@core_app.route("/lnurlwallet")
async def lnurlwallet():
async with httpx.AsyncClient() as client:
try:
lnurl = decode_lnurl(request.args.get("lightning"))
r = await client.get(str(lnurl))
withdraw_res = LnurlResponse.from_dict(r.json())
if not withdraw_res.ok:
return f"Could not process lnurl-withdraw: {withdraw_res.error_msg}", HTTPStatus.BAD_REQUEST
if not isinstance(withdraw_res, LnurlWithdrawResponse):
return f"Expected an lnurl-withdraw code, got {withdraw_res.tag}", HTTPStatus.BAD_REQUEST
except Exception as exc:
return f"Could not process lnurl-withdraw: {exc}", HTTPStatus.INTERNAL_SERVER_ERROR
account = create_account()
user = get_user(account.id)
wallet = create_wallet(user_id=user.id)
run_on_pseudo_request(redeem_lnurl_withdraw(wallet.id, withdraw_res, "LNbits initial funding: voucher redeem."))
return redirect(url_for("core.wallet", usr=user.id, wal=wallet.id))

View file

@ -1,66 +0,0 @@
import requests
from quart import abort, redirect, request, url_for
from http import HTTPStatus
from time import sleep
from lnurl import LnurlWithdrawResponse, handle as handle_lnurl # type: ignore
from lnurl.exceptions import LnurlException # type: ignore
from lnbits import bolt11
from lnbits.core import core_app
from lnbits.settings import WALLET
from ..crud import create_account, get_user, create_wallet, create_payment
@core_app.route("/lnurlwallet")
async def lnurlwallet():
memo = "LNbits LNURL funding"
try:
withdraw_res = handle_lnurl(request.args.get("lightning"))
if not withdraw_res.ok:
abort(HTTPStatus.BAD_REQUEST, f"Could not process LNURL-withdraw: {withdraw_res.error_msg}")
if not isinstance(withdraw_res, LnurlWithdrawResponse):
abort(HTTPStatus.BAD_REQUEST, "Not a valid LNURL-withdraw.")
except LnurlException:
abort(HTTPStatus.INTERNAL_SERVER_ERROR, "Could not process LNURL-withdraw.")
try:
ok, checking_id, payment_request, error_message = WALLET.create_invoice(withdraw_res.max_sats, memo)
except Exception as e:
ok, error_message = False, str(e)
if not ok:
abort(HTTPStatus.INTERNAL_SERVER_ERROR, error_message)
r = requests.get(
withdraw_res.callback.base,
params={**withdraw_res.callback.query_params, **{"k1": withdraw_res.k1, "pr": payment_request}},
)
if not r.ok:
abort(HTTPStatus.INTERNAL_SERVER_ERROR, "Could not process LNURL-withdraw.")
inv = bolt11.decode(payment_request)
for i in range(10):
invoice_status = WALLET.get_invoice_status(checking_id)
sleep(i)
if not invoice_status.paid:
continue
break
user = get_user(create_account().id)
wallet = create_wallet(user_id=user.id)
create_payment(
wallet_id=wallet.id,
checking_id=checking_id,
amount=withdraw_res.max_sats * 1000,
memo=memo,
pending=invoice_status.pending,
payment_request=payment_request,
payment_hash=inv.payment_hash,
)
return redirect(url_for("core.wallet", usr=user.id, wal=wallet.id))

View file

@ -152,8 +152,6 @@ async def api_lnurl_multi_response(unique_hash, id_unique_hash):
found = True
else:
usescsv += "," + x
print(x)
print("usescsv: " + usescsv)
if not found:
return jsonify({"status": "ERROR", "reason": "LNURL-withdraw not found."}), HTTPStatus.OK