1
0

fix: prevent lnrater from blocking if no good peers

the while loop in `suggest_node_channel_open()` of lnrater would not
break if there are no "good" peers available available. As a result the gui
blocks and electrum has to be killed. This can happen for example on
signet.
This removes the tested pk from the list of candidates so each candidate
gets tested only once.
This commit is contained in:
f321x
2025-06-02 18:06:29 +02:00
parent 4aed0582a1
commit b4e93e7e38
2 changed files with 19 additions and 16 deletions

View File

@@ -93,7 +93,8 @@ class NewChannelDialog(WindowModalDialog):
if not nodeid: if not nodeid:
self.remote_nodeid.setText("") self.remote_nodeid.setText("")
self.remote_nodeid.setPlaceholderText( self.remote_nodeid.setPlaceholderText(
"Please wait until the graph is synchronized to 30%, and then try again.") _("Couldn't find suitable peer yet, try again later.")
)
else: else:
self.remote_nodeid.setText(nodeid) self.remote_nodeid.setText(nodeid)
self.remote_nodeid.repaint() # macOS hack for #6269 self.remote_nodeid.repaint() # macOS hack for #6269

View File

@@ -18,9 +18,9 @@ from .logging import Logger
from .util import profiler, get_running_loop from .util import profiler, get_running_loop
from .lnrouter import fee_for_edge_msat from .lnrouter import fee_for_edge_msat
from .lnutil import LnFeatures, ln_compare_features, IncompatibleLightningFeatures from .lnutil import LnFeatures, ln_compare_features, IncompatibleLightningFeatures
from .network import Network
if TYPE_CHECKING: if TYPE_CHECKING:
from .network import Network
from .channel_db import Policy, NodeInfo from .channel_db import Policy, NodeInfo
from .lnchannel import ShortChannelID from .lnchannel import ShortChannelID
from .lnworker import LNWallet from .lnworker import LNWallet
@@ -86,16 +86,12 @@ class LNRater(Logger):
self._last_progress_percent = 0 self._last_progress_percent = 0
def maybe_analyze_graph(self): def maybe_analyze_graph(self):
loop = self.network.asyncio_loop Network.run_from_another_thread(self._maybe_analyze_graph())
fut = asyncio.run_coroutine_threadsafe(self._maybe_analyze_graph(), loop)
fut.result()
def analyze_graph(self): def analyze_graph(self):
"""Forces a graph analysis, e.g., due to external triggers like """Forces a graph analysis, e.g., due to external triggers like
the graph info reaching 50%.""" the graph info reaching 50%."""
loop = self.network.asyncio_loop Network.run_from_another_thread(self._analyze_graph())
fut = asyncio.run_coroutine_threadsafe(self._analyze_graph(), loop)
fut.result()
async def _maybe_analyze_graph(self): async def _maybe_analyze_graph(self):
"""Analyzes the graph when in early sync stage (>30%) or when caching """Analyzes the graph when in early sync stage (>30%) or when caching
@@ -197,7 +193,7 @@ class LNRater(Logger):
self.logger.debug(pformat(channel_policies)) self.logger.debug(pformat(channel_policies))
continue continue
self.logger.info(f"node statistics done, calculated statistics" self.logger.info(f"node statistics done, calculated statistics "
f"for {len(self._node_stats)} nodes") f"for {len(self._node_stats)} nodes")
def _rate_nodes(self): def _rate_nodes(self):
@@ -234,15 +230,18 @@ class LNRater(Logger):
self._node_ratings[n] = weighted_sum(heuristics, heuristics_weights) self._node_ratings[n] = weighted_sum(heuristics, heuristics_weights)
def suggest_node_channel_open(self) -> Tuple[bytes, NodeStats]: @profiler
node_keys = list(self._node_stats.keys()) def suggest_node_channel_open(self) -> Optional[bytes]:
node_ratings = list(self._node_ratings.values()) node_stats = self._node_stats.copy()
node_ratings = self._node_ratings.copy()
channel_peers = self.lnworker.channel_peers() channel_peers = self.lnworker.channel_peers()
node_info: Optional["NodeInfo"] = None node_info: Optional["NodeInfo"] = None
while True: while node_stats:
# randomly pick nodes weighted by node_rating # randomly pick nodes weighted by node_rating
pk = choices(node_keys, weights=node_ratings, k=1)[0] pk = choices(list(node_stats.keys()), weights=list(node_ratings.values()), k=1)[0]
# remove the pk so it doesn't get tried again
node_stats.pop(pk); node_ratings.pop(pk)
# node should have compatible features # node should have compatible features
node_info = self.network.channel_db.get_node_infos().get(pk, None) node_info = self.network.channel_db.get_node_infos().get(pk, None)
peer_features = LnFeatures(node_info.features) peer_features = LnFeatures(node_info.features)
@@ -265,13 +264,16 @@ class LNRater(Logger):
else: else:
continue continue
break break
else:
self.logger.info(f"no suitable channel peer found")
return None
alias = node_info.alias if node_info else 'unknown node alias' alias = node_info.alias if node_info else 'unknown node alias'
self.logger.info( self.logger.info(
f"node rating for {alias}:\n" f"node rating for {alias}:\n"
f"{pformat(self._node_stats[pk])} (score {self._node_ratings[pk]})") f"{pformat(self._node_stats[pk])} (score {self._node_ratings[pk]})")
return pk, self._node_stats[pk] return pk
def suggest_peer(self) -> Optional[bytes]: def suggest_peer(self) -> Optional[bytes]:
"""Suggests a LN node to open a channel with. """Suggests a LN node to open a channel with.
@@ -279,6 +281,6 @@ class LNRater(Logger):
""" """
self.maybe_analyze_graph() self.maybe_analyze_graph()
if self._node_ratings: if self._node_ratings:
return self.suggest_node_channel_open()[0] return self.suggest_node_channel_open()
else: else:
return None return None