Skip to content

Commit 6ba9894

Browse files
Support aggregating htlc_minimum_msat for BlindedPayInfo
1 parent 584075b commit 6ba9894

File tree

1 file changed

+113
-1
lines changed

1 file changed

+113
-1
lines changed

lightning/src/blinded_path/payment.rs

+113-1
Original file line numberDiff line numberDiff line change
@@ -184,11 +184,39 @@ 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+
.skip(1)
196+
.map(|(_, (_, tlvs))| tlvs);
197+
for node in next_nodes {
198+
// The min htlc for a hop is that hop's htlc_minimum_msat minus the following hops' fees
199+
// because the sender will automatically include that following fee in the amount that this
200+
// hop forwards.
201+
let prop_fee = node.payment_relay.fee_proportional_millionths as u128;
202+
let base_fee = node.payment_relay.fee_base_msat as u128;
203+
let hop_fee = htlc_min_candidate
204+
.checked_mul(prop_fee)
205+
.and_then(|prop_fee| (prop_fee / 1_000_000).checked_add(base_fee))
206+
.ok_or(())?;
207+
htlc_min_candidate = htlc_min_candidate.saturating_sub(hop_fee);
208+
if htlc_min_candidate == 0 { break }
209+
}
210+
htlc_minimum_msat = core::cmp::max(htlc_min_candidate, htlc_minimum_msat);
211+
}
212+
htlc_minimum_msat =
213+
core::cmp::max(payee_tlvs.payment_constraints.htlc_minimum_msat as u128, htlc_minimum_msat);
214+
187215
Ok(BlindedPayInfo {
188216
fee_base_msat: u32::try_from(curr_base_fee).map_err(|_| ())?,
189217
fee_proportional_millionths: u32::try_from(curr_prop_mil).map_err(|_| ())?,
190218
cltv_expiry_delta,
191-
htlc_minimum_msat: 1, // TODO
219+
htlc_minimum_msat: u64::try_from(htlc_minimum_msat).map_err(|_| ())?,
192220
htlc_maximum_msat: 21_000_000 * 100_000_000 * 1_000, // TODO
193221
features: BlindedHopFeatures::empty(),
194222
})
@@ -253,6 +281,7 @@ mod tests {
253281
assert_eq!(blinded_payinfo.fee_base_msat, 201);
254282
assert_eq!(blinded_payinfo.fee_proportional_millionths, 1001);
255283
assert_eq!(blinded_payinfo.cltv_expiry_delta, 288);
284+
assert_eq!(blinded_payinfo.htlc_minimum_msat, 1000);
256285
}
257286

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

0 commit comments

Comments
 (0)