2021-11-02 19:02:08 +01:00
|
|
|
import asyncio
|
refactor: improve startup procedure
During startup the API will try to connect to Bitcoin Core and the
Lightning Node. If it can't connect it will check every "n" seconds
(currently 2s) and connect when available. A new SSE event
called "system_startup_info" is introduced. This event contains
all startup status information during the startup procedure.
The old wallet_locked event is obsolete.
Sample:
--------------------------
event: system_startup_info
data: {"bitcoin": "offline", "bitcoin_msg": "", "lightning": "offline", "lightning_msg": "Unable to connect to LND daemon, waiting..."}
--------------------------
event: system_startup_info
data: {"bitcoin": "done", "bitcoin_msg": "", "lightning": "offline", "lightning_msg": "Unable to connect to LND daemon, waiting..."}
--------------------------
event: system_startup_info
data: {"bitcoin": "done", "bitcoin_msg": "", "lightning": "locked", "lightning_msg": "Wallet locked, unlock it to enable full RPC access"}
--------------------------
event: system_startup_info
data: {"bitcoin": "done", "bitcoin_msg": "", "lightning": "done", "lightning_msg": ""}
--------------------------
event: system_startup_info
data: {"bitcoin": "done", "bitcoin_msg": "", "lightning": "bootstraping", "lightning_msg": "RPC not yet available"}
--------------------------
refs #97
2022-06-06 19:29:21 +02:00
|
|
|
import os
|
|
|
|
|
from typing import AsyncGenerator, List, Optional
|
2021-07-25 18:15:26 +02:00
|
|
|
|
2022-03-20 17:20:56 +01:00
|
|
|
import grpc
|
|
|
|
|
from fastapi.exceptions import HTTPException
|
2023-04-03 20:17:48 +02:00
|
|
|
from loguru import logger
|
2022-03-20 17:20:56 +01:00
|
|
|
from starlette import status
|
|
|
|
|
|
2022-11-28 19:07:25 +01:00
|
|
|
import app.bitcoind.service as btc
|
2022-10-09 20:16:49 +02:00
|
|
|
import app.lightning.impl.protos.lnd.lightning_pb2 as ln
|
|
|
|
|
import app.lightning.impl.protos.lnd.lightning_pb2_grpc as lnrpc
|
|
|
|
|
import app.lightning.impl.protos.lnd.router_pb2 as router
|
|
|
|
|
import app.lightning.impl.protos.lnd.router_pb2_grpc as routerrpc
|
|
|
|
|
import app.lightning.impl.protos.lnd.walletunlocker_pb2 as unlocker
|
|
|
|
|
import app.lightning.impl.protos.lnd.walletunlocker_pb2_grpc as unlockerrpc
|
2025-02-16 20:35:21 +01:00
|
|
|
from app.api.config import config as dconfig
|
2026-07-11 11:29:55 +02:00
|
|
|
from app.api.utils import Event, broadcast_msg, config_get_hex_str
|
2023-05-01 20:48:10 +02:00
|
|
|
from app.lightning.exceptions import NodeNotFoundError
|
2022-10-09 20:16:49 +02:00
|
|
|
from app.lightning.impl.ln_base import LightningNodeBase
|
|
|
|
|
from app.lightning.models import (
|
2022-05-18 18:20:13 +02:00
|
|
|
Channel,
|
2022-01-09 20:20:21 +01:00
|
|
|
FeeRevenue,
|
2022-01-15 14:27:08 +01:00
|
|
|
ForwardSuccessEvent,
|
2021-11-02 19:02:08 +01:00
|
|
|
GenericTx,
|
refactor: improve startup procedure
During startup the API will try to connect to Bitcoin Core and the
Lightning Node. If it can't connect it will check every "n" seconds
(currently 2s) and connect when available. A new SSE event
called "system_startup_info" is introduced. This event contains
all startup status information during the startup procedure.
The old wallet_locked event is obsolete.
Sample:
--------------------------
event: system_startup_info
data: {"bitcoin": "offline", "bitcoin_msg": "", "lightning": "offline", "lightning_msg": "Unable to connect to LND daemon, waiting..."}
--------------------------
event: system_startup_info
data: {"bitcoin": "done", "bitcoin_msg": "", "lightning": "offline", "lightning_msg": "Unable to connect to LND daemon, waiting..."}
--------------------------
event: system_startup_info
data: {"bitcoin": "done", "bitcoin_msg": "", "lightning": "locked", "lightning_msg": "Wallet locked, unlock it to enable full RPC access"}
--------------------------
event: system_startup_info
data: {"bitcoin": "done", "bitcoin_msg": "", "lightning": "done", "lightning_msg": ""}
--------------------------
event: system_startup_info
data: {"bitcoin": "done", "bitcoin_msg": "", "lightning": "bootstraping", "lightning_msg": "RPC not yet available"}
--------------------------
refs #97
2022-06-06 19:29:21 +02:00
|
|
|
InitLnRepoUpdate,
|
2021-09-05 08:56:53 +02:00
|
|
|
Invoice,
|
|
|
|
|
InvoiceState,
|
|
|
|
|
LnInfo,
|
refactor: improve startup procedure
During startup the API will try to connect to Bitcoin Core and the
Lightning Node. If it can't connect it will check every "n" seconds
(currently 2s) and connect when available. A new SSE event
called "system_startup_info" is introduced. This event contains
all startup status information during the startup procedure.
The old wallet_locked event is obsolete.
Sample:
--------------------------
event: system_startup_info
data: {"bitcoin": "offline", "bitcoin_msg": "", "lightning": "offline", "lightning_msg": "Unable to connect to LND daemon, waiting..."}
--------------------------
event: system_startup_info
data: {"bitcoin": "done", "bitcoin_msg": "", "lightning": "offline", "lightning_msg": "Unable to connect to LND daemon, waiting..."}
--------------------------
event: system_startup_info
data: {"bitcoin": "done", "bitcoin_msg": "", "lightning": "locked", "lightning_msg": "Wallet locked, unlock it to enable full RPC access"}
--------------------------
event: system_startup_info
data: {"bitcoin": "done", "bitcoin_msg": "", "lightning": "done", "lightning_msg": ""}
--------------------------
event: system_startup_info
data: {"bitcoin": "done", "bitcoin_msg": "", "lightning": "bootstraping", "lightning_msg": "RPC not yet available"}
--------------------------
refs #97
2022-06-06 19:29:21 +02:00
|
|
|
LnInitState,
|
2021-11-22 21:54:42 +01:00
|
|
|
NewAddressInput,
|
|
|
|
|
OnchainAddressType,
|
2021-10-31 16:49:15 +01:00
|
|
|
OnChainTransaction,
|
2021-09-05 08:56:53 +02:00
|
|
|
Payment,
|
2021-09-20 17:00:36 +02:00
|
|
|
PaymentRequest,
|
2021-10-03 20:55:28 +02:00
|
|
|
SendCoinsInput,
|
|
|
|
|
SendCoinsResponse,
|
2021-09-05 08:56:53 +02:00
|
|
|
WalletBalance,
|
|
|
|
|
)
|
2026-07-11 12:43:31 +02:00
|
|
|
from app.lightning.utils import alias_or_empty, raise_for_pay_req_decode_error
|
refactor: improve startup procedure
During startup the API will try to connect to Bitcoin Core and the
Lightning Node. If it can't connect it will check every "n" seconds
(currently 2s) and connect when available. A new SSE event
called "system_startup_info" is introduced. This event contains
all startup status information during the startup procedure.
The old wallet_locked event is obsolete.
Sample:
--------------------------
event: system_startup_info
data: {"bitcoin": "offline", "bitcoin_msg": "", "lightning": "offline", "lightning_msg": "Unable to connect to LND daemon, waiting..."}
--------------------------
event: system_startup_info
data: {"bitcoin": "done", "bitcoin_msg": "", "lightning": "offline", "lightning_msg": "Unable to connect to LND daemon, waiting..."}
--------------------------
event: system_startup_info
data: {"bitcoin": "done", "bitcoin_msg": "", "lightning": "locked", "lightning_msg": "Wallet locked, unlock it to enable full RPC access"}
--------------------------
event: system_startup_info
data: {"bitcoin": "done", "bitcoin_msg": "", "lightning": "done", "lightning_msg": ""}
--------------------------
event: system_startup_info
data: {"bitcoin": "done", "bitcoin_msg": "", "lightning": "bootstraping", "lightning_msg": "RPC not yet available"}
--------------------------
refs #97
2022-06-06 19:29:21 +02:00
|
|
|
|
|
|
|
|
|
2023-04-03 20:17:48 +02:00
|
|
|
@logger.catch(exclude=(HTTPException,))
|
2026-07-11 10:02:12 +02:00
|
|
|
def _check_transient_ln_error(error):
|
|
|
|
|
"""Map known transient LND gRPC errors to appropriate HTTP statuses.
|
2022-10-03 10:44:34 +02:00
|
|
|
|
2026-07-11 10:02:12 +02:00
|
|
|
Returns without raising for unknown errors, leaving the caller to turn
|
|
|
|
|
them into a 500.
|
|
|
|
|
"""
|
|
|
|
|
logger.debug("logger._check_transient_ln_error()")
|
|
|
|
|
|
|
|
|
|
details = error.details()
|
|
|
|
|
if details is None:
|
|
|
|
|
return
|
|
|
|
|
|
|
|
|
|
if details.find("wallet locked") > -1:
|
2022-10-03 10:44:34 +02:00
|
|
|
raise HTTPException(
|
|
|
|
|
status.HTTP_423_LOCKED,
|
|
|
|
|
detail="Wallet is locked. Unlock via /lightning/unlock-wallet",
|
|
|
|
|
)
|
refactor: improve startup procedure
During startup the API will try to connect to Bitcoin Core and the
Lightning Node. If it can't connect it will check every "n" seconds
(currently 2s) and connect when available. A new SSE event
called "system_startup_info" is introduced. This event contains
all startup status information during the startup procedure.
The old wallet_locked event is obsolete.
Sample:
--------------------------
event: system_startup_info
data: {"bitcoin": "offline", "bitcoin_msg": "", "lightning": "offline", "lightning_msg": "Unable to connect to LND daemon, waiting..."}
--------------------------
event: system_startup_info
data: {"bitcoin": "done", "bitcoin_msg": "", "lightning": "offline", "lightning_msg": "Unable to connect to LND daemon, waiting..."}
--------------------------
event: system_startup_info
data: {"bitcoin": "done", "bitcoin_msg": "", "lightning": "locked", "lightning_msg": "Wallet locked, unlock it to enable full RPC access"}
--------------------------
event: system_startup_info
data: {"bitcoin": "done", "bitcoin_msg": "", "lightning": "done", "lightning_msg": ""}
--------------------------
event: system_startup_info
data: {"bitcoin": "done", "bitcoin_msg": "", "lightning": "bootstraping", "lightning_msg": "RPC not yet available"}
--------------------------
refs #97
2022-06-06 19:29:21 +02:00
|
|
|
|
2026-07-11 10:02:12 +02:00
|
|
|
if "the RPC server is in the process of starting up" in details:
|
|
|
|
|
# LND is up but its RPC server isn't ready yet; signal a retryable
|
|
|
|
|
# status instead of a generic 500 (blitz_api#247)
|
|
|
|
|
raise HTTPException(
|
|
|
|
|
status.HTTP_425_TOO_EARLY,
|
|
|
|
|
detail=(
|
|
|
|
|
"The Lightning RPC server is starting up and not yet ready. "
|
|
|
|
|
"Please try again shortly."
|
|
|
|
|
),
|
|
|
|
|
)
|
|
|
|
|
|
refactor: improve startup procedure
During startup the API will try to connect to Bitcoin Core and the
Lightning Node. If it can't connect it will check every "n" seconds
(currently 2s) and connect when available. A new SSE event
called "system_startup_info" is introduced. This event contains
all startup status information during the startup procedure.
The old wallet_locked event is obsolete.
Sample:
--------------------------
event: system_startup_info
data: {"bitcoin": "offline", "bitcoin_msg": "", "lightning": "offline", "lightning_msg": "Unable to connect to LND daemon, waiting..."}
--------------------------
event: system_startup_info
data: {"bitcoin": "done", "bitcoin_msg": "", "lightning": "offline", "lightning_msg": "Unable to connect to LND daemon, waiting..."}
--------------------------
event: system_startup_info
data: {"bitcoin": "done", "bitcoin_msg": "", "lightning": "locked", "lightning_msg": "Wallet locked, unlock it to enable full RPC access"}
--------------------------
event: system_startup_info
data: {"bitcoin": "done", "bitcoin_msg": "", "lightning": "done", "lightning_msg": ""}
--------------------------
event: system_startup_info
data: {"bitcoin": "done", "bitcoin_msg": "", "lightning": "bootstraping", "lightning_msg": "RPC not yet available"}
--------------------------
refs #97
2022-06-06 19:29:21 +02:00
|
|
|
|
|
|
|
|
# Due to updated ECDSA generated tls.cert we need to let gprc know that
|
|
|
|
|
# we need to use that cipher suite otherwise there will be a handshake
|
|
|
|
|
# error when we communicate with the lnd rpc server.
|
|
|
|
|
os.environ["GRPC_SSL_CIPHER_SUITES"] = "HIGH+ECDSA"
|
|
|
|
|
|
|
|
|
|
# Uncomment to see full gRPC logs
|
|
|
|
|
# os.environ["GRPC_TRACE"] = "all"
|
|
|
|
|
# os.environ["GRPC_VERBOSITY"] = "DEBUG"
|
|
|
|
|
|
|
|
|
|
|
2022-10-03 10:44:34 +02:00
|
|
|
class LnNodeLNDgRPC(LightningNodeBase):
|
|
|
|
|
_lnd_connect_error_debug_msg = """
|
2023-04-03 20:17:48 +02:00
|
|
|
Unable to connect to LND. Possible reasons:
|
2022-10-03 10:44:34 +02:00
|
|
|
* Node is not reachable (ports, network down, ...)
|
|
|
|
|
* Macaroon is not correct
|
|
|
|
|
* IP is not included in LND tls certificate
|
|
|
|
|
Add tlsextraip=192.168.1.xxx to lnd.conf and restart LND.
|
|
|
|
|
This will recreate the TLS certificate. The .env must be adapted accordingly.
|
|
|
|
|
* TLS certificate is wrong. (settings changed, ...)
|
2022-06-17 21:14:33 +02:00
|
|
|
|
2022-10-09 20:16:49 +02:00
|
|
|
To Debug gRPC problems uncomment the following line in app.lightning.impl.lnd_grpc.py
|
2022-10-03 10:44:34 +02:00
|
|
|
# os.environ["GRPC_VERBOSITY"] = "DEBUG"
|
|
|
|
|
This will show more debug information.
|
|
|
|
|
"""
|
2022-06-26 10:59:56 +02:00
|
|
|
|
2022-10-03 10:44:34 +02:00
|
|
|
# Decoding the payment request take a long time,
|
|
|
|
|
# hence we build a simple cache here.
|
|
|
|
|
_memo_cache = {}
|
|
|
|
|
_initialized = False
|
2022-06-26 10:59:56 +02:00
|
|
|
|
2022-10-03 10:44:34 +02:00
|
|
|
def _create_stubs(self) -> None:
|
|
|
|
|
if self._channel is not None:
|
2023-04-03 20:17:48 +02:00
|
|
|
logger.warning("gRPC channel already created.")
|
2022-10-03 10:44:34 +02:00
|
|
|
return
|
refactor: improve startup procedure
During startup the API will try to connect to Bitcoin Core and the
Lightning Node. If it can't connect it will check every "n" seconds
(currently 2s) and connect when available. A new SSE event
called "system_startup_info" is introduced. This event contains
all startup status information during the startup procedure.
The old wallet_locked event is obsolete.
Sample:
--------------------------
event: system_startup_info
data: {"bitcoin": "offline", "bitcoin_msg": "", "lightning": "offline", "lightning_msg": "Unable to connect to LND daemon, waiting..."}
--------------------------
event: system_startup_info
data: {"bitcoin": "done", "bitcoin_msg": "", "lightning": "offline", "lightning_msg": "Unable to connect to LND daemon, waiting..."}
--------------------------
event: system_startup_info
data: {"bitcoin": "done", "bitcoin_msg": "", "lightning": "locked", "lightning_msg": "Wallet locked, unlock it to enable full RPC access"}
--------------------------
event: system_startup_info
data: {"bitcoin": "done", "bitcoin_msg": "", "lightning": "done", "lightning_msg": ""}
--------------------------
event: system_startup_info
data: {"bitcoin": "done", "bitcoin_msg": "", "lightning": "bootstraping", "lightning_msg": "RPC not yet available"}
--------------------------
refs #97
2022-06-06 19:29:21 +02:00
|
|
|
|
2023-04-03 18:49:48 +02:00
|
|
|
opts = [("grpc.max_receive_message_length", 1024 * 1024 * 10)]
|
|
|
|
|
|
2022-10-03 10:44:34 +02:00
|
|
|
self._channel = grpc.aio.secure_channel(
|
2023-04-03 18:49:48 +02:00
|
|
|
self._lnd_grpc_url,
|
|
|
|
|
self._combined_creds,
|
|
|
|
|
options=opts,
|
2022-10-03 10:44:34 +02:00
|
|
|
)
|
|
|
|
|
self._lnd_stub = lnrpc.LightningStub(self._channel)
|
|
|
|
|
self._router_stub = routerrpc.RouterStub(self._channel)
|
|
|
|
|
self._wallet_unlocker = unlockerrpc.WalletUnlockerStub(self._channel)
|
|
|
|
|
|
2023-04-03 20:17:48 +02:00
|
|
|
logger.debug("logger.Created LND gRPC stubs")
|
2022-10-03 10:44:34 +02:00
|
|
|
|
|
|
|
|
def get_implementation_name(self) -> str:
|
|
|
|
|
return "LND_GRPC"
|
|
|
|
|
|
2023-04-03 20:17:48 +02:00
|
|
|
@logger.catch(exclude=(HTTPException,))
|
2022-10-03 10:44:34 +02:00
|
|
|
async def _check_lnd_status(
|
|
|
|
|
self,
|
|
|
|
|
sleep_time: float = 2,
|
|
|
|
|
) -> AsyncGenerator[InitLnRepoUpdate, None]:
|
2023-04-03 20:17:48 +02:00
|
|
|
logger.debug("_check_lnd_status() start")
|
2022-10-03 10:44:34 +02:00
|
|
|
|
|
|
|
|
self._lnd_connect_error_debug_msg_sent = False
|
|
|
|
|
|
|
|
|
|
# Create a temporary channel which will be destroyed at each iteration
|
|
|
|
|
# Reason is that gRPC seems to only try and connect every 5 seconds to
|
|
|
|
|
# the node if it is not running. To avoid the delay we create a new
|
|
|
|
|
# channel each iteration.
|
|
|
|
|
temp_channel = None
|
|
|
|
|
temp_stub = None
|
2023-04-21 22:58:29 +02:00
|
|
|
|
|
|
|
|
# We want to log the wallet locked error only once to avoid spamming the log
|
|
|
|
|
wallet_locked_sent = False
|
2022-10-03 10:44:34 +02:00
|
|
|
while True:
|
|
|
|
|
try:
|
|
|
|
|
if temp_channel is None:
|
|
|
|
|
if self._channel is not None:
|
|
|
|
|
temp_channel = self._channel
|
|
|
|
|
temp_stub = self._lnd_stub
|
|
|
|
|
else:
|
|
|
|
|
temp_channel = grpc.aio.secure_channel(
|
|
|
|
|
self._lnd_grpc_url, self._combined_creds
|
|
|
|
|
)
|
|
|
|
|
temp_stub = lnrpc.LightningStub(temp_channel)
|
|
|
|
|
await temp_stub.GetInfo(ln.GetInfoRequest())
|
|
|
|
|
|
|
|
|
|
if self._channel is None:
|
|
|
|
|
self._create_stubs()
|
|
|
|
|
|
|
|
|
|
await self._init_queue.put(InitLnRepoUpdate(state=LnInitState.DONE))
|
|
|
|
|
break
|
|
|
|
|
except grpc.aio._call.AioRpcError as error:
|
|
|
|
|
details = error.details()
|
2023-04-03 20:17:48 +02:00
|
|
|
logger.debug(f"Waiting for LND daemon... Details {details}")
|
2022-10-03 10:44:34 +02:00
|
|
|
|
|
|
|
|
if "failed to connect to all addresses" in details:
|
|
|
|
|
await self._init_queue.put(
|
|
|
|
|
InitLnRepoUpdate(
|
|
|
|
|
state=LnInitState.OFFLINE,
|
|
|
|
|
msg="Unable to connect to LND daemon, waiting...",
|
|
|
|
|
)
|
2022-06-17 21:14:33 +02:00
|
|
|
)
|
refactor: improve startup procedure
During startup the API will try to connect to Bitcoin Core and the
Lightning Node. If it can't connect it will check every "n" seconds
(currently 2s) and connect when available. A new SSE event
called "system_startup_info" is introduced. This event contains
all startup status information during the startup procedure.
The old wallet_locked event is obsolete.
Sample:
--------------------------
event: system_startup_info
data: {"bitcoin": "offline", "bitcoin_msg": "", "lightning": "offline", "lightning_msg": "Unable to connect to LND daemon, waiting..."}
--------------------------
event: system_startup_info
data: {"bitcoin": "done", "bitcoin_msg": "", "lightning": "offline", "lightning_msg": "Unable to connect to LND daemon, waiting..."}
--------------------------
event: system_startup_info
data: {"bitcoin": "done", "bitcoin_msg": "", "lightning": "locked", "lightning_msg": "Wallet locked, unlock it to enable full RPC access"}
--------------------------
event: system_startup_info
data: {"bitcoin": "done", "bitcoin_msg": "", "lightning": "done", "lightning_msg": ""}
--------------------------
event: system_startup_info
data: {"bitcoin": "done", "bitcoin_msg": "", "lightning": "bootstraping", "lightning_msg": "RPC not yet available"}
--------------------------
refs #97
2022-06-06 19:29:21 +02:00
|
|
|
|
2022-10-03 10:44:34 +02:00
|
|
|
if not self._lnd_connect_error_debug_msg_sent:
|
2023-04-03 20:17:48 +02:00
|
|
|
logger.debug(self._lnd_connect_error_debug_msg)
|
2022-10-03 10:44:34 +02:00
|
|
|
self._lnd_connect_error_debug_msg_sent = True
|
|
|
|
|
|
|
|
|
|
await temp_channel.close()
|
|
|
|
|
temp_channel = None
|
|
|
|
|
elif "waiting to start, RPC services not available" in details:
|
|
|
|
|
await self._init_queue.put(
|
|
|
|
|
InitLnRepoUpdate(
|
|
|
|
|
state=LnInitState.BOOTSTRAPPING,
|
2023-05-17 16:02:28 +02:00
|
|
|
msg=(
|
|
|
|
|
"Connected but waiting to start, RPC services "
|
|
|
|
|
"not available"
|
|
|
|
|
),
|
2022-10-03 10:44:34 +02:00
|
|
|
)
|
2022-06-17 21:14:33 +02:00
|
|
|
)
|
2022-10-03 10:44:34 +02:00
|
|
|
await temp_channel.close()
|
|
|
|
|
temp_channel = None
|
|
|
|
|
elif "wallet locked, unlock it to enable full RPC access" in details:
|
2023-04-21 22:58:29 +02:00
|
|
|
if not wallet_locked_sent:
|
|
|
|
|
logger.info(
|
2023-05-17 16:02:28 +02:00
|
|
|
(
|
|
|
|
|
"Wallet is locked. Unlock by calling "
|
|
|
|
|
"/lightning/unlock-wallet"
|
|
|
|
|
)
|
2023-04-21 22:58:29 +02:00
|
|
|
)
|
|
|
|
|
|
|
|
|
|
wallet_locked_sent = True
|
2022-10-03 10:44:34 +02:00
|
|
|
await self._init_queue.put(
|
|
|
|
|
InitLnRepoUpdate(
|
|
|
|
|
state=LnInitState.LOCKED,
|
|
|
|
|
msg="Wallet locked, unlock it to enable full RPC access",
|
|
|
|
|
)
|
2022-06-17 21:14:33 +02:00
|
|
|
)
|
2022-10-03 10:44:34 +02:00
|
|
|
if temp_channel != self._channel:
|
|
|
|
|
await temp_channel.close()
|
|
|
|
|
temp_channel = None
|
|
|
|
|
elif (
|
2023-05-17 16:02:28 +02:00
|
|
|
"the RPC server is in the process of starting up, but not yet "
|
|
|
|
|
"ready to accept calls"
|
|
|
|
|
) in details:
|
2022-10-03 10:44:34 +02:00
|
|
|
# message from LND AFTER unlocking the wallet
|
|
|
|
|
await self._init_queue.put(
|
|
|
|
|
InitLnRepoUpdate(
|
|
|
|
|
state=LnInitState.BOOTSTRAPPING_AFTER_UNLOCK,
|
2023-05-17 16:02:28 +02:00
|
|
|
msg=(
|
|
|
|
|
"The RPC server is in the process of starting up, "
|
|
|
|
|
"but not yet ready to accept calls"
|
|
|
|
|
),
|
2022-10-03 10:44:34 +02:00
|
|
|
)
|
2022-06-17 21:14:33 +02:00
|
|
|
)
|
2022-10-03 10:44:34 +02:00
|
|
|
else:
|
2023-04-03 20:17:48 +02:00
|
|
|
logger.error(f"Unknown error: {details}")
|
2022-10-03 10:44:34 +02:00
|
|
|
raise
|
|
|
|
|
|
2023-04-03 20:17:48 +02:00
|
|
|
logger.debug(f"_check_lnd_status() sleeping {sleep_time} seconds...")
|
2022-10-03 10:44:34 +02:00
|
|
|
await asyncio.sleep(sleep_time)
|
refactor: improve startup procedure
During startup the API will try to connect to Bitcoin Core and the
Lightning Node. If it can't connect it will check every "n" seconds
(currently 2s) and connect when available. A new SSE event
called "system_startup_info" is introduced. This event contains
all startup status information during the startup procedure.
The old wallet_locked event is obsolete.
Sample:
--------------------------
event: system_startup_info
data: {"bitcoin": "offline", "bitcoin_msg": "", "lightning": "offline", "lightning_msg": "Unable to connect to LND daemon, waiting..."}
--------------------------
event: system_startup_info
data: {"bitcoin": "done", "bitcoin_msg": "", "lightning": "offline", "lightning_msg": "Unable to connect to LND daemon, waiting..."}
--------------------------
event: system_startup_info
data: {"bitcoin": "done", "bitcoin_msg": "", "lightning": "locked", "lightning_msg": "Wallet locked, unlock it to enable full RPC access"}
--------------------------
event: system_startup_info
data: {"bitcoin": "done", "bitcoin_msg": "", "lightning": "done", "lightning_msg": ""}
--------------------------
event: system_startup_info
data: {"bitcoin": "done", "bitcoin_msg": "", "lightning": "bootstraping", "lightning_msg": "RPC not yet available"}
--------------------------
refs #97
2022-06-06 19:29:21 +02:00
|
|
|
|
2023-04-03 20:17:48 +02:00
|
|
|
logger.debug("_check_lnd_status() done")
|
2022-06-17 21:14:33 +02:00
|
|
|
|
2023-04-03 20:17:48 +02:00
|
|
|
@logger.catch(exclude=(HTTPException,))
|
2022-10-03 10:44:34 +02:00
|
|
|
async def initialize(self) -> AsyncGenerator[InitLnRepoUpdate, None]:
|
2023-04-03 20:17:48 +02:00
|
|
|
logger.trace("Unable to connect to LND daemon, waiting...")
|
2022-06-26 11:06:56 +02:00
|
|
|
|
2022-10-03 10:44:34 +02:00
|
|
|
if self._initialized:
|
2023-04-03 20:17:48 +02:00
|
|
|
logger.warning(
|
2023-05-17 16:02:28 +02:00
|
|
|
(
|
|
|
|
|
"Connection already initialized. "
|
|
|
|
|
"This function must not be called twice."
|
|
|
|
|
)
|
2022-10-03 10:44:34 +02:00
|
|
|
)
|
|
|
|
|
yield InitLnRepoUpdate(state=LnInitState.DONE)
|
2022-06-17 21:14:33 +02:00
|
|
|
|
2025-02-16 20:35:21 +01:00
|
|
|
lnd_macaroon = config_get_hex_str(
|
|
|
|
|
str(dconfig("BAPI_LND_MACAROON")), name="lnd_macaroon"
|
|
|
|
|
)
|
2022-10-03 10:44:34 +02:00
|
|
|
lnd_cert = bytes.fromhex(
|
2025-02-16 20:35:21 +01:00
|
|
|
config_get_hex_str(str(dconfig("BAPI_LND_CERT")), name="lnd_cert")
|
2022-06-17 21:14:33 +02:00
|
|
|
)
|
|
|
|
|
|
2022-10-03 10:44:34 +02:00
|
|
|
def metadata_callback(context, callback):
|
|
|
|
|
# for more info see grpc docs
|
|
|
|
|
callback([("macaroon", lnd_macaroon)], None)
|
2022-06-17 21:14:33 +02:00
|
|
|
|
2025-02-16 20:35:21 +01:00
|
|
|
lnd_grpc_ip = str(dconfig("BAPI_LND_GRPC_IP"))
|
|
|
|
|
lnd_grpc_port = str(dconfig("BAPI_LND_GRPC_PORT"))
|
2022-10-03 10:44:34 +02:00
|
|
|
self._lnd_grpc_url = lnd_grpc_ip + ":" + lnd_grpc_port
|
2022-06-17 21:14:33 +02:00
|
|
|
|
2022-10-03 10:44:34 +02:00
|
|
|
auth_creds = grpc.metadata_call_credentials(metadata_callback)
|
|
|
|
|
ssl_creds = grpc.ssl_channel_credentials(lnd_cert)
|
|
|
|
|
self._combined_creds = grpc.composite_channel_credentials(ssl_creds, auth_creds)
|
|
|
|
|
self._channel = None
|
|
|
|
|
self._lnd_stub = None
|
|
|
|
|
self._router_stub = None
|
|
|
|
|
self._wallet_unlocker = None
|
2022-06-17 21:14:33 +02:00
|
|
|
|
2022-10-03 10:44:34 +02:00
|
|
|
self._init_queue = asyncio.Queue()
|
2022-07-01 19:38:57 +02:00
|
|
|
|
2023-04-03 20:17:48 +02:00
|
|
|
logger.info("Trying to connect to LND daemon ...")
|
2022-06-17 21:14:33 +02:00
|
|
|
|
2026-07-03 22:34:44 +02:00
|
|
|
task = asyncio.create_task(self._check_lnd_status(sleep_time=2))
|
refactor: improve startup procedure
During startup the API will try to connect to Bitcoin Core and the
Lightning Node. If it can't connect it will check every "n" seconds
(currently 2s) and connect when available. A new SSE event
called "system_startup_info" is introduced. This event contains
all startup status information during the startup procedure.
The old wallet_locked event is obsolete.
Sample:
--------------------------
event: system_startup_info
data: {"bitcoin": "offline", "bitcoin_msg": "", "lightning": "offline", "lightning_msg": "Unable to connect to LND daemon, waiting..."}
--------------------------
event: system_startup_info
data: {"bitcoin": "done", "bitcoin_msg": "", "lightning": "offline", "lightning_msg": "Unable to connect to LND daemon, waiting..."}
--------------------------
event: system_startup_info
data: {"bitcoin": "done", "bitcoin_msg": "", "lightning": "locked", "lightning_msg": "Wallet locked, unlock it to enable full RPC access"}
--------------------------
event: system_startup_info
data: {"bitcoin": "done", "bitcoin_msg": "", "lightning": "done", "lightning_msg": ""}
--------------------------
event: system_startup_info
data: {"bitcoin": "done", "bitcoin_msg": "", "lightning": "bootstraping", "lightning_msg": "RPC not yet available"}
--------------------------
refs #97
2022-06-06 19:29:21 +02:00
|
|
|
|
2022-10-03 10:44:34 +02:00
|
|
|
while not self._initialized:
|
|
|
|
|
res = await self._init_queue.get() # type: InitLnRepoUpdate
|
refactor: improve startup procedure
During startup the API will try to connect to Bitcoin Core and the
Lightning Node. If it can't connect it will check every "n" seconds
(currently 2s) and connect when available. A new SSE event
called "system_startup_info" is introduced. This event contains
all startup status information during the startup procedure.
The old wallet_locked event is obsolete.
Sample:
--------------------------
event: system_startup_info
data: {"bitcoin": "offline", "bitcoin_msg": "", "lightning": "offline", "lightning_msg": "Unable to connect to LND daemon, waiting..."}
--------------------------
event: system_startup_info
data: {"bitcoin": "done", "bitcoin_msg": "", "lightning": "offline", "lightning_msg": "Unable to connect to LND daemon, waiting..."}
--------------------------
event: system_startup_info
data: {"bitcoin": "done", "bitcoin_msg": "", "lightning": "locked", "lightning_msg": "Wallet locked, unlock it to enable full RPC access"}
--------------------------
event: system_startup_info
data: {"bitcoin": "done", "bitcoin_msg": "", "lightning": "done", "lightning_msg": ""}
--------------------------
event: system_startup_info
data: {"bitcoin": "done", "bitcoin_msg": "", "lightning": "bootstraping", "lightning_msg": "RPC not yet available"}
--------------------------
refs #97
2022-06-06 19:29:21 +02:00
|
|
|
|
2022-10-03 10:44:34 +02:00
|
|
|
if (
|
|
|
|
|
res.state == LnInitState.BOOTSTRAPPING_AFTER_UNLOCK
|
|
|
|
|
and self._channel is None
|
|
|
|
|
):
|
|
|
|
|
task.cancel()
|
2021-07-21 19:38:49 +02:00
|
|
|
|
2023-05-17 16:02:28 +02:00
|
|
|
if self._channel is None:
|
2022-10-03 10:44:34 +02:00
|
|
|
# if res == _API_WALLET_UNLOCK_EVENT the endpoint function will have
|
|
|
|
|
# created the channel for us.
|
|
|
|
|
self._create_stubs()
|
2021-07-21 19:38:49 +02:00
|
|
|
|
2026-07-03 22:34:44 +02:00
|
|
|
task = asyncio.create_task(self._check_lnd_status(sleep_time=0.5))
|
2022-10-03 10:44:34 +02:00
|
|
|
elif res.state == LnInitState.DONE:
|
|
|
|
|
self._initialized = True
|
|
|
|
|
if not task.cancelled():
|
|
|
|
|
task.cancel()
|
|
|
|
|
elif (
|
|
|
|
|
res.state == LnInitState.OFFLINE
|
|
|
|
|
or res.state == LnInitState.LOCKED
|
2023-04-21 22:58:29 +02:00
|
|
|
or res.state == LnInitState.BOOTSTRAPPING
|
2022-10-03 10:44:34 +02:00
|
|
|
or res.state == LnInitState.BOOTSTRAPPING_AFTER_UNLOCK
|
|
|
|
|
):
|
|
|
|
|
pass # do nothing here
|
2021-11-24 11:12:10 +01:00
|
|
|
else:
|
2024-03-10 07:57:38 +01:00
|
|
|
logger.warning(f"Unhandled initialization event: {res.model_dump()}")
|
2021-11-24 11:12:10 +01:00
|
|
|
|
2022-10-03 10:44:34 +02:00
|
|
|
yield res
|
2021-11-24 11:12:10 +01:00
|
|
|
|
2023-04-21 22:58:29 +02:00
|
|
|
logger.success("Initialization complete.")
|
2021-11-24 11:12:10 +01:00
|
|
|
|
2023-04-03 20:17:48 +02:00
|
|
|
@logger.catch(exclude=(HTTPException,))
|
2022-10-03 10:44:34 +02:00
|
|
|
async def get_wallet_balance(self) -> WalletBalance:
|
2023-04-03 20:17:48 +02:00
|
|
|
logger.trace("get_wallet_balance() ")
|
2021-11-24 11:12:10 +01:00
|
|
|
|
2022-10-03 10:44:34 +02:00
|
|
|
try:
|
|
|
|
|
w_req = ln.WalletBalanceRequest()
|
|
|
|
|
onchain = await self._lnd_stub.WalletBalance(w_req)
|
2021-11-02 19:02:08 +01:00
|
|
|
|
2022-10-03 10:44:34 +02:00
|
|
|
c_req = ln.ChannelBalanceRequest()
|
|
|
|
|
channel = await self._lnd_stub.ChannelBalance(c_req)
|
2021-11-02 19:02:08 +01:00
|
|
|
|
2022-10-03 10:44:34 +02:00
|
|
|
return WalletBalance.from_lnd_grpc(onchain, channel)
|
|
|
|
|
except grpc.aio._call.AioRpcError as error:
|
2026-07-11 10:02:12 +02:00
|
|
|
_check_transient_ln_error(error)
|
2022-10-03 10:44:34 +02:00
|
|
|
raise HTTPException(
|
|
|
|
|
status.HTTP_500_INTERNAL_SERVER_ERROR, detail=error.details()
|
|
|
|
|
)
|
2022-06-26 11:06:56 +02:00
|
|
|
|
2023-04-03 20:17:48 +02:00
|
|
|
@logger.catch(exclude=(HTTPException,))
|
2022-10-03 10:44:34 +02:00
|
|
|
async def list_all_tx(
|
|
|
|
|
self, successful_only: bool, index_offset: int, max_tx: int, reversed: bool
|
|
|
|
|
) -> List[GenericTx]:
|
2023-04-03 20:17:48 +02:00
|
|
|
logger.trace(
|
2023-05-17 16:02:28 +02:00
|
|
|
(
|
|
|
|
|
f"logger.list_all_tx(successful_only={successful_only}, "
|
|
|
|
|
f"index_offset={index_offset}, max_tx={max_tx}, reversed={reversed})"
|
|
|
|
|
)
|
2021-11-24 11:12:10 +01:00
|
|
|
)
|
2021-10-31 19:04:39 +01:00
|
|
|
|
2022-10-03 10:44:34 +02:00
|
|
|
# TODO: find a better caching strategy
|
|
|
|
|
list_invoice_req = ln.ListInvoiceRequest(
|
|
|
|
|
pending_only=successful_only,
|
|
|
|
|
index_offset=0,
|
|
|
|
|
num_max_invoices=0,
|
|
|
|
|
reversed=reversed,
|
2021-11-24 11:12:10 +01:00
|
|
|
)
|
2021-10-31 16:49:15 +01:00
|
|
|
|
2022-10-03 10:44:34 +02:00
|
|
|
get_tx_req = ln.GetTransactionsRequest()
|
2021-10-31 16:49:15 +01:00
|
|
|
|
2022-10-03 10:44:34 +02:00
|
|
|
list_payments_req = ln.ListPaymentsRequest(
|
|
|
|
|
include_incomplete=not successful_only,
|
|
|
|
|
index_offset=0,
|
|
|
|
|
max_payments=0,
|
2021-11-24 11:12:10 +01:00
|
|
|
reversed=reversed,
|
|
|
|
|
)
|
2021-10-31 19:33:35 +01:00
|
|
|
|
2022-10-03 10:44:34 +02:00
|
|
|
try:
|
|
|
|
|
res = await asyncio.gather(
|
|
|
|
|
*[
|
|
|
|
|
self._lnd_stub.ListInvoices(list_invoice_req),
|
|
|
|
|
self._lnd_stub.GetTransactions(get_tx_req),
|
|
|
|
|
self._lnd_stub.ListPayments(list_payments_req),
|
|
|
|
|
]
|
|
|
|
|
)
|
2021-10-31 19:33:35 +01:00
|
|
|
|
2022-10-03 10:44:34 +02:00
|
|
|
tx = []
|
|
|
|
|
for i in res[0].invoices:
|
|
|
|
|
tx.append(GenericTx.from_lnd_grpc_invoice(i))
|
|
|
|
|
for t in res[1].transactions:
|
|
|
|
|
tx.append(GenericTx.from_lnd_grpc_onchain_tx(t))
|
|
|
|
|
for p in res[2].payments:
|
|
|
|
|
comment = ""
|
|
|
|
|
if p.payment_request in self._memo_cache:
|
|
|
|
|
comment = self._memo_cache[p.payment_request]
|
|
|
|
|
else:
|
|
|
|
|
if p.payment_request is not None and p.payment_request != "":
|
|
|
|
|
pr = await self.decode_pay_request(p.payment_request)
|
2024-03-10 21:16:26 +01:00
|
|
|
if pr is None:
|
|
|
|
|
logger.error(
|
|
|
|
|
f"Unable to decode payment request {p.payment_request}"
|
|
|
|
|
)
|
|
|
|
|
continue
|
|
|
|
|
|
2022-10-03 10:44:34 +02:00
|
|
|
comment = pr.description
|
|
|
|
|
self._memo_cache[p.payment_request] = pr.description
|
|
|
|
|
tx.append(GenericTx.from_lnd_grpc_payment(p, comment))
|
2022-06-26 11:06:56 +02:00
|
|
|
|
2022-10-03 10:44:34 +02:00
|
|
|
def sortKey(e: GenericTx):
|
|
|
|
|
return e.time_stamp
|
2021-07-21 19:38:49 +02:00
|
|
|
|
2022-10-03 10:44:34 +02:00
|
|
|
tx.sort(key=sortKey)
|
2021-07-21 19:38:49 +02:00
|
|
|
|
2022-10-03 10:44:34 +02:00
|
|
|
if reversed:
|
|
|
|
|
tx.reverse()
|
2021-07-25 18:15:26 +02:00
|
|
|
|
2023-05-17 16:02:28 +02:00
|
|
|
current_tx = len(tx)
|
|
|
|
|
for i in range(current_tx):
|
2022-10-03 10:44:34 +02:00
|
|
|
tx[i].index = i
|
2021-07-25 18:15:26 +02:00
|
|
|
|
2022-10-03 10:44:34 +02:00
|
|
|
if max_tx == 0:
|
2023-05-17 16:02:28 +02:00
|
|
|
max_tx = current_tx
|
2022-06-26 11:06:56 +02:00
|
|
|
|
2022-10-03 10:44:34 +02:00
|
|
|
return tx[index_offset : index_offset + max_tx]
|
|
|
|
|
except grpc.aio._call.AioRpcError as error:
|
2026-07-11 10:02:12 +02:00
|
|
|
_check_transient_ln_error(error)
|
2021-09-20 17:00:36 +02:00
|
|
|
raise HTTPException(
|
|
|
|
|
status.HTTP_500_INTERNAL_SERVER_ERROR, detail=error.details()
|
|
|
|
|
)
|
|
|
|
|
|
2023-04-03 20:17:48 +02:00
|
|
|
@logger.catch(exclude=(HTTPException,))
|
2022-10-03 10:44:34 +02:00
|
|
|
async def list_invoices(
|
|
|
|
|
self,
|
|
|
|
|
pending_only: bool,
|
|
|
|
|
index_offset: int,
|
|
|
|
|
num_max_invoices: int,
|
|
|
|
|
reversed: bool,
|
|
|
|
|
):
|
2023-04-03 20:17:48 +02:00
|
|
|
logger.trace("logger.list_invoices() ")
|
2021-09-20 17:00:36 +02:00
|
|
|
|
2022-10-03 10:44:34 +02:00
|
|
|
try:
|
|
|
|
|
req = ln.ListInvoiceRequest(
|
|
|
|
|
pending_only=pending_only,
|
|
|
|
|
index_offset=index_offset,
|
|
|
|
|
num_max_invoices=num_max_invoices,
|
|
|
|
|
reversed=reversed,
|
|
|
|
|
)
|
|
|
|
|
response = await self._lnd_stub.ListInvoices(req)
|
|
|
|
|
return [Invoice.from_lnd_grpc(i) for i in response.invoices]
|
|
|
|
|
except grpc.aio._call.AioRpcError as error:
|
2026-07-11 10:02:12 +02:00
|
|
|
_check_transient_ln_error(error)
|
2022-10-03 10:44:34 +02:00
|
|
|
raise HTTPException(
|
|
|
|
|
status.HTTP_500_INTERNAL_SERVER_ERROR, detail=error.details()
|
|
|
|
|
)
|
2022-06-26 11:06:56 +02:00
|
|
|
|
2023-04-03 20:17:48 +02:00
|
|
|
@logger.catch(exclude=(HTTPException,))
|
2022-10-03 10:44:34 +02:00
|
|
|
async def list_on_chain_tx(self) -> List[OnChainTransaction]:
|
2023-04-03 20:17:48 +02:00
|
|
|
logger.trace("logger.list_on_chain_tx() ")
|
2021-10-03 20:55:28 +02:00
|
|
|
|
2022-10-03 10:44:34 +02:00
|
|
|
try:
|
|
|
|
|
req = ln.GetTransactionsRequest()
|
|
|
|
|
response = await self._lnd_stub.GetTransactions(req)
|
|
|
|
|
return [OnChainTransaction.from_lnd_grpc(t) for t in response.transactions]
|
|
|
|
|
except grpc.aio._call.AioRpcError as error:
|
2026-07-11 10:02:12 +02:00
|
|
|
_check_transient_ln_error(error)
|
2021-10-03 20:55:28 +02:00
|
|
|
raise HTTPException(
|
2022-10-03 10:44:34 +02:00
|
|
|
status.HTTP_500_INTERNAL_SERVER_ERROR, detail=error.details()
|
2021-10-03 20:55:28 +02:00
|
|
|
)
|
2022-10-03 10:44:34 +02:00
|
|
|
|
2023-04-03 20:17:48 +02:00
|
|
|
@logger.catch(exclude=(HTTPException,))
|
2022-10-03 10:44:34 +02:00
|
|
|
async def list_payments(
|
|
|
|
|
self,
|
|
|
|
|
include_incomplete: bool,
|
|
|
|
|
index_offset: int,
|
|
|
|
|
max_payments: int,
|
|
|
|
|
reversed: bool,
|
|
|
|
|
):
|
2023-04-03 20:17:48 +02:00
|
|
|
logger.trace(
|
2023-05-17 16:02:28 +02:00
|
|
|
(
|
|
|
|
|
f"logger.list_payments(include_incomplete={include_incomplete}, "
|
|
|
|
|
f"index_offset{index_offset}, max_payments={max_payments}, "
|
|
|
|
|
f"reversed={reversed})"
|
|
|
|
|
)
|
2021-07-27 21:10:25 +02:00
|
|
|
)
|
|
|
|
|
|
2022-10-03 10:44:34 +02:00
|
|
|
try:
|
|
|
|
|
req = ln.ListPaymentsRequest(
|
|
|
|
|
include_incomplete=include_incomplete,
|
|
|
|
|
index_offset=index_offset,
|
|
|
|
|
max_payments=max_payments,
|
|
|
|
|
reversed=reversed,
|
2022-01-09 18:04:10 +01:00
|
|
|
)
|
2022-10-03 10:44:34 +02:00
|
|
|
response = await self._lnd_stub.ListPayments(req)
|
|
|
|
|
return [Payment.from_lnd_grpc(p) for p in response.payments]
|
|
|
|
|
except grpc.aio._call.AioRpcError as error:
|
2026-07-11 10:02:12 +02:00
|
|
|
_check_transient_ln_error(error)
|
2021-09-05 08:56:53 +02:00
|
|
|
raise HTTPException(
|
|
|
|
|
status.HTTP_500_INTERNAL_SERVER_ERROR, detail=error.details()
|
|
|
|
|
)
|
2021-07-27 21:10:25 +02:00
|
|
|
|
2023-04-03 20:17:48 +02:00
|
|
|
@logger.catch(exclude=(HTTPException,))
|
2022-10-03 10:44:34 +02:00
|
|
|
async def add_invoice(
|
|
|
|
|
self,
|
|
|
|
|
value_msat: int,
|
|
|
|
|
memo: str = "",
|
|
|
|
|
expiry: int = 3600,
|
|
|
|
|
is_keysend: bool = False,
|
|
|
|
|
) -> Invoice:
|
2023-04-03 20:17:48 +02:00
|
|
|
logger.trace(
|
2023-05-17 16:02:28 +02:00
|
|
|
(
|
|
|
|
|
f"logger.add_invoice(value_msat={value_msat}, memo={memo}, "
|
|
|
|
|
f"expiry={expiry}, is_keysend={is_keysend})"
|
|
|
|
|
)
|
2022-06-17 21:14:33 +02:00
|
|
|
)
|
|
|
|
|
|
2022-10-03 10:44:34 +02:00
|
|
|
try:
|
|
|
|
|
i = ln.Invoice(
|
|
|
|
|
memo=memo,
|
|
|
|
|
value_msat=value_msat,
|
|
|
|
|
expiry=expiry,
|
|
|
|
|
is_keysend=is_keysend,
|
|
|
|
|
)
|
2021-11-24 11:12:10 +01:00
|
|
|
|
2022-10-03 10:44:34 +02:00
|
|
|
response = await self._lnd_stub.AddInvoice(i)
|
|
|
|
|
|
|
|
|
|
# Can't use Invoice.from_lnd_grpc() here because
|
|
|
|
|
# the response is not a standard invoice
|
|
|
|
|
invoice = Invoice(
|
|
|
|
|
memo=memo,
|
|
|
|
|
expiry=expiry,
|
|
|
|
|
r_hash=response.r_hash.hex(),
|
|
|
|
|
payment_request=response.payment_request,
|
2024-02-24 15:30:32 +01:00
|
|
|
add_index=str(response.add_index),
|
2022-10-03 10:44:34 +02:00
|
|
|
payment_addr=response.payment_addr.hex(),
|
|
|
|
|
state=InvoiceState.OPEN,
|
|
|
|
|
is_keysend=is_keysend,
|
|
|
|
|
value_msat=value_msat,
|
|
|
|
|
)
|
2021-11-24 11:12:10 +01:00
|
|
|
|
2022-10-03 10:44:34 +02:00
|
|
|
return invoice
|
|
|
|
|
except grpc.aio._call.AioRpcError as error:
|
2026-07-11 10:02:12 +02:00
|
|
|
_check_transient_ln_error(error)
|
2022-10-03 10:44:34 +02:00
|
|
|
raise HTTPException(
|
|
|
|
|
status.HTTP_500_INTERNAL_SERVER_ERROR, detail=error.details()
|
|
|
|
|
)
|
2022-06-26 11:06:56 +02:00
|
|
|
|
2023-04-03 20:17:48 +02:00
|
|
|
@logger.catch(exclude=(HTTPException,))
|
2022-10-03 10:44:34 +02:00
|
|
|
async def decode_pay_request(self, pay_req: str) -> PaymentRequest:
|
2023-04-03 20:17:48 +02:00
|
|
|
logger.trace(f"logger.decode_pay_request(pay_req={pay_req})")
|
2022-06-17 21:14:33 +02:00
|
|
|
|
|
|
|
|
try:
|
2022-10-03 10:44:34 +02:00
|
|
|
req = ln.PayReqString(pay_req=pay_req)
|
|
|
|
|
res = await self._lnd_stub.DecodePayReq(req)
|
|
|
|
|
return PaymentRequest.from_lnd_grpc(res)
|
2022-06-17 21:14:33 +02:00
|
|
|
except grpc.aio._call.AioRpcError as error:
|
2026-07-11 10:02:12 +02:00
|
|
|
_check_transient_ln_error(error)
|
2026-07-11 12:43:31 +02:00
|
|
|
details = error.details() or ""
|
|
|
|
|
raise_for_pay_req_decode_error(details)
|
|
|
|
|
raise HTTPException(
|
|
|
|
|
status.HTTP_500_INTERNAL_SERVER_ERROR, detail=details
|
|
|
|
|
)
|
2022-06-17 21:14:33 +02:00
|
|
|
|
2023-04-03 20:17:48 +02:00
|
|
|
@logger.catch(exclude=(HTTPException,))
|
2022-10-03 10:44:34 +02:00
|
|
|
async def get_fee_revenue(self) -> FeeRevenue:
|
2023-05-17 16:02:28 +02:00
|
|
|
logger.trace("logger.get_fee_revenue()")
|
2022-06-17 21:14:33 +02:00
|
|
|
|
2022-10-03 10:44:34 +02:00
|
|
|
req = ln.FeeReportRequest()
|
|
|
|
|
res = await self._lnd_stub.FeeReport(req)
|
|
|
|
|
return FeeRevenue.from_lnd_grpc(res)
|
2022-06-26 11:06:56 +02:00
|
|
|
|
2023-04-03 20:17:48 +02:00
|
|
|
@logger.catch(exclude=(HTTPException,))
|
2022-10-03 10:44:34 +02:00
|
|
|
async def new_address(self, input: NewAddressInput) -> str:
|
2023-04-03 20:17:48 +02:00
|
|
|
logger.trace(f"logger.new_address(input={input})")
|
2022-06-17 21:14:33 +02:00
|
|
|
|
2022-10-03 10:44:34 +02:00
|
|
|
t = 1 if input.type == OnchainAddressType.NP2WKH else 2
|
|
|
|
|
try:
|
|
|
|
|
req = ln.NewAddressRequest(type=t)
|
|
|
|
|
response = await self._lnd_stub.NewAddress(req)
|
|
|
|
|
return response.address
|
|
|
|
|
except grpc.aio._call.AioRpcError as error:
|
2026-07-11 10:02:12 +02:00
|
|
|
_check_transient_ln_error(error)
|
2021-11-24 11:12:10 +01:00
|
|
|
raise HTTPException(
|
|
|
|
|
status.HTTP_500_INTERNAL_SERVER_ERROR, detail=error.details()
|
|
|
|
|
)
|
2021-08-02 20:30:18 +02:00
|
|
|
|
2023-04-03 20:17:48 +02:00
|
|
|
@logger.catch(exclude=(HTTPException,))
|
2022-10-03 10:44:34 +02:00
|
|
|
async def send_coins(self, input: SendCoinsInput) -> SendCoinsResponse:
|
2023-04-03 20:17:48 +02:00
|
|
|
logger.trace(f"logger.send_coins(input={input})")
|
2021-08-02 20:30:18 +02:00
|
|
|
|
2022-10-03 10:44:34 +02:00
|
|
|
try:
|
|
|
|
|
r = ln.SendCoinsRequest(
|
|
|
|
|
addr=input.address,
|
|
|
|
|
amount=input.amount,
|
|
|
|
|
target_conf=input.target_conf,
|
|
|
|
|
sat_per_vbyte=input.sat_per_vbyte,
|
|
|
|
|
min_confs=input.min_confs,
|
|
|
|
|
label=input.label,
|
2022-11-28 19:07:25 +01:00
|
|
|
send_all=input.send_all,
|
2022-10-03 10:44:34 +02:00
|
|
|
)
|
2022-06-26 11:06:56 +02:00
|
|
|
|
2022-11-28 19:07:25 +01:00
|
|
|
bi = await btc.get_blockchain_info()
|
|
|
|
|
sendResponse = await self._lnd_stub.SendCoins(r)
|
|
|
|
|
txResponse = await self._lnd_stub.GetTransactions(
|
|
|
|
|
ln.GetTransactionsRequest(start_height=-1, end_height=bi.blocks)
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
tx = None
|
|
|
|
|
for t in txResponse.transactions:
|
|
|
|
|
if t.tx_hash == sendResponse.txid:
|
|
|
|
|
tx = t
|
|
|
|
|
break
|
|
|
|
|
|
|
|
|
|
r = SendCoinsResponse.from_lnd_grpc(tx, input)
|
2026-07-11 11:29:55 +02:00
|
|
|
await broadcast_msg(Event.LN_ONCHAIN_PAYMENT_STATUS, r.model_dump())
|
2022-10-03 10:44:34 +02:00
|
|
|
return r
|
|
|
|
|
except grpc.aio._call.AioRpcError as error:
|
2026-07-11 10:02:12 +02:00
|
|
|
_check_transient_ln_error(error)
|
2022-10-03 10:44:34 +02:00
|
|
|
details = error.details()
|
|
|
|
|
if details and details.find("invalid bech32 string") > -1:
|
|
|
|
|
raise HTTPException(
|
|
|
|
|
status.HTTP_400_BAD_REQUEST,
|
2023-05-17 16:02:28 +02:00
|
|
|
detail=(
|
|
|
|
|
"Could not parse destination address, destination "
|
|
|
|
|
"should be a valid address."
|
|
|
|
|
),
|
2022-10-03 10:44:34 +02:00
|
|
|
)
|
|
|
|
|
elif details and details.find("insufficient funds available") > -1:
|
|
|
|
|
raise HTTPException(status.HTTP_412_PRECONDITION_FAILED, detail=details)
|
|
|
|
|
else:
|
|
|
|
|
raise HTTPException(
|
|
|
|
|
status.HTTP_500_INTERNAL_SERVER_ERROR, detail=details
|
|
|
|
|
)
|
|
|
|
|
|
2023-04-03 20:17:48 +02:00
|
|
|
@logger.catch(exclude=(HTTPException,))
|
2022-10-03 10:44:34 +02:00
|
|
|
async def send_payment(
|
|
|
|
|
self,
|
|
|
|
|
pay_req: str,
|
|
|
|
|
timeout_seconds: int,
|
|
|
|
|
fee_limit_msat: int,
|
|
|
|
|
amount_msat: Optional[int] = None,
|
|
|
|
|
) -> Payment:
|
2023-04-03 20:17:48 +02:00
|
|
|
logger.trace(
|
2023-05-17 16:02:28 +02:00
|
|
|
(
|
|
|
|
|
f"logger.send_payment(pay_req={pay_req}, "
|
|
|
|
|
f"timeout_seconds={timeout_seconds}, fee_limit_msat={fee_limit_msat}, "
|
|
|
|
|
f"amount_msat={amount_msat})"
|
|
|
|
|
)
|
2021-11-24 11:12:10 +01:00
|
|
|
)
|
|
|
|
|
|
2022-10-03 10:44:34 +02:00
|
|
|
try:
|
|
|
|
|
r = router.SendPaymentRequest(
|
|
|
|
|
payment_request=pay_req,
|
|
|
|
|
timeout_seconds=timeout_seconds,
|
|
|
|
|
fee_limit_msat=fee_limit_msat,
|
|
|
|
|
amt_msat=amount_msat,
|
|
|
|
|
)
|
2021-11-24 11:12:10 +01:00
|
|
|
|
2022-10-03 10:44:34 +02:00
|
|
|
p = None
|
|
|
|
|
async for response in self._router_stub.SendPaymentV2(r):
|
|
|
|
|
p = Payment.from_lnd_grpc(response)
|
2026-07-11 11:29:55 +02:00
|
|
|
await broadcast_msg(Event.LN_PAYMENT_STATUS, p.model_dump())
|
2022-10-03 10:44:34 +02:00
|
|
|
return p
|
|
|
|
|
except grpc.aio._call.AioRpcError as error:
|
2026-07-11 10:02:12 +02:00
|
|
|
_check_transient_ln_error(error)
|
2022-10-03 10:44:34 +02:00
|
|
|
if (
|
2023-05-17 16:02:28 +02:00
|
|
|
error.details() is not None
|
2022-10-03 10:44:34 +02:00
|
|
|
and error.details().find("invalid bech32 string") > -1
|
|
|
|
|
):
|
|
|
|
|
raise HTTPException(
|
|
|
|
|
status.HTTP_400_BAD_REQUEST, detail="Invalid payment request string"
|
|
|
|
|
)
|
|
|
|
|
elif (
|
2023-05-17 16:02:28 +02:00
|
|
|
error.details() is not None
|
2022-10-03 10:44:34 +02:00
|
|
|
and error.details().find("OPENSSL_internal:CERTIFICATE_VERIFY_FAILED.")
|
|
|
|
|
> -1
|
|
|
|
|
):
|
|
|
|
|
raise HTTPException(
|
|
|
|
|
status.HTTP_500_INTERNAL_SERVER_ERROR,
|
|
|
|
|
detail="Invalid LND credentials. SSL certificate verify failed.",
|
|
|
|
|
)
|
|
|
|
|
elif (
|
2023-05-17 16:02:28 +02:00
|
|
|
error.details() is not None
|
2022-10-03 10:44:34 +02:00
|
|
|
and error.details().find(
|
|
|
|
|
"amount must be specified when paying a zero amount invoice"
|
|
|
|
|
)
|
|
|
|
|
> -1
|
|
|
|
|
):
|
|
|
|
|
raise HTTPException(
|
|
|
|
|
status.HTTP_400_BAD_REQUEST,
|
|
|
|
|
detail="amount must be specified when paying a zero amount invoice",
|
|
|
|
|
)
|
|
|
|
|
elif (
|
2023-05-17 16:02:28 +02:00
|
|
|
error.details() is not None
|
2022-10-03 10:44:34 +02:00
|
|
|
and error.details().find(
|
2023-05-17 16:02:28 +02:00
|
|
|
"amount must not be specified when paying a non-zero amount invoice"
|
2022-10-03 10:44:34 +02:00
|
|
|
)
|
|
|
|
|
> -1
|
|
|
|
|
):
|
|
|
|
|
raise HTTPException(
|
|
|
|
|
status.HTTP_400_BAD_REQUEST,
|
2023-05-17 16:02:28 +02:00
|
|
|
detail=(
|
|
|
|
|
"amount must not be specified when paying a non-zero "
|
|
|
|
|
"amount invoice"
|
|
|
|
|
),
|
2022-10-03 10:44:34 +02:00
|
|
|
)
|
|
|
|
|
elif (
|
2023-05-17 16:02:28 +02:00
|
|
|
error.details() is not None
|
2022-10-03 10:44:34 +02:00
|
|
|
and error.details().find("invoice is already paid") > -1
|
|
|
|
|
):
|
|
|
|
|
raise HTTPException(
|
|
|
|
|
status.HTTP_409_CONFLICT, detail="invoice is already paid"
|
|
|
|
|
)
|
|
|
|
|
else:
|
2026-02-02 12:25:30 +01:00
|
|
|
logger.error(error.details())
|
2022-10-03 10:44:34 +02:00
|
|
|
raise HTTPException(
|
|
|
|
|
status.HTTP_500_INTERNAL_SERVER_ERROR, detail=error.details()
|
|
|
|
|
)
|
2022-06-26 11:06:56 +02:00
|
|
|
|
2023-04-03 20:17:48 +02:00
|
|
|
@logger.catch(exclude=(HTTPException,))
|
2022-10-03 10:44:34 +02:00
|
|
|
async def get_ln_info(self) -> LnInfo:
|
2023-05-17 16:02:28 +02:00
|
|
|
logger.trace("logger.get_ln_info()")
|
2022-01-15 14:27:08 +01:00
|
|
|
|
2022-10-03 10:44:34 +02:00
|
|
|
if not self._initialized:
|
|
|
|
|
raise HTTPException(
|
|
|
|
|
status.HTTP_503_SERVICE_UNAVAILABLE, detail="LND not fully initialized"
|
|
|
|
|
)
|
2022-01-15 14:27:08 +01:00
|
|
|
|
2022-10-03 10:44:34 +02:00
|
|
|
try:
|
|
|
|
|
req = ln.GetInfoRequest()
|
|
|
|
|
response = await self._lnd_stub.GetInfo(req)
|
|
|
|
|
return LnInfo.from_lnd_grpc(self.get_implementation_name(), response)
|
|
|
|
|
except grpc.aio._call.AioRpcError as error:
|
2026-07-11 10:02:12 +02:00
|
|
|
_check_transient_ln_error(error)
|
2022-10-03 10:44:34 +02:00
|
|
|
raise HTTPException(
|
|
|
|
|
status.HTTP_500_INTERNAL_SERVER_ERROR, detail=error.details()
|
|
|
|
|
)
|
2022-01-15 14:27:08 +01:00
|
|
|
|
2023-04-03 20:17:48 +02:00
|
|
|
@logger.catch(exclude=(HTTPException,))
|
2022-10-03 10:44:34 +02:00
|
|
|
async def _wait_wallet_fully_ready(self):
|
2023-05-17 16:02:28 +02:00
|
|
|
logger.trace("logger._wait_wallet_fully_ready()")
|
2022-01-15 14:27:08 +01:00
|
|
|
|
2022-10-03 10:44:34 +02:00
|
|
|
# This must only be called after unlocking the wallet.
|
2022-01-15 14:27:08 +01:00
|
|
|
|
2022-10-03 10:44:34 +02:00
|
|
|
while True:
|
|
|
|
|
try:
|
2022-10-04 19:23:09 +02:00
|
|
|
info = await self._lnd_stub.GetInfo(ln.GetInfoRequest())
|
2022-06-26 11:06:56 +02:00
|
|
|
|
2023-05-17 16:02:28 +02:00
|
|
|
if info is not None:
|
2023-04-03 20:17:48 +02:00
|
|
|
logger.debug(
|
2023-05-17 16:02:28 +02:00
|
|
|
(
|
|
|
|
|
"logger._wait_wallet_fully_ready() breaking out of "
|
|
|
|
|
"wait ready loop"
|
|
|
|
|
)
|
2022-10-03 10:44:34 +02:00
|
|
|
)
|
|
|
|
|
break
|
|
|
|
|
except grpc.aio._call.AioRpcError as error:
|
|
|
|
|
details = error.details()
|
|
|
|
|
if (
|
2023-05-17 16:02:28 +02:00
|
|
|
"the RPC server is in the process of starting up, but not yet "
|
|
|
|
|
"ready to accept calls"
|
|
|
|
|
) in details:
|
2022-10-03 10:44:34 +02:00
|
|
|
# message from LND AFTER unlocking the wallet
|
|
|
|
|
await self._init_queue.put(
|
|
|
|
|
InitLnRepoUpdate(
|
|
|
|
|
state=LnInitState.BOOTSTRAPPING_AFTER_UNLOCK,
|
2023-05-17 16:02:28 +02:00
|
|
|
msg=(
|
|
|
|
|
"The RPC server is in the process of starting up, "
|
|
|
|
|
"but not yet ready to accept calls"
|
|
|
|
|
),
|
2022-10-03 10:44:34 +02:00
|
|
|
)
|
|
|
|
|
)
|
|
|
|
|
await asyncio.sleep(0.1)
|
|
|
|
|
else:
|
2023-04-03 20:17:48 +02:00
|
|
|
logger.error(f"logger.Unknown error: {details}")
|
2022-10-03 10:44:34 +02:00
|
|
|
raise
|
2022-05-09 19:45:18 +02:00
|
|
|
|
2023-04-03 20:17:48 +02:00
|
|
|
@logger.catch(exclude=(HTTPException,))
|
2022-10-03 10:44:34 +02:00
|
|
|
async def unlock_wallet(self, password: str) -> bool:
|
2023-05-17 16:02:28 +02:00
|
|
|
logger.trace("logger.unlock_wallet(password=wedontlogpasswords)")
|
2022-05-18 18:20:13 +02:00
|
|
|
|
2022-10-03 10:44:34 +02:00
|
|
|
try:
|
|
|
|
|
if self._channel is None:
|
|
|
|
|
self._create_stubs()
|
2022-05-09 19:45:18 +02:00
|
|
|
|
2022-10-03 10:44:34 +02:00
|
|
|
req = unlocker.UnlockWalletRequest(wallet_password=bytes(password, "utf-8"))
|
|
|
|
|
await self._wallet_unlocker.UnlockWallet(req)
|
|
|
|
|
await self._wait_wallet_fully_ready()
|
|
|
|
|
return True
|
|
|
|
|
except grpc.aio._call.AioRpcError as error:
|
|
|
|
|
if error.details().find("invalid passphrase") > -1:
|
|
|
|
|
raise HTTPException(
|
|
|
|
|
status.HTTP_401_UNAUTHORIZED, detail=error.details()
|
|
|
|
|
)
|
|
|
|
|
elif error.details().find("wallet already unlocked") > -1:
|
|
|
|
|
raise HTTPException(
|
|
|
|
|
status.HTTP_412_PRECONDITION_FAILED, detail=error.details()
|
|
|
|
|
)
|
|
|
|
|
else:
|
|
|
|
|
raise HTTPException(
|
|
|
|
|
status.HTTP_500_INTERNAL_SERVER_ERROR, detail=error.details()
|
|
|
|
|
)
|
2022-05-09 19:45:18 +02:00
|
|
|
|
2023-04-03 20:17:48 +02:00
|
|
|
@logger.catch(exclude=(HTTPException,))
|
2022-10-03 10:44:34 +02:00
|
|
|
async def listen_invoices(self) -> AsyncGenerator[Invoice, None]:
|
2023-05-17 16:02:28 +02:00
|
|
|
logger.trace("logger.listen_invoices()")
|
2022-05-09 19:45:18 +02:00
|
|
|
|
2022-10-03 10:44:34 +02:00
|
|
|
request = ln.InvoiceSubscription()
|
2022-05-09 19:45:18 +02:00
|
|
|
try:
|
2022-10-03 10:44:34 +02:00
|
|
|
async for r in self._lnd_stub.SubscribeInvoices(request):
|
|
|
|
|
yield Invoice.from_lnd_grpc(r)
|
2022-05-09 19:45:18 +02:00
|
|
|
except grpc.aio._call.AioRpcError as error:
|
2026-07-11 10:02:12 +02:00
|
|
|
_check_transient_ln_error(error)
|
2022-10-03 10:44:34 +02:00
|
|
|
raise HTTPException(
|
|
|
|
|
status.HTTP_500_INTERNAL_SERVER_ERROR, detail=error.details()
|
|
|
|
|
)
|
2022-05-09 19:45:18 +02:00
|
|
|
|
2023-04-03 20:17:48 +02:00
|
|
|
@logger.catch(exclude=(HTTPException,))
|
2022-10-03 10:44:34 +02:00
|
|
|
async def listen_forward_events(self) -> ForwardSuccessEvent:
|
2023-05-17 16:02:28 +02:00
|
|
|
logger.trace("logger.listen_forward_events()")
|
2022-05-09 19:45:18 +02:00
|
|
|
|
2022-10-03 10:44:34 +02:00
|
|
|
request = router.SubscribeHtlcEventsRequest()
|
|
|
|
|
try:
|
|
|
|
|
_fwd_cache = {}
|
|
|
|
|
|
|
|
|
|
async for e in self._router_stub.SubscribeHtlcEvents(request):
|
|
|
|
|
if e.event_type != 3:
|
|
|
|
|
continue
|
|
|
|
|
|
|
|
|
|
evt = str(e)
|
|
|
|
|
failed_event = "forward_fail_event" in evt or "link_fail_event" in evt
|
2023-05-17 16:02:28 +02:00
|
|
|
if e.incoming_htlc_id not in _fwd_cache and not failed_event:
|
2022-10-03 10:44:34 +02:00
|
|
|
_fwd_cache[e.incoming_htlc_id] = e
|
|
|
|
|
elif e.incoming_htlc_id in _fwd_cache and not failed_event:
|
|
|
|
|
if hasattr(e, "settle_event") and len(e.settle_event.preimage) > 0:
|
|
|
|
|
old_e = _fwd_cache[e.incoming_htlc_id]
|
|
|
|
|
del _fwd_cache[e.incoming_htlc_id]
|
|
|
|
|
amt_in_msat = old_e.forward_event.info.incoming_amt_msat
|
|
|
|
|
amt_out_msat = old_e.forward_event.info.outgoing_amt_msat
|
|
|
|
|
fee = amt_in_msat - amt_out_msat
|
|
|
|
|
yield ForwardSuccessEvent(
|
|
|
|
|
timestamp_ns=e.timestamp_ns,
|
|
|
|
|
chan_id_in=e.incoming_channel_id,
|
|
|
|
|
chan_id_out=e.outgoing_channel_id,
|
|
|
|
|
amt_in_msat=amt_in_msat,
|
|
|
|
|
amt_out_msat=amt_out_msat,
|
|
|
|
|
fee_msat=fee,
|
|
|
|
|
)
|
|
|
|
|
elif failed_event and e.incoming_htlc_id in _fwd_cache:
|
|
|
|
|
del _fwd_cache[e.incoming_htlc_id]
|
2022-05-09 19:45:18 +02:00
|
|
|
|
2022-10-03 10:44:34 +02:00
|
|
|
except grpc.aio._call.AioRpcError as error:
|
2026-07-11 10:02:12 +02:00
|
|
|
_check_transient_ln_error(error)
|
2022-10-03 10:44:34 +02:00
|
|
|
raise HTTPException(
|
|
|
|
|
status.HTTP_500_INTERNAL_SERVER_ERROR, detail=error.details()
|
|
|
|
|
)
|
|
|
|
|
|
2023-04-03 20:17:48 +02:00
|
|
|
@logger.catch(exclude=(HTTPException,))
|
2022-10-03 10:44:34 +02:00
|
|
|
async def channel_open(
|
|
|
|
|
self, local_funding_amount: int, node_URI: str, target_confs: int
|
|
|
|
|
) -> str:
|
2023-04-03 20:17:48 +02:00
|
|
|
logger.trace(
|
2023-05-17 16:02:28 +02:00
|
|
|
(
|
|
|
|
|
f"logger.channel_open(local_funding_amount={local_funding_amount}, "
|
|
|
|
|
f"node_URI={node_URI}, target_confs={target_confs})"
|
|
|
|
|
)
|
2022-05-09 19:45:18 +02:00
|
|
|
)
|
|
|
|
|
|
2022-10-03 10:44:34 +02:00
|
|
|
try:
|
|
|
|
|
pubkey = node_URI.split("@")[0]
|
|
|
|
|
host = node_URI.split("@")[1]
|
|
|
|
|
|
|
|
|
|
# make sure to be connected to peer
|
|
|
|
|
r = ln.ConnectPeerRequest(
|
|
|
|
|
addr=ln.LightningAddress(pubkey=pubkey, host=host),
|
|
|
|
|
perm=False,
|
|
|
|
|
timeout=10,
|
|
|
|
|
)
|
|
|
|
|
try:
|
|
|
|
|
await self._lnd_stub.ConnectPeer(r)
|
|
|
|
|
except grpc.aio._call.AioRpcError as error:
|
|
|
|
|
if (
|
2023-05-17 16:02:28 +02:00
|
|
|
error.details() is not None
|
2022-10-03 10:44:34 +02:00
|
|
|
and error.details().find("already connected to peer") > -1
|
|
|
|
|
):
|
2023-04-03 20:17:48 +02:00
|
|
|
logger.debug(f"already connected to peer {pubkey}")
|
2022-10-03 10:44:34 +02:00
|
|
|
else:
|
|
|
|
|
raise error
|
2022-05-09 19:45:18 +02:00
|
|
|
|
2022-10-03 10:44:34 +02:00
|
|
|
# open channel
|
|
|
|
|
r = ln.OpenChannelRequest(
|
|
|
|
|
node_pubkey=bytes.fromhex(pubkey),
|
|
|
|
|
local_funding_amount=local_funding_amount,
|
|
|
|
|
target_conf=target_confs,
|
|
|
|
|
)
|
|
|
|
|
async for response in self._lnd_stub.OpenChannel(r):
|
|
|
|
|
return str(response.chan_pending.txid.hex())
|
2022-05-09 19:45:18 +02:00
|
|
|
|
2022-10-03 10:44:34 +02:00
|
|
|
except grpc.aio._call.AioRpcError as error:
|
|
|
|
|
raise HTTPException(
|
|
|
|
|
status.HTTP_500_INTERNAL_SERVER_ERROR, detail=error.details()
|
|
|
|
|
)
|
2022-05-09 19:45:18 +02:00
|
|
|
|
2023-05-01 20:48:10 +02:00
|
|
|
@logger.catch(exclude=(HTTPException, NodeNotFoundError))
|
2022-10-03 10:44:34 +02:00
|
|
|
async def peer_resolve_alias(self, node_pub: str) -> str:
|
2023-04-03 20:17:48 +02:00
|
|
|
logger.trace(f"logger.peer_resolve_alias(node_pub={node_pub})")
|
2022-05-09 19:45:18 +02:00
|
|
|
|
2022-10-03 10:44:34 +02:00
|
|
|
# get fresh list of peers and their aliases
|
|
|
|
|
try:
|
|
|
|
|
request = ln.NodeInfoRequest(pub_key=node_pub, include_channels=False)
|
|
|
|
|
response = await self._lnd_stub.GetNodeInfo(request)
|
2022-05-09 19:45:18 +02:00
|
|
|
|
2023-05-01 20:48:10 +02:00
|
|
|
return str(response.node.alias)
|
2022-10-03 10:44:34 +02:00
|
|
|
except grpc.aio._call.AioRpcError as error:
|
2023-05-01 20:48:10 +02:00
|
|
|
details = error.details()
|
|
|
|
|
|
|
|
|
|
if "unable to find node" in details:
|
|
|
|
|
raise NodeNotFoundError(node_pub)
|
|
|
|
|
|
|
|
|
|
logger.error(details)
|
|
|
|
|
raise HTTPException(status.HTTP_500_INTERNAL_SERVER_ERROR, detail=details)
|
2022-05-09 19:45:18 +02:00
|
|
|
|
2023-04-03 20:17:48 +02:00
|
|
|
@logger.catch(exclude=(HTTPException,))
|
2022-10-03 10:44:34 +02:00
|
|
|
async def channel_list(self) -> List[Channel]:
|
2023-05-17 16:02:28 +02:00
|
|
|
logger.trace("logger.channel_list()")
|
2022-05-09 19:45:18 +02:00
|
|
|
|
2022-10-03 10:44:34 +02:00
|
|
|
try:
|
|
|
|
|
request = ln.ListChannelsRequest()
|
|
|
|
|
response = await self._lnd_stub.ListChannels(request)
|
2022-05-09 19:45:18 +02:00
|
|
|
|
2022-10-03 10:44:34 +02:00
|
|
|
channels = []
|
|
|
|
|
for channel_grpc in response.channels:
|
|
|
|
|
channel = Channel.from_lnd_grpc(channel_grpc)
|
2023-05-01 20:48:10 +02:00
|
|
|
channel.peer_alias = await alias_or_empty(
|
|
|
|
|
self.peer_resolve_alias, channel.peer_publickey
|
2022-10-03 10:44:34 +02:00
|
|
|
)
|
|
|
|
|
channels.append(channel)
|
|
|
|
|
|
|
|
|
|
request = ln.PendingChannelsRequest()
|
|
|
|
|
response = await self._lnd_stub.PendingChannels(request)
|
|
|
|
|
for channel_grpc in response.pending_open_channels:
|
|
|
|
|
channel = Channel.from_lnd_grpc_pending(channel_grpc.channel)
|
2023-05-01 20:48:10 +02:00
|
|
|
channel.peer_alias = await alias_or_empty(
|
|
|
|
|
self.peer_resolve_alias, channel.peer_publickey
|
2022-10-03 10:44:34 +02:00
|
|
|
)
|
|
|
|
|
channels.append(channel)
|
2022-05-09 19:45:18 +02:00
|
|
|
|
2022-10-03 10:44:34 +02:00
|
|
|
return channels
|
2022-05-09 19:45:18 +02:00
|
|
|
|
2022-10-03 10:44:34 +02:00
|
|
|
except grpc.aio._call.AioRpcError as error:
|
|
|
|
|
raise HTTPException(
|
|
|
|
|
status.HTTP_500_INTERNAL_SERVER_ERROR, detail=error.details()
|
|
|
|
|
)
|
2022-05-18 18:20:13 +02:00
|
|
|
|
2023-04-03 20:17:48 +02:00
|
|
|
@logger.catch(exclude=(HTTPException,))
|
2022-10-03 10:44:34 +02:00
|
|
|
async def channel_close(self, channel_id: int, force_close: bool) -> str:
|
2023-04-03 20:17:48 +02:00
|
|
|
logger.trace(
|
|
|
|
|
f"logger.channel_close(channel_id={channel_id}, force_close={force_close})"
|
2022-10-03 10:44:34 +02:00
|
|
|
)
|
2022-05-09 19:45:18 +02:00
|
|
|
|
2023-05-17 16:02:28 +02:00
|
|
|
if ":" not in channel_id:
|
2022-10-03 10:44:34 +02:00
|
|
|
raise ValueError("channel_id must contain : for lnd")
|
2022-05-09 19:45:18 +02:00
|
|
|
|
2022-10-03 10:44:34 +02:00
|
|
|
try:
|
|
|
|
|
funding_txid = channel_id.split(":")[0]
|
|
|
|
|
output_index = channel_id.split(":")[1]
|
2022-05-09 19:45:18 +02:00
|
|
|
|
2022-10-03 10:44:34 +02:00
|
|
|
request = ln.CloseChannelRequest(
|
|
|
|
|
channel_point=ln.ChannelPoint(
|
|
|
|
|
funding_txid_str=funding_txid, output_index=int(output_index)
|
|
|
|
|
),
|
|
|
|
|
force=force_close,
|
|
|
|
|
target_conf=6,
|
|
|
|
|
)
|
|
|
|
|
async for response in self._lnd_stub.CloseChannel(request):
|
|
|
|
|
return str(response.close_pending.txid.hex())
|
2022-05-09 19:45:18 +02:00
|
|
|
|
2022-10-03 10:44:34 +02:00
|
|
|
except grpc.aio._call.AioRpcError as error:
|
|
|
|
|
raise HTTPException(
|
|
|
|
|
status.HTTP_500_INTERNAL_SERVER_ERROR, detail=error.details()
|
|
|
|
|
)
|