askrene: add test for reservation leaks
What changed, and why it matters
This commit only adds a new automated test to check that a routing-reservation leak does not occur. It does not change any production code, so by itself it cannot introduce or fix a security vulnerability in running software. The test may be related to a known bug, but the commit message and diff do not describe a fix or disclose security relevance.
No action required for this commit alone; treat as test coverage. If investigating a related reservation-leak issue, review preceding or accompanying commits that modify `plugins/askrene.c` or related reservation logic.
Security signals we found
Test targets reservation leak behavior in askrene routing layer
Uses hold_htlcs.py plugin to delay HTLC settlement
Asserts no 'askrene-unreserve failed' log output
Evidence from the diff
The diff adds test_reservations_leak in tests/test_askrene.py. It sets up a six-node network, initiates two concurrent xpay payments through overlapping paths, uses a hold-HTLC plugin to delay one payment, and asserts that askrene_listreservations is empty after the delayed payment completes and that no ‘askrene-unreserve failed’ log line appears. No C/lightningd source files are modified.
Changed components
tests/test_askrene.pyInspect captured patch +60 / −0
diff --git a/tests/test_askrene.py b/tests/test_askrene.py
index 09a669e7..7f2c84db 100644
--- a/tests/test_askrene.py
+++ b/tests/test_askrene.py
@@ -1868,6 +1868,66 @@ def test_askrene_timeout(node_factory, bitcoind):
final_cltv=5)
+def test_reservations_leak(node_factory, executor):
+ l1, l2, l3, l4, l5, l6 = node_factory.get_nodes(
+ 6,
+ opts=[
+ {"fee-base": 0, "fee-per-satoshi": 0},
+ {"fee-base": 0, "fee-per-satoshi": 0},
+ {
+ "fee-base": 0,
+ "fee-per-satoshi": 0,
+ "plugin": os.path.join(os.getcwd(), "tests/plugins/hold_htlcs.py"),
+ },
+ {"fee-base": 0, "fee-per-satoshi": 0},
+ {"fee-base": 0, "fee-per-satoshi": 0},
+ {"fee-base": 1000, "fee-per-satoshi": 0},
+ ],
+ )
+
+ # There must be a common non-local channel in both payment paths.
+ # With a local channel we cannot trigger the reservation leak because we
+ # reserve slightly different amounts locally due to HTLC onchain costs.
+ node_factory.join_nodes([l1, l2, l4, l6, l3], wait_for_announce=True)
+ node_factory.join_nodes([l1, l2, l4, l5], wait_for_announce=True)
+
+ # Use offers instead of bolt11 because we are going to pay through a blinded
+ # path and trigger a fake channel collision between both payments.
+ offer1 = l3.rpc.offer("any")["bolt12"]
+ offer2 = l5.rpc.offer("any")["bolt12"]
+
+ inv1 = l1.rpc.fetchinvoice(offer1, "100sat")["invoice"]
+ inv2 = l1.rpc.fetchinvoice(offer2, "101sat")["invoice"]
+
+ # Initiate the first payment that has a delay.
+ fut = executor.submit(l1.rpc.xpay, (inv1))
+
+ # Wait for the first payment to reserve the path.
+ l1.daemon.wait_for_log(r"json_askrene_reserve called")
+
+ # A second payment starts.
+ l1.rpc.xpay(inv2)
+ l1.daemon.wait_for_log(r"json_askrene_unreserve called")
+
+ l3.daemon.wait_for_log(r"Holding onto an incoming htlc for 10 seconds")
+
+ # There is a payment pending therefore we expect reservations.
+ reservations = l1.rpc.askrene_listreservations()
+ assert reservations != {"reservations": []}
+
+ l3.daemon.wait_for_log(r"htlc_accepted hook called")
+ fut.result()
+ l1.daemon.wait_for_log(r"json_askrene_unreserve called")
+
+ # The first payment has finished we expect no reservations.
+ reservations = l1.rpc.askrene_listreservations()
+ assert reservations == {"reservations": []}
+
+ # We shouldn't fail askrene-unreserve. If it does it means something went
+ # wrong.
+ assert l1.daemon.is_in_log("askrene-unreserve failed") is None
+
+
def test_askrene_reserve_clash(node_factory, bitcoind):
"""Reserves get (erroneously) counted globally by scid, even for fake scids."""
l1 = node_factory.get_node()
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.