Browse Source

lnrouter: rework blacklist a bit

separate from LiquidityHintMgr
master
SomberNight 2 years ago
parent
commit
1dd0608718
No known key found for this signature in database
GPG Key ID: B33B5F232C6271E9
  1. 3
      electrum/commands.py
  2. 65
      electrum/lnrouter.py
  3. 2
      electrum/lnworker.py

3
electrum/commands.py

@ -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):

65
electrum/lnrouter.py

@ -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

2
electrum/lnworker.py

@ -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

Loading…
Cancel
Save