Skip to content

Commit 752a021

Browse files
Support aggregating htlc_minimum_msat for BlindedPayInfo
1 parent fcad1cf commit 752a021

File tree

1 file changed

+113
-1
lines changed

1 file changed

+113
-1
lines changed

lightning/src/blinded_path/payment.rs

Lines changed: 113 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -182,11 +182,38 @@ pub(super) fn compute_payinfo(
182182

183183
cltv_expiry_delta = cltv_expiry_delta.checked_add(tlvs.payment_relay.cltv_expiry_delta).ok_or(())?;
184184
}
185+
186+
let mut htlc_minimum_msat = 0;
187+
for (idx, node) in intermediate_nodes.iter().map(|(_, tlvs)| tlvs).enumerate() {
188+
let mut htlc_min_candidate = node.payment_constraints.htlc_minimum_msat as u128;
189+
// Get an iterator over `[curr_hop_tlvs..last_intermediate_hop_tlvs]`.
190+
let next_nodes = intermediate_nodes.iter()
191+
.enumerate()
192+
.skip_while(|(i, _)| *i != idx)
193+
.map(|(_, (_, tlvs))| tlvs);
194+
for node in next_nodes {
195+
// The min htlc for a hop is that hop's htlc_minimum_msat minus the fees paid from that hop to
196+
// the end of the path, because the sender will automatically include that following fee
197+
// amount in the amount that this hop receives.
198+
let prop_fee = node.payment_relay.fee_proportional_millionths as u128;
199+
let base_fee = node.payment_relay.fee_base_msat as u128;
200+
let hop_fee = htlc_min_candidate
201+
.checked_mul(prop_fee)
202+
.and_then(|prop_fee| (prop_fee / 1_000_000).checked_add(base_fee))
203+
.ok_or(())?;
204+
htlc_min_candidate = htlc_min_candidate.saturating_sub(hop_fee);
205+
if htlc_min_candidate == 0 { break }
206+
}
207+
htlc_minimum_msat = core::cmp::max(htlc_min_candidate, htlc_minimum_msat);
208+
}
209+
htlc_minimum_msat =
210+
core::cmp::max(payee_tlvs.payment_constraints.htlc_minimum_msat as u128, htlc_minimum_msat);
211+
185212
Ok(BlindedPayInfo {
186213
fee_base_msat: u32::try_from(curr_base_fee).map_err(|_| ())?,
187214
fee_proportional_millionths: u32::try_from(curr_prop_mil).map_err(|_| ())?,
188215
cltv_expiry_delta,
189-
htlc_minimum_msat: 1, // TODO
216+
htlc_minimum_msat: u64::try_from(htlc_minimum_msat).map_err(|_| ())?,
190217
htlc_maximum_msat: 21_000_000 * 100_000_000 * 1_000, // TODO
191218
features: BlindedHopFeatures::empty(),
192219
})
@@ -251,6 +278,7 @@ mod tests {
251278
assert_eq!(blinded_payinfo.fee_base_msat, 201);
252279
assert_eq!(blinded_payinfo.fee_proportional_millionths, 1001);
253280
assert_eq!(blinded_payinfo.cltv_expiry_delta, 288);
281+
assert_eq!(blinded_payinfo.htlc_minimum_msat, 900);
254282
}
255283

256284
#[test]
@@ -266,5 +294,89 @@ mod tests {
266294
assert_eq!(blinded_payinfo.fee_base_msat, 0);
267295
assert_eq!(blinded_payinfo.fee_proportional_millionths, 0);
268296
assert_eq!(blinded_payinfo.cltv_expiry_delta, 0);
297+
assert_eq!(blinded_payinfo.htlc_minimum_msat, 1);
298+
}
299+
300+
#[test]
301+
fn simple_aggregated_htlc_min() {
302+
// If no hops charge fees, the htlc_minimum_msat should just be the maximum htlc_minimum_msat
303+
// along the path.
304+
let dummy_pk = PublicKey::from_slice(&[2; 33]).unwrap();
305+
let intermediate_nodes = vec![(dummy_pk, ForwardTlvs {
306+
short_channel_id: 0,
307+
payment_relay: PaymentRelay {
308+
cltv_expiry_delta: 0,
309+
fee_proportional_millionths: 0,
310+
fee_base_msat: 0,
311+
},
312+
payment_constraints: PaymentConstraints {
313+
max_cltv_expiry: 0,
314+
htlc_minimum_msat: 1,
315+
},
316+
features: BlindedHopFeatures::empty(),
317+
}), (dummy_pk, ForwardTlvs {
318+
short_channel_id: 0,
319+
payment_relay: PaymentRelay {
320+
cltv_expiry_delta: 0,
321+
fee_proportional_millionths: 0,
322+
fee_base_msat: 0,
323+
},
324+
payment_constraints: PaymentConstraints {
325+
max_cltv_expiry: 0,
326+
htlc_minimum_msat: 2_000,
327+
},
328+
features: BlindedHopFeatures::empty(),
329+
})];
330+
let recv_tlvs = ReceiveTlvs {
331+
payment_secret: PaymentSecret([0; 32]),
332+
payment_constraints: PaymentConstraints {
333+
max_cltv_expiry: 0,
334+
htlc_minimum_msat: 3,
335+
},
336+
};
337+
let blinded_payinfo = super::compute_payinfo(&intermediate_nodes[..], &recv_tlvs).unwrap();
338+
assert_eq!(blinded_payinfo.htlc_minimum_msat, 2_000);
339+
}
340+
341+
#[test]
342+
fn aggregated_htlc_min() {
343+
// Create a path with varying fees and htlc_mins, and make sure htlc_minimum_msat ends up as the
344+
// max (htlc_min - following_fees) along the path.
345+
let dummy_pk = PublicKey::from_slice(&[2; 33]).unwrap();
346+
let intermediate_nodes = vec![(dummy_pk, ForwardTlvs {
347+
short_channel_id: 0,
348+
payment_relay: PaymentRelay {
349+
cltv_expiry_delta: 0,
350+
fee_proportional_millionths: 500,
351+
fee_base_msat: 1_000,
352+
},
353+
payment_constraints: PaymentConstraints {
354+
max_cltv_expiry: 0,
355+
htlc_minimum_msat: 5_000,
356+
},
357+
features: BlindedHopFeatures::empty(),
358+
}), (dummy_pk, ForwardTlvs {
359+
short_channel_id: 0,
360+
payment_relay: PaymentRelay {
361+
cltv_expiry_delta: 0,
362+
fee_proportional_millionths: 500,
363+
fee_base_msat: 200,
364+
},
365+
payment_constraints: PaymentConstraints {
366+
max_cltv_expiry: 0,
367+
htlc_minimum_msat: 2_000,
368+
},
369+
features: BlindedHopFeatures::empty(),
370+
})];
371+
let recv_tlvs = ReceiveTlvs {
372+
payment_secret: PaymentSecret([0; 32]),
373+
payment_constraints: PaymentConstraints {
374+
max_cltv_expiry: 0,
375+
htlc_minimum_msat: 1,
376+
},
377+
};
378+
379+
let blinded_payinfo = super::compute_payinfo(&intermediate_nodes[..], &recv_tlvs).unwrap();
380+
assert_eq!(blinded_payinfo.htlc_minimum_msat, 3797);
269381
}
270382
}

0 commit comments

Comments
 (0)