chore: adhere to ruff's "N" rules (#2377)
* chore: adhere to ruff's "N" rules WARN: reinstall failing extensions! bunch of more consistent variable naming. inspired by this issue. https://github.com/lnbits/lnbits/issues/2308 * fixup! chore: adhere to ruff's "N" rules * rename to funding_source * skip jmeter --------- Co-authored-by: Pavol Rusnak <pavol@rusnak.io>
This commit is contained in:
parent
055426ab53
commit
6d5ad9e229
28 changed files with 191 additions and 173 deletions
20
.github/workflows/ci.yml
vendored
20
.github/workflows/ci.yml
vendored
|
|
@ -53,13 +53,13 @@ jobs:
|
||||||
secrets:
|
secrets:
|
||||||
CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }}
|
CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }}
|
||||||
|
|
||||||
jmeter:
|
# jmeter:
|
||||||
needs: [ lint ]
|
# needs: [ lint ]
|
||||||
strategy:
|
# strategy:
|
||||||
matrix:
|
# matrix:
|
||||||
python-version: ["3.9"]
|
# python-version: ["3.9"]
|
||||||
poetry-version: ["1.5.1"]
|
# poetry-version: ["1.5.1"]
|
||||||
uses: ./.github/workflows/jmeter.yml
|
# uses: ./.github/workflows/jmeter.yml
|
||||||
with:
|
# with:
|
||||||
python-version: ${{ matrix.python-version }}
|
# python-version: ${{ matrix.python-version }}
|
||||||
poetry-version: ${{ matrix.poetry-version }}
|
# poetry-version: ${{ matrix.poetry-version }}
|
||||||
|
|
|
||||||
|
|
@ -25,7 +25,7 @@ from starlette.responses import JSONResponse
|
||||||
|
|
||||||
from lnbits.core.crud import get_dbversions, get_installed_extensions
|
from lnbits.core.crud import get_dbversions, get_installed_extensions
|
||||||
from lnbits.core.helpers import migrate_extension_database
|
from lnbits.core.helpers import migrate_extension_database
|
||||||
from lnbits.core.services import websocketUpdater
|
from lnbits.core.services import websocket_updater
|
||||||
from lnbits.core.tasks import ( # watchdog_task
|
from lnbits.core.tasks import ( # watchdog_task
|
||||||
killswitch_task,
|
killswitch_task,
|
||||||
wait_for_paid_invoices,
|
wait_for_paid_invoices,
|
||||||
|
|
@ -37,7 +37,7 @@ from lnbits.tasks import (
|
||||||
register_invoice_listener,
|
register_invoice_listener,
|
||||||
)
|
)
|
||||||
from lnbits.utils.cache import cache
|
from lnbits.utils.cache import cache
|
||||||
from lnbits.wallets import get_wallet_class, set_wallet_class
|
from lnbits.wallets import get_funding_source, set_funding_source
|
||||||
|
|
||||||
from .commands import migrate_databases
|
from .commands import migrate_databases
|
||||||
from .core import init_core_routers
|
from .core import init_core_routers
|
||||||
|
|
@ -81,7 +81,7 @@ async def startup(app: FastAPI):
|
||||||
|
|
||||||
# initialize WALLET
|
# initialize WALLET
|
||||||
try:
|
try:
|
||||||
set_wallet_class()
|
set_funding_source()
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error(f"Error initializing {settings.lnbits_backend_wallet_class}: {e}")
|
logger.error(f"Error initializing {settings.lnbits_backend_wallet_class}: {e}")
|
||||||
set_void_wallet_class()
|
set_void_wallet_class()
|
||||||
|
|
@ -110,8 +110,8 @@ async def shutdown():
|
||||||
|
|
||||||
# wait a bit to allow them to finish, so that cleanup can run without problems
|
# wait a bit to allow them to finish, so that cleanup can run without problems
|
||||||
await asyncio.sleep(0.1)
|
await asyncio.sleep(0.1)
|
||||||
WALLET = get_wallet_class()
|
funding_source = get_funding_source()
|
||||||
await WALLET.cleanup()
|
await funding_source.cleanup()
|
||||||
|
|
||||||
|
|
||||||
@asynccontextmanager
|
@asynccontextmanager
|
||||||
|
|
@ -178,33 +178,35 @@ def create_app() -> FastAPI:
|
||||||
|
|
||||||
async def check_funding_source() -> None:
|
async def check_funding_source() -> None:
|
||||||
|
|
||||||
WALLET = get_wallet_class()
|
funding_source = get_funding_source()
|
||||||
|
|
||||||
max_retries = settings.funding_source_max_retries
|
max_retries = settings.funding_source_max_retries
|
||||||
retry_counter = 0
|
retry_counter = 0
|
||||||
|
|
||||||
while True:
|
while True:
|
||||||
try:
|
try:
|
||||||
logger.info(f"Connecting to backend {WALLET.__class__.__name__}...")
|
logger.info(f"Connecting to backend {funding_source.__class__.__name__}...")
|
||||||
error_message, balance = await WALLET.status()
|
error_message, balance = await funding_source.status()
|
||||||
if not error_message:
|
if not error_message:
|
||||||
retry_counter = 0
|
retry_counter = 0
|
||||||
logger.success(
|
logger.success(
|
||||||
f"✔️ Backend {WALLET.__class__.__name__} connected "
|
f"✔️ Backend {funding_source.__class__.__name__} connected "
|
||||||
f"and with a balance of {balance} msat."
|
f"and with a balance of {balance} msat."
|
||||||
)
|
)
|
||||||
break
|
break
|
||||||
logger.error(
|
logger.error(
|
||||||
f"The backend for {WALLET.__class__.__name__} isn't "
|
f"The backend for {funding_source.__class__.__name__} isn't "
|
||||||
f"working properly: '{error_message}'",
|
f"working properly: '{error_message}'",
|
||||||
RuntimeWarning,
|
RuntimeWarning,
|
||||||
)
|
)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error(f"Error connecting to {WALLET.__class__.__name__}: {e}")
|
logger.error(
|
||||||
|
f"Error connecting to {funding_source.__class__.__name__}: {e}"
|
||||||
|
)
|
||||||
|
|
||||||
if retry_counter >= max_retries:
|
if retry_counter >= max_retries:
|
||||||
set_void_wallet_class()
|
set_void_wallet_class()
|
||||||
WALLET = get_wallet_class()
|
funding_source = get_funding_source()
|
||||||
break
|
break
|
||||||
|
|
||||||
retry_counter += 1
|
retry_counter += 1
|
||||||
|
|
@ -221,7 +223,7 @@ def set_void_wallet_class():
|
||||||
"Fallback to VoidWallet, because the backend for "
|
"Fallback to VoidWallet, because the backend for "
|
||||||
f"{settings.lnbits_backend_wallet_class} isn't working properly"
|
f"{settings.lnbits_backend_wallet_class} isn't working properly"
|
||||||
)
|
)
|
||||||
set_wallet_class("VoidWallet")
|
set_funding_source("VoidWallet")
|
||||||
|
|
||||||
|
|
||||||
async def check_installed_extensions(app: FastAPI):
|
async def check_installed_extensions(app: FastAPI):
|
||||||
|
|
@ -414,7 +416,7 @@ def initialize_server_logger():
|
||||||
async def update_websocket_serverlog():
|
async def update_websocket_serverlog():
|
||||||
while True:
|
while True:
|
||||||
msg = await serverlog_queue.get()
|
msg = await serverlog_queue.get()
|
||||||
await websocketUpdater(super_user_hash, msg)
|
await websocket_updater(super_user_hash, msg)
|
||||||
|
|
||||||
create_permanent_task(update_websocket_serverlog)
|
create_permanent_task(update_websocket_serverlog)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -17,7 +17,7 @@ from lnbits.db import Connection, FilterModel, FromRowModel
|
||||||
from lnbits.helpers import url_for
|
from lnbits.helpers import url_for
|
||||||
from lnbits.lnurl import encode as lnurl_encode
|
from lnbits.lnurl import encode as lnurl_encode
|
||||||
from lnbits.settings import settings
|
from lnbits.settings import settings
|
||||||
from lnbits.wallets import get_wallet_class
|
from lnbits.wallets import get_funding_source
|
||||||
from lnbits.wallets.base import PaymentPendingStatus, PaymentStatus
|
from lnbits.wallets.base import PaymentPendingStatus, PaymentStatus
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -265,11 +265,11 @@ class Payment(FromRowModel):
|
||||||
f"pending payment {self.checking_id}"
|
f"pending payment {self.checking_id}"
|
||||||
)
|
)
|
||||||
|
|
||||||
WALLET = get_wallet_class()
|
funding_source = get_funding_source()
|
||||||
if self.is_out:
|
if self.is_out:
|
||||||
status = await WALLET.get_payment_status(self.checking_id)
|
status = await funding_source.get_payment_status(self.checking_id)
|
||||||
else:
|
else:
|
||||||
status = await WALLET.get_invoice_status(self.checking_id)
|
status = await funding_source.get_invoice_status(self.checking_id)
|
||||||
|
|
||||||
logger.debug(f"Status: {status}")
|
logger.debug(f"Status: {status}")
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -30,7 +30,7 @@ from lnbits.settings import (
|
||||||
settings,
|
settings,
|
||||||
)
|
)
|
||||||
from lnbits.utils.exchange_rates import fiat_amount_as_satoshis, satoshis_amount_as_fiat
|
from lnbits.utils.exchange_rates import fiat_amount_as_satoshis, satoshis_amount_as_fiat
|
||||||
from lnbits.wallets import FAKE_WALLET, get_wallet_class, set_wallet_class
|
from lnbits.wallets import fake_wallet, get_funding_source, set_funding_source
|
||||||
from lnbits.wallets.base import (
|
from lnbits.wallets.base import (
|
||||||
PaymentPendingStatus,
|
PaymentPendingStatus,
|
||||||
PaymentResponse,
|
PaymentResponse,
|
||||||
|
|
@ -62,11 +62,11 @@ from .helpers import to_valid_user_id
|
||||||
from .models import Payment, UserConfig, Wallet
|
from .models import Payment, UserConfig, Wallet
|
||||||
|
|
||||||
|
|
||||||
class PaymentFailure(Exception):
|
class PaymentError(Exception):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
class InvoiceFailure(Exception):
|
class InvoiceError(Exception):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -123,16 +123,16 @@ async def create_invoice(
|
||||||
conn: Optional[Connection] = None,
|
conn: Optional[Connection] = None,
|
||||||
) -> Tuple[str, str]:
|
) -> Tuple[str, str]:
|
||||||
if not amount > 0:
|
if not amount > 0:
|
||||||
raise InvoiceFailure("Amountless invoices not supported.")
|
raise InvoiceError("Amountless invoices not supported.")
|
||||||
|
|
||||||
user_wallet = await get_wallet(wallet_id, conn=conn)
|
user_wallet = await get_wallet(wallet_id, conn=conn)
|
||||||
if not user_wallet:
|
if not user_wallet:
|
||||||
raise InvoiceFailure(f"Could not fetch wallet '{wallet_id}'.")
|
raise InvoiceError(f"Could not fetch wallet '{wallet_id}'.")
|
||||||
|
|
||||||
invoice_memo = None if description_hash else memo
|
invoice_memo = None if description_hash else memo
|
||||||
|
|
||||||
# use the fake wallet if the invoice is for internal use only
|
# use the fake wallet if the invoice is for internal use only
|
||||||
wallet = FAKE_WALLET if internal else get_wallet_class()
|
funding_source = fake_wallet if internal else get_funding_source()
|
||||||
|
|
||||||
amount_sat, extra = await calculate_fiat_amounts(
|
amount_sat, extra = await calculate_fiat_amounts(
|
||||||
amount, wallet_id, currency=currency, extra=extra, conn=conn
|
amount, wallet_id, currency=currency, extra=extra, conn=conn
|
||||||
|
|
@ -141,20 +141,22 @@ async def create_invoice(
|
||||||
if settings.is_wallet_max_balance_exceeded(
|
if settings.is_wallet_max_balance_exceeded(
|
||||||
user_wallet.balance_msat / 1000 + amount_sat
|
user_wallet.balance_msat / 1000 + amount_sat
|
||||||
):
|
):
|
||||||
raise InvoiceFailure(
|
raise InvoiceError(
|
||||||
f"Wallet balance cannot exceed "
|
f"Wallet balance cannot exceed "
|
||||||
f"{settings.lnbits_wallet_limit_max_balance} sats."
|
f"{settings.lnbits_wallet_limit_max_balance} sats."
|
||||||
)
|
)
|
||||||
|
|
||||||
ok, checking_id, payment_request, error_message = await wallet.create_invoice(
|
ok, checking_id, payment_request, error_message = (
|
||||||
amount=amount_sat,
|
await funding_source.create_invoice(
|
||||||
memo=invoice_memo,
|
amount=amount_sat,
|
||||||
description_hash=description_hash,
|
memo=invoice_memo,
|
||||||
unhashed_description=unhashed_description,
|
description_hash=description_hash,
|
||||||
expiry=expiry or settings.lightning_invoice_expiry,
|
unhashed_description=unhashed_description,
|
||||||
|
expiry=expiry or settings.lightning_invoice_expiry,
|
||||||
|
)
|
||||||
)
|
)
|
||||||
if not ok or not payment_request or not checking_id:
|
if not ok or not payment_request or not checking_id:
|
||||||
raise InvoiceFailure(error_message or "unexpected backend error.")
|
raise InvoiceError(error_message or "unexpected backend error.")
|
||||||
|
|
||||||
invoice = bolt11_decode(payment_request)
|
invoice = bolt11_decode(payment_request)
|
||||||
|
|
||||||
|
|
@ -197,12 +199,12 @@ async def pay_invoice(
|
||||||
try:
|
try:
|
||||||
invoice = bolt11_decode(payment_request)
|
invoice = bolt11_decode(payment_request)
|
||||||
except Exception:
|
except Exception:
|
||||||
raise InvoiceFailure("Bolt11 decoding failed.")
|
raise InvoiceError("Bolt11 decoding failed.")
|
||||||
|
|
||||||
if not invoice.amount_msat or not invoice.amount_msat > 0:
|
if not invoice.amount_msat or not invoice.amount_msat > 0:
|
||||||
raise InvoiceFailure("Amountless invoices not supported.")
|
raise InvoiceError("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 InvoiceFailure("Amount in invoice is too high.")
|
raise InvoiceError("Amount in invoice is too high.")
|
||||||
|
|
||||||
await check_wallet_limits(wallet_id, conn, invoice.amount_msat)
|
await check_wallet_limits(wallet_id, conn, invoice.amount_msat)
|
||||||
|
|
||||||
|
|
@ -237,7 +239,7 @@ async def pay_invoice(
|
||||||
# we check if an internal invoice exists that has already been paid
|
# we check if an internal invoice exists that has already been paid
|
||||||
# (not pending anymore)
|
# (not pending anymore)
|
||||||
if not await check_internal_pending(invoice.payment_hash, conn=conn):
|
if not await check_internal_pending(invoice.payment_hash, conn=conn):
|
||||||
raise PaymentFailure("Internal invoice already paid.")
|
raise PaymentError("Internal invoice already paid.")
|
||||||
|
|
||||||
# check_internal() returns the checking_id of the invoice we're waiting for
|
# check_internal() returns the checking_id of the invoice we're waiting for
|
||||||
# (pending only)
|
# (pending only)
|
||||||
|
|
@ -256,7 +258,7 @@ async def pay_invoice(
|
||||||
internal_invoice.amount != invoice.amount_msat
|
internal_invoice.amount != invoice.amount_msat
|
||||||
or internal_invoice.bolt11 != payment_request.lower()
|
or internal_invoice.bolt11 != payment_request.lower()
|
||||||
):
|
):
|
||||||
raise PaymentFailure("Invalid invoice.")
|
raise PaymentError("Invalid invoice.")
|
||||||
|
|
||||||
logger.debug(f"creating temporary internal payment with id {internal_id}")
|
logger.debug(f"creating temporary internal payment with id {internal_id}")
|
||||||
# create a new payment from this wallet
|
# create a new payment from this wallet
|
||||||
|
|
@ -284,7 +286,7 @@ async def pay_invoice(
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error(f"could not create temporary payment: {e}")
|
logger.error(f"could not create temporary payment: {e}")
|
||||||
# happens if the same wallet tries to pay an invoice twice
|
# happens if the same wallet tries to pay an invoice twice
|
||||||
raise PaymentFailure("Could not make payment.")
|
raise PaymentError("Could not make payment.")
|
||||||
|
|
||||||
# do the balance check
|
# do the balance check
|
||||||
wallet = await get_wallet(wallet_id, conn=conn)
|
wallet = await get_wallet(wallet_id, conn=conn)
|
||||||
|
|
@ -295,7 +297,7 @@ async def pay_invoice(
|
||||||
not internal_checking_id
|
not internal_checking_id
|
||||||
and wallet.balance_msat > -fee_reserve_total_msat
|
and wallet.balance_msat > -fee_reserve_total_msat
|
||||||
):
|
):
|
||||||
raise PaymentFailure(
|
raise PaymentError(
|
||||||
f"You must reserve at least ({round(fee_reserve_total_msat/1000)}"
|
f"You must reserve at least ({round(fee_reserve_total_msat/1000)}"
|
||||||
" sat) to cover potential routing fees."
|
" sat) to cover potential routing fees."
|
||||||
)
|
)
|
||||||
|
|
@ -323,8 +325,8 @@ async def pay_invoice(
|
||||||
service_fee_msat = service_fee(invoice.amount_msat, internal=False)
|
service_fee_msat = service_fee(invoice.amount_msat, internal=False)
|
||||||
logger.debug(f"backend: sending payment {temp_id}")
|
logger.debug(f"backend: sending payment {temp_id}")
|
||||||
# actually pay the external invoice
|
# actually pay the external invoice
|
||||||
WALLET = get_wallet_class()
|
funding_source = get_funding_source()
|
||||||
payment: PaymentResponse = await WALLET.pay_invoice(
|
payment: PaymentResponse = await funding_source.pay_invoice(
|
||||||
payment_request, fee_reserve_msat
|
payment_request, fee_reserve_msat
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
@ -363,7 +365,7 @@ async def pay_invoice(
|
||||||
async with db.connect() as conn:
|
async with db.connect() as conn:
|
||||||
logger.debug(f"deleting temporary payment {temp_id}")
|
logger.debug(f"deleting temporary payment {temp_id}")
|
||||||
await delete_wallet_payment(temp_id, wallet_id, conn=conn)
|
await delete_wallet_payment(temp_id, wallet_id, conn=conn)
|
||||||
raise PaymentFailure(
|
raise PaymentError(
|
||||||
f"Payment failed: {payment.error_message}"
|
f"Payment failed: {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."
|
||||||
)
|
)
|
||||||
|
|
@ -499,7 +501,6 @@ async def redeem_lnurl_withdraw(
|
||||||
async def perform_lnurlauth(
|
async def perform_lnurlauth(
|
||||||
callback: str,
|
callback: str,
|
||||||
wallet: WalletTypeInfo = Depends(require_admin_key),
|
wallet: WalletTypeInfo = Depends(require_admin_key),
|
||||||
conn: Optional[Connection] = None,
|
|
||||||
) -> Optional[LnurlErrorResponse]:
|
) -> Optional[LnurlErrorResponse]:
|
||||||
cb = urlparse(callback)
|
cb = urlparse(callback)
|
||||||
|
|
||||||
|
|
@ -592,7 +593,7 @@ async def check_transaction_status(
|
||||||
|
|
||||||
|
|
||||||
# WARN: this same value must be used for balance check and passed to
|
# WARN: this same value must be used for balance check and passed to
|
||||||
# WALLET.pay_invoice(), it may cause a vulnerability if the values differ
|
# funding_source.pay_invoice(), it may cause a vulnerability if the values differ
|
||||||
def fee_reserve(amount_msat: int, internal: bool = False) -> int:
|
def fee_reserve(amount_msat: int, internal: bool = False) -> int:
|
||||||
if internal:
|
if internal:
|
||||||
return 0
|
return 0
|
||||||
|
|
@ -621,7 +622,7 @@ def fee_reserve_total(amount_msat: int, internal: bool = False) -> int:
|
||||||
|
|
||||||
|
|
||||||
async def send_payment_notification(wallet: Wallet, payment: Payment):
|
async def send_payment_notification(wallet: Wallet, payment: Payment):
|
||||||
await websocketUpdater(
|
await websocket_updater(
|
||||||
wallet.id,
|
wallet.id,
|
||||||
json.dumps(
|
json.dumps(
|
||||||
{
|
{
|
||||||
|
|
@ -760,25 +761,25 @@ class WebsocketConnectionManager:
|
||||||
await connection.send_text(message)
|
await connection.send_text(message)
|
||||||
|
|
||||||
|
|
||||||
websocketManager = WebsocketConnectionManager()
|
websocket_manager = WebsocketConnectionManager()
|
||||||
|
|
||||||
|
|
||||||
async def websocketUpdater(item_id, data):
|
async def websocket_updater(item_id, data):
|
||||||
return await websocketManager.send_data(f"{data}", item_id)
|
return await websocket_manager.send_data(f"{data}", item_id)
|
||||||
|
|
||||||
|
|
||||||
async def switch_to_voidwallet() -> None:
|
async def switch_to_voidwallet() -> None:
|
||||||
WALLET = get_wallet_class()
|
funding_source = get_funding_source()
|
||||||
if WALLET.__class__.__name__ == "VoidWallet":
|
if funding_source.__class__.__name__ == "VoidWallet":
|
||||||
return
|
return
|
||||||
set_wallet_class("VoidWallet")
|
set_funding_source("VoidWallet")
|
||||||
settings.lnbits_backend_wallet_class = "VoidWallet"
|
settings.lnbits_backend_wallet_class = "VoidWallet"
|
||||||
|
|
||||||
|
|
||||||
async def get_balance_delta() -> Tuple[int, int, int]:
|
async def get_balance_delta() -> Tuple[int, int, int]:
|
||||||
WALLET = get_wallet_class()
|
funding_source = get_funding_source()
|
||||||
total_balance = await get_total_balance()
|
total_balance = await get_total_balance()
|
||||||
error_message, node_balance = await WALLET.status()
|
error_message, node_balance = await funding_source.status()
|
||||||
if error_message:
|
if error_message:
|
||||||
raise Exception(error_message)
|
raise Exception(error_message)
|
||||||
return node_balance - total_balance, node_balance, total_balance
|
return node_balance - total_balance, node_balance, total_balance
|
||||||
|
|
|
||||||
|
|
@ -16,7 +16,7 @@ from lnbits.core.services import (
|
||||||
send_payment_notification,
|
send_payment_notification,
|
||||||
switch_to_voidwallet,
|
switch_to_voidwallet,
|
||||||
)
|
)
|
||||||
from lnbits.settings import get_wallet_class, settings
|
from lnbits.settings import get_funding_source, settings
|
||||||
from lnbits.tasks import send_push_notification
|
from lnbits.tasks import send_push_notification
|
||||||
|
|
||||||
api_invoice_listeners: Dict[str, asyncio.Queue] = {}
|
api_invoice_listeners: Dict[str, asyncio.Queue] = {}
|
||||||
|
|
@ -28,8 +28,11 @@ async def killswitch_task():
|
||||||
LNbits and will switch to VoidWallet if the killswitch is triggered.
|
LNbits and will switch to VoidWallet if the killswitch is triggered.
|
||||||
"""
|
"""
|
||||||
while True:
|
while True:
|
||||||
WALLET = get_wallet_class()
|
funding_source = get_funding_source()
|
||||||
if settings.lnbits_killswitch and WALLET.__class__.__name__ != "VoidWallet":
|
if (
|
||||||
|
settings.lnbits_killswitch
|
||||||
|
and funding_source.__class__.__name__ != "VoidWallet"
|
||||||
|
):
|
||||||
with httpx.Client() as client:
|
with httpx.Client() as client:
|
||||||
try:
|
try:
|
||||||
r = client.get(settings.lnbits_status_manifest, timeout=4)
|
r = client.get(settings.lnbits_status_manifest, timeout=4)
|
||||||
|
|
@ -55,8 +58,11 @@ async def watchdog_task():
|
||||||
and will switch to VoidWallet if the watchdog delta is reached.
|
and will switch to VoidWallet if the watchdog delta is reached.
|
||||||
"""
|
"""
|
||||||
while True:
|
while True:
|
||||||
WALLET = get_wallet_class()
|
funding_source = get_funding_source()
|
||||||
if settings.lnbits_watchdog and WALLET.__class__.__name__ != "VoidWallet":
|
if (
|
||||||
|
settings.lnbits_watchdog
|
||||||
|
and funding_source.__class__.__name__ != "VoidWallet"
|
||||||
|
):
|
||||||
try:
|
try:
|
||||||
delta, *_ = await get_balance_delta()
|
delta, *_ = await get_balance_delta()
|
||||||
logger.debug(f"Running watchdog task. current delta: {delta}")
|
logger.debug(f"Running watchdog task. current delta: {delta}")
|
||||||
|
|
|
||||||
|
|
@ -316,16 +316,16 @@ def _new_sso(provider: str) -> Optional[SSOBase]:
|
||||||
logger.warning(f"{provider} auth allowed but not configured.")
|
logger.warning(f"{provider} auth allowed but not configured.")
|
||||||
return None
|
return None
|
||||||
|
|
||||||
SSOProviderClass = _find_auth_provider_class(provider)
|
sso_provider_class = _find_auth_provider_class(provider)
|
||||||
ssoProvider = SSOProviderClass(
|
sso_provider = sso_provider_class(
|
||||||
client_id, client_secret, None, allow_insecure_http=True
|
client_id, client_secret, None, allow_insecure_http=True
|
||||||
)
|
)
|
||||||
if (
|
if (
|
||||||
discovery_url
|
discovery_url
|
||||||
and getattr(ssoProvider, "discovery_url", discovery_url) != discovery_url
|
and getattr(sso_provider, "discovery_url", discovery_url) != discovery_url
|
||||||
):
|
):
|
||||||
ssoProvider.discovery_url = discovery_url
|
sso_provider.discovery_url = discovery_url
|
||||||
return ssoProvider
|
return sso_provider
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.warning(e)
|
logger.warning(e)
|
||||||
|
|
||||||
|
|
@ -337,9 +337,9 @@ def _find_auth_provider_class(provider: str) -> Callable:
|
||||||
for module in sso_modules:
|
for module in sso_modules:
|
||||||
try:
|
try:
|
||||||
provider_module = importlib.import_module(f"{module}.{provider}")
|
provider_module = importlib.import_module(f"{module}.{provider}")
|
||||||
ProviderClass = getattr(provider_module, f"{provider.title()}SSO")
|
provider_class = getattr(provider_module, f"{provider.title()}SSO")
|
||||||
if ProviderClass:
|
if provider_class:
|
||||||
return ProviderClass
|
return provider_class
|
||||||
except Exception:
|
except Exception:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -18,7 +18,7 @@ from lnbits.core.models import User
|
||||||
from lnbits.decorators import check_admin, check_user_exists
|
from lnbits.decorators import check_admin, check_user_exists
|
||||||
from lnbits.helpers import template_renderer, url_for
|
from lnbits.helpers import template_renderer, url_for
|
||||||
from lnbits.settings import settings
|
from lnbits.settings import settings
|
||||||
from lnbits.wallets import get_wallet_class
|
from lnbits.wallets import get_funding_source
|
||||||
|
|
||||||
from ...extension_manager import InstallableExtension, get_valid_extensions
|
from ...extension_manager import InstallableExtension, get_valid_extensions
|
||||||
from ...utils.exchange_rates import allowed_currencies, currencies
|
from ...utils.exchange_rates import allowed_currencies, currencies
|
||||||
|
|
@ -452,8 +452,8 @@ async def node(request: Request, user: User = Depends(check_admin)):
|
||||||
if not settings.lnbits_node_ui:
|
if not settings.lnbits_node_ui:
|
||||||
raise HTTPException(status_code=HTTPStatus.SERVICE_UNAVAILABLE)
|
raise HTTPException(status_code=HTTPStatus.SERVICE_UNAVAILABLE)
|
||||||
|
|
||||||
WALLET = get_wallet_class()
|
funding_source = get_funding_source()
|
||||||
_, balance = await WALLET.status()
|
_, balance = await funding_source.status()
|
||||||
|
|
||||||
return template_renderer().TemplateResponse(
|
return template_renderer().TemplateResponse(
|
||||||
request,
|
request,
|
||||||
|
|
@ -472,8 +472,8 @@ async def node_public(request: Request):
|
||||||
if not settings.lnbits_public_node_ui:
|
if not settings.lnbits_public_node_ui:
|
||||||
raise HTTPException(status_code=HTTPStatus.SERVICE_UNAVAILABLE)
|
raise HTTPException(status_code=HTTPStatus.SERVICE_UNAVAILABLE)
|
||||||
|
|
||||||
WALLET = get_wallet_class()
|
funding_source = get_funding_source()
|
||||||
_, balance = await WALLET.status()
|
_, balance = await funding_source.status()
|
||||||
|
|
||||||
return template_renderer().TemplateResponse(
|
return template_renderer().TemplateResponse(
|
||||||
request,
|
request,
|
||||||
|
|
@ -490,8 +490,8 @@ async def index(request: Request, user: User = Depends(check_admin)):
|
||||||
if not settings.lnbits_admin_ui:
|
if not settings.lnbits_admin_ui:
|
||||||
raise HTTPException(status_code=HTTPStatus.NOT_FOUND)
|
raise HTTPException(status_code=HTTPStatus.NOT_FOUND)
|
||||||
|
|
||||||
WALLET = get_wallet_class()
|
funding_source = get_funding_source()
|
||||||
_, balance = await WALLET.status()
|
_, balance = await funding_source.status()
|
||||||
|
|
||||||
return template_renderer().TemplateResponse(
|
return template_renderer().TemplateResponse(
|
||||||
request,
|
request,
|
||||||
|
|
|
||||||
|
|
@ -27,8 +27,8 @@ from ...utils.cache import cache
|
||||||
|
|
||||||
|
|
||||||
def require_node():
|
def require_node():
|
||||||
NODE = get_node_class()
|
node_class = get_node_class()
|
||||||
if not NODE:
|
if not node_class:
|
||||||
raise HTTPException(
|
raise HTTPException(
|
||||||
status_code=HTTPStatus.NOT_IMPLEMENTED,
|
status_code=HTTPStatus.NOT_IMPLEMENTED,
|
||||||
detail="Active backend does not implement Node API",
|
detail="Active backend does not implement Node API",
|
||||||
|
|
@ -38,7 +38,7 @@ def require_node():
|
||||||
status_code=HTTPStatus.SERVICE_UNAVAILABLE,
|
status_code=HTTPStatus.SERVICE_UNAVAILABLE,
|
||||||
detail="Not enabled",
|
detail="Not enabled",
|
||||||
)
|
)
|
||||||
return NODE
|
return node_class
|
||||||
|
|
||||||
|
|
||||||
def check_public():
|
def check_public():
|
||||||
|
|
|
||||||
|
|
@ -56,8 +56,8 @@ from ..crud import (
|
||||||
update_pending_payments,
|
update_pending_payments,
|
||||||
)
|
)
|
||||||
from ..services import (
|
from ..services import (
|
||||||
InvoiceFailure,
|
InvoiceError,
|
||||||
PaymentFailure,
|
PaymentError,
|
||||||
check_transaction_status,
|
check_transaction_status,
|
||||||
create_invoice,
|
create_invoice,
|
||||||
fee_reserve_total,
|
fee_reserve_total,
|
||||||
|
|
@ -171,7 +171,7 @@ async def api_payments_create_invoice(data: CreateInvoice, wallet: Wallet):
|
||||||
payment_db = await get_standalone_payment(payment_hash, conn=conn)
|
payment_db = await get_standalone_payment(payment_hash, conn=conn)
|
||||||
assert payment_db is not None, "payment not found"
|
assert payment_db is not None, "payment not found"
|
||||||
checking_id = payment_db.checking_id
|
checking_id = payment_db.checking_id
|
||||||
except InvoiceFailure as e:
|
except InvoiceError as e:
|
||||||
raise HTTPException(status_code=520, detail=str(e))
|
raise HTTPException(status_code=520, detail=str(e))
|
||||||
except Exception as exc:
|
except Exception as exc:
|
||||||
raise exc
|
raise exc
|
||||||
|
|
@ -230,7 +230,7 @@ async def api_payments_pay_invoice(
|
||||||
raise HTTPException(status_code=HTTPStatus.BAD_REQUEST, detail=str(e))
|
raise HTTPException(status_code=HTTPStatus.BAD_REQUEST, detail=str(e))
|
||||||
except PermissionError as e:
|
except PermissionError as e:
|
||||||
raise HTTPException(status_code=HTTPStatus.FORBIDDEN, detail=str(e))
|
raise HTTPException(status_code=HTTPStatus.FORBIDDEN, detail=str(e))
|
||||||
except PaymentFailure as e:
|
except PaymentError as e:
|
||||||
raise HTTPException(status_code=520, detail=str(e))
|
raise HTTPException(status_code=520, detail=str(e))
|
||||||
except Exception as exc:
|
except Exception as exc:
|
||||||
raise exc
|
raise exc
|
||||||
|
|
@ -257,20 +257,20 @@ async def api_payments_pay_invoice(
|
||||||
)
|
)
|
||||||
async def api_payments_create(
|
async def api_payments_create(
|
||||||
wallet: WalletTypeInfo = Depends(require_invoice_key),
|
wallet: WalletTypeInfo = Depends(require_invoice_key),
|
||||||
invoiceData: CreateInvoice = Body(...),
|
invoice_data: CreateInvoice = Body(...),
|
||||||
):
|
):
|
||||||
if invoiceData.out is True and wallet.wallet_type == WalletType.admin:
|
if invoice_data.out is True and wallet.wallet_type == WalletType.admin:
|
||||||
if not invoiceData.bolt11:
|
if not invoice_data.bolt11:
|
||||||
raise HTTPException(
|
raise HTTPException(
|
||||||
status_code=HTTPStatus.BAD_REQUEST,
|
status_code=HTTPStatus.BAD_REQUEST,
|
||||||
detail="BOLT11 string is invalid or not given",
|
detail="BOLT11 string is invalid or not given",
|
||||||
)
|
)
|
||||||
return await api_payments_pay_invoice(
|
return await api_payments_pay_invoice(
|
||||||
invoiceData.bolt11, wallet.wallet, invoiceData.extra
|
invoice_data.bolt11, wallet.wallet, invoice_data.extra
|
||||||
) # admin key
|
) # admin key
|
||||||
elif not invoiceData.out:
|
elif not invoice_data.out:
|
||||||
# invoice key
|
# invoice key
|
||||||
return await api_payments_create_invoice(invoiceData, wallet.wallet)
|
return await api_payments_create_invoice(invoice_data, wallet.wallet)
|
||||||
else:
|
else:
|
||||||
raise HTTPException(
|
raise HTTPException(
|
||||||
status_code=HTTPStatus.UNAUTHORIZED,
|
status_code=HTTPStatus.UNAUTHORIZED,
|
||||||
|
|
@ -415,10 +415,10 @@ async def api_payments_sse(
|
||||||
|
|
||||||
# TODO: refactor this route into a public and admin one
|
# TODO: refactor this route into a public and admin one
|
||||||
@payment_router.get("/{payment_hash}")
|
@payment_router.get("/{payment_hash}")
|
||||||
async def api_payment(payment_hash, X_Api_Key: Optional[str] = Header(None)):
|
async def api_payment(payment_hash, x_api_key: Optional[str] = Header(None)):
|
||||||
# We use X_Api_Key here because we want this call to work with and without keys
|
# We use X_Api_Key here because we want this call to work with and without keys
|
||||||
# If a valid key is given, we also return the field "details", otherwise not
|
# If a valid key is given, we also return the field "details", otherwise not
|
||||||
wallet = await get_wallet_for_key(X_Api_Key) if isinstance(X_Api_Key, str) else None
|
wallet = await get_wallet_for_key(x_api_key) if isinstance(x_api_key, str) else None
|
||||||
|
|
||||||
payment = await get_standalone_payment(
|
payment = await get_standalone_payment(
|
||||||
payment_hash, wallet_id=wallet.id if wallet else None
|
payment_hash, wallet_id=wallet.id if wallet else None
|
||||||
|
|
|
||||||
|
|
@ -5,8 +5,8 @@ from fastapi import (
|
||||||
)
|
)
|
||||||
|
|
||||||
from ..services import (
|
from ..services import (
|
||||||
websocketManager,
|
websocket_manager,
|
||||||
websocketUpdater,
|
websocket_updater,
|
||||||
)
|
)
|
||||||
|
|
||||||
websocket_router = APIRouter(prefix="/api/v1/ws", tags=["Websocket"])
|
websocket_router = APIRouter(prefix="/api/v1/ws", tags=["Websocket"])
|
||||||
|
|
@ -14,18 +14,18 @@ websocket_router = APIRouter(prefix="/api/v1/ws", tags=["Websocket"])
|
||||||
|
|
||||||
@websocket_router.websocket("/{item_id}")
|
@websocket_router.websocket("/{item_id}")
|
||||||
async def websocket_connect(websocket: WebSocket, item_id: str):
|
async def websocket_connect(websocket: WebSocket, item_id: str):
|
||||||
await websocketManager.connect(websocket, item_id)
|
await websocket_manager.connect(websocket, item_id)
|
||||||
try:
|
try:
|
||||||
while True:
|
while True:
|
||||||
await websocket.receive_text()
|
await websocket.receive_text()
|
||||||
except WebSocketDisconnect:
|
except WebSocketDisconnect:
|
||||||
websocketManager.disconnect(websocket)
|
websocket_manager.disconnect(websocket)
|
||||||
|
|
||||||
|
|
||||||
@websocket_router.post("/{item_id}")
|
@websocket_router.post("/{item_id}")
|
||||||
async def websocket_update_post(item_id: str, data: str):
|
async def websocket_update_post(item_id: str, data: str):
|
||||||
try:
|
try:
|
||||||
await websocketUpdater(item_id, data)
|
await websocket_updater(item_id, data)
|
||||||
return {"sent": True, "data": data}
|
return {"sent": True, "data": data}
|
||||||
except Exception:
|
except Exception:
|
||||||
return {"sent": False, "data": data}
|
return {"sent": False, "data": data}
|
||||||
|
|
@ -34,7 +34,7 @@ async def websocket_update_post(item_id: str, data: str):
|
||||||
@websocket_router.get("/{item_id}/{data}")
|
@websocket_router.get("/{item_id}/{data}")
|
||||||
async def websocket_update_get(item_id: str, data: str):
|
async def websocket_update_get(item_id: str, data: str):
|
||||||
try:
|
try:
|
||||||
await websocketUpdater(item_id, data)
|
await websocket_updater(item_id, data)
|
||||||
return {"sent": True, "data": data}
|
return {"sent": True, "data": data}
|
||||||
except Exception:
|
except Exception:
|
||||||
return {"sent": False, "data": data}
|
return {"sent": False, "data": data}
|
||||||
|
|
|
||||||
|
|
@ -140,14 +140,14 @@ class Connection(Compat):
|
||||||
|
|
||||||
def rewrite_values(self, values):
|
def rewrite_values(self, values):
|
||||||
# strip html
|
# strip html
|
||||||
CLEANR = re.compile("<.*?>|&([a-z0-9]+|#[0-9]{1,6}|#x[0-9a-f]{1,6});")
|
clean_regex = re.compile("<.*?>|&([a-z0-9]+|#[0-9]{1,6}|#x[0-9a-f]{1,6});")
|
||||||
|
|
||||||
# tuple to list and back to tuple
|
# tuple to list and back to tuple
|
||||||
raw_values = [values] if isinstance(values, str) else list(values)
|
raw_values = [values] if isinstance(values, str) else list(values)
|
||||||
values = []
|
values = []
|
||||||
for raw_value in raw_values:
|
for raw_value in raw_values:
|
||||||
if isinstance(raw_value, str):
|
if isinstance(raw_value, str):
|
||||||
values.append(re.sub(CLEANR, "", raw_value))
|
values.append(re.sub(clean_regex, "", raw_value))
|
||||||
elif isinstance(raw_value, datetime.datetime):
|
elif isinstance(raw_value, datetime.datetime):
|
||||||
ts = raw_value.timestamp()
|
ts = raw_value.timestamp()
|
||||||
if self.type == SQLITE:
|
if self.type == SQLITE:
|
||||||
|
|
|
||||||
|
|
@ -138,12 +138,12 @@ async def get_key_type(
|
||||||
detail="Invoice (or Admin) key required.",
|
detail="Invoice (or Admin) key required.",
|
||||||
)
|
)
|
||||||
|
|
||||||
for wallet_type, WalletChecker in zip(
|
for wallet_type, wallet_checker in zip(
|
||||||
[WalletType.admin, WalletType.invoice],
|
[WalletType.admin, WalletType.invoice],
|
||||||
[WalletAdminKeyChecker, WalletInvoiceKeyChecker],
|
[WalletAdminKeyChecker, WalletInvoiceKeyChecker],
|
||||||
):
|
):
|
||||||
try:
|
try:
|
||||||
checker = WalletChecker(api_key=token)
|
checker = wallet_checker(api_key=token)
|
||||||
await checker.__call__(r)
|
await checker.__call__(r)
|
||||||
if checker.wallet is None:
|
if checker.wallet is None:
|
||||||
raise HTTPException(
|
raise HTTPException(
|
||||||
|
|
|
||||||
|
|
@ -532,10 +532,10 @@ if not settings.lnbits_admin_ui:
|
||||||
logger.debug(f"{key}: {value}")
|
logger.debug(f"{key}: {value}")
|
||||||
|
|
||||||
|
|
||||||
def get_wallet_class():
|
def get_funding_source():
|
||||||
"""
|
"""
|
||||||
Backwards compatibility
|
Backwards compatibility
|
||||||
"""
|
"""
|
||||||
from lnbits.wallets import get_wallet_class
|
from lnbits.wallets import get_funding_source
|
||||||
|
|
||||||
return get_wallet_class()
|
return get_funding_source()
|
||||||
|
|
|
||||||
|
|
@ -17,7 +17,7 @@ from lnbits.core.crud import (
|
||||||
get_standalone_payment,
|
get_standalone_payment,
|
||||||
)
|
)
|
||||||
from lnbits.settings import settings
|
from lnbits.settings import settings
|
||||||
from lnbits.wallets import get_wallet_class
|
from lnbits.wallets import get_funding_source
|
||||||
|
|
||||||
tasks: List[asyncio.Task] = []
|
tasks: List[asyncio.Task] = []
|
||||||
unique_tasks: Dict[str, asyncio.Task] = {}
|
unique_tasks: Dict[str, asyncio.Task] = {}
|
||||||
|
|
@ -119,8 +119,8 @@ async def invoice_listener():
|
||||||
|
|
||||||
Called by the app startup sequence.
|
Called by the app startup sequence.
|
||||||
"""
|
"""
|
||||||
WALLET = get_wallet_class()
|
funding_source = get_funding_source()
|
||||||
async for checking_id in WALLET.paid_invoices_stream():
|
async for checking_id in funding_source.paid_invoices_stream():
|
||||||
logger.info("> got a payment notification", checking_id)
|
logger.info("> got a payment notification", checking_id)
|
||||||
create_task(invoice_callback_dispatcher(checking_id))
|
create_task(invoice_callback_dispatcher(checking_id))
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -28,21 +28,21 @@ from .void import VoidWallet
|
||||||
from .zbd import ZBDWallet
|
from .zbd import ZBDWallet
|
||||||
|
|
||||||
|
|
||||||
def set_wallet_class(class_name: Optional[str] = None):
|
def set_funding_source(class_name: Optional[str] = None):
|
||||||
backend_wallet_class = class_name or settings.lnbits_backend_wallet_class
|
backend_wallet_class = class_name or settings.lnbits_backend_wallet_class
|
||||||
wallet_class = getattr(wallets_module, backend_wallet_class)
|
funding_source_constructor = getattr(wallets_module, backend_wallet_class)
|
||||||
global WALLET
|
global funding_source
|
||||||
WALLET = wallet_class()
|
funding_source = funding_source_constructor()
|
||||||
if WALLET.__node_cls__:
|
if funding_source.__node_cls__:
|
||||||
set_node_class(WALLET.__node_cls__(WALLET))
|
set_node_class(funding_source.__node_cls__(funding_source))
|
||||||
|
|
||||||
|
|
||||||
def get_wallet_class() -> Wallet:
|
def get_funding_source() -> Wallet:
|
||||||
return WALLET
|
return funding_source
|
||||||
|
|
||||||
|
|
||||||
wallets_module = importlib.import_module("lnbits.wallets")
|
wallets_module = importlib.import_module("lnbits.wallets")
|
||||||
FAKE_WALLET = FakeWallet()
|
fake_wallet = FakeWallet()
|
||||||
|
|
||||||
# initialize as fake wallet
|
# initialize as fake wallet
|
||||||
WALLET: Wallet = FAKE_WALLET
|
funding_source: Wallet = fake_wallet
|
||||||
|
|
|
||||||
|
|
@ -144,5 +144,5 @@ class Wallet(ABC):
|
||||||
return endpoint
|
return endpoint
|
||||||
|
|
||||||
|
|
||||||
class Unsupported(Exception):
|
class UnsupportedError(Exception):
|
||||||
pass
|
pass
|
||||||
|
|
|
||||||
|
|
@ -18,7 +18,7 @@ from .base import (
|
||||||
PaymentStatus,
|
PaymentStatus,
|
||||||
PaymentSuccessStatus,
|
PaymentSuccessStatus,
|
||||||
StatusResponse,
|
StatusResponse,
|
||||||
Unsupported,
|
UnsupportedError,
|
||||||
Wallet,
|
Wallet,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
@ -73,12 +73,12 @@ class CoreLightningWallet(Wallet):
|
||||||
msat: int = int(amount * 1000)
|
msat: int = int(amount * 1000)
|
||||||
try:
|
try:
|
||||||
if description_hash and not unhashed_description:
|
if description_hash and not unhashed_description:
|
||||||
raise Unsupported(
|
raise UnsupportedError(
|
||||||
"'description_hash' unsupported by CoreLightning, provide"
|
"'description_hash' unsupported by CoreLightning, provide"
|
||||||
" 'unhashed_description'"
|
" 'unhashed_description'"
|
||||||
)
|
)
|
||||||
if unhashed_description and not self.supports_description_hash:
|
if unhashed_description and not self.supports_description_hash:
|
||||||
raise Unsupported("unhashed_description")
|
raise UnsupportedError("unhashed_description")
|
||||||
r: dict = self.ln.invoice( # type: ignore
|
r: dict = self.ln.invoice( # type: ignore
|
||||||
msatoshi=msat,
|
msatoshi=msat,
|
||||||
label=label,
|
label=label,
|
||||||
|
|
|
||||||
|
|
@ -16,7 +16,7 @@ from .base import (
|
||||||
PaymentResponse,
|
PaymentResponse,
|
||||||
PaymentStatus,
|
PaymentStatus,
|
||||||
StatusResponse,
|
StatusResponse,
|
||||||
Unsupported,
|
UnsupportedError,
|
||||||
Wallet,
|
Wallet,
|
||||||
)
|
)
|
||||||
from .macaroon import load_macaroon
|
from .macaroon import load_macaroon
|
||||||
|
|
@ -104,7 +104,7 @@ class CoreLightningRestWallet(Wallet):
|
||||||
"label": label,
|
"label": label,
|
||||||
}
|
}
|
||||||
if description_hash and not unhashed_description:
|
if description_hash and not unhashed_description:
|
||||||
raise Unsupported(
|
raise UnsupportedError(
|
||||||
"'description_hash' unsupported by CoreLightningRest, "
|
"'description_hash' unsupported by CoreLightningRest, "
|
||||||
"provide 'unhashed_description'"
|
"provide 'unhashed_description'"
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -40,8 +40,8 @@ class EclairWallet(Wallet):
|
||||||
self.ws_url = f"ws://{urllib.parse.urlsplit(self.url).netloc}/ws"
|
self.ws_url = f"ws://{urllib.parse.urlsplit(self.url).netloc}/ws"
|
||||||
|
|
||||||
password = settings.eclair_pass
|
password = settings.eclair_pass
|
||||||
encodedAuth = base64.b64encode(f":{password}".encode())
|
encoded_auth = base64.b64encode(f":{password}".encode())
|
||||||
auth = str(encodedAuth, "utf-8")
|
auth = str(encoded_auth, "utf-8")
|
||||||
self.headers = {
|
self.headers = {
|
||||||
"Authorization": f"Basic {auth}",
|
"Authorization": f"Basic {auth}",
|
||||||
"User-Agent": settings.user_agent,
|
"User-Agent": settings.user_agent,
|
||||||
|
|
|
||||||
|
|
@ -164,13 +164,13 @@ class LndRestWallet(Wallet):
|
||||||
|
|
||||||
async def pay_invoice(self, bolt11: str, fee_limit_msat: int) -> PaymentResponse:
|
async def pay_invoice(self, bolt11: str, fee_limit_msat: int) -> PaymentResponse:
|
||||||
# set the fee limit for the payment
|
# set the fee limit for the payment
|
||||||
lnrpcFeeLimit = {}
|
lnrpc_fee_limit = {}
|
||||||
lnrpcFeeLimit["fixed_msat"] = f"{fee_limit_msat}"
|
lnrpc_fee_limit["fixed_msat"] = f"{fee_limit_msat}"
|
||||||
|
|
||||||
try:
|
try:
|
||||||
r = await self.client.post(
|
r = await self.client.post(
|
||||||
url="/v1/channels/transactions",
|
url="/v1/channels/transactions",
|
||||||
json={"payment_request": bolt11, "fee_limit": lnrpcFeeLimit},
|
json={"payment_request": bolt11, "fee_limit": lnrpc_fee_limit},
|
||||||
timeout=None,
|
timeout=None,
|
||||||
)
|
)
|
||||||
r.raise_for_status()
|
r.raise_for_status()
|
||||||
|
|
|
||||||
|
|
@ -12,7 +12,7 @@ from .base import (
|
||||||
PaymentResponse,
|
PaymentResponse,
|
||||||
PaymentStatus,
|
PaymentStatus,
|
||||||
StatusResponse,
|
StatusResponse,
|
||||||
Unsupported,
|
UnsupportedError,
|
||||||
Wallet,
|
Wallet,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
@ -74,7 +74,7 @@ class OpenNodeWallet(Wallet):
|
||||||
**kwargs,
|
**kwargs,
|
||||||
) -> InvoiceResponse:
|
) -> InvoiceResponse:
|
||||||
if description_hash or unhashed_description:
|
if description_hash or unhashed_description:
|
||||||
raise Unsupported("description_hash")
|
raise UnsupportedError("description_hash")
|
||||||
|
|
||||||
r = await self.client.post(
|
r = await self.client.post(
|
||||||
"/v1/charges",
|
"/v1/charges",
|
||||||
|
|
|
||||||
|
|
@ -13,7 +13,7 @@ from .base import (
|
||||||
PaymentResponse,
|
PaymentResponse,
|
||||||
PaymentStatus,
|
PaymentStatus,
|
||||||
StatusResponse,
|
StatusResponse,
|
||||||
Unsupported,
|
UnsupportedError,
|
||||||
Wallet,
|
Wallet,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
@ -65,7 +65,7 @@ class ZBDWallet(Wallet):
|
||||||
) -> InvoiceResponse:
|
) -> InvoiceResponse:
|
||||||
# https://api.zebedee.io/v0/charges
|
# https://api.zebedee.io/v0/charges
|
||||||
if description_hash or unhashed_description:
|
if description_hash or unhashed_description:
|
||||||
raise Unsupported("description_hash")
|
raise UnsupportedError("description_hash")
|
||||||
|
|
||||||
msats_amount = amount * 1000
|
msats_amount = amount * 1000
|
||||||
data: Dict = {
|
data: Dict = {
|
||||||
|
|
|
||||||
|
|
@ -168,7 +168,8 @@ extend-exclude = [
|
||||||
# I - isort
|
# I - isort
|
||||||
# A - flake8-builtins
|
# A - flake8-builtins
|
||||||
# C - mccabe
|
# C - mccabe
|
||||||
select = ["F", "E", "W", "I", "A", "C"]
|
# N - naming
|
||||||
|
select = ["F", "E", "W", "I", "A", "C", "N"]
|
||||||
ignore = []
|
ignore = []
|
||||||
|
|
||||||
# Allow autofix for all enabled rules (when `--fix`) is provided.
|
# Allow autofix for all enabled rules (when `--fix`) is provided.
|
||||||
|
|
@ -178,6 +179,12 @@ unfixable = []
|
||||||
# Allow unused variables when underscore-prefixed.
|
# Allow unused variables when underscore-prefixed.
|
||||||
dummy-variable-rgx = "^(_+|(_+[a-zA-Z0-9_]*[a-zA-Z0-9]+?))$"
|
dummy-variable-rgx = "^(_+|(_+[a-zA-Z0-9_]*[a-zA-Z0-9]+?))$"
|
||||||
|
|
||||||
|
# needed for pydantic
|
||||||
|
[tool.ruff.lint.pep8-naming]
|
||||||
|
classmethod-decorators = [
|
||||||
|
"root_validator",
|
||||||
|
]
|
||||||
|
|
||||||
# Ignore unused imports in __init__.py files.
|
# Ignore unused imports in __init__.py files.
|
||||||
[tool.ruff.lint.extend-per-file-ignores]
|
[tool.ruff.lint.extend-per-file-ignores]
|
||||||
"__init__.py" = ["F401", "F403"]
|
"__init__.py" = ["F401", "F403"]
|
||||||
|
|
|
||||||
|
|
@ -176,8 +176,8 @@ async def adminkey_headers_to(to_wallet):
|
||||||
@pytest_asyncio.fixture(scope="session")
|
@pytest_asyncio.fixture(scope="session")
|
||||||
async def invoice(to_wallet):
|
async def invoice(to_wallet):
|
||||||
data = await get_random_invoice_data()
|
data = await get_random_invoice_data()
|
||||||
invoiceData = CreateInvoice(**data)
|
invoice_data = CreateInvoice(**data)
|
||||||
invoice = await api_payments_create_invoice(invoiceData, to_wallet)
|
invoice = await api_payments_create_invoice(invoice_data, to_wallet)
|
||||||
yield invoice
|
yield invoice
|
||||||
del invoice
|
del invoice
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -10,7 +10,7 @@ from lnbits.core.services import fee_reserve_total
|
||||||
from lnbits.core.views.admin_api import api_auditor
|
from lnbits.core.views.admin_api import api_auditor
|
||||||
from lnbits.core.views.payment_api import api_payment
|
from lnbits.core.views.payment_api import api_payment
|
||||||
from lnbits.settings import settings
|
from lnbits.settings import settings
|
||||||
from lnbits.wallets import get_wallet_class
|
from lnbits.wallets import get_funding_source
|
||||||
|
|
||||||
from ...helpers import (
|
from ...helpers import (
|
||||||
cancel_invoice,
|
cancel_invoice,
|
||||||
|
|
@ -22,7 +22,7 @@ from ...helpers import (
|
||||||
settle_invoice,
|
settle_invoice,
|
||||||
)
|
)
|
||||||
|
|
||||||
WALLET = get_wallet_class()
|
funding_source = get_funding_source()
|
||||||
|
|
||||||
|
|
||||||
# create account POST /api/v1/account
|
# create account POST /api/v1/account
|
||||||
|
|
@ -407,7 +407,8 @@ async def test_api_payment_with_key(invoice, inkey_headers_from):
|
||||||
|
|
||||||
# check POST /api/v1/payments: invoice creation with a description hash
|
# check POST /api/v1/payments: invoice creation with a description hash
|
||||||
@pytest.mark.skipif(
|
@pytest.mark.skipif(
|
||||||
WALLET.__class__.__name__ in ["CoreLightningWallet", "CoreLightningRestWallet"],
|
funding_source.__class__.__name__
|
||||||
|
in ["CoreLightningWallet", "CoreLightningRestWallet"],
|
||||||
reason="wallet does not support description_hash",
|
reason="wallet does not support description_hash",
|
||||||
)
|
)
|
||||||
@pytest.mark.asyncio
|
@pytest.mark.asyncio
|
||||||
|
|
@ -428,7 +429,7 @@ async def test_create_invoice_with_description_hash(client, inkey_headers_to):
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.skipif(
|
@pytest.mark.skipif(
|
||||||
WALLET.__class__.__name__ in ["CoreLightningRestWallet"],
|
funding_source.__class__.__name__ in ["CoreLightningRestWallet"],
|
||||||
reason="wallet does not support unhashed_description",
|
reason="wallet does not support unhashed_description",
|
||||||
)
|
)
|
||||||
@pytest.mark.asyncio
|
@pytest.mark.asyncio
|
||||||
|
|
@ -540,8 +541,8 @@ async def test_pay_real_invoice(
|
||||||
payment_status = response.json()
|
payment_status = response.json()
|
||||||
assert payment_status["paid"]
|
assert payment_status["paid"]
|
||||||
|
|
||||||
WALLET = get_wallet_class()
|
funding_source = get_funding_source()
|
||||||
status = await WALLET.get_payment_status(invoice["payment_hash"])
|
status = await funding_source.get_payment_status(invoice["payment_hash"])
|
||||||
assert status.paid
|
assert status.paid
|
||||||
|
|
||||||
await asyncio.sleep(1)
|
await asyncio.sleep(1)
|
||||||
|
|
@ -571,7 +572,7 @@ async def test_create_real_invoice(client, adminkey_headers_from, inkey_headers_
|
||||||
|
|
||||||
async def listen():
|
async def listen():
|
||||||
found_checking_id = False
|
found_checking_id = False
|
||||||
async for checking_id in get_wallet_class().paid_invoices_stream():
|
async for checking_id in get_funding_source().paid_invoices_stream():
|
||||||
if checking_id == invoice["checking_id"]:
|
if checking_id == invoice["checking_id"]:
|
||||||
found_checking_id = True
|
found_checking_id = True
|
||||||
return
|
return
|
||||||
|
|
@ -622,8 +623,8 @@ async def test_pay_real_invoice_set_pending_and_check_state(
|
||||||
assert response["paid"]
|
assert response["paid"]
|
||||||
|
|
||||||
# make sure that the backend also thinks it's paid
|
# make sure that the backend also thinks it's paid
|
||||||
WALLET = get_wallet_class()
|
funding_source = get_funding_source()
|
||||||
status = await WALLET.get_payment_status(invoice["payment_hash"])
|
status = await funding_source.get_payment_status(invoice["payment_hash"])
|
||||||
assert status.paid
|
assert status.paid
|
||||||
|
|
||||||
# get the outgoing payment from the db
|
# get the outgoing payment from the db
|
||||||
|
|
@ -800,7 +801,7 @@ async def test_receive_real_invoice_set_pending_and_check_state(
|
||||||
|
|
||||||
async def listen():
|
async def listen():
|
||||||
found_checking_id = False
|
found_checking_id = False
|
||||||
async for checking_id in get_wallet_class().paid_invoices_stream():
|
async for checking_id in get_funding_source().paid_invoices_stream():
|
||||||
if checking_id == invoice["checking_id"]:
|
if checking_id == invoice["checking_id"]:
|
||||||
found_checking_id = True
|
found_checking_id = True
|
||||||
return
|
return
|
||||||
|
|
|
||||||
|
|
@ -10,14 +10,15 @@ from lnbits.nodes.base import ChannelPoint, ChannelState, NodeChannel
|
||||||
from tests.conftest import pytest_asyncio, settings
|
from tests.conftest import pytest_asyncio, settings
|
||||||
|
|
||||||
from ...helpers import (
|
from ...helpers import (
|
||||||
WALLET,
|
funding_source,
|
||||||
get_random_invoice_data,
|
get_random_invoice_data,
|
||||||
get_unconnected_node_uri,
|
get_unconnected_node_uri,
|
||||||
mine_blocks,
|
mine_blocks,
|
||||||
)
|
)
|
||||||
|
|
||||||
pytestmark = pytest.mark.skipif(
|
pytestmark = pytest.mark.skipif(
|
||||||
WALLET.__node_cls__ is None, reason="Cant test if node implementation isnt avilable"
|
funding_source.__node_cls__ is None,
|
||||||
|
reason="Cant test if node implementation isnt available",
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -14,7 +14,7 @@ from pydantic import BaseModel
|
||||||
|
|
||||||
from lnbits import core
|
from lnbits import core
|
||||||
from lnbits.db import DB_TYPE, POSTGRES, FromRowModel
|
from lnbits.db import DB_TYPE, POSTGRES, FromRowModel
|
||||||
from lnbits.wallets import get_wallet_class, set_wallet_class
|
from lnbits.wallets import get_funding_source, set_funding_source
|
||||||
|
|
||||||
|
|
||||||
class DbTestModel(FromRowModel):
|
class DbTestModel(FromRowModel):
|
||||||
|
|
@ -23,10 +23,10 @@ class DbTestModel(FromRowModel):
|
||||||
value: Optional[str] = None
|
value: Optional[str] = None
|
||||||
|
|
||||||
|
|
||||||
def get_random_string(N: int = 10):
|
def get_random_string(iterations: int = 10):
|
||||||
return "".join(
|
return "".join(
|
||||||
random.SystemRandom().choice(string.ascii_uppercase + string.digits)
|
random.SystemRandom().choice(string.ascii_uppercase + string.digits)
|
||||||
for _ in range(N)
|
for _ in range(iterations)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -34,9 +34,9 @@ async def get_random_invoice_data():
|
||||||
return {"out": False, "amount": 10, "memo": f"test_memo_{get_random_string(10)}"}
|
return {"out": False, "amount": 10, "memo": f"test_memo_{get_random_string(10)}"}
|
||||||
|
|
||||||
|
|
||||||
set_wallet_class()
|
set_funding_source()
|
||||||
WALLET = get_wallet_class()
|
funding_source = get_funding_source()
|
||||||
is_fake: bool = WALLET.__class__.__name__ == "FakeWallet"
|
is_fake: bool = funding_source.__class__.__name__ == "FakeWallet"
|
||||||
is_regtest: bool = not is_fake
|
is_regtest: bool = not is_fake
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -131,39 +131,39 @@ def migrate_db(file: str, schema: str, exclude_tables: List[str] = []):
|
||||||
).fetchall()
|
).fetchall()
|
||||||
|
|
||||||
for table in tables:
|
for table in tables:
|
||||||
tableName = table[0]
|
table_name = table[0]
|
||||||
print(f"Migrating table {tableName}")
|
print(f"Migrating table {table_name}")
|
||||||
# hard coded skip for dbversions (already produced during startup)
|
# hard coded skip for dbversions (already produced during startup)
|
||||||
if tableName == "dbversions":
|
if table_name == "dbversions":
|
||||||
continue
|
continue
|
||||||
if exclude_tables and tableName in exclude_tables:
|
if exclude_tables and table_name in exclude_tables:
|
||||||
continue
|
continue
|
||||||
|
|
||||||
columns = cursor.execute(f"PRAGMA table_info({tableName})").fetchall()
|
columns = cursor.execute(f"PRAGMA table_info({table_name})").fetchall()
|
||||||
q = build_insert_query(schema, tableName, columns)
|
q = build_insert_query(schema, table_name, columns)
|
||||||
|
|
||||||
data = cursor.execute(f"SELECT * FROM {tableName};").fetchall()
|
data = cursor.execute(f"SELECT * FROM {table_name};").fetchall()
|
||||||
|
|
||||||
if len(data) == 0:
|
if len(data) == 0:
|
||||||
print(f"🛑 You sneaky dev! Table {tableName} is empty!")
|
print(f"🛑 You sneaky dev! Table {table_name} is empty!")
|
||||||
|
|
||||||
insert_to_pg(q, data)
|
insert_to_pg(q, data)
|
||||||
cursor.close()
|
cursor.close()
|
||||||
|
|
||||||
|
|
||||||
def build_insert_query(schema, tableName, columns):
|
def build_insert_query(schema, table_name, columns):
|
||||||
to_columns = ", ".join([f'"{column[1].lower()}"' for column in columns])
|
to_columns = ", ".join([f'"{column[1].lower()}"' for column in columns])
|
||||||
values = ", ".join([to_column_type(column[2]) for column in columns])
|
values = ", ".join([to_column_type(column[2]) for column in columns])
|
||||||
return f"""
|
return f"""
|
||||||
INSERT INTO {schema}.{tableName}({to_columns})
|
INSERT INTO {schema}.{table_name}({to_columns})
|
||||||
VALUES ({values});
|
VALUES ({values});
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
|
||||||
def to_column_type(columnType):
|
def to_column_type(column_type):
|
||||||
if columnType == "TIMESTAMP":
|
if column_type == "TIMESTAMP":
|
||||||
return "to_timestamp(%s)"
|
return "to_timestamp(%s)"
|
||||||
if columnType in ["BOOLEAN", "BOOL"]:
|
if column_type in ["BOOLEAN", "BOOL"]:
|
||||||
return "%s::boolean"
|
return "%s::boolean"
|
||||||
return "%s"
|
return "%s"
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue