servo: clightning-sane: minor bugfixes
This commit is contained in:
@@ -37,43 +37,51 @@ class TxBounds:
|
|||||||
min_msat: int
|
min_msat: int
|
||||||
max_msat: int
|
max_msat: int
|
||||||
|
|
||||||
|
def __repr__(self) -> str:
|
||||||
|
return f"TxBounds({self.min_msat} <= msat <= {self.max_msat})"
|
||||||
|
|
||||||
def is_satisfiable(self) -> bool:
|
def is_satisfiable(self) -> bool:
|
||||||
return self.min_msat <= self.max_msat
|
return self.min_msat <= self.max_msat
|
||||||
|
|
||||||
def restrict_to_htlc(self, ch: "LocalChannel") -> "Self":
|
def restrict_to_htlc(self, ch: "LocalChannel", why: str = "") -> "Self":
|
||||||
"""
|
"""
|
||||||
apply min/max HTLC size restrictions of the given channel.
|
apply min/max HTLC size restrictions of the given channel.
|
||||||
"""
|
"""
|
||||||
|
if ch:
|
||||||
|
why = why or ch.scid
|
||||||
|
if why: why = f"{why}: "
|
||||||
|
|
||||||
new_min, new_max = self.min_msat, self.max_msat
|
new_min, new_max = self.min_msat, self.max_msat
|
||||||
if ch.htlc_minimum_msat > self.min_msat:
|
if ch.htlc_minimum > self.min_msat:
|
||||||
new_min = ch.htlc_minimum_msat
|
new_min = ch.htlc_minimum
|
||||||
logger.debug(f"raising min_msat due to HTLC requirements: {self.min_msat} -> {new_min}")
|
logger.debug(f"{why}raising min_msat due to HTLC requirements: {self.min_msat} -> {new_min}")
|
||||||
if ch.htlc_maximum_msat < self.max_msat:
|
if ch.htlc_maximum < self.max_msat:
|
||||||
new_max = ch.htlc_maximum_msat
|
new_max = ch.htlc_maximum
|
||||||
logger.debug(f"lowering max_msat due to HTLC requirements: {self.max_msat} -> {new_max}")
|
logger.debug(f"{why}lowering max_msat due to HTLC requirements: {self.max_msat} -> {new_max}")
|
||||||
return TxBounds(min_msat=new_min, max_msat=new_max)
|
return TxBounds(min_msat=new_min, max_msat=new_max)
|
||||||
|
|
||||||
def restrict_to_zero_fees(self, ch: "LocalChannel"=None, base: int=0, ppm: int=0) -> "Self":
|
def restrict_to_zero_fees(self, ch: "LocalChannel"=None, base: int=0, ppm: int=0, why:str = "") -> "Self":
|
||||||
"""
|
"""
|
||||||
restrict tx size such that PPM fees are zero.
|
restrict tx size such that PPM fees are zero.
|
||||||
if the channel has a base fee, then `max_msat` is forced to 0.
|
if the channel has a base fee, then `max_msat` is forced to 0.
|
||||||
"""
|
"""
|
||||||
if ch:
|
if ch:
|
||||||
self = self.restrict_to_zero_fees(base=ch.to_me["base_fee_millisatoshi"], ppm=ch.to_me["fee_per_millionth"])
|
why = why or ch.directed_scid_to_me
|
||||||
|
self = self.restrict_to_zero_fees(base=ch.to_me["base_fee_millisatoshi"], ppm=ch.to_me["fee_per_millionth"], why=why)
|
||||||
|
|
||||||
|
if why: why = f"{why}: "
|
||||||
|
|
||||||
new_max = self.max_msat
|
new_max = self.max_msat
|
||||||
if ppm != 0:
|
ppm_max = math.ceil(1000000 / ppm) - 1 if ppm != 0 else new_max
|
||||||
new_max = math.ceil(1000000 / ppm) - 1
|
if ppm_max < new_max:
|
||||||
if new_max < self.max_msat:
|
logger.debug(f"{why}decreasing max_msat due to fee ppm: {new_max} -> {ppm_max}")
|
||||||
logger.debug(f"decreasing max_msat due to fee ppm: {self.max_msat} -> {new_max}")
|
new_max = ppm_max
|
||||||
|
|
||||||
if base != 0:
|
if base != 0:
|
||||||
logger.debug("free route impossible: channel has base fees")
|
logger.debug(f"{why}free route impossible: channel has base fees")
|
||||||
new_max = 0
|
new_max = 0
|
||||||
|
|
||||||
return TxBounds(
|
return TxBounds(min_msat=self.min_msat, max_msat=new_max)
|
||||||
min_msat = self.min_msat,
|
|
||||||
max_msat = new_max,
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
class LocalChannel:
|
class LocalChannel:
|
||||||
@@ -102,15 +110,17 @@ class LocalChannel:
|
|||||||
self.peer_ch = rpc.peerchannel(self.scid, self.remote_peer)
|
self.peer_ch = rpc.peerchannel(self.scid, self.remote_peer)
|
||||||
|
|
||||||
def __repr__(self) -> str:
|
def __repr__(self) -> str:
|
||||||
return self.to_str(with_scid=True, with_bal_ratio=True, with_cost=True)
|
return self.to_str(with_scid=True, with_bal_ratio=True, with_cost=True, with_ppm=True)
|
||||||
|
|
||||||
def to_str(self, with_scid: bool=False, with_bal_msat: bool=False, with_bal_ratio: bool=False, with_cost:bool = False) -> str:
|
def to_str(self, with_scid: bool=False, with_bal_msat: bool=False, with_bal_ratio: bool=False, with_cost:bool = False, with_ppm:bool = False) -> str:
|
||||||
alias = f"({self.remote_alias})"
|
alias = f"({self.remote_alias})"
|
||||||
scid = f" scid:{self.scid:>13}" if with_scid else ""
|
scid = f" scid:{self.scid:>13}" if with_scid else ""
|
||||||
bal = f" S:{int(self.sendable):11}/R:{int(self.receivable):11}" if with_bal_msat else ""
|
bal = f" S:{int(self.sendable):11}/R:{int(self.receivable):11}" if with_bal_msat else ""
|
||||||
ratio = f" MINE:{(100*self.send_ratio):>7.3f}%" if with_bal_ratio else ""
|
ratio = f" MINE:{(100*self.send_ratio):>7.3f}%" if with_bal_ratio else ""
|
||||||
cost = f" COST:{self.opportunity_cost_lent:>11}" if with_cost else ""
|
cost = f" COST:{self.opportunity_cost_lent:>11}" if with_cost else ""
|
||||||
return f"channel{alias:30}{scid}{bal}{ratio}{cost}"
|
ppm_to_me = self.to_me["fee_per_millionth"] if self.to_me else "N/A"
|
||||||
|
ppm = f" THEIR_PPM:{ppm_to_me:>6}" if with_ppm else ""
|
||||||
|
return f"channel{alias:30}{scid}{bal}{ratio}{cost}{ppm}"
|
||||||
|
|
||||||
|
|
||||||
@property
|
@property
|
||||||
@@ -136,12 +146,28 @@ class LocalChannel:
|
|||||||
return self.to_me["short_channel_id"]
|
return self.to_me["short_channel_id"]
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def htlc_minimum_msat(self) -> Millisatoshi:
|
def htlc_minimum_to_me(self) -> Millisatoshi:
|
||||||
return max(self.from_me["htlc_minimum_msat"], self.to_me["htlc_minimum_msat"])
|
return self.to_me["htlc_minimum_msat"]
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def htlc_maximum_msat(self) -> Millisatoshi:
|
def htlc_minimum_from_me(self) -> Millisatoshi:
|
||||||
return min(self.from_me["htlc_maximum_msat"], self.to_me["htlc_maximum_msat"])
|
return self.from_me["htlc_minimum_msat"]
|
||||||
|
|
||||||
|
@property
|
||||||
|
def htlc_minimum(self) -> Millisatoshi:
|
||||||
|
return max(self.htlc_minimum_to_me, self.htlc_minimum_from_me)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def htlc_maximum_to_me(self) -> Millisatoshi:
|
||||||
|
return self.to_me["htlc_maximum_msat"]
|
||||||
|
|
||||||
|
@property
|
||||||
|
def htlc_maximum_from_me(self) -> Millisatoshi:
|
||||||
|
return self.from_me["htlc_maximum_msat"]
|
||||||
|
|
||||||
|
@property
|
||||||
|
def htlc_maximum(self) -> Millisatoshi:
|
||||||
|
return min(self.htlc_maximum_to_me, self.htlc_maximum_from_me)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def direction_to_me(self) -> int:
|
def direction_to_me(self) -> int:
|
||||||
@@ -242,12 +268,13 @@ class Balancer:
|
|||||||
logger.info(f"rebalance {out_scid} -> {in_scid} failed in our own channel")
|
logger.info(f"rebalance {out_scid} -> {in_scid} failed in our own channel")
|
||||||
return RebalanceResult.FAIL_PERMANENT
|
return RebalanceResult.FAIL_PERMANENT
|
||||||
|
|
||||||
bounds = bounds.restrict_to_htlc(out_ch)
|
# bounds = bounds.restrict_to_htlc(out_ch) # htlc bounds seem to be enforced only in the outward direction
|
||||||
bounds = bounds.restrict_to_htlc(in_ch)
|
bounds = bounds.restrict_to_htlc(in_ch)
|
||||||
bounds = bounds.restrict_to_zero_fees(in_ch)
|
bounds = bounds.restrict_to_zero_fees(in_ch)
|
||||||
if not bounds.is_satisfiable():
|
if not bounds.is_satisfiable():
|
||||||
return RebalanceResult.FAIL_PERMANENT # no valid bounds
|
return RebalanceResult.FAIL_PERMANENT # no valid bounds
|
||||||
|
|
||||||
|
logger.debug(f"route with bounds {bounds}")
|
||||||
route = self.route(out_ch, in_ch, bounds)
|
route = self.route(out_ch, in_ch, bounds)
|
||||||
logger.debug(f"route: {route}")
|
logger.debug(f"route: {route}")
|
||||||
if route == RouteError.NO_ROUTE:
|
if route == RouteError.NO_ROUTE:
|
||||||
@@ -316,18 +343,17 @@ class Balancer:
|
|||||||
if send_msat != Millisatoshi(bounds.max_msat):
|
if send_msat != Millisatoshi(bounds.max_msat):
|
||||||
logger.debug(f"found route with non-zero fee: {send_msat} -> {bounds.max_msat}. {route}")
|
logger.debug(f"found route with non-zero fee: {send_msat} -> {bounds.max_msat}. {route}")
|
||||||
|
|
||||||
|
error = None
|
||||||
for hop in route:
|
for hop in route:
|
||||||
hop_scid = hop["channel"]
|
hop_scid = hop["channel"]
|
||||||
hop_dir = hop["direction"]
|
hop_dir = hop["direction"]
|
||||||
ch = self._get_directed_scid(hop_scid, hop_dir)
|
ch = self._get_directed_scid(hop_scid, hop_dir)
|
||||||
if ch["base_fee_millisatoshi"] != 0:
|
if ch["base_fee_millisatoshi"] != 0:
|
||||||
self.nonzero_base_channels.append(f"{hop_scid}/{hop_dir}")
|
self.nonzero_base_channels.append(f"{hop_scid}/{hop_dir}")
|
||||||
|
error = RouteError.HAS_BASE_FEE
|
||||||
bounds = bounds.restrict_to_zero_fees(ppm=ch["fee_per_millionth"])
|
bounds = bounds.restrict_to_zero_fees(ppm=ch["fee_per_millionth"])
|
||||||
|
|
||||||
if any(hop["base_fee_millisatoshi"] != 0 for hop in route):
|
return bounds if error is None else error
|
||||||
return RouteError.HAS_BASE_FEE
|
|
||||||
|
|
||||||
return bounds
|
|
||||||
|
|
||||||
return route
|
return route
|
||||||
|
|
||||||
|
Reference in New Issue
Block a user