mirror of
https://github.com/lnbits/lnbits-legend.git
synced 2025-02-22 14:22:55 +01:00
overengineered async fix for /lnurlwallet internal hanging.
This commit is contained in:
parent
e0b8470d40
commit
211ac0391b
7 changed files with 92 additions and 76 deletions
1
Pipfile
1
Pipfile
|
@ -20,6 +20,7 @@ quart-cors = "*"
|
||||||
quart-compress = "*"
|
quart-compress = "*"
|
||||||
secure = "*"
|
secure = "*"
|
||||||
typing-extensions = "*"
|
typing-extensions = "*"
|
||||||
|
httpx = "*"
|
||||||
|
|
||||||
[dev-packages]
|
[dev-packages]
|
||||||
black = "==20.8b1"
|
black = "==20.8b1"
|
||||||
|
|
|
@ -8,4 +8,6 @@ core_app: Blueprint = Blueprint(
|
||||||
|
|
||||||
from .views.api import * # noqa
|
from .views.api import * # noqa
|
||||||
from .views.generic 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)
|
||||||
|
|
|
@ -1,5 +1,7 @@
|
||||||
|
import httpx
|
||||||
from typing import Optional, Tuple, Dict
|
from typing import Optional, Tuple, Dict
|
||||||
from quart import g
|
from quart import g
|
||||||
|
from lnurl import LnurlWithdrawResponse
|
||||||
|
|
||||||
try:
|
try:
|
||||||
from typing import TypedDict # type: ignore
|
from typing import TypedDict # type: ignore
|
||||||
|
@ -94,7 +96,7 @@ def pay_invoice(
|
||||||
|
|
||||||
# do the balance check
|
# do the balance check
|
||||||
wallet = get_wallet(wallet_id)
|
wallet = get_wallet(wallet_id)
|
||||||
assert wallet, "invalid wallet id"
|
assert wallet
|
||||||
if wallet.balance_msat < 0:
|
if wallet.balance_msat < 0:
|
||||||
g.db.rollback()
|
g.db.rollback()
|
||||||
raise PermissionError("Insufficient balance.")
|
raise PermissionError("Insufficient balance.")
|
||||||
|
@ -119,6 +121,24 @@ def pay_invoice(
|
||||||
return invoice.payment_hash
|
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:
|
def check_invoice_status(wallet_id: str, payment_hash: str) -> PaymentStatus:
|
||||||
payment = get_wallet_payment(wallet_id, payment_hash)
|
payment = get_wallet_payment(wallet_id, payment_hash)
|
||||||
if not payment:
|
if not payment:
|
||||||
|
|
33
lnbits/core/tasks.py
Normal file
33
lnbits/core/tasks.py
Normal 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))
|
|
@ -1,6 +1,8 @@
|
||||||
from quart import g, abort, redirect, request, render_template, send_from_directory, url_for
|
import httpx
|
||||||
from http import HTTPStatus
|
|
||||||
from os import path
|
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.core import core_app
|
||||||
from lnbits.decorators import check_user_exists, validate_uuids
|
from lnbits.decorators import check_user_exists, validate_uuids
|
||||||
|
@ -13,6 +15,8 @@ from ..crud import (
|
||||||
create_wallet,
|
create_wallet,
|
||||||
delete_wallet,
|
delete_wallet,
|
||||||
)
|
)
|
||||||
|
from ..services import redeem_lnurl_withdraw
|
||||||
|
from ..tasks import run_on_pseudo_request
|
||||||
|
|
||||||
|
|
||||||
@core_app.route("/favicon.ico")
|
@core_app.route("/favicon.ico")
|
||||||
|
@ -73,12 +77,11 @@ async def wallet():
|
||||||
|
|
||||||
return redirect(url_for("core.wallet", usr=user.id, wal=wallet.id))
|
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.")
|
abort(HTTPStatus.FORBIDDEN, "Not your wallet.")
|
||||||
|
|
||||||
return await render_template(
|
return await render_template("core/wallet.html", user=user, wallet=wallet, service_fee=service_fee)
|
||||||
"core/wallet.html", user=user, wallet=user.get_wallet(wallet_id), service_fee=service_fee
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
@core_app.route("/deletewallet")
|
@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.wallet", usr=g.user.id, wal=user_wallet_ids[0]))
|
||||||
|
|
||||||
return redirect(url_for("core.home"))
|
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))
|
||||||
|
|
|
@ -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))
|
|
|
@ -152,8 +152,6 @@ async def api_lnurl_multi_response(unique_hash, id_unique_hash):
|
||||||
found = True
|
found = True
|
||||||
else:
|
else:
|
||||||
usescsv += "," + x
|
usescsv += "," + x
|
||||||
print(x)
|
|
||||||
print("usescsv: " + usescsv)
|
|
||||||
if not found:
|
if not found:
|
||||||
return jsonify({"status": "ERROR", "reason": "LNURL-withdraw not found."}), HTTPStatus.OK
|
return jsonify({"status": "ERROR", "reason": "LNURL-withdraw not found."}), HTTPStatus.OK
|
||||||
|
|
||||||
|
|
Loading…
Add table
Reference in a new issue