renepay: fix CLTV value for the first hop
What changed, and why it matters
This commit fixes a bug in Core Lightning's renepay plugin where the time-lock expiry (CLTV) for the first hop of a payment was incorrectly calculated by adding the current block height twice. This caused HTLCs to be created with expiry values roughly 900,000 blocks too far in the future. Such far-future expiries could make payments fail or behave unexpectedly, and in edge cases could affect fund safety by making HTLCs resolve very slowly.
Review whether any payments sent with the buggy renepay version used far-future CLTVs and could have been stranded, failed, or exploited via timeout manipulation. Apply the patch and run the now-enabled test_cltv_value.
Security signals we found
CLTV/expiry miscalculation in payment routing
Double-counting of blockheight leading to far-future HTLC expiries
Test previously marked xfail now passing after fix
Schema fixture values corrected to realistic expiry
Evidence from the diff
In plugins/renepay/routetracker.c, route_sendpay_request() was adding payment->blockheight to hop->delay when emitting the JSON “delay” field. However, hop->delay was already computed relative to the current block height elsewhere, so this double-counted the blockheight. The fix removes the extra payment->blockheight addition. Test and schema fixtures were updated to reflect realistic expiry values (281 -> 143). The test that was marked as expected-to-fail is now enabled.
Changed components
plugins/renepay/routetracker.ctests/test_renepay.pycontrib/msggen/msggen/schema.jsondoc/schemas/listhtlcs.jsonInspect captured patch +3 / −5
diff --git a/contrib/msggen/msggen/schema.json b/contrib/msggen/msggen/schema.json
index d5eb1279..2757c55b 100644
--- a/contrib/msggen/msggen/schema.json
+++ b/contrib/msggen/msggen/schema.json
@@ -24597,7 +24597,7 @@
"updated_index": 113,
"short_channel_id": "116x1x1",
"id": 11,
- "expiry": 281,
+ "expiry": 143,
"direction": "out",
"amount_msat": 400000,
"payment_hash": "3fe5289854b8924b4ebb3f61cd8f5a29f8f509cc781919230f7ee95ec2fa7c46",
diff --git a/doc/schemas/listhtlcs.json b/doc/schemas/listhtlcs.json
index b3d317b5..fefde9b6 100644
--- a/doc/schemas/listhtlcs.json
+++ b/doc/schemas/listhtlcs.json
@@ -314,7 +314,7 @@
"updated_index": 113,
"short_channel_id": "116x1x1",
"id": 11,
- "expiry": 281,
+ "expiry": 143,
"direction": "out",
"amount_msat": 400000,
"payment_hash": "3fe5289854b8924b4ebb3f61cd8f5a29f8f509cc781919230f7ee95ec2fa7c46",
diff --git a/plugins/renepay/routetracker.c b/plugins/renepay/routetracker.c
index b8810465..8668d307 100644
--- a/plugins/renepay/routetracker.c
+++ b/plugins/renepay/routetracker.c
@@ -449,8 +449,7 @@ struct command_result *route_sendpay_request(struct command *cmd,
json_add_amount_msat(req->js, "amount_msat", hop->amount);
json_add_node_id(req->js, "id", &hop->node_id);
json_add_short_channel_id(req->js, "channel", hop->scid);
- json_add_num(req->js, "delay",
- hop->delay + payment->blockheight);
+ json_add_num(req->js, "delay", hop->delay);
json_object_end(req->js);
// FIXME: No localinvreqid is provided
diff --git a/tests/test_renepay.py b/tests/test_renepay.py
index 12ef1fde..52d4b951 100644
--- a/tests/test_renepay.py
+++ b/tests/test_renepay.py
@@ -876,7 +876,6 @@ def test_unannounced(node_factory):
assert ret["status"] == "complete"
-@pytest.mark.xfail(strict=True)
def test_cltv_value(node_factory, bitcoind):
cltv_delta = 5
l1, l2, l3 = node_factory.line_graph(
Why this scored 60/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.