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:
self.remote_nodeid.setText("")
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:
self.remote_nodeid.setText(nodeid)
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 .lnrouter import fee_for_edge_msat
from .lnutil import LnFeatures, ln_compare_features, IncompatibleLightningFeatures
from .network import Network
if TYPE_CHECKING:
from .network import Network
from .channel_db import Policy, NodeInfo
from .lnchannel import ShortChannelID
from .lnworker import LNWallet
@@ -86,16 +86,12 @@ class LNRater(Logger):
self._last_progress_percent = 0
def maybe_analyze_graph(self):
loop = self.network.asyncio_loop
fut = asyncio.run_coroutine_threadsafe(self._maybe_analyze_graph(), loop)
fut.result()
Network.run_from_another_thread(self._maybe_analyze_graph())
def analyze_graph(self):
"""Forces a graph analysis, e.g., due to external triggers like
the graph info reaching 50%."""
loop = self.network.asyncio_loop
fut = asyncio.run_coroutine_threadsafe(self._analyze_graph(), loop)
fut.result()
Network.run_from_another_thread(self._analyze_graph())
async def _maybe_analyze_graph(self):
"""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))
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")
def _rate_nodes(self):
@@ -234,15 +230,18 @@ class LNRater(Logger):
self._node_ratings[n] = weighted_sum(heuristics, heuristics_weights)
def suggest_node_channel_open(self) -> Tuple[bytes, NodeStats]:
node_keys = list(self._node_stats.keys())
node_ratings = list(self._node_ratings.values())
@profiler
def suggest_node_channel_open(self) -> Optional[bytes]:
node_stats = self._node_stats.copy()
node_ratings = self._node_ratings.copy()
channel_peers = self.lnworker.channel_peers()
node_info: Optional["NodeInfo"] = None
while True:
while node_stats:
# 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_info = self.network.channel_db.get_node_infos().get(pk, None)
peer_features = LnFeatures(node_info.features)
@@ -265,13 +264,16 @@ class LNRater(Logger):
else:
continue
break
else:
self.logger.info(f"no suitable channel peer found")
return None
alias = node_info.alias if node_info else 'unknown node alias'
self.logger.info(
f"node rating for {alias}:\n"
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]:
"""Suggests a LN node to open a channel with.
@@ -279,6 +281,6 @@ class LNRater(Logger):
"""
self.maybe_analyze_graph()
if self._node_ratings:
return self.suggest_node_channel_open()[0]
return self.suggest_node_channel_open()
else:
return None