mirror of
https://github.com/fusion44/blitz_api.git
synced 2026-08-19 12:37:30 +02:00
Merge 5fb819b8e1 into b8677cbbf1
This commit is contained in:
commit
30fc76e5d6
10 changed files with 461 additions and 100 deletions
|
|
@ -1,6 +1,17 @@
|
|||
from fastapi import HTTPException, status
|
||||
|
||||
|
||||
class OpenChannelPushAmountError(HTTPException):
|
||||
"""Raised when a the push amount is lower than the channel size."""
|
||||
|
||||
def __init__(self, local_funding, push_amt):
|
||||
self.status_code = status.HTTP_400_BAD_REQUEST
|
||||
self.detail = (
|
||||
f"Push amount {push_amt} must be lower than "
|
||||
f"local funding amount {local_funding}"
|
||||
)
|
||||
|
||||
|
||||
class NodeNotFoundError(HTTPException):
|
||||
"""Raised when a node is not found in the graph."""
|
||||
|
||||
|
|
|
|||
|
|
@ -908,12 +908,17 @@ class LnNodeCLNgRPC(LightningNodeBase):
|
|||
|
||||
@logger.catch(exclude=(HTTPException,))
|
||||
async def channel_open(
|
||||
self, local_funding_amount: int, node_URI: str, target_confs: int
|
||||
self,
|
||||
local_funding_amount: int,
|
||||
node_URI: str,
|
||||
target_confs: int,
|
||||
push_amount_sat: int,
|
||||
) -> str:
|
||||
logger.trace(
|
||||
(
|
||||
f"channel_open(local_funding_amount={local_funding_amount}, "
|
||||
f"node_URI={node_URI}, target_confs={target_confs})"
|
||||
f"node_URI={node_URI}, target_confs={target_confs}, "
|
||||
f"push_amount_sat={push_amount_sat})"
|
||||
)
|
||||
)
|
||||
|
||||
|
|
@ -931,8 +936,11 @@ class LnNodeCLNgRPC(LightningNodeBase):
|
|||
h = bytes.fromhex(node_URI.split("@")[0])
|
||||
req = ln.FundchannelRequest(
|
||||
id=h,
|
||||
amount=lnp.AmountOrAll(amount=lnp.Amount(msat=local_funding_amount)),
|
||||
amount=lnp.AmountOrAll(
|
||||
amount=lnp.Amount(msat=local_funding_amount * 1000)
|
||||
),
|
||||
feerate=fee_rate,
|
||||
push_msat=lnp.Amount(msat=push_amount_sat * 1000),
|
||||
)
|
||||
except TypeError as e:
|
||||
logger.error(f"channel_open() failed at ln.FundchannelRequest(): {e}")
|
||||
|
|
@ -986,10 +994,15 @@ class LnNodeCLNgRPC(LightningNodeBase):
|
|||
raise HTTPException(status.HTTP_500_INTERNAL_SERVER_ERROR, detail=details)
|
||||
|
||||
@logger.catch(exclude=(HTTPException,))
|
||||
async def channel_list(self) -> List[Channel]:
|
||||
logger.trace("channel_list()")
|
||||
async def channel_list(
|
||||
self,
|
||||
include_closed: bool,
|
||||
peer_alias_lookup: bool,
|
||||
) -> List[Channel]:
|
||||
logger.trace(f"channel_list({include_closed}, {peer_alias_lookup})")
|
||||
|
||||
try:
|
||||
# TODO: switch to ListPeerChannels
|
||||
res = await self._cln_stub.ListFunds(ln.ListfundsRequest())
|
||||
peer_ids = [c.peer_id for c in res.channels]
|
||||
peer_res = await asyncio.gather(
|
||||
|
|
|
|||
|
|
@ -718,21 +718,35 @@ class LnNodeCLNjRPC(LightningNodeBase):
|
|||
|
||||
@logger.catch(exclude=(HTTPException,))
|
||||
async def channel_open(
|
||||
self, local_funding_amount: int, node_URI: str, target_confs: int
|
||||
self,
|
||||
local_funding_amount: int,
|
||||
node_URI: str,
|
||||
target_confs: int,
|
||||
push_amount_sat: int,
|
||||
) -> str:
|
||||
logger.trace(
|
||||
(
|
||||
f"channel_open(local_funding_amount={local_funding_amount}, "
|
||||
f"node_URI={node_URI}, target_confs={target_confs})"
|
||||
f"logger.channel_open(local_funding_amount={local_funding_amount}, "
|
||||
f"node_URI={node_URI}, target_confs={target_confs}, "
|
||||
f"push_amount_sat={push_amount_sat})"
|
||||
)
|
||||
)
|
||||
# https://lightning.readthedocs.io/lightning-fundchannel.7.html
|
||||
# fundchannel id amount [feerate] [announce] [minconf] [utxos] [push_msat]
|
||||
# [close_to] [request_amt] [compact_lease] [reserve]
|
||||
|
||||
await self.connect_peer(node_URI)
|
||||
fee_rate = calc_fee_rate_str(None, target_confs)
|
||||
pub = node_URI.split("@")[0]
|
||||
params = {"id": pub, "amount": local_funding_amount, "feerate": fee_rate}
|
||||
params = {
|
||||
"id": pub,
|
||||
"amount": local_funding_amount,
|
||||
"feerate": fee_rate,
|
||||
}
|
||||
|
||||
if push_amount_sat is not None and push_amount_sat > 0:
|
||||
params["push_msat"] = push_amount_sat * 1000
|
||||
|
||||
res = await self._send_request("fundchannel", params)
|
||||
|
||||
if "error" in res:
|
||||
|
|
@ -815,20 +829,26 @@ class LnNodeCLNjRPC(LightningNodeBase):
|
|||
return str(nodes[0]["alias"])
|
||||
|
||||
@logger.catch(exclude=(HTTPException,))
|
||||
async def channel_list(self) -> List[Channel]:
|
||||
logger.trace("channel_list()")
|
||||
async def channel_list(
|
||||
self,
|
||||
include_closed: bool,
|
||||
peer_alias_lookup: bool,
|
||||
) -> List[Channel]:
|
||||
logger.trace(f"channel_list({include_closed}, {peer_alias_lookup})")
|
||||
|
||||
res = await self._send_request("listfunds")
|
||||
res = await self._send_request("listpeerchannels")
|
||||
if "error" in res:
|
||||
self._raise_internal_server_error("listing channels", res)
|
||||
|
||||
res = res["result"]
|
||||
|
||||
peer_ids = [c["peer_id"] for c in res["channels"]]
|
||||
peers = await asyncio.gather(
|
||||
*[alias_or_empty(self.peer_resolve_alias, p) for p in peer_ids],
|
||||
return_exceptions=True,
|
||||
)
|
||||
peers = [""] * len(res["channels"])
|
||||
if peer_alias_lookup:
|
||||
peer_ids = [c["peer_id"] for c in res["channels"]]
|
||||
peers = await asyncio.gather(
|
||||
*[alias_or_empty(self.peer_resolve_alias, p) for p in peer_ids],
|
||||
return_exceptions=True,
|
||||
)
|
||||
|
||||
channels = []
|
||||
for c, p in zip(res["channels"], peers):
|
||||
|
|
|
|||
|
|
@ -116,7 +116,11 @@ class LightningNodeBase:
|
|||
|
||||
@abstractmethod
|
||||
async def channel_open(
|
||||
self, local_funding_amount: int, node_URI: str, target_confs: int
|
||||
self,
|
||||
local_funding_amount: int,
|
||||
node_URI: str,
|
||||
target_confs: int,
|
||||
push_amount_sat: int,
|
||||
) -> str:
|
||||
raise NotImplementedError()
|
||||
|
||||
|
|
@ -125,7 +129,11 @@ class LightningNodeBase:
|
|||
raise NotImplementedError()
|
||||
|
||||
@abstractmethod
|
||||
async def channel_list(self) -> List[Channel]:
|
||||
async def channel_list(
|
||||
self,
|
||||
include_closed: bool,
|
||||
peer_alias_lookup: bool,
|
||||
) -> List[Channel]:
|
||||
raise NotImplementedError()
|
||||
|
||||
@abstractmethod
|
||||
|
|
|
|||
|
|
@ -16,10 +16,12 @@ import app.lightning.impl.protos.lnd.walletunlocker_pb2 as unlocker
|
|||
import app.lightning.impl.protos.lnd.walletunlocker_pb2_grpc as unlockerrpc
|
||||
from app.api.config import config as dconfig
|
||||
from app.api.utils import SSE, broadcast_sse_msg, config_get_hex_str
|
||||
from app.lightning.exceptions import NodeNotFoundError
|
||||
from app.lightning.exceptions import NodeNotFoundError, OpenChannelPushAmountError
|
||||
from app.lightning.impl.ln_base import LightningNodeBase
|
||||
from app.lightning.models import (
|
||||
Channel,
|
||||
ChannelInitiator,
|
||||
ChannelState,
|
||||
FeeRevenue,
|
||||
ForwardSuccessEvent,
|
||||
GenericTx,
|
||||
|
|
@ -818,12 +820,17 @@ This will show more debug information.
|
|||
|
||||
@logger.catch(exclude=(HTTPException,))
|
||||
async def channel_open(
|
||||
self, local_funding_amount: int, node_URI: str, target_confs: int
|
||||
self,
|
||||
local_funding_amount: int,
|
||||
node_URI: str,
|
||||
target_confs: int,
|
||||
push_amount_sat: int,
|
||||
) -> str:
|
||||
logger.trace(
|
||||
(
|
||||
f"logger.channel_open(local_funding_amount={local_funding_amount}, "
|
||||
f"node_URI={node_URI}, target_confs={target_confs})"
|
||||
f"node_URI={node_URI}, target_confs={target_confs}, "
|
||||
f"push_amount_sat={push_amount_sat})"
|
||||
)
|
||||
)
|
||||
|
||||
|
|
@ -853,14 +860,28 @@ This will show more debug information.
|
|||
node_pubkey=bytes.fromhex(pubkey),
|
||||
local_funding_amount=local_funding_amount,
|
||||
target_conf=target_confs,
|
||||
push_sat=push_amount_sat,
|
||||
)
|
||||
async for response in self._lnd_stub.OpenChannel(r):
|
||||
return str(response.chan_pending.txid.hex())
|
||||
|
||||
except grpc.aio._call.AioRpcError as error:
|
||||
raise HTTPException(
|
||||
status.HTTP_500_INTERNAL_SERVER_ERROR, detail=error.details()
|
||||
)
|
||||
details = error.details()
|
||||
if (
|
||||
"amount pushed to remote peer for initial state must "
|
||||
"be below the local funding amount"
|
||||
) in details:
|
||||
raise OpenChannelPushAmountError(local_funding_amount, push_amount_sat)
|
||||
if "EOF" in details:
|
||||
raise HTTPException(
|
||||
status.HTTP_500_INTERNAL_SERVER_ERROR,
|
||||
detail=(
|
||||
'Got "EOF" from LND. Is the peer reachable '
|
||||
"and the pubkey correct?"
|
||||
),
|
||||
)
|
||||
|
||||
raise HTTPException(status.HTTP_500_INTERNAL_SERVER_ERROR, detail=details)
|
||||
|
||||
@logger.catch(exclude=(HTTPException, NodeNotFoundError))
|
||||
async def peer_resolve_alias(self, node_pub: str) -> str:
|
||||
|
|
@ -882,29 +903,152 @@ This will show more debug information.
|
|||
raise HTTPException(status.HTTP_500_INTERNAL_SERVER_ERROR, detail=details)
|
||||
|
||||
@logger.catch(exclude=(HTTPException,))
|
||||
async def channel_list(self) -> List[Channel]:
|
||||
logger.trace("logger.channel_list()")
|
||||
async def channel_list(
|
||||
self,
|
||||
include_closed: bool,
|
||||
peer_alias_lookup: bool,
|
||||
) -> List[Channel]:
|
||||
logger.trace(f"channel_list({include_closed}, {peer_alias_lookup})")
|
||||
|
||||
try:
|
||||
request = ln.ListChannelsRequest()
|
||||
response = await self._lnd_stub.ListChannels(request)
|
||||
res = await asyncio.gather(
|
||||
*[
|
||||
self._lnd_stub.PendingChannels(ln.PendingChannelsRequest()),
|
||||
self._lnd_stub.ListChannels(
|
||||
ln.ListChannelsRequest(peer_alias_lookup=peer_alias_lookup)
|
||||
),
|
||||
(
|
||||
self._lnd_stub.ClosedChannels(ln.ListChannelsRequest())
|
||||
if include_closed
|
||||
else None
|
||||
),
|
||||
]
|
||||
)
|
||||
|
||||
async def _chan_data(c, state: ChannelState):
|
||||
_channel: Channel = None
|
||||
if state == ChannelState.NORMAL:
|
||||
_channel = Channel(
|
||||
active=c.active,
|
||||
state=state,
|
||||
# use channel point as id because thats
|
||||
# needed for closing the channel with lnd
|
||||
channel_id=c.channel_point,
|
||||
peer_publickey=c.remote_pubkey,
|
||||
balance_local=c.local_balance,
|
||||
balance_remote=c.remote_balance,
|
||||
balance_capacity=c.capacity,
|
||||
initiator=ChannelInitiator.from_lnd_grpc(c.initiator),
|
||||
)
|
||||
elif state == ChannelState.OPENING:
|
||||
_channel = Channel(
|
||||
active=False,
|
||||
state=state,
|
||||
# use channel point as id because thats
|
||||
# needed for closing the channel with lnd
|
||||
channel_id=c.channel.channel_point,
|
||||
peer_publickey=c.channel.remote_node_pub,
|
||||
balance_local=c.channel.local_balance,
|
||||
balance_remote=c.channel.remote_balance,
|
||||
balance_capacity=c.channel.capacity,
|
||||
initiator=ChannelInitiator.from_lnd_grpc(c.channel.initiator),
|
||||
)
|
||||
elif state == ChannelState.CLOSING:
|
||||
closer = ChannelInitiator.UNKNOWN
|
||||
if "ChanStatusLocalCloseInitiator" in c.channel.chan_status_flags:
|
||||
closer = ChannelInitiator.LOCAL
|
||||
elif (
|
||||
"ChanStatusRemoteCloseInitiator" in c.channel.chan_status_flags
|
||||
):
|
||||
closer = ChannelInitiator.REMOTE
|
||||
|
||||
_channel = Channel(
|
||||
active=False,
|
||||
state=state,
|
||||
# use channel point as id because thats
|
||||
# needed for closing the channel with lnd
|
||||
channel_id=c.channel.channel_point,
|
||||
peer_publickey=c.channel.remote_node_pub,
|
||||
balance_local=c.channel.local_balance,
|
||||
balance_remote=c.channel.remote_balance,
|
||||
balance_capacity=c.channel.capacity,
|
||||
initiator=ChannelInitiator.from_lnd_grpc(c.channel.initiator),
|
||||
closer=closer,
|
||||
)
|
||||
elif state == ChannelState.FORCE_CLOSING:
|
||||
closer = ChannelInitiator.UNKNOWN
|
||||
if "ChanStatusLocalCloseInitiator" in c.channel.chan_status_flags:
|
||||
closer = ChannelInitiator.LOCAL
|
||||
elif (
|
||||
"ChanStatusRemoteCloseInitiator" in c.channel.chan_status_flags
|
||||
):
|
||||
closer = ChannelInitiator.REMOTE
|
||||
|
||||
_channel = Channel(
|
||||
active=False,
|
||||
state=state,
|
||||
# use channel point as id because thats
|
||||
# needed for closing the channel with lnd
|
||||
channel_id=c.channel.channel_point,
|
||||
peer_publickey=c.channel.remote_node_pub,
|
||||
balance_local=c.recovered_balance,
|
||||
balance_remote=c.channel.remote_balance,
|
||||
balance_capacity=c.channel.capacity,
|
||||
initiator=ChannelInitiator.from_lnd_grpc(c.channel.initiator),
|
||||
closer=closer,
|
||||
)
|
||||
elif state == ChannelState.CLOSED:
|
||||
_channel = Channel(
|
||||
active=False,
|
||||
state=state,
|
||||
# use channel point as id because thats
|
||||
# needed for closing the channel with lnd
|
||||
channel_id=c.channel_point,
|
||||
peer_publickey=c.remote_pubkey,
|
||||
balance_local=c.settled_balance,
|
||||
balance_remote=c.capacity - c.settled_balance,
|
||||
balance_capacity=c.capacity,
|
||||
initiator=ChannelInitiator.from_lnd_grpc(c.open_initiator),
|
||||
closer=ChannelInitiator.from_lnd_grpc(c.close_initiator),
|
||||
)
|
||||
|
||||
if _channel is None:
|
||||
logger.error("Unhandled channel state {state}")
|
||||
return None
|
||||
|
||||
# LND returns "unable to lookup peer alias: alias for node
|
||||
# not found" when peer_alias_lookup is True and node_pub is
|
||||
# not found. We ignore this and just return an empty string
|
||||
if (
|
||||
peer_alias_lookup
|
||||
and hasattr(c, "peer_alias")
|
||||
and "unable to lookup peer alias: alias for node not found"
|
||||
not in c.peer_alias
|
||||
):
|
||||
_channel.peer_alias = c.peer_alias
|
||||
elif peer_alias_lookup and not hasattr(c, "peer_alias"):
|
||||
_channel.peer_alias = await alias_or_empty(
|
||||
self.peer_resolve_alias, _channel.peer_publickey
|
||||
)
|
||||
|
||||
return _channel
|
||||
|
||||
channels = []
|
||||
for channel_grpc in response.channels:
|
||||
channel = Channel.from_lnd_grpc(channel_grpc)
|
||||
channel.peer_alias = await alias_or_empty(
|
||||
self.peer_resolve_alias, channel.peer_publickey
|
||||
)
|
||||
channels.append(channel)
|
||||
for c in res[0].pending_open_channels:
|
||||
channels.append(await _chan_data(c, ChannelState.OPENING))
|
||||
for c in res[0].pending_force_closing_channels:
|
||||
channels.append(await _chan_data(c, ChannelState.FORCE_CLOSING))
|
||||
for c in res[0].waiting_close_channels:
|
||||
channels.append(await _chan_data(c, ChannelState.CLOSING))
|
||||
|
||||
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)
|
||||
channel.peer_alias = await alias_or_empty(
|
||||
self.peer_resolve_alias, channel.peer_publickey
|
||||
)
|
||||
channels.append(channel)
|
||||
for c in res[1].channels: # open
|
||||
channels.append(await _chan_data(c, ChannelState.NORMAL))
|
||||
|
||||
if not include_closed:
|
||||
return channels
|
||||
|
||||
for c in res[2].channels:
|
||||
channels.append(await _chan_data(c, ChannelState.CLOSED))
|
||||
|
||||
return channels
|
||||
|
||||
|
|
@ -920,7 +1064,10 @@ This will show more debug information.
|
|||
)
|
||||
|
||||
if ":" not in channel_id:
|
||||
raise ValueError("channel_id must contain : for lnd")
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_400_BAD_REQUEST,
|
||||
detail="channel_id must contain : for lnd",
|
||||
)
|
||||
|
||||
try:
|
||||
funding_txid = channel_id.split(":")[0]
|
||||
|
|
@ -931,7 +1078,7 @@ This will show more debug information.
|
|||
funding_txid_str=funding_txid, output_index=int(output_index)
|
||||
),
|
||||
force=force_close,
|
||||
target_conf=6,
|
||||
target_conf=None if force_close else 6,
|
||||
)
|
||||
async for response in self._lnd_stub.CloseChannel(request):
|
||||
return str(response.close_pending.txid.hex())
|
||||
|
|
|
|||
|
|
@ -3,6 +3,8 @@
|
|||
# clone lnd repo
|
||||
# cd into lnd/lnrpc
|
||||
# git clone https://github.com/googleapis/googleapis.git
|
||||
# poetry shell
|
||||
# bash gen_protos_python.sh
|
||||
|
||||
set -e
|
||||
|
||||
|
|
|
|||
|
|
@ -179,10 +179,20 @@ class LnNodeCLNgRPCBlitz(LnNodeCLNgRPC):
|
|||
yield i
|
||||
|
||||
async def channel_open(
|
||||
self, local_funding_amount: int, node_URI: str, target_confs: int
|
||||
self,
|
||||
local_funding_amount: int,
|
||||
node_URI: str,
|
||||
target_confs: int,
|
||||
push_amount_sat: int,
|
||||
) -> str:
|
||||
self._check_if_locked()
|
||||
return await super().channel_open(local_funding_amount, node_URI, target_confs)
|
||||
|
||||
return await super().channel_open(
|
||||
local_funding_amount,
|
||||
node_URI,
|
||||
target_confs,
|
||||
push_amount_sat,
|
||||
)
|
||||
|
||||
async def peer_resolve_alias(self, node_pub: str) -> str:
|
||||
self._check_if_locked()
|
||||
|
|
|
|||
|
|
@ -450,52 +450,152 @@ class RouteHint(BaseModel):
|
|||
return cls(hop_hints=hop_hints)
|
||||
|
||||
|
||||
class ChannelInitiator(str, Enum):
|
||||
UNKNOWN = "unknown"
|
||||
LOCAL = "local"
|
||||
REMOTE = "remote"
|
||||
BOTH = "both"
|
||||
|
||||
@classmethod
|
||||
def from_cln(cls, i: str) -> "ChannelInitiator":
|
||||
if i == "local":
|
||||
return cls.LOCAL
|
||||
if i == "remote":
|
||||
return cls.REMOTE
|
||||
|
||||
raise NotImplementedError(f"Unknown CLN channel initiator id: {i}")
|
||||
|
||||
@classmethod
|
||||
def from_lnd_grpc(cls, i: Union[int, bool]) -> "ChannelInitiator":
|
||||
# LND only returns whether we are initiator
|
||||
# or not for NORMAL state channels
|
||||
if i is True:
|
||||
return cls.LOCAL
|
||||
if i is False:
|
||||
return cls.REMOTE
|
||||
|
||||
# LND differentiates further with pending channels
|
||||
if i == 1:
|
||||
return cls.LOCAL
|
||||
if i == 2:
|
||||
return cls.REMOTE
|
||||
if i == 3:
|
||||
return cls.BOTH
|
||||
|
||||
if i > 3:
|
||||
# leave a notice in the logs
|
||||
# that we don't know this case yet
|
||||
logger.warning(f"Unknown channel initiator id: {i}")
|
||||
|
||||
return cls.UNKNOWN
|
||||
|
||||
|
||||
class ChannelState(str, Enum):
|
||||
OPENING = "opening"
|
||||
NORMAL = "normal"
|
||||
CLOSING = "closing"
|
||||
FORCE_CLOSING = "force_closing"
|
||||
CLOSED = "closed"
|
||||
ABANDONED = "abandoned"
|
||||
UNKNOWN = "unknown"
|
||||
|
||||
@classmethod
|
||||
def from_cln_json(cls, s) -> "ChannelState":
|
||||
openings = [
|
||||
"OPENINGD",
|
||||
"CHANNELD_AWAITING_LOCKIN",
|
||||
"DUALOPEND_OPEN_INIT",
|
||||
"DUALOPEND_AWAITING_LOCKIN",
|
||||
]
|
||||
closings = [
|
||||
"CHANNELD_SHUTTING_DOWN",
|
||||
"CLOSINGD_SIGEXCHANGE",
|
||||
"CLOSINGD_COMPLETE",
|
||||
"FUNDING_SPEND_SEEN",
|
||||
]
|
||||
|
||||
if s in openings:
|
||||
return cls.OPENING
|
||||
if s == "CHANNELD_NORMAL":
|
||||
return cls.NORMAL
|
||||
if s in closings:
|
||||
return cls.CLOSING
|
||||
if s == "AWAITING_UNILATERAL":
|
||||
return cls.FORCE_CLOSING
|
||||
if s == "ONCHAIN":
|
||||
return cls.CLOSED
|
||||
|
||||
return cls.UNKNOWN
|
||||
|
||||
|
||||
class Channel(BaseModel):
|
||||
channel_id: Optional[str]
|
||||
active: Optional[bool]
|
||||
channel_id: str = Query(
|
||||
"",
|
||||
description="The unique identifier of the channel",
|
||||
)
|
||||
|
||||
peer_publickey: Optional[str]
|
||||
peer_alias: Optional[str]
|
||||
state: ChannelState = Query(..., description="The state of the channel")
|
||||
|
||||
balance_local: Optional[int]
|
||||
balance_remote: Optional[int]
|
||||
balance_capacity: Optional[int]
|
||||
active: bool = Query(
|
||||
...,
|
||||
description=(
|
||||
"Whether the channel is active or not. "
|
||||
"True if the channel is not closing and the peer is online."
|
||||
),
|
||||
)
|
||||
|
||||
@classmethod
|
||||
def from_lnd_grpc(cls, c) -> "Channel":
|
||||
return cls(
|
||||
active=c.active,
|
||||
# use channel point as id because thats needed
|
||||
# for closing the channel with lnd
|
||||
channel_id=c.channel_point,
|
||||
peer_publickey=c.remote_pubkey,
|
||||
peer_alias="n/a",
|
||||
balance_local=c.local_balance,
|
||||
balance_remote=c.remote_balance,
|
||||
balance_capacity=c.capacity,
|
||||
)
|
||||
peer_publickey: str = Query(
|
||||
...,
|
||||
description="The public key of the peer",
|
||||
)
|
||||
|
||||
@classmethod
|
||||
def from_lnd_grpc_pending(cls, c) -> "Channel":
|
||||
return cls(
|
||||
active=False,
|
||||
# use channel point as id because thats needed
|
||||
# for closing the channel with lnd
|
||||
channel_id=c.channel_point,
|
||||
peer_publickey=c.remote_node_pub,
|
||||
peer_alias="n/a",
|
||||
balance_local=-1,
|
||||
balance_remote=-1,
|
||||
balance_capacity=c.capacity,
|
||||
)
|
||||
peer_alias: str = Query(
|
||||
"",
|
||||
description="The alias of the peer if available",
|
||||
)
|
||||
|
||||
balance_local: int = Query(
|
||||
0,
|
||||
description="This node's current balance in this channel",
|
||||
)
|
||||
|
||||
balance_remote: int = Query(
|
||||
0,
|
||||
description="The counterparty's current balance in this channel",
|
||||
)
|
||||
|
||||
balance_capacity: int = Query(
|
||||
0,
|
||||
description="The total capacity of the channel",
|
||||
)
|
||||
|
||||
dual_funded: Optional[bool] = Query(
|
||||
None,
|
||||
description="Whether the channel was dual funded or not",
|
||||
)
|
||||
|
||||
initiator: ChannelInitiator = Query(
|
||||
ChannelInitiator.UNKNOWN,
|
||||
description="Whether the channel was initiated by us, our peer or both",
|
||||
)
|
||||
|
||||
closer: Union[ChannelInitiator, None] = Query(
|
||||
None,
|
||||
description=(
|
||||
"If state is closing, this shows who initiated the close. "
|
||||
"None, if not in a closing state."
|
||||
),
|
||||
)
|
||||
|
||||
@classmethod
|
||||
def from_cln_grpc(cls, c, peer_alias="n/a") -> "Channel":
|
||||
# TODO: get alias and balance of the channel
|
||||
return cls(
|
||||
active=c.connected,
|
||||
state=ChannelState.UNKNOWN,
|
||||
# state=ChannelState.from_cln_jrpc(c.state),
|
||||
# use channel point as id because thats needed
|
||||
# for closing the channel with lnd
|
||||
# for closing the channel with lnd
|
||||
channel_id=c.short_channel_id,
|
||||
peer_publickey=c.peer_id.hex(),
|
||||
peer_alias=peer_alias,
|
||||
|
|
@ -505,17 +605,25 @@ class Channel(BaseModel):
|
|||
)
|
||||
|
||||
@classmethod
|
||||
def from_cln_jrpc(cls, c, peer_alias="n/a") -> "Channel":
|
||||
# TODO: get alias and balance of the channel
|
||||
def from_cln_jrpc(cls, c, peer_alias="") -> "Channel":
|
||||
state = ChannelState.from_cln_json(c["state"])
|
||||
return cls(
|
||||
active=c["connected"],
|
||||
channel_id=c["short_channel_id"] if "short_channel_id" in c else None,
|
||||
active=c["peer_connected"],
|
||||
state=state,
|
||||
channel_id=c["short_channel_id"] if "short_channel_id" in c else "",
|
||||
peer_publickey=c["peer_id"],
|
||||
peer_alias=peer_alias,
|
||||
balance_local=parse_cln_msat(c["our_amount_msat"]),
|
||||
balance_remote=parse_cln_msat(c["amount_msat"])
|
||||
- parse_cln_msat(c["our_amount_msat"]),
|
||||
balance_capacity=parse_cln_msat(c["amount_msat"]),
|
||||
balance_local=parse_cln_msat(c["to_us_msat"]) if "to_us_msat" in c else 0,
|
||||
balance_remote=(
|
||||
parse_cln_msat(c["total_msat"]) - parse_cln_msat(c["to_us_msat"])
|
||||
if "total_msat" in c
|
||||
else 0
|
||||
),
|
||||
balance_capacity=(
|
||||
parse_cln_msat(c["total_msat"]) if "total_msat" in c else 0
|
||||
),
|
||||
initiator=ChannelInitiator.from_cln(c["opener"]),
|
||||
closer=ChannelInitiator.from_cln(c["closer"]) if "closer" in c else None,
|
||||
)
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -358,10 +358,32 @@ async def send_coins_path(input: SendCoinsInput):
|
|||
},
|
||||
)
|
||||
async def open_channel_path(
|
||||
local_funding_amount: int, node_URI: str, target_confs: int = 3
|
||||
local_funding_amount: int = Query(
|
||||
..., description="The amount of satoshis to commit to the channel."
|
||||
),
|
||||
node_URI: str = Query(
|
||||
...,
|
||||
description=(
|
||||
"The URI of the peer to open a channel with. "
|
||||
"Format: <pubkey>@<host>:<port>"
|
||||
),
|
||||
),
|
||||
target_confs: int = Query(
|
||||
3, description="The block target for the funding transaction."
|
||||
),
|
||||
push_amount_sat: int = Query(
|
||||
# Note: LND only supports satoshis as push amount, so we do the same.
|
||||
None,
|
||||
description="The amount of sats to push to the peer.",
|
||||
),
|
||||
):
|
||||
try:
|
||||
return await channel_open(local_funding_amount, node_URI, target_confs)
|
||||
return await channel_open(
|
||||
local_funding_amount,
|
||||
node_URI,
|
||||
target_confs,
|
||||
push_amount_sat,
|
||||
)
|
||||
except HTTPException:
|
||||
raise
|
||||
except NotImplementedError as r:
|
||||
|
|
@ -373,15 +395,27 @@ async def open_channel_path(
|
|||
@router.get(
|
||||
"/list-channels",
|
||||
name=f"{_PREFIX}.list-channels",
|
||||
summary="Returns a list of open channels",
|
||||
summary="Returns a list of all channels",
|
||||
response_model=List[Channel],
|
||||
response_description="A list of all open channels.",
|
||||
response_description="A list of all channels.",
|
||||
dependencies=[Depends(JWTBearer())],
|
||||
responses=responses,
|
||||
)
|
||||
async def list_channels_path():
|
||||
async def list_channels_path(
|
||||
include_closed: bool = Query(
|
||||
True, description="If true, then include closed channels."
|
||||
),
|
||||
peer_alias_lookup: bool = Query(
|
||||
False,
|
||||
description=(
|
||||
"If true, then include the peer alias of the channel. "
|
||||
"⚠️ Enabling this flag does come with a performance cost "
|
||||
"in the form of another roundtrip to the node."
|
||||
),
|
||||
),
|
||||
) -> List[Channel]:
|
||||
try:
|
||||
return await channel_list()
|
||||
return await channel_list(include_closed, peer_alias_lookup)
|
||||
except HTTPException:
|
||||
raise
|
||||
except NotImplementedError as r:
|
||||
|
|
@ -394,7 +428,10 @@ async def list_channels_path():
|
|||
"/close-channel",
|
||||
name=f"{_PREFIX}.close-channel",
|
||||
summary="close a channel",
|
||||
description="For additional information see [LND docs](https://api.lightning.community/#closechannel)",
|
||||
description=(
|
||||
"For additional information see "
|
||||
"[LND docs](https://api.lightning.community/#closechannel)"
|
||||
),
|
||||
dependencies=[Depends(JWTBearer())],
|
||||
response_model=str,
|
||||
responses=responses,
|
||||
|
|
|
|||
|
|
@ -140,7 +140,7 @@ async def send_payment(
|
|||
|
||||
|
||||
async def channel_open(
|
||||
local_funding_amount: int, node_URI: str, target_confs: int
|
||||
local_funding_amount: int, node_URI: str, target_confs: int, push_amount_sat: int
|
||||
) -> str:
|
||||
if local_funding_amount < 1:
|
||||
raise ValueError("funding amount needs to be positive")
|
||||
|
|
@ -154,12 +154,17 @@ async def channel_open(
|
|||
if "@" not in node_URI:
|
||||
raise ValueError("node_URI must contain @ with node physical address")
|
||||
|
||||
res = await ln.channel_open(local_funding_amount, node_URI, target_confs)
|
||||
res = await ln.channel_open(
|
||||
local_funding_amount, node_URI, target_confs, push_amount_sat
|
||||
)
|
||||
return res
|
||||
|
||||
|
||||
async def channel_list() -> List[Channel]:
|
||||
res = await ln.channel_list()
|
||||
async def channel_list(
|
||||
include_closed: bool,
|
||||
peer_alias_lookup: bool,
|
||||
) -> List[Channel]:
|
||||
res = await ln.channel_list(include_closed, peer_alias_lookup)
|
||||
return res
|
||||
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue