feat: introduce self.features to wallets, refactor feature nodemanager (#3260)

This commit is contained in:
dni ⚡ 2025-07-10 15:40:01 +02:00
parent 256a8098c6
commit 6e9f451419
No known key found for this signature in database
GPG key ID: D1F416F29AD26E87
9 changed files with 36 additions and 29 deletions

View file

@ -7,8 +7,9 @@ from pydantic import BaseModel
from starlette.status import HTTP_503_SERVICE_UNAVAILABLE from starlette.status import HTTP_503_SERVICE_UNAVAILABLE
from lnbits.decorators import check_admin, check_super_user, parse_filters from lnbits.decorators import check_admin, check_super_user, parse_filters
from lnbits.nodes import get_node_class
from lnbits.settings import settings from lnbits.settings import settings
from lnbits.wallets import get_funding_source
from lnbits.wallets.base import Feature
from ...db import Filters, Page from ...db import Filters, Page
from ...nodes.base import ( from ...nodes.base import (
@ -26,9 +27,13 @@ from ...nodes.base import (
from ...utils.cache import cache from ...utils.cache import cache
def require_node(): def require_node() -> Node:
node_class = get_node_class() funding_source = get_funding_source()
if not node_class: if (
not funding_source.features
or Feature.nodemanager not in funding_source.features
or not funding_source.__node_cls__
):
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 +43,7 @@ def require_node():
status_code=HTTPStatus.SERVICE_UNAVAILABLE, status_code=HTTPStatus.SERVICE_UNAVAILABLE,
detail="Not enabled", detail="Not enabled",
) )
return node_class return funding_source.__node_cls__(funding_source)
def check_public(): def check_public():

View file

@ -16,7 +16,6 @@ from packaging import version
from pydantic.schema import field_schema from pydantic.schema import field_schema
from lnbits.jinja2_templating import Jinja2Templates from lnbits.jinja2_templating import Jinja2Templates
from lnbits.nodes import get_node_class
from lnbits.settings import settings from lnbits.settings import settings
from lnbits.utils.crypto import AESCipher from lnbits.utils.crypto import AESCipher
@ -83,8 +82,8 @@ def template_renderer(additional_folders: Optional[list] = None) -> Jinja2Templa
"LNBITS_CUSTOM_BADGE_COLOR": settings.lnbits_custom_badge_color, "LNBITS_CUSTOM_BADGE_COLOR": settings.lnbits_custom_badge_color,
"LNBITS_EXTENSIONS_DEACTIVATE_ALL": settings.lnbits_extensions_deactivate_all, "LNBITS_EXTENSIONS_DEACTIVATE_ALL": settings.lnbits_extensions_deactivate_all,
"LNBITS_NEW_ACCOUNTS_ALLOWED": settings.new_accounts_allowed, "LNBITS_NEW_ACCOUNTS_ALLOWED": settings.new_accounts_allowed,
"LNBITS_NODE_UI": settings.lnbits_node_ui and get_node_class() is not None, "LNBITS_NODE_UI": settings.lnbits_node_ui and settings.has_nodemanager,
"LNBITS_NODE_UI_AVAILABLE": get_node_class() is not None, "LNBITS_NODE_UI_AVAILABLE": settings.has_nodemanager,
"LNBITS_QR_LOGO": settings.lnbits_qr_logo, "LNBITS_QR_LOGO": settings.lnbits_qr_logo,
"LNBITS_SERVICE_FEE": settings.lnbits_service_fee, "LNBITS_SERVICE_FEE": settings.lnbits_service_fee,
"LNBITS_SERVICE_FEE_MAX": settings.lnbits_service_fee_max, "LNBITS_SERVICE_FEE_MAX": settings.lnbits_service_fee_max,

View file

@ -1,15 +0,0 @@
from typing import Optional
from .base import Node
def get_node_class() -> Optional[Node]:
return NODE
def set_node_class(node: Node):
global NODE
NODE = node
NODE: Optional[Node] = None

View file

@ -11,12 +11,12 @@ from httpx import HTTPStatusError
from loguru import logger from loguru import logger
from lnbits.db import Filters, Page from lnbits.db import Filters, Page
from lnbits.nodes import Node
from lnbits.nodes.base import ( from lnbits.nodes.base import (
ChannelBalance, ChannelBalance,
ChannelPoint, ChannelPoint,
ChannelState, ChannelState,
ChannelStats, ChannelStats,
Node,
NodeChannel, NodeChannel,
NodeFees, NodeFees,
NodeInfoResponse, NodeInfoResponse,

View file

@ -987,6 +987,8 @@ class TransientSettings(InstalledExtensionsSettings, ExchangeHistorySettings):
server_startup_time: int = Field(default=time()) server_startup_time: int = Field(default=time())
has_nodemanager: bool = Field(default=False)
@property @property
def lnbits_server_up_time(self) -> str: def lnbits_server_up_time(self) -> str:
up_time = int(time() - self.server_startup_time) up_time = int(time() - self.server_startup_time)

View file

@ -2,9 +2,8 @@ from __future__ import annotations
import importlib import importlib
from lnbits.nodes import set_node_class
from lnbits.settings import settings from lnbits.settings import settings
from lnbits.wallets.base import Wallet from lnbits.wallets.base import Feature, Wallet
from .alby import AlbyWallet from .alby import AlbyWallet
from .blink import BlinkWallet from .blink import BlinkWallet
@ -35,13 +34,12 @@ from .void import VoidWallet
from .zbd import ZBDWallet from .zbd import ZBDWallet
def set_funding_source(class_name: str | None = None): def set_funding_source(class_name: str | None = None) -> None:
backend_wallet_class = class_name or settings.lnbits_backend_wallet_class backend_wallet_class = class_name or settings.lnbits_backend_wallet_class
funding_source_constructor = getattr(wallets_module, backend_wallet_class) funding_source_constructor = getattr(wallets_module, backend_wallet_class)
global funding_source global funding_source
funding_source = funding_source_constructor() funding_source = funding_source_constructor()
if funding_source.__node_cls__: settings.has_nodemanager = funding_source.has_feature(Feature.nodemanager)
set_node_class(funding_source.__node_cls__(funding_source))
def get_funding_source() -> Wallet: def get_funding_source() -> Wallet:

View file

@ -3,6 +3,7 @@ from __future__ import annotations
import asyncio import asyncio
from abc import ABC, abstractmethod from abc import ABC, abstractmethod
from collections.abc import AsyncGenerator, Coroutine from collections.abc import AsyncGenerator, Coroutine
from enum import Enum
from typing import TYPE_CHECKING, NamedTuple from typing import TYPE_CHECKING, NamedTuple
from loguru import logger from loguru import logger
@ -13,6 +14,12 @@ if TYPE_CHECKING:
from lnbits.nodes.base import Node from lnbits.nodes.base import Node
class Feature(Enum):
nodemanager = "nodemanager"
# hold = "hold"
# bolt12 = "bolt12"
class StatusResponse(NamedTuple): class StatusResponse(NamedTuple):
error_message: str | None error_message: str | None
balance_msat: int balance_msat: int
@ -100,6 +107,10 @@ class PaymentPendingStatus(PaymentStatus):
class Wallet(ABC): class Wallet(ABC):
__node_cls__: type[Node] | None = None __node_cls__: type[Node] | None = None
features: list[Feature] | None = None
def has_feature(self, feature: Feature) -> bool:
return self.features is not None and feature in self.features
def __init__(self) -> None: def __init__(self) -> None:
self.pending_invoices: list[str] = [] self.pending_invoices: list[str] = []

View file

@ -14,6 +14,7 @@ from lnbits.settings import settings
from lnbits.utils.crypto import random_secret_and_hash from lnbits.utils.crypto import random_secret_and_hash
from .base import ( from .base import (
Feature,
InvoiceResponse, InvoiceResponse,
PaymentFailedStatus, PaymentFailedStatus,
PaymentPendingStatus, PaymentPendingStatus,
@ -31,12 +32,16 @@ async def run_sync(func) -> Any:
class CoreLightningWallet(Wallet): class CoreLightningWallet(Wallet):
"""Core Lightning RPC implementation."""
__node_cls__ = CoreLightningNode __node_cls__ = CoreLightningNode
features = [Feature.nodemanager]
async def cleanup(self): async def cleanup(self):
pass pass
def __init__(self): def __init__(self):
rpc = settings.corelightning_rpc or settings.clightning_rpc rpc = settings.corelightning_rpc or settings.clightning_rpc
if not rpc: if not rpc:
raise ValueError( raise ValueError(

View file

@ -14,6 +14,7 @@ from lnbits.settings import settings
from lnbits.utils.crypto import random_secret_and_hash from lnbits.utils.crypto import random_secret_and_hash
from .base import ( from .base import (
Feature,
InvoiceResponse, InvoiceResponse,
PaymentFailedStatus, PaymentFailedStatus,
PaymentPendingStatus, PaymentPendingStatus,
@ -30,6 +31,7 @@ class LndRestWallet(Wallet):
"""https://api.lightning.community/rest/index.html#lnd-rest-api-reference""" """https://api.lightning.community/rest/index.html#lnd-rest-api-reference"""
__node_cls__ = LndRestNode __node_cls__ = LndRestNode
features = [Feature.nodemanager]
def __init__(self): def __init__(self):
if not settings.lnd_rest_endpoint: if not settings.lnd_rest_endpoint: