tests: check liquidity hints are updated for all htlcs
What changed, and why it matters
This commit only adds new automated tests for Electrum's Lightning payment routing code. It checks that internal 'liquidity hints' correctly track in-flight payment parts and are cleaned up after multipart payments succeed or time out. There are no changes to production wallet code, so users are not directly affected by this patch.
No security action needed; this is a test-only change. Reviewers may optionally run the new tests to confirm they pass.
Security signals we found
No strong security signals were identified.
Evidence from the diff
The diff adds test-only helper methods and assertions in tests/test_lnpeer.py and a new unit test in tests/test_lnrouter.py. The new code verifies that LiquidityHintMgr clears inflight HTLC counters when reset, and that after a multipart Lightning payment completes or times out no HTLCs remain marked inflight and every used direction has a can_send report. No application logic is modified.
Changed components
tests/test_lnpeer.pytests/test_lnrouter.pyInspect captured patch +33 / −0
diff --git a/tests/test_lnpeer.py b/tests/test_lnpeer.py
index 19e0231..d4d0204 100644
--- a/tests/test_lnpeer.py
+++ b/tests/test_lnpeer.py
@@ -2250,6 +2250,23 @@ class TestPeerForwarding(TestPeer):
with self.assertRaises(PaymentDone):
await f()
+ async def _assert_no_inflight_htlcs_in_liquidity_hints(self, w: MockLNWallet):
+ # pay_invoice returns when the first htlc resolution is processed; the remaining
+ # parts resolve later. only when all parts have resolved is the paysession removed.
+ async def wait_for_paysessions_to_be_cleaned_up():
+ while w._paysessions:
+ await asyncio.sleep(0.01)
+ await util.wait_for2(wait_for_paysessions_to_be_cleaned_up(), timeout=10)
+ for hint in w.network.path_finder.liquidity_hints._liquidity_hints.values():
+ self.assertEqual(0, hint.num_inflight_htlcs(True))
+ self.assertEqual(0, hint.num_inflight_htlcs(False))
+
+ def _assert_all_parts_reported_can_send(self, w: MockLNWallet):
+ # every part of the mpp resolved successfully, so every edge an htlc was
+ # sent over should have received a can_send report for its direction of use
+ for hint in w.network.path_finder.liquidity_hints._liquidity_hints.values():
+ self.assertTrue(hint.can_send(True) is not None or hint.can_send(False) is not None)
+
async def _run_mpp(self, graph, kwargs):
"""Tests a multipart payment scenario for failing and successful cases."""
self.assertEqual(500_000_000_000, graph.channels[('alice', 'bob')][0].balance(LOCAL))
@@ -2299,8 +2316,11 @@ class TestPeerForwarding(TestPeer):
await g.spawn(peer.wait_one_htlc_switch_iteration())
for peer in peers:
self.assertEqual(len(peer.lnworker.received_mpp_htlcs), 0)
+ await self._assert_no_inflight_htlcs_in_liquidity_hints(alice_w)
+ self._assert_all_parts_reported_can_send(alice_w)
raise PaymentDone()
elif len(log) == 1 and log[0].failure_msg.code == OnionFailureCode.MPP_TIMEOUT:
+ await self._assert_no_inflight_htlcs_in_liquidity_hints(alice_w)
raise PaymentTimeout()
else:
raise NoPathFound()
diff --git a/tests/test_lnrouter.py b/tests/test_lnrouter.py
index 3ad775c..bcbe3e5 100644
--- a/tests/test_lnrouter.py
+++ b/tests/test_lnrouter.py
@@ -373,6 +373,19 @@ class Test_LNRouter(ElectrumTestCase):
# we have got 600 (attempt) + 600 (inflight) penalty
self.assertEqual(1200, liquidity_hints.penalty(node_from, node_to, channel_id, 1_000_000))
+ def test_reset_liquidity_hints_clears_inflight_htlcs(self):
+ liquidity_hints = LiquidityHintMgr()
+ node_from, node_to = bytes(0), bytes(1)
+ channel_id = ShortChannelID.from_components(0, 0, 0)
+ liquidity_hints.add_htlc(node_from, node_to, channel_id)
+ liquidity_hints.add_htlc(node_to, node_from, channel_id)
+ hint = liquidity_hints.get_hint(channel_id)
+ self.assertEqual(1, hint.num_inflight_htlcs(node_from < node_to))
+ self.assertEqual(1, hint.num_inflight_htlcs(node_to < node_from))
+ liquidity_hints.reset_liquidity_hints()
+ self.assertEqual(0, hint.num_inflight_htlcs(node_from < node_to))
+ self.assertEqual(0, hint.num_inflight_htlcs(node_to < node_from))
+
@needs_test_with_all_chacha20_implementations
def test_new_onion_packet(self):
# test vector from bolt-04
Why this scored 12/100
Community notes
Notes can correct, qualify, or add evidence to the AI analysis. Every note shown here has been validated by a human moderator.
The AI analysis stands alone for now. Submit a note if you can add evidence or important context.