xpay: add helper to fetch the MPP amount ...
What changed, and why it matters
This commit adds a helper function in Core Lightning's xpay plugin to correctly determine the multi-path payment (MPP) amount used in the final hop of a payment route. For normal xpay payments, this amount is already known, but for 'sendamount' payments where fees are included in the amount, the actual delivered amount must be used instead. The change ensures the correct value is passed when building both regular and blinded payment routes, likely fixing a bug where the wrong MPP amount could be set.
Review the prior behavior to confirm whether incorrect MPP amounts could cause payment failure, routing loops, or exploitable fee/amount manipulation. Treat as a bugfix with possible reliability or minor security implications. No immediate emergency action is indicated from the diff alone.
Security signals we found
Logic correction in payment onion payload construction
Use of assert on maxparts in fee-inclusive path
Potential prior mismatch between delivered amount and MPP amount in final hop
No explicit security framing in commit message
Evidence from the diff
The patch introduces attempt_mpp_amount(), which returns attempt->payment->mpp_amount when includefees is false, and otherwise asserts maxparts == 1 and returns attempt_deliver(attempt). This value is then used in place of attempt->payment->mpp_amount in append_blinded_payloads() and create_onion() when constructing the final hop payload. The change appears to address an inconsistency where the MPP amount in the final hop did not account for the fee-inclusive case, potentially causing payment failures or incorrect routing behavior for sendamount payments with includefees enabled.
Changed components
plugins/xpay/xpay.cxpay pluginMPP final hop payload constructionblinded path payload constructionsendamount with includefeesInspect captured patch +12 / −2
diff --git a/plugins/xpay/xpay.c b/plugins/xpay/xpay.c
index 2a74906a..af516cfa 100644
--- a/plugins/xpay/xpay.c
+++ b/plugins/xpay/xpay.c
@@ -394,6 +394,14 @@ static struct amount_msat attempt_deliver(const struct attempt *attempt)
return attempt->hops[len - 1].amount_out;
}
+static struct amount_msat attempt_mpp_amount(const struct attempt *attempt)
+{
+ if (!attempt->payment->includefees)
+ return attempt->payment->mpp_amount;
+ assert(attempt->payment->maxparts == 1);
+ return attempt_deliver(attempt);
+}
+
static u32 initial_cltv_delta(const struct attempt *attempt)
{
if (tal_count(attempt->hops) == 0)
@@ -1326,6 +1334,7 @@ static void append_blinded_payloads(struct sphinx_path *sp,
const struct blinded_path *path = attempt->payment->paths[path_num];
u32 final_cltv = effective_bheight;
struct amount_msat deliver = attempt_deliver(attempt);
+ const struct amount_msat mpp_amount = attempt_mpp_amount(attempt);
for (size_t i = 0; i < tal_count(path->path); i++) {
bool first = (i == 0);
@@ -1346,7 +1355,7 @@ static void append_blinded_payloads(struct sphinx_path *sp,
*/
payload = onion_blinded_hop(NULL,
final ? &deliver : NULL,
- final ? &attempt->payment->mpp_amount : NULL,
+ final ? &mpp_amount : NULL,
final ? &final_cltv : NULL,
path->path[i]->encrypted_recipient_data,
first ? &path->first_path_key : NULL);
@@ -1390,6 +1399,7 @@ static const u8 *create_onion(const tal_t *ctx,
struct sphinx_path *sp;
const u8 *payload, *ret;
const struct pubkey *node;
+ const struct amount_msat mpp_amount = attempt_mpp_amount(attempt);
sp = sphinx_path_new(ctx, attempt->payment->payment_hash.u.u8,
sizeof(attempt->payment->payment_hash.u.u8));
@@ -1423,7 +1433,7 @@ static const u8 *create_onion(const tal_t *ctx,
u8 *final = onion_final_hop(NULL,
attempt_deliver(attempt),
attempt->payment->final_cltv + effective_bheight,
- attempt->payment->mpp_amount,
+ mpp_amount,
attempt->payment->payment_secret,
attempt->payment->payment_metadata);
hop_append(&final, attempt->payment->extra_tlvs);
Why this scored 32/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.