@@ -150,6 +150,98 @@ def test_pay_limits(node_factory):
150150 assert status [0 ]['strategy' ] == "Initial attempt"
151151
152152
153+ def test_pay_no_excessive_splitting_on_cltv (node_factory ):
154+ """Test that CLTV budget exceeded doesn't cause excessive splitting (#8167).
155+ """
156+ l1 , l2 , l3 = node_factory .line_graph (3 , wait_for_announce = True )
157+
158+ inv = l3 .rpc .invoice (1000000 , "test_cltv" , 'description' )['bolt11' ]
159+
160+ PAY_STOPPED_RETRYING = 210
161+ with pytest .raises (RpcError , match = r'CLTV delay exceeds our CLTV budget' ) as err :
162+ l1 .rpc .call ('pay' , {'bolt11' : inv , 'maxdelay' : 5 })
163+
164+ assert err .value .error ['code' ] == PAY_STOPPED_RETRYING
165+
166+ status = l1 .rpc .call ('paystatus' , {'bolt11' : inv })['pay' ][0 ]['attempts' ]
167+
168+ # Without fix: ~62 attempts. With fix: ~30 (depth-3 split limit).
169+ assert len (status ) < 35 , \
170+ f"Too many attempts ({ len (status )} ), possible infinite loop bug"
171+
172+
173+ def test_pay_mpp_splitting_still_works (node_factory , bitcoind ):
174+ """Regression test: MPP splitting still works after #8167 fix.
175+ """
176+ l1 , l2 , l3 , l4 = node_factory .get_nodes (4 )
177+
178+ # Diamond topology: l1->l2->l4 and l1->l3->l4, 200k capacity each
179+ l1 .rpc .connect (l2 .info ['id' ], 'localhost' , l2 .port )
180+ l1 .rpc .connect (l3 .info ['id' ], 'localhost' , l3 .port )
181+ l2 .rpc .connect (l4 .info ['id' ], 'localhost' , l4 .port )
182+ l3 .rpc .connect (l4 .info ['id' ], 'localhost' , l4 .port )
183+
184+ l1 .fundchannel (l2 , 200000 , wait_for_active = True )
185+ l1 .fundchannel (l3 , 200000 , wait_for_active = True )
186+ l2 .fundchannel (l4 , 200000 , wait_for_active = True )
187+ l3 .fundchannel (l4 , 200000 , wait_for_active = True )
188+
189+ mine_funding_to_announce (bitcoind , [l1 , l2 , l3 , l4 ])
190+ wait_for (lambda : len (l1 .rpc .listchannels ()['channels' ]) == 8 )
191+
192+ # 300k needs splitting across both paths
193+ inv = l4 .rpc .invoice (300000000 , "diamond_split" , "Needs splitting" )['bolt11' ]
194+
195+ result = l1 .rpc .pay (inv )
196+ assert result ['status' ] == 'complete'
197+
198+ payments = l1 .rpc .listsendpays (bolt11 = inv )['payments' ]
199+ successful = [p for p in payments if p ['status' ] == 'complete' ]
200+ assert len (successful ) > 1 , "Payment should have been split"
201+
202+
203+ def test_pay_splitting_bypass_cltv_multiple_paths (node_factory , bitcoind ):
204+ """Test that splitting can bypass high-CLTV path via multiple low-CLTV paths.
205+
206+ Topology:
207+ l1 ---(100k)---> l2 ---(100k)---> l5 [normal CLTV]
208+ l1 ---(100k)---> l3 ---(100k)---> l5 [normal CLTV]
209+ l1 ---(200k)---> l4 ---(200k)---> l5 [HIGH CLTV]
210+
211+ 150k payment with tight CLTV budget must split via l2+l3, can't use l4.
212+ """
213+ l1 , l2 , l3 , l4 , l5 = node_factory .get_nodes (5 , opts = [
214+ {},
215+ {'cltv-delta' : 6 },
216+ {'cltv-delta' : 6 },
217+ {'cltv-delta' : 100 },
218+ {}
219+ ])
220+
221+ l1 .rpc .connect (l2 .info ['id' ], 'localhost' , l2 .port )
222+ l1 .rpc .connect (l3 .info ['id' ], 'localhost' , l3 .port )
223+ l1 .rpc .connect (l4 .info ['id' ], 'localhost' , l4 .port )
224+ l2 .rpc .connect (l5 .info ['id' ], 'localhost' , l5 .port )
225+ l3 .rpc .connect (l5 .info ['id' ], 'localhost' , l5 .port )
226+ l4 .rpc .connect (l5 .info ['id' ], 'localhost' , l5 .port )
227+
228+ l1 .fundchannel (l2 , 100000 , wait_for_active = True )
229+ l1 .fundchannel (l3 , 100000 , wait_for_active = True )
230+ l1 .fundchannel (l4 , 200000 , wait_for_active = True )
231+ l2 .fundchannel (l5 , 100000 , wait_for_active = True )
232+ l3 .fundchannel (l5 , 100000 , wait_for_active = True )
233+ l4 .fundchannel (l5 , 200000 , wait_for_active = True )
234+
235+ mine_funding_to_announce (bitcoind , [l1 , l2 , l3 , l4 , l5 ])
236+ wait_for (lambda : len (l1 .rpc .listchannels ()['channels' ]) == 12 )
237+
238+ inv = l5 .rpc .invoice (150000000 , "test_split_cltv" , "Test splitting" )['bolt11' ]
239+
240+ # maxdelay=30 allows l2/l3 paths (~12 blocks) but not l4 (~106 blocks)
241+ result = l1 .rpc .call ('pay' , {'bolt11' : inv , 'maxdelay' : 30 })
242+ assert result ['status' ] == 'complete'
243+
244+
153245def test_pay_exclude_node (node_factory , bitcoind ):
154246 """Test excluding the node if there's the NODE-level error in the failure_code
155247 """
0 commit comments