lnrouter: rework blacklist a bit
separate from LiquidityHintMgr
This commit is contained in:
@@ -1204,12 +1204,13 @@ class Commands:
|
||||
@command('n')
|
||||
async def clear_ln_blacklist(self):
|
||||
if self.network.path_finder:
|
||||
self.network.path_finder.liquidity_hints.clear_blacklist()
|
||||
self.network.path_finder.clear_blacklist()
|
||||
|
||||
@command('n')
|
||||
async def reset_liquidity_hints(self):
|
||||
if self.network.path_finder:
|
||||
self.network.path_finder.liquidity_hints.reset_liquidity_hints()
|
||||
self.network.path_finder.clear_blacklist()
|
||||
|
||||
@command('wnl')
|
||||
async def close_channel(self, channel_point, force=False, wallet: Abstract_Wallet = None):
|
||||
|
||||
@@ -27,6 +27,7 @@ import queue
|
||||
from collections import defaultdict
|
||||
from typing import Sequence, Tuple, Optional, Dict, TYPE_CHECKING, Set
|
||||
import time
|
||||
import threading
|
||||
from threading import RLock
|
||||
import attr
|
||||
from math import inf
|
||||
@@ -42,7 +43,6 @@ if TYPE_CHECKING:
|
||||
|
||||
DEFAULT_PENALTY_BASE_MSAT = 500 # how much base fee we apply for unknown sending capability of a channel
|
||||
DEFAULT_PENALTY_PROPORTIONAL_MILLIONTH = 100 # how much relative fee we apply for unknown sending capability of a channel
|
||||
BLACKLIST_DURATION = 3600 # how long (in seconds) a channel remains blacklisted
|
||||
HINT_DURATION = 3600 # how long (in seconds) a liquidity hint remains valid
|
||||
|
||||
|
||||
@@ -173,7 +173,7 @@ def is_fee_sane(fee_msat: int, *, payment_amount_msat: int) -> bool:
|
||||
|
||||
class LiquidityHint:
|
||||
"""Encodes the amounts that can and cannot be sent over the direction of a
|
||||
channel and whether the channel is blacklisted.
|
||||
channel.
|
||||
|
||||
A LiquidityHint is the value of a dict, which is keyed to node ids and the
|
||||
channel.
|
||||
@@ -184,7 +184,6 @@ class LiquidityHint:
|
||||
self._cannot_send_forward = None
|
||||
self._can_send_backward = None
|
||||
self._cannot_send_backward = None
|
||||
self.blacklist_timestamp = 0
|
||||
self.hint_timestamp = 0
|
||||
self._inflight_htlcs_forward = 0
|
||||
self._inflight_htlcs_backward = 0
|
||||
@@ -297,10 +296,8 @@ class LiquidityHint:
|
||||
self._inflight_htlcs_backward = max(0, self._inflight_htlcs_forward - 1)
|
||||
|
||||
def __repr__(self):
|
||||
is_blacklisted = False if not self.blacklist_timestamp else int(time.time()) - self.blacklist_timestamp < BLACKLIST_DURATION
|
||||
return f"forward: can send: {self._can_send_forward} msat, cannot send: {self._cannot_send_forward} msat, htlcs: {self._inflight_htlcs_forward}\n" \
|
||||
f"backward: can send: {self._can_send_backward} msat, cannot send: {self._cannot_send_backward} msat, htlcs: {self._inflight_htlcs_backward}\n" \
|
||||
f"blacklisted: {is_blacklisted}"
|
||||
f"backward: can send: {self._can_send_backward} msat, cannot send: {self._cannot_send_backward} msat, htlcs: {self._inflight_htlcs_backward}\n"
|
||||
|
||||
|
||||
class LiquidityHintMgr:
|
||||
@@ -382,22 +379,6 @@ class LiquidityHintMgr:
|
||||
inflight_htlc_fee = num_inflight_htlcs * success_fee
|
||||
return success_fee + inflight_htlc_fee
|
||||
|
||||
@with_lock
|
||||
def add_to_blacklist(self, channel_id: ShortChannelID):
|
||||
hint = self.get_hint(channel_id)
|
||||
now = int(time.time())
|
||||
hint.blacklist_timestamp = now
|
||||
|
||||
@with_lock
|
||||
def get_blacklist(self) -> Set[ShortChannelID]:
|
||||
now = int(time.time())
|
||||
return set(k for k, v in self._liquidity_hints.items() if now - v.blacklist_timestamp < BLACKLIST_DURATION)
|
||||
|
||||
@with_lock
|
||||
def clear_blacklist(self):
|
||||
for k, v in self._liquidity_hints.items():
|
||||
v.blacklist_timestamp = 0
|
||||
|
||||
@with_lock
|
||||
def reset_liquidity_hints(self):
|
||||
for k, v in self._liquidity_hints.items():
|
||||
@@ -417,6 +398,33 @@ class LNPathFinder(Logger):
|
||||
Logger.__init__(self)
|
||||
self.channel_db = channel_db
|
||||
self.liquidity_hints = LiquidityHintMgr()
|
||||
self._edge_blacklist = dict() # type: Dict[ShortChannelID, int] # scid -> expiration
|
||||
self._blacklist_lock = threading.Lock()
|
||||
|
||||
def _is_edge_blacklisted(self, short_channel_id: ShortChannelID, *, now: int) -> bool:
|
||||
blacklist_expiration = self._edge_blacklist.get(short_channel_id)
|
||||
if blacklist_expiration is None:
|
||||
return False
|
||||
if blacklist_expiration < now:
|
||||
return False
|
||||
return True
|
||||
|
||||
def add_edge_to_blacklist(
|
||||
self,
|
||||
short_channel_id: ShortChannelID,
|
||||
*,
|
||||
now: int = None,
|
||||
duration: int = 3600, # seconds
|
||||
) -> None:
|
||||
if now is None:
|
||||
now = int(time.time())
|
||||
with self._blacklist_lock:
|
||||
blacklist_expiration = self._edge_blacklist.get(short_channel_id, 0)
|
||||
self._edge_blacklist[short_channel_id] = max(blacklist_expiration, now + duration)
|
||||
|
||||
def clear_blacklist(self):
|
||||
with self._blacklist_lock:
|
||||
self._edge_blacklist = dict()
|
||||
|
||||
def update_liquidity_hints(
|
||||
self,
|
||||
@@ -450,7 +458,7 @@ class LNPathFinder(Logger):
|
||||
def _edge_cost(
|
||||
self,
|
||||
*,
|
||||
short_channel_id: bytes,
|
||||
short_channel_id: ShortChannelID,
|
||||
start_node: bytes,
|
||||
end_node: bytes,
|
||||
payment_amt_msat: int,
|
||||
@@ -458,10 +466,13 @@ class LNPathFinder(Logger):
|
||||
is_mine=False,
|
||||
my_channels: Dict[ShortChannelID, 'Channel'] = None,
|
||||
private_route_edges: Dict[ShortChannelID, RouteEdge] = None,
|
||||
now: int, # unix ts
|
||||
) -> Tuple[float, int]:
|
||||
"""Heuristic cost (distance metric) of going through a channel.
|
||||
Returns (heuristic_cost, fee_for_edge_msat).
|
||||
"""
|
||||
if self._is_edge_blacklisted(short_channel_id, now=now):
|
||||
return float('inf'), 0
|
||||
if private_route_edges is None:
|
||||
private_route_edges = {}
|
||||
channel_info = self.channel_db.get_channel_info(
|
||||
@@ -537,12 +548,12 @@ class LNPathFinder(Logger):
|
||||
# run Dijkstra
|
||||
# The search is run in the REVERSE direction, from nodeB to nodeA,
|
||||
# to properly calculate compound routing fees.
|
||||
blacklist = self.liquidity_hints.get_blacklist()
|
||||
distance_from_start = defaultdict(lambda: float('inf'))
|
||||
distance_from_start[nodeB] = 0
|
||||
previous_hops = {} # type: Dict[bytes, PathEdge]
|
||||
nodes_to_explore = queue.PriorityQueue()
|
||||
nodes_to_explore.put((0, invoice_amount_msat, nodeB)) # order of fields (in tuple) matters!
|
||||
now = int(time.time())
|
||||
|
||||
# main loop of search
|
||||
while nodes_to_explore.qsize() > 0:
|
||||
@@ -569,7 +580,7 @@ class LNPathFinder(Logger):
|
||||
|
||||
for edge_channel_id in channels_for_endnode:
|
||||
assert isinstance(edge_channel_id, bytes)
|
||||
if blacklist and edge_channel_id in blacklist:
|
||||
if self._is_edge_blacklisted(edge_channel_id, now=now):
|
||||
continue
|
||||
channel_info = self.channel_db.get_channel_info(
|
||||
edge_channel_id, my_channels=my_sending_channels, private_route_edges=private_route_edges)
|
||||
@@ -589,7 +600,9 @@ class LNPathFinder(Logger):
|
||||
ignore_costs=(edge_startnode == nodeA),
|
||||
is_mine=is_mine,
|
||||
my_channels=my_sending_channels,
|
||||
private_route_edges=private_route_edges)
|
||||
private_route_edges=private_route_edges,
|
||||
now=now,
|
||||
)
|
||||
alt_dist_to_neighbour = distance_from_start[edge_endnode] + edge_cost
|
||||
if alt_dist_to_neighbour < distance_from_start[edge_startnode]:
|
||||
distance_from_start[edge_startnode] = alt_dist_to_neighbour
|
||||
|
||||
@@ -1604,7 +1604,7 @@ class LNWallet(LNWorker):
|
||||
else:
|
||||
blacklist = True
|
||||
if blacklist:
|
||||
self.network.path_finder.liquidity_hints.add_to_blacklist(failing_channel)
|
||||
self.network.path_finder.add_edge_to_blacklist(short_channel_id=failing_channel)
|
||||
|
||||
def _handle_chanupd_from_failed_htlc(self, payload, *, route, sender_idx) -> Tuple[bool, bool]:
|
||||
blacklist = False
|
||||
|
||||
Reference in New Issue
Block a user