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:
@@ -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
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
Reference in New Issue
Block a user