Skip to content

Commit f068df0

Browse files
authored
Merge pull request #2312 from TheBlueMatt/2023-05-next-htlc-min-max
Avoid generating unpayable routes due to balance restrictions
2 parents 7a78998 + 4bc3a79 commit f068df0

File tree

7 files changed

+266
-223
lines changed

7 files changed

+266
-223
lines changed

fuzz/src/chanmon_consistency.rs

Lines changed: 38 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -285,7 +285,7 @@ impl KeyProvider {
285285
}
286286

287287
#[inline]
288-
fn check_api_err(api_err: APIError) {
288+
fn check_api_err(api_err: APIError, sendable_bounds_violated: bool) {
289289
match api_err {
290290
APIError::APIMisuseError { .. } => panic!("We can't misuse the API"),
291291
APIError::FeeRateTooHigh { .. } => panic!("We can't send too much fee?"),
@@ -296,15 +296,11 @@ fn check_api_err(api_err: APIError) {
296296
// is probably just stale and you should add new messages here.
297297
match err.as_str() {
298298
"Peer for first hop currently disconnected" => {},
299-
_ if err.starts_with("Cannot push more than their max accepted HTLCs ") => {},
300-
_ if err.starts_with("Cannot send value that would put us over the max HTLC value in flight our peer will accept ") => {},
301-
_ if err.starts_with("Cannot send value that would put our balance under counterparty-announced channel reserve value") => {},
302-
_ if err.starts_with("Cannot send value that would put counterparty balance under holder-announced channel reserve value") => {},
303-
_ if err.starts_with("Cannot send value that would overdraw remaining funds.") => {},
304-
_ if err.starts_with("Cannot send value that would not leave enough to pay for fees.") => {},
305-
_ if err.starts_with("Cannot send value that would put our exposure to dust HTLCs at") => {},
299+
_ if err.starts_with("Cannot send less than our next-HTLC minimum - ") => {},
300+
_ if err.starts_with("Cannot send more than our next-HTLC maximum - ") => {},
306301
_ => panic!("{}", err),
307302
}
303+
assert!(sendable_bounds_violated);
308304
},
309305
APIError::MonitorUpdateInProgress => {
310306
// We can (obviously) temp-fail a monitor update
@@ -313,17 +309,17 @@ fn check_api_err(api_err: APIError) {
313309
}
314310
}
315311
#[inline]
316-
fn check_payment_err(send_err: PaymentSendFailure) {
312+
fn check_payment_err(send_err: PaymentSendFailure, sendable_bounds_violated: bool) {
317313
match send_err {
318-
PaymentSendFailure::ParameterError(api_err) => check_api_err(api_err),
314+
PaymentSendFailure::ParameterError(api_err) => check_api_err(api_err, sendable_bounds_violated),
319315
PaymentSendFailure::PathParameterError(per_path_results) => {
320-
for res in per_path_results { if let Err(api_err) = res { check_api_err(api_err); } }
316+
for res in per_path_results { if let Err(api_err) = res { check_api_err(api_err, sendable_bounds_violated); } }
321317
},
322318
PaymentSendFailure::AllFailedResendSafe(per_path_results) => {
323-
for api_err in per_path_results { check_api_err(api_err); }
319+
for api_err in per_path_results { check_api_err(api_err, sendable_bounds_violated); }
324320
},
325321
PaymentSendFailure::PartialFailure { results, .. } => {
326-
for res in results { if let Err(api_err) = res { check_api_err(api_err); } }
322+
for res in results { if let Err(api_err) = res { check_api_err(api_err, sendable_bounds_violated); } }
327323
},
328324
PaymentSendFailure::DuplicatePayment => panic!(),
329325
}
@@ -351,6 +347,11 @@ fn send_payment(source: &ChanMan, dest: &ChanMan, dest_chan_id: u64, amt: u64, p
351347
let mut payment_id = [0; 32];
352348
payment_id[0..8].copy_from_slice(&payment_idx.to_ne_bytes());
353349
*payment_idx += 1;
350+
let (min_value_sendable, max_value_sendable) = source.list_usable_channels()
351+
.iter().find(|chan| chan.short_channel_id == Some(dest_chan_id))
352+
.map(|chan|
353+
(chan.next_outbound_htlc_minimum_msat, chan.next_outbound_htlc_limit_msat))
354+
.unwrap_or((0, 0));
354355
if let Err(err) = source.send_payment_with_route(&Route {
355356
paths: vec![Path { hops: vec![RouteHop {
356357
pubkey: dest.get_our_node_id(),
@@ -362,9 +363,15 @@ fn send_payment(source: &ChanMan, dest: &ChanMan, dest_chan_id: u64, amt: u64, p
362363
}], blinded_tail: None }],
363364
payment_params: None,
364365
}, payment_hash, RecipientOnionFields::secret_only(payment_secret), PaymentId(payment_id)) {
365-
check_payment_err(err);
366+
check_payment_err(err, amt > max_value_sendable || amt < min_value_sendable);
366367
false
367-
} else { true }
368+
} else {
369+
// Note that while the max is a strict upper-bound, we can occasionally send substantially
370+
// below the minimum, with some gap which is unusable immediately below the minimum. Thus,
371+
// we don't check against min_value_sendable here.
372+
assert!(amt <= max_value_sendable);
373+
true
374+
}
368375
}
369376
#[inline]
370377
fn send_hop_payment(source: &ChanMan, middle: &ChanMan, middle_chan_id: u64, dest: &ChanMan, dest_chan_id: u64, amt: u64, payment_id: &mut u8, payment_idx: &mut u64) -> bool {
@@ -373,13 +380,19 @@ fn send_hop_payment(source: &ChanMan, middle: &ChanMan, middle_chan_id: u64, des
373380
let mut payment_id = [0; 32];
374381
payment_id[0..8].copy_from_slice(&payment_idx.to_ne_bytes());
375382
*payment_idx += 1;
383+
let (min_value_sendable, max_value_sendable) = source.list_usable_channels()
384+
.iter().find(|chan| chan.short_channel_id == Some(middle_chan_id))
385+
.map(|chan|
386+
(chan.next_outbound_htlc_minimum_msat, chan.next_outbound_htlc_limit_msat))
387+
.unwrap_or((0, 0));
388+
let first_hop_fee = 50_000;
376389
if let Err(err) = source.send_payment_with_route(&Route {
377390
paths: vec![Path { hops: vec![RouteHop {
378391
pubkey: middle.get_our_node_id(),
379392
node_features: middle.node_features(),
380393
short_channel_id: middle_chan_id,
381394
channel_features: middle.channel_features(),
382-
fee_msat: 50000,
395+
fee_msat: first_hop_fee,
383396
cltv_expiry_delta: 100,
384397
},RouteHop {
385398
pubkey: dest.get_our_node_id(),
@@ -391,9 +404,16 @@ fn send_hop_payment(source: &ChanMan, middle: &ChanMan, middle_chan_id: u64, des
391404
}], blinded_tail: None }],
392405
payment_params: None,
393406
}, payment_hash, RecipientOnionFields::secret_only(payment_secret), PaymentId(payment_id)) {
394-
check_payment_err(err);
407+
let sent_amt = amt + first_hop_fee;
408+
check_payment_err(err, sent_amt < min_value_sendable || sent_amt > max_value_sendable);
395409
false
396-
} else { true }
410+
} else {
411+
// Note that while the max is a strict upper-bound, we can occasionally send substantially
412+
// below the minimum, with some gap which is unusable immediately below the minimum. Thus,
413+
// we don't check against min_value_sendable here.
414+
assert!(amt + first_hop_fee <= max_value_sendable);
415+
true
416+
}
397417
}
398418

399419
#[inline]

fuzz/src/router.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -265,6 +265,7 @@ pub fn do_test<Out: test_logger::Output>(data: &[u8], out: Out) {
265265
balance_msat: 0,
266266
outbound_capacity_msat: capacity.saturating_mul(1000),
267267
next_outbound_htlc_limit_msat: capacity.saturating_mul(1000),
268+
next_outbound_htlc_minimum_msat: 0,
268269
inbound_htlc_minimum_msat: None,
269270
inbound_htlc_maximum_msat: None,
270271
config: None,

0 commit comments

Comments
 (0)