diff --git a/app/models/lightning.py b/app/models/lightning.py index 5e22b34..2d1e274 100644 --- a/app/models/lightning.py +++ b/app/models/lightning.py @@ -2,6 +2,7 @@ from enum import Enum from typing import List, Optional, Union from deepdiff import DeepDiff +from fastapi.param_functions import Query from pydantic import BaseModel @@ -857,38 +858,80 @@ def ln_info_from_grpc(i) -> LnInfo: ) -class Amount(BaseModel): - sat: int - msat: int +class LightningStatus(BaseModel): + implementation: str = Query( + ..., description="Lightning software implementation (LND, c-lightning)" + ) + version: str = Query(..., description="Version of the implementation") + num_pending_channels: int = Query(..., description="Number of pending channels") + num_active_channels: int = Query(..., description="Number of active channels") + num_inactive_channels: int = Query(..., description="Number of inactive channels") + block_height: int = Query( + ..., description="The node's current view of the height of the best block" + ) + synced_to_chain: bool = Query( + ..., description="Whether the wallet's view is synced to the main chain" + ) + synced_to_graph: bool = Query( + ..., + description="Whether we consider ourselves synced with the public channel graph.", + ) - -def amount_from_grpc(amount) -> Amount: - return Amount(sat=amount.sat, msat=amount.msat) + @classmethod + def from_grpc(cls, name: str, info: LnInfo): + return cls( + implementation=name, + version=info.version, + num_pending_channels=info.num_pending_channels, + num_active_channels=info.num_active_channels, + num_inactive_channels=info.num_inactive_channels, + block_height=info.block_height, + synced_to_chain=info.synced_to_chain, + synced_to_graph=info.synced_to_graph, + ) class WalletBalance(BaseModel): - onchain_confirmed_balance: int - onchain_total_balance: int - onchain_unconfirmed_balance: int - local_balance: Amount - remote_balance: Amount - unsettled_local_balance: Amount - unsettled_remote_balance: Amount - pending_open_local_balance: Amount - pending_open_remote_balance: Amount - - -def wallet_balance_from_grpc(onchain, channel) -> WalletBalance: - return WalletBalance( - onchain_confirmed_balance=onchain.confirmed_balance, - onchain_total_balance=onchain.total_balance, - onchain_unconfirmed_balance=onchain.unconfirmed_balance, - local_balance=amount_from_grpc(channel.local_balance), - remote_balance=amount_from_grpc(channel.remote_balance), - unsettled_local_balance=amount_from_grpc(channel.unsettled_local_balance), - unsettled_remote_balance=amount_from_grpc(channel.unsettled_remote_balance), - pending_open_local_balance=amount_from_grpc(channel.pending_open_local_balance), - pending_open_remote_balance=amount_from_grpc( - channel.pending_open_remote_balance - ), + onchain_confirmed_balance: int = Query( + ..., + description="Confirmed onchain balance (more than three confirmations) in sat", ) + onchain_total_balance: int = Query( + ..., description="Total combined onchain balance in sat" + ) + onchain_unconfirmed_balance: int = Query( + ..., + description="Unconfirmed onchain balance (less than three confirmations) in sat", + ) + channel_local_balance: int = Query( + ..., description="Sum of channels local balances in msat" + ) + channel_remote_balance: int = Query( + ..., description="Sum of channels remote balances in msat." + ) + channel_unsettled_local_balance: int = Query( + ..., description="Sum of channels local unsettled balances in msat." + ) + channel_unsettled_remote_balance: int = Query( + ..., description="Sum of channels remote unsettled balances in msat." + ) + channel_pending_open_local_balance: int = Query( + ..., description="Sum of channels pending local balances in msat." + ) + channel_pending_open_remote_balance: int = Query( + ..., description="Sum of channels pending remote balances in msat." + ) + + @classmethod + def from_grpc(cls, onchain, channel) -> "WalletBalance": + return cls( + onchain_confirmed_balance=onchain.confirmed_balance, + onchain_total_balance=onchain.total_balance, + onchain_unconfirmed_balance=onchain.unconfirmed_balance, + channel_local_balance=channel.local_balance.msat, + channel_remote_balance=channel.remote_balance.msat, + channel_unsettled_local_balance=channel.unsettled_local_balance.msat, + channel_unsettled_remote_balance=channel.unsettled_remote_balance.msat, + channel_pending_open_local_balance=channel.pending_open_local_balance.msat, + channel_pending_open_remote_balance=channel.pending_open_remote_balance.msat, + ) diff --git a/app/repositories/lightning.py b/app/repositories/lightning.py index 5c30ade..f393fef 100644 --- a/app/repositories/lightning.py +++ b/app/repositories/lightning.py @@ -1,9 +1,10 @@ -from app.models.lightning import Invoice, LnInfo, Payment +from app.models.lightning import Invoice, LightningStatus, LnInfo, Payment from app.utils import SSE, lightning_config, send_sse_message if lightning_config.ln_node == "lnd": from app.repositories.ln_impl.lnd import ( add_invoice_impl, + get_implementation_name, get_ln_info_impl, get_wallet_balance_impl, register_lightning_listener_impl, @@ -12,6 +13,7 @@ if lightning_config.ln_node == "lnd": else: from app.repositories.ln_impl.clightning import ( add_invoice_impl, + get_implementation_name, get_ln_info_impl, get_wallet_balance_impl, register_lightning_listener_impl, @@ -19,6 +21,12 @@ else: ) +async def get_ln_status() -> LightningStatus: + ln_info = await get_ln_info_impl() + name = get_implementation_name() + return LightningStatus.from_grpc(name, ln_info) + + async def get_wallet_balance(): return await get_wallet_balance_impl() diff --git a/app/repositories/ln_impl/clightning.py b/app/repositories/ln_impl/clightning.py index f1f1d00..7478c04 100644 --- a/app/repositories/ln_impl/clightning.py +++ b/app/repositories/ln_impl/clightning.py @@ -1,6 +1,10 @@ from app.models.lightning import Invoice, LnInfo, Payment +def get_implementation_name() -> str: + return "c-lightning" + + async def get_wallet_balance_impl(): raise NotImplementedError("c-lightning not yet implemented") diff --git a/app/repositories/ln_impl/lnd.py b/app/repositories/ln_impl/lnd.py index 20d8f8a..98a3bd3 100644 --- a/app/repositories/ln_impl/lnd.py +++ b/app/repositories/ln_impl/lnd.py @@ -13,7 +13,6 @@ from app.models.lightning import ( invoice_from_grpc, ln_info_from_grpc, payment_from_grpc, - wallet_balance_from_grpc, ) from app.utils import SSE from app.utils import lightning_config as lncfg @@ -27,13 +26,17 @@ GATHER_INFO_INTERVALL = config("gather_ln_info_interval", default=5, cast=float) _CACHE = {"wallet_balance": None} +def get_implementation_name() -> str: + return "LND" + + async def get_wallet_balance_impl() -> WalletBalance: req = ln.WalletBalanceRequest() req = ln.ChannelBalanceRequest() onchain = await lncfg.lnd_stub.WalletBalance(req) channel = await lncfg.lnd_stub.ChannelBalance(req) - return wallet_balance_from_grpc(onchain, channel) + return WalletBalance.from_grpc(onchain, channel) async def add_invoice_impl( diff --git a/app/routers/lightning.py b/app/routers/lightning.py index a373c55..03972db 100644 --- a/app/routers/lightning.py +++ b/app/routers/lightning.py @@ -1,8 +1,15 @@ from app.auth.auth_bearer import JWTBearer -from app.models.lightning import Invoice, LnInfo, Payment, WalletBalance +from app.models.lightning import ( + Invoice, + LightningStatus, + LnInfo, + Payment, + WalletBalance, +) from app.repositories.lightning import ( add_invoice, get_ln_info, + get_ln_status, get_wallet_balance, send_payment, ) @@ -13,6 +20,22 @@ from fastapi.params import Depends router = APIRouter(prefix="/lightning", tags=["Lightning"]) +@router.get( + "/get_ln_status", + summary="Get current lightning system status", + dependencies=[Depends(JWTBearer())], + status_code=status.HTTP_200_OK, + response_model=LightningStatus, +) +async def get_ln_status_path(): + try: + return await get_ln_status() + except HTTPException as r: + raise HTTPException(r.status_code, detail=r.reason) + except NotImplementedError as r: + raise HTTPException(status.HTTP_501_NOT_IMPLEMENTED, detail=r.args[0]) + + @router.post( "/addinvoice", summary="Addinvoice adds a new Invoice to the database.",