Skip to content

Commit d3a508d

Browse files
Support aggregating htlc_minimum_msat for BlindedPayInfo
1 parent 957b17f commit d3a508d

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
@@ -184,11 +184,38 @@ pub(super) fn compute_payinfo(
184184

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

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

0 commit comments

Comments
 (0)