askrene: add test that triggers infinite loop
What changed, and why it matters
This commit only adds a new test case for the askrene routing module. The test is marked to be skipped and demonstrates a scenario where an impossible payment could cause askrene's main loop to run forever. It does not change any production code, so it cannot by itself introduce or fix a vulnerability. It is a diagnostic test that documents a potential bug.
Treat this as a test/documentation commit. If the infinite-loop behavior is confirmed, a separate fix in askrene's refine step should be developed and the @unittest.skip decorator removed. No immediate action is required for this commit alone.
Security signals we found
Potential denial-of-service via infinite loop / timeout in askrene route refinement
Test-only commit; no production code change
Evidence from the diff
The commit adds a skipped pytest test, test_impossible_payment, in tests/test_askrene.py. The test constructs a two-hop channel layer where the first hop’s htlc_maximum_msat is smaller than the amount needed to deliver the payment plus fees, making the route impossible. The docstring notes this ‘might cause a timeout in askrene’s main loop due to the refine step.’ The test asserts that getroutes raises an RpcError with a specific message. No askrene implementation code is modified.
Changed components
tests/test_askrene.pyaskrene routing module (referenced only, not modified)Inspect captured patch +73 / −0
diff --git a/tests/test_askrene.py b/tests/test_askrene.py
index 71366f06..c613d2c7 100644
--- a/tests/test_askrene.py
+++ b/tests/test_askrene.py
@@ -2431,3 +2431,76 @@ def test_includefees(node_factory):
],
layers=["auto.sourcefree", "auto.include_fees"],
)
+
+
+@unittest.skip
+def test_impossible_payment(node_factory):
+ """A payment that is impossible due to HTLC constraints and fees. The
+ constraint might cause a timeout in in askrene's main loop due to the refine
+ step."""
+ l1 = node_factory.get_node()
+ node1 = "020000000000000000000000000000000000000000000000000000000000000001"
+ node2 = "020000000000000000000000000000000000000000000000000000000000000002"
+ node3 = "020000000000000000000000000000000000000000000000000000000000000003"
+ million_sats = 1000000000
+ pay_amt = 10000000
+ base_amt = int(pay_amt * 1.1)
+ l1.rpc.askrene_create_layer("mylayer")
+ l1.rpc.askrene_create_channel(
+ layer="mylayer",
+ source=node1,
+ destination=node2,
+ short_channel_id="0x0x1",
+ capacity_msat=million_sats,
+ )
+ l1.rpc.askrene_update_channel(
+ layer="mylayer",
+ short_channel_id_dir="0x0x1/0",
+ enabled=True,
+ htlc_minimum_msat=0,
+ htlc_maximum_msat=base_amt,
+ fee_base_msat=0,
+ fee_proportional_millionths=0,
+ cltv_expiry_delta=18,
+ )
+ l1.rpc.askrene_create_channel(
+ layer="mylayer",
+ source=node2,
+ destination=node3,
+ short_channel_id="0x0x2",
+ capacity_msat=million_sats,
+ )
+ l1.rpc.askrene_update_channel(
+ layer="mylayer",
+ short_channel_id_dir="0x0x2/0",
+ enabled=True,
+ htlc_minimum_msat=0,
+ htlc_maximum_msat=million_sats,
+ fee_base_msat=base_amt,
+ fee_proportional_millionths=0,
+ cltv_expiry_delta=18,
+ )
+ with pytest.raises(
+ RpcError,
+ match=r"We could not find a usable set of paths. The shortest path is 0x0x1->0x0x2, but 0x0x1/0 exceeds htlc_maximum_msat",
+ ):
+ l1.rpc.getroutes(
+ source=node1,
+ destination=node3,
+ amount_msat=pay_amt,
+ layers=["mylayer"],
+ maxfee_msat=2 * pay_amt,
+ final_cltv=5,
+ )
+ with pytest.raises(
+ RpcError,
+ match=r"We could not find a usable set of paths. The shortest path is 0x0x1->0x0x2, but 0x0x1/0 exceeds htlc_maximum_msat",
+ ):
+ l1.rpc.getroutes(
+ source=node1,
+ destination=node3,
+ amount_msat=pay_amt,
+ layers=["mylayer", "auto.no_mpp_support"],
+ maxfee_msat=2 * pay_amt,
+ final_cltv=5,
+ )
Why this scored 25/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.