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') @command('n')
async def clear_ln_blacklist(self): async def clear_ln_blacklist(self):
if self.network.path_finder: if self.network.path_finder:
self.network.path_finder.liquidity_hints.clear_blacklist() self.network.path_finder.clear_blacklist()
@command('n') @command('n')
async def reset_liquidity_hints(self): async def reset_liquidity_hints(self):
if self.network.path_finder: if self.network.path_finder:
self.network.path_finder.liquidity_hints.reset_liquidity_hints() self.network.path_finder.liquidity_hints.reset_liquidity_hints()
self.network.path_finder.clear_blacklist()
@command('wnl') @command('wnl')
async def close_channel(self, channel_point, force=False, wallet: Abstract_Wallet = None): 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 collections import defaultdict
from typing import Sequence, Tuple, Optional, Dict, TYPE_CHECKING, Set from typing import Sequence, Tuple, Optional, Dict, TYPE_CHECKING, Set
import time import time
import threading
from threading import RLock from threading import RLock
import attr import attr
from math import inf 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_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 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 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: class LiquidityHint:
"""Encodes the amounts that can and cannot be sent over the direction of a """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 A LiquidityHint is the value of a dict, which is keyed to node ids and the
channel. channel.
@ -184,7 +184,6 @@ class LiquidityHint:
self._cannot_send_forward = None self._cannot_send_forward = None
self._can_send_backward = None self._can_send_backward = None
self._cannot_send_backward = None self._cannot_send_backward = None
self.blacklist_timestamp = 0
self.hint_timestamp = 0 self.hint_timestamp = 0
self._inflight_htlcs_forward = 0 self._inflight_htlcs_forward = 0
self._inflight_htlcs_backward = 0 self._inflight_htlcs_backward = 0
@ -297,10 +296,8 @@ class LiquidityHint:
self._inflight_htlcs_backward = max(0, self._inflight_htlcs_forward - 1) self._inflight_htlcs_backward = max(0, self._inflight_htlcs_forward - 1)
def __repr__(self): 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" \ 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"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}"
class LiquidityHintMgr: class LiquidityHintMgr:
@ -382,22 +379,6 @@ class LiquidityHintMgr:
inflight_htlc_fee = num_inflight_htlcs * success_fee inflight_htlc_fee = num_inflight_htlcs * success_fee
return success_fee + inflight_htlc_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 @with_lock
def reset_liquidity_hints(self): def reset_liquidity_hints(self):
for k, v in self._liquidity_hints.items(): for k, v in self._liquidity_hints.items():
@ -417,6 +398,33 @@ class LNPathFinder(Logger):
Logger.__init__(self) Logger.__init__(self)
self.channel_db = channel_db self.channel_db = channel_db
self.liquidity_hints = LiquidityHintMgr() 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( def update_liquidity_hints(
self, self,
@ -450,7 +458,7 @@ class LNPathFinder(Logger):
def _edge_cost( def _edge_cost(
self, self,
*, *,
short_channel_id: bytes, short_channel_id: ShortChannelID,
start_node: bytes, start_node: bytes,
end_node: bytes, end_node: bytes,
payment_amt_msat: int, payment_amt_msat: int,
@ -458,10 +466,13 @@ class LNPathFinder(Logger):
is_mine=False, is_mine=False,
my_channels: Dict[ShortChannelID, 'Channel'] = None, my_channels: Dict[ShortChannelID, 'Channel'] = None,
private_route_edges: Dict[ShortChannelID, RouteEdge] = None, private_route_edges: Dict[ShortChannelID, RouteEdge] = None,
now: int, # unix ts
) -> Tuple[float, int]: ) -> Tuple[float, int]:
"""Heuristic cost (distance metric) of going through a channel. """Heuristic cost (distance metric) of going through a channel.
Returns (heuristic_cost, fee_for_edge_msat). 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: if private_route_edges is None:
private_route_edges = {} private_route_edges = {}
channel_info = self.channel_db.get_channel_info( channel_info = self.channel_db.get_channel_info(
@ -537,12 +548,12 @@ class LNPathFinder(Logger):
# run Dijkstra # run Dijkstra
# The search is run in the REVERSE direction, from nodeB to nodeA, # The search is run in the REVERSE direction, from nodeB to nodeA,
# to properly calculate compound routing fees. # to properly calculate compound routing fees.
blacklist = self.liquidity_hints.get_blacklist()
distance_from_start = defaultdict(lambda: float('inf')) distance_from_start = defaultdict(lambda: float('inf'))
distance_from_start[nodeB] = 0 distance_from_start[nodeB] = 0
previous_hops = {} # type: Dict[bytes, PathEdge] previous_hops = {} # type: Dict[bytes, PathEdge]
nodes_to_explore = queue.PriorityQueue() nodes_to_explore = queue.PriorityQueue()
nodes_to_explore.put((0, invoice_amount_msat, nodeB)) # order of fields (in tuple) matters! nodes_to_explore.put((0, invoice_amount_msat, nodeB)) # order of fields (in tuple) matters!
now = int(time.time())
# main loop of search # main loop of search
while nodes_to_explore.qsize() > 0: while nodes_to_explore.qsize() > 0:
@ -569,7 +580,7 @@ class LNPathFinder(Logger):
for edge_channel_id in channels_for_endnode: for edge_channel_id in channels_for_endnode:
assert isinstance(edge_channel_id, bytes) 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 continue
channel_info = self.channel_db.get_channel_info( channel_info = self.channel_db.get_channel_info(
edge_channel_id, my_channels=my_sending_channels, private_route_edges=private_route_edges) 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), ignore_costs=(edge_startnode == nodeA),
is_mine=is_mine, is_mine=is_mine,
my_channels=my_sending_channels, 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 alt_dist_to_neighbour = distance_from_start[edge_endnode] + edge_cost
if alt_dist_to_neighbour < distance_from_start[edge_startnode]: if alt_dist_to_neighbour < distance_from_start[edge_startnode]:
distance_from_start[edge_startnode] = alt_dist_to_neighbour distance_from_start[edge_startnode] = alt_dist_to_neighbour

2
electrum/lnworker.py

@ -1604,7 +1604,7 @@ class LNWallet(LNWorker):
else: else:
blacklist = True blacklist = True
if blacklist: 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]: def _handle_chanupd_from_failed_htlc(self, payload, *, route, sender_idx) -> Tuple[bool, bool]:
blacklist = False blacklist = False

Loading…
Cancel
Save