Skip to content

Commit 769a500

Browse files
Support aggregating htlc_maximum_msat for BlindedPayInfo
1 parent 2353d3f commit 769a500

File tree

2 files changed

+80
-21
lines changed

2 files changed

+80
-21
lines changed

lightning/src/blinded_path/mod.rs

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -76,7 +76,8 @@ impl BlindedPath {
7676
})
7777
}
7878

79-
/// Create a blinded path for a payment, to be forwarded along `intermediate_nodes`.
79+
/// Create a blinded path for a payment, to be forwarded along `intermediate_nodes`, where each
80+
/// node is composed of `(node_id, tlvs, htlc_maximum_msat)`.
8081
///
8182
/// Errors if:
8283
/// * a provided node id is invalid
@@ -86,13 +87,14 @@ impl BlindedPath {
8687
/// [`ForwardTlvs`]: crate::blinded_path::payment::ForwardTlvs
8788
// TODO: make all payloads the same size with padding + add dummy hops
8889
pub fn new_for_payment<ES: EntropySource, T: secp256k1::Signing + secp256k1::Verification>(
89-
intermediate_nodes: &[(PublicKey, payment::ForwardTlvs)], payee_node_id: PublicKey,
90-
payee_tlvs: payment::ReceiveTlvs, entropy_source: &ES, secp_ctx: &Secp256k1<T>
90+
intermediate_nodes: &[(PublicKey, payment::ForwardTlvs, u64)], payee_node_id: PublicKey,
91+
payee_tlvs: payment::ReceiveTlvs, htlc_maximum_msat: u64, entropy_source: &ES,
92+
secp_ctx: &Secp256k1<T>
9193
) -> Result<(BlindedPayInfo, Self), ()> {
9294
let blinding_secret_bytes = entropy_source.get_secure_random_bytes();
9395
let blinding_secret = SecretKey::from_slice(&blinding_secret_bytes[..]).expect("RNG is busted");
9496

95-
let blinded_payinfo = payment::compute_payinfo(intermediate_nodes, &payee_tlvs)?;
97+
let blinded_payinfo = payment::compute_payinfo(intermediate_nodes, &payee_tlvs, htlc_maximum_msat)?;
9698
Ok((blinded_payinfo, BlindedPath {
9799
introduction_node_id: intermediate_nodes.first().map_or(payee_node_id, |n| n.0),
98100
blinding_point: PublicKey::from_secret_key(secp_ctx, &blinding_secret),

lightning/src/blinded_path/payment.rs

Lines changed: 74 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -144,12 +144,12 @@ impl Readable for BlindedPaymentTlvs {
144144

145145
/// Construct blinded payment hops for the given `intermediate_nodes` and payee info.
146146
pub(super) fn blinded_hops<T: secp256k1::Signing + secp256k1::Verification>(
147-
secp_ctx: &Secp256k1<T>, intermediate_nodes: &[(PublicKey, ForwardTlvs)],
147+
secp_ctx: &Secp256k1<T>, intermediate_nodes: &[(PublicKey, ForwardTlvs, u64)],
148148
payee_node_id: PublicKey, payee_tlvs: ReceiveTlvs, session_priv: &SecretKey
149149
) -> Result<Vec<BlindedHop>, secp256k1::Error> {
150-
let pks = intermediate_nodes.iter().map(|(pk, _)| pk)
150+
let pks = intermediate_nodes.iter().map(|(pk, _, _)| pk)
151151
.chain(core::iter::once(&payee_node_id));
152-
let tlvs = intermediate_nodes.iter().map(|(_, tlvs)| BlindedPaymentTlvsRef::Forward(tlvs))
152+
let tlvs = intermediate_nodes.iter().map(|(_, tlvs, _)| BlindedPaymentTlvsRef::Forward(tlvs))
153153
.chain(core::iter::once(BlindedPaymentTlvsRef::Receive(&payee_tlvs)));
154154
utils::construct_blinded_hops(secp_ctx, pks, tlvs, session_priv)
155155
}
@@ -176,12 +176,13 @@ fn amt_to_forward_msat(inbound_amt_msat: u64, payment_relay: &PaymentRelay) -> O
176176
}
177177

178178
pub(super) fn compute_payinfo(
179-
intermediate_nodes: &[(PublicKey, ForwardTlvs)], payee_tlvs: &ReceiveTlvs
179+
intermediate_nodes: &[(PublicKey, ForwardTlvs, u64)], payee_tlvs: &ReceiveTlvs,
180+
payee_htlc_maximum_msat: u64
180181
) -> Result<BlindedPayInfo, ()> {
181182
let mut curr_base_fee: u64 = 0;
182183
let mut curr_prop_mil: u64 = 0;
183184
let mut cltv_expiry_delta: u16 = 0;
184-
for (_, tlvs) in intermediate_nodes.iter().rev() {
185+
for (_, tlvs, _) in intermediate_nodes.iter().rev() {
185186
// In the future, we'll want to take the intersection of all supported features for the
186187
// `BlindedPayInfo`, but there are no features in that context right now.
187188
if tlvs.features.requires_unknown_bits_from(&BlindedHopFeatures::empty()) { return Err(()) }
@@ -208,25 +209,31 @@ pub(super) fn compute_payinfo(
208209
}
209210

210211
let mut htlc_minimum_msat: u64 = 1;
211-
for (_, tlvs) in intermediate_nodes.iter() {
212+
let mut htlc_maximum_msat: u64 = 21_000_000 * 100_000_000 * 1_000; // Total bitcoin supply
213+
for (_, tlvs, max_htlc_candidate) in intermediate_nodes.iter() {
212214
// The min htlc for an intermediate node is that node's min minus the fees charged by all of the
213215
// following hops for forwarding that min, since that fee amount will automatically be included
214216
// in the amount that this node receives and contribute towards reaching its min.
215217
htlc_minimum_msat = amt_to_forward_msat(
216218
core::cmp::max(tlvs.payment_constraints.htlc_minimum_msat, htlc_minimum_msat),
217219
&tlvs.payment_relay
218220
).unwrap_or(1); // If underflow occurs, we definitely reached this node's min
221+
htlc_maximum_msat = amt_to_forward_msat(
222+
core::cmp::min(*max_htlc_candidate, htlc_maximum_msat), &tlvs.payment_relay
223+
).ok_or(())?; // If underflow occurs, we cannot send to this hop without exceeding their max
219224
}
220225
htlc_minimum_msat = core::cmp::max(
221226
payee_tlvs.payment_constraints.htlc_minimum_msat, htlc_minimum_msat
222227
);
228+
htlc_maximum_msat = core::cmp::min(payee_htlc_maximum_msat, htlc_maximum_msat);
223229

230+
if htlc_maximum_msat < htlc_minimum_msat { return Err(()) }
224231
Ok(BlindedPayInfo {
225232
fee_base_msat: u32::try_from(curr_base_fee).map_err(|_| ())?,
226233
fee_proportional_millionths: u32::try_from(curr_prop_mil).map_err(|_| ())?,
227234
cltv_expiry_delta,
228235
htlc_minimum_msat,
229-
htlc_maximum_msat: 21_000_000 * 100_000_000 * 1_000, // TODO
236+
htlc_maximum_msat,
230237
features: BlindedHopFeatures::empty(),
231238
})
232239
}
@@ -266,7 +273,7 @@ mod tests {
266273
htlc_minimum_msat: 100,
267274
},
268275
features: BlindedHopFeatures::empty(),
269-
}), (dummy_pk, ForwardTlvs {
276+
}, u64::max_value()), (dummy_pk, ForwardTlvs {
270277
short_channel_id: 0,
271278
payment_relay: PaymentRelay {
272279
cltv_expiry_delta: 144,
@@ -278,19 +285,21 @@ mod tests {
278285
htlc_minimum_msat: 1_000,
279286
},
280287
features: BlindedHopFeatures::empty(),
281-
})];
288+
}, u64::max_value())];
282289
let recv_tlvs = ReceiveTlvs {
283290
payment_secret: PaymentSecret([0; 32]),
284291
payment_constraints: PaymentConstraints {
285292
max_cltv_expiry: 0,
286293
htlc_minimum_msat: 1,
287294
},
288295
};
289-
let blinded_payinfo = super::compute_payinfo(&intermediate_nodes[..], &recv_tlvs).unwrap();
296+
let htlc_maximum_msat = 100_000;
297+
let blinded_payinfo = super::compute_payinfo(&intermediate_nodes[..], &recv_tlvs, htlc_maximum_msat).unwrap();
290298
assert_eq!(blinded_payinfo.fee_base_msat, 201);
291299
assert_eq!(blinded_payinfo.fee_proportional_millionths, 1001);
292300
assert_eq!(blinded_payinfo.cltv_expiry_delta, 288);
293301
assert_eq!(blinded_payinfo.htlc_minimum_msat, 900);
302+
assert_eq!(blinded_payinfo.htlc_maximum_msat, htlc_maximum_msat);
294303
}
295304

296305
#[test]
@@ -302,11 +311,12 @@ mod tests {
302311
htlc_minimum_msat: 1,
303312
},
304313
};
305-
let blinded_payinfo = super::compute_payinfo(&[], &recv_tlvs).unwrap();
314+
let blinded_payinfo = super::compute_payinfo(&[], &recv_tlvs, 4242).unwrap();
306315
assert_eq!(blinded_payinfo.fee_base_msat, 0);
307316
assert_eq!(blinded_payinfo.fee_proportional_millionths, 0);
308317
assert_eq!(blinded_payinfo.cltv_expiry_delta, 0);
309318
assert_eq!(blinded_payinfo.htlc_minimum_msat, 1);
319+
assert_eq!(blinded_payinfo.htlc_maximum_msat, 4242);
310320
}
311321

