From 9c078ce75cf8b3fc67de993f70847359165e8dac Mon Sep 17 00:00:00 2001 From: wqxoxo Date: Fri, 28 Nov 2025 08:37:35 +0000 Subject: [PATCH] pay: Enforce maxdelay for direct channel payments When paying through a direct channel, direct_pay_override() creates a route bypassing the normal routing path, which skips the CLTV budget check in payment_getroute(). This allows payments to succeed even when maxdelay is set below the required min_final_cltv_expiry. Add a check in direct_pay_override() to verify the required CLTV doesn't exceed cltv_budget before using the direct channel shortcut. If it exceeds, skip the direct channel and let normal routing handle the failure with a proper error message. Fixes: #8609 Changelog-Fixed: pay: `maxdelay` parameter now enforced for direct channel payments --- plugins/libplugin-pay.c | 9 +++++++++ tests/test_pay.py | 11 +++++++++++ 2 files changed, 20 insertions(+) diff --git a/plugins/libplugin-pay.c b/plugins/libplugin-pay.c index cb212236bd7e..2792bc1b136b 100644 --- a/plugins/libplugin-pay.c +++ b/plugins/libplugin-pay.c @@ -3479,6 +3479,15 @@ static struct command_result *direct_pay_override(struct payment *p) hint = channel_hint_set_find(root->hints, d->chan); if (hint && hint->enabled && amount_msat_greater(hint->estimated_capacity, p->our_amount)) { + if (p->getroute->cltv > p->constraints.cltv_budget) { + paymod_log(p, LOG_DBG, + "Direct channel (%s) skipped: " + "CLTV delay %u exceeds budget %u.", + fmt_short_channel_id_dir(tmpctx, &hint->scid), + p->getroute->cltv, p->constraints.cltv_budget); + return payment_continue(p); + } + /* Now build a route that consists only of this single hop */ p->route = tal_arr(p, struct route_hop, 1); p->route[0].amount = p->our_amount; diff --git a/tests/test_pay.py b/tests/test_pay.py index 442e35d73756..f159689b1cce 100644 --- a/tests/test_pay.py +++ b/tests/test_pay.py @@ -150,6 +150,17 @@ def test_pay_limits(node_factory): assert status[0]['strategy'] == "Initial attempt" +def test_pay_maxdelay_direct_channel(node_factory): + """Test that maxdelay is enforced even for direct channel payments""" + l1, l2 = node_factory.line_graph(2, wait_for_announce=True) + + inv = l2.rpc.invoice('10000msat', 'test_pay_maxdelay_direct', 'description')['bolt11'] + + # Delay too low for direct channel. + with pytest.raises(RpcError, match=r'CLTV delay exceeds our CLTV budget'): + l1.rpc.call('pay', {'bolt11': inv, 'maxdelay': 1}) + + def test_pay_exclude_node(node_factory, bitcoind): """Test excluding the node if there's the NODE-level error in the failure_code """