@@ -1473,3 +1473,63 @@ def test_simple_dummy_channel(node_factory):
14731473 final_cltv = 5 ,
14741474 layers = ["mylayer" ],
14751475 )
1476+
1477+
1478+ def test_reservations_leak (node_factory , executor ):
1479+ l1 , l2 , l3 , l4 , l5 , l6 = node_factory .get_nodes (
1480+ 6 ,
1481+ opts = [
1482+ {"fee-base" : 0 , "fee-per-satoshi" : 0 },
1483+ {"fee-base" : 0 , "fee-per-satoshi" : 0 },
1484+ {
1485+ "fee-base" : 0 ,
1486+ "fee-per-satoshi" : 0 ,
1487+ "plugin" : os .path .join (os .getcwd (), "tests/plugins/hold_htlcs.py" ),
1488+ },
1489+ {"fee-base" : 0 , "fee-per-satoshi" : 0 },
1490+ {"fee-base" : 0 , "fee-per-satoshi" : 0 },
1491+ {"fee-base" : 1000 , "fee-per-satoshi" : 0 },
1492+ ],
1493+ )
1494+
1495+ # There must be a common non-local channel in both payment paths.
1496+ # With a local channel we cannot trigger the reservation leak because we
1497+ # reserve slightly different amounts locally due to HTLC onchain costs.
1498+ node_factory .join_nodes ([l1 , l2 , l4 , l6 , l3 ], wait_for_announce = True )
1499+ node_factory .join_nodes ([l1 , l2 , l4 , l5 ], wait_for_announce = True )
1500+
1501+ # Use offers instead of bolt11 because we are going to pay through a blinded
1502+ # path and trigger a fake channel collision between both payments.
1503+ offer1 = l3 .rpc .offer ("any" )["bolt12" ]
1504+ offer2 = l5 .rpc .offer ("any" )["bolt12" ]
1505+
1506+ inv1 = l1 .rpc .fetchinvoice (offer1 , "100sat" )["invoice" ]
1507+ inv2 = l1 .rpc .fetchinvoice (offer2 , "101sat" )["invoice" ]
1508+
1509+ # Initiate the first payment that has a delay.
1510+ fut = executor .submit (l1 .rpc .xpay , (inv1 ))
1511+
1512+ # Wait for the first payment to reserve the path.
1513+ l1 .daemon .wait_for_log (r"json_askrene_reserve called" )
1514+
1515+ # A second payment starts.
1516+ l1 .rpc .xpay (inv2 )
1517+ l1 .daemon .wait_for_log (r"json_askrene_unreserve called" )
1518+
1519+ l3 .daemon .wait_for_log (r"Holding onto an incoming htlc for 10 seconds" )
1520+
1521+ # There is a payment pending therefore we expect reservations.
1522+ reservations = l1 .rpc .askrene_listreservations ()
1523+ assert reservations != {"reservations" : []}
1524+
1525+ l3 .daemon .wait_for_log (r"htlc_accepted hook called" )
1526+ fut .result ()
1527+ l1 .daemon .wait_for_log (r"json_askrene_unreserve called" )
1528+
1529+ # The first payment has finished we expect no reservations.
1530+ reservations = l1 .rpc .askrene_listreservations ()
1531+ assert reservations == {"reservations" : []}
1532+
1533+ # We shouldn't fail askrene-unreserve. If it does it means something went
1534+ # wrong.
1535+ assert l1 .daemon .is_in_log ("askrene-unreserve failed" ) is None
0 commit comments