servo: clightning-sane: minor bugfixes
This commit is contained in:
@@ -37,43 +37,51 @@ class TxBounds:
|
||||
min_msat: int
|
||||
max_msat: int
|
||||
|
||||
def __repr__(self) -> str:
|
||||
return f"TxBounds({self.min_msat} <= msat <= {self.max_msat})"
|
||||
|
||||
def is_satisfiable(self) -> bool:
|
||||
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.
|
||||
"""
|
||||
if ch:
|
||||
why = why or ch.scid
|
||||
if why: why = f"{why}: "
|
||||
|
||||
new_min, new_max = self.min_msat, self.max_msat
|
||||
if ch.htlc_minimum_msat > self.min_msat:
|
||||
new_min = ch.htlc_minimum_msat
|
||||
logger.debug(f"raising min_msat due to HTLC requirements: {self.min_msat} -> {new_min}")
|
||||
if ch.htlc_maximum_msat < self.max_msat:
|
||||
new_max = ch.htlc_maximum_msat
|
||||
logger.debug(f"lowering max_msat due to HTLC requirements: {self.max_msat} -> {new_max}")
|
||||
if ch.htlc_minimum > self.min_msat:
|
||||
new_min = ch.htlc_minimum
|
||||
logger.debug(f"{why}raising min_msat due to HTLC requirements: {self.min_msat} -> {new_min}")
|
||||
if ch.htlc_maximum < self.max_msat:
|
||||
new_max = ch.htlc_maximum
|
||||
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)
|
||||
|
||||
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.
|
||||
if the channel has a base fee, then `max_msat` is forced to 0.
|
||||
"""
|
||||
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
|
||||
if ppm != 0:
|
||||
new_max = math.ceil(1000000 / ppm) - 1
|
||||
if new_max < self.max_msat:
|
||||
logger.debug(f"decreasing max_msat due to fee ppm: {self.max_msat} -> {new_max}")
|
||||
ppm_max = math.ceil(1000000 / ppm) - 1 if ppm != 0 else new_max
|
||||
if ppm_max < new_max:
|
||||
logger.debug(f"{why}decreasing max_msat due to fee ppm: {new_max} -> {ppm_max}")
|
||||
new_max = ppm_max
|
||||
|
||||
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
|
||||
|
||||
return TxBounds(
|
||||
min_msat = self.min_msat,
|
||||
max_msat = new_max,
|
||||
)
|
||||
return TxBounds(min_msat=self.min_msat, max_msat=new_max)
|
||||
|
||||
|
||||
class LocalChannel:
|
||||
@@ -102,15 +110,17 @@ class LocalChannel:
|
||||
self.peer_ch = rpc.peerchannel(self.scid, self.remote_peer)
|
||||
|
||||
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})"
|
||||
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 ""
|
||||
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 ""
|
||||
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
|
||||
@@ -136,12 +146,28 @@ class LocalChannel:
|
||||
return self.to_me["short_channel_id"]
|
||||
|
||||
@property
|
||||
def htlc_minimum_msat(self) -> Millisatoshi:
|
||||
return max(self.from_me["htlc_minimum_msat"], self.to_me["htlc_minimum_msat"])
|
||||
def htlc_minimum_to_me(self) -> Millisatoshi:
|
||||
return self.to_me["htlc_minimum_msat"]
|
||||
|
||||
@property
|
||||
def htlc_maximum_msat(self) -> Millisatoshi:
|
||||
return min(self.from_me["htlc_maximum_msat"], self.to_me["htlc_maximum_msat"])
|
||||
def htlc_minimum_from_me(self) -> Millisatoshi:
|
||||
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
|
||||
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")
|
||||
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_zero_fees(in_ch)
|
||||
if not bounds.is_satisfiable():
|
||||
return RebalanceResult.FAIL_PERMANENT # no valid bounds
|
||||
|
||||
logger.debug(f"route with bounds {bounds}")
|
||||
route = self.route(out_ch, in_ch, bounds)
|
||||
logger.debug(f"route: {route}")
|
||||
if route == RouteError.NO_ROUTE:
|
||||
@@ -316,18 +343,17 @@ class Balancer:
|
||||
if send_msat != Millisatoshi(bounds.max_msat):
|
||||
logger.debug(f"found route with non-zero fee: {send_msat} -> {bounds.max_msat}. {route}")
|
||||
|
||||
error = None
|
||||
for hop in route:
|
||||
hop_scid = hop["channel"]
|
||||
hop_dir = hop["direction"]
|
||||
ch = self._get_directed_scid(hop_scid, hop_dir)
|
||||
if ch["base_fee_millisatoshi"] != 0:
|
||||
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"])
|
||||
|
||||
if any(hop["base_fee_millisatoshi"] != 0 for hop in route):
|
||||
return RouteError.HAS_BASE_FEE
|
||||
|
||||
return bounds
|
||||
return bounds if error is None else error
|
||||
|
||||
return route
|
||||
|
||||
|
Reference in New Issue
Block a user