312322
#[test]
@@ -326,7 +336,7 @@ mod tests {
326336
htlc_minimum_msat: 1,
327337
},
328338
features: BlindedHopFeatures::empty(),
329-
}), (dummy_pk, ForwardTlvs {
339+
}, u64::max_value()), (dummy_pk, ForwardTlvs {
330340
short_channel_id: 0,
331341
payment_relay: PaymentRelay {
332342
cltv_expiry_delta: 0,
@@ -338,15 +348,16 @@ mod tests {
338348
htlc_minimum_msat: 2_000,
339349
},
340350
features: BlindedHopFeatures::empty(),
341-
})];
351+
}, u64::max_value())];
342352
let recv_tlvs = ReceiveTlvs {
343353
payment_secret: PaymentSecret([0; 32]),
344354
payment_constraints: PaymentConstraints {
345355
max_cltv_expiry: 0,
346356
htlc_minimum_msat: 3,
347357
},
348358
};
349-
let blinded_payinfo = super::compute_payinfo(&intermediate_nodes[..], &recv_tlvs).unwrap();
359+
let htlc_maximum_msat = 100_000;
360+
let blinded_payinfo = super::compute_payinfo(&intermediate_nodes[..], &recv_tlvs, htlc_maximum_msat).unwrap();
350361
assert_eq!(blinded_payinfo.htlc_minimum_msat, 2_000);
351362
}
352363

@@ -367,7 +378,7 @@ mod tests {
367378
htlc_minimum_msat: 5_000,
368379
},
369380
features: BlindedHopFeatures::empty(),
370-
}), (dummy_pk, ForwardTlvs {
381+
}, u64::max_value()), (dummy_pk, ForwardTlvs {
371382
short_channel_id: 0,
372383
payment_relay: PaymentRelay {
373384
cltv_expiry_delta: 0,
@@ -379,7 +390,7 @@ mod tests {
379390
htlc_minimum_msat: 2_000,
380391
},
381392
features: BlindedHopFeatures::empty(),
382-
})];
393+
}, u64::max_value())];
383394
let recv_tlvs = ReceiveTlvs {
384395
payment_secret: PaymentSecret([0; 32]),
385396
payment_constraints: PaymentConstraints {
@@ -388,7 +399,53 @@ mod tests {
388399
},
389400
};
390401
let htlc_minimum_msat = 3798;
391-
let blinded_payinfo = super::compute_payinfo(&intermediate_nodes[..], &recv_tlvs).unwrap();
402+
assert!(super::compute_payinfo(&intermediate_nodes[..], &recv_tlvs, htlc_minimum_msat - 1).is_err());
403+
404+
let htlc_maximum_msat = htlc_minimum_msat + 1;
405+
let blinded_payinfo = super::compute_payinfo(&intermediate_nodes[..], &recv_tlvs, htlc_maximum_msat).unwrap();
392406
assert_eq!(blinded_payinfo.htlc_minimum_msat, htlc_minimum_msat);
407+
assert_eq!(blinded_payinfo.htlc_maximum_msat, htlc_maximum_msat);
408+
}
409+
410+
#[test]
411+
fn aggregated_htlc_max() {
412+
// Create a path with varying fees and `htlc_maximum_msat`s, and make sure the aggregated max
413+
// htlc ends up as the min (htlc_max - following_fees) along the path.
414+
let dummy_pk = PublicKey::from_slice(&[2; 33]).unwrap();
415+
let intermediate_nodes = vec![(dummy_pk, ForwardTlvs {
416+
short_channel_id: 0,
417+
payment_relay: PaymentRelay {
418+
cltv_expiry_delta: 0,
419+
fee_proportional_millionths: 500,
420+
fee_base_msat: 1_000,
421+
},
422+
payment_constraints: PaymentConstraints {
423+
max_cltv_expiry: 0,
424+
htlc_minimum_msat: 1,
425+
},
426+
features: BlindedHopFeatures::empty(),
427+
}, 5_000), (dummy_pk, ForwardTlvs {
428+
short_channel_id: 0,
429+
payment_relay: PaymentRelay {
430+
cltv_expiry_delta: 0,
431+
fee_proportional_millionths: 500,
432+
fee_base_msat: 1,
433+
},
434+
payment_constraints: PaymentConstraints {
435+
max_cltv_expiry: 0,
436+
htlc_minimum_msat: 1,
437+
},
438+
features: BlindedHopFeatures::empty(),
439+
}, 10_000)];
440+
let recv_tlvs = ReceiveTlvs {
441+
payment_secret: PaymentSecret([0; 32]),
442+
payment_constraints: PaymentConstraints {
443+
max_cltv_expiry: 0,
444+
htlc_minimum_msat: 1,
445+
},
446+
};
447+
448+
let blinded_payinfo = super::compute_payinfo(&intermediate_nodes[..], &recv_tlvs, 10_000).unwrap();
449+
assert_eq!(blinded_payinfo.htlc_maximum_msat, 3997);
393450
}
394451
}

0 commit comments

Comments
 (0)