Skip to content

Commit 4055c76

Browse files
committed
Use only the failed amount when retrying payments, not the full amt
When we landed the initial in-`ChannelManager` payment retries, we stored the `RouteParameters` in the payment info, and then re-use it as-is for new payments. `RouteParameters` is intended to store the information specific to the *route*, `PaymentParameters` exists to store information specific to a payment. Worse, because we don't recalculate the amount stored in the `RouteParameters` before fetching a new route with it, we end up attempting to retry the full payment amount, rather than only the failed part. This issue brought to you by having redundant data in datastructures, part 5,001.
1 parent 56a2035 commit 4055c76

File tree

3 files changed

+217
-23
lines changed

3 files changed

+217
-23
lines changed

lightning/src/ln/channelmanager.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7363,7 +7363,7 @@ where
73637363
entry.insert(PendingOutboundPayment::Retryable {
73647364
retry_strategy: None,
73657365
attempts: PaymentAttempts::new(),
7366-
route_params: None,
7366+
payment_params: None,
73677367
session_privs: [session_priv_bytes].iter().map(|a| *a).collect(),
73687368
payment_hash: htlc.payment_hash,
73697369
payment_secret,

lightning/src/ln/outbound_payment.rs

Lines changed: 58 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ use bitcoin::secp256k1::{self, Secp256k1, SecretKey};
1616
use crate::chain::keysinterface::{EntropySource, NodeSigner, Recipient};
1717
use crate::ln::{PaymentHash, PaymentPreimage, PaymentSecret};
1818
use crate::ln::channelmanager::{ChannelDetails, HTLCSource, IDEMPOTENCY_TIMEOUT_TICKS, MIN_HTLC_RELAY_HOLDING_CELL_MILLIS, PaymentId};
19+
use crate::ln::channelmanager::MIN_FINAL_CLTV_EXPIRY_DELTA as LDK_DEFAULT_MIN_FINAL_CLTV_EXPIRY_DELTA;
1920
use crate::ln::msgs::DecodeError;
2021
use crate::ln::onion_utils::HTLCFailReason;
2122
use crate::routing::router::{InFlightHtlcs, PaymentParameters, Route, RouteHop, RouteParameters, RoutePath, Router};
@@ -43,7 +44,7 @@ pub(crate) enum PendingOutboundPayment {
4344
Retryable {
4445
retry_strategy: Option<Retry>,
4546
attempts: PaymentAttempts,
46-
route_params: Option<RouteParameters>,
47+
payment_params: Option<PaymentParameters>,
4748
session_privs: HashSet<[u8; 32]>,
4849
payment_hash: PaymentHash,
4950
payment_secret: Option<PaymentSecret>,
@@ -102,9 +103,17 @@ impl PendingOutboundPayment {
102103
_ => false,
103104
}
104105
}
106+
fn payment_parameters(&mut self) -> Option<&mut PaymentParameters> {
107+
match self {
108+
PendingOutboundPayment::Retryable { payment_params: Some(ref mut params), .. } => {
109+
Some(params)
110+
},
111+
_ => None,
112+
}
113+
}
105114
pub fn insert_previously_failed_scid(&mut self, scid: u64) {
106-
if let PendingOutboundPayment::Retryable { route_params: Some(params), .. } = self {
107-
params.payment_params.previously_failed_channels.push(scid);
115+
if let PendingOutboundPayment::Retryable { payment_params: Some(params), .. } = self {
116+
params.previously_failed_channels.push(scid);
108117
}
109118
}
110119
pub(super) fn is_fulfilled(&self) -> bool {
@@ -474,9 +483,18 @@ impl OutboundPayments {
474483
let mut retry_id_route_params = None;
475484
for (pmt_id, pmt) in outbounds.iter_mut() {
476485
if pmt.is_auto_retryable_now() {
477-
if let PendingOutboundPayment::Retryable { pending_amt_msat, total_msat, route_params: Some(params), .. } = pmt {
486+
if let PendingOutboundPayment::Retryable { pending_amt_msat, total_msat, payment_params: Some(params), .. } = pmt {
478487
if pending_amt_msat < total_msat {
479-
retry_id_route_params = Some((*pmt_id, params.clone()));
488+
retry_id_route_params = Some((*pmt_id, RouteParameters {
489+
final_value_msat: *total_msat - *pending_amt_msat,
490+
final_cltv_expiry_delta:
491+
if let Some(delta) = params.final_cltv_expiry_delta { delta }
492+
else {
493+
debug_assert!(false, "We always set the final_cltv_expiry_delta when a path fails");
494+
LDK_DEFAULT_MIN_FINAL_CLTV_EXPIRY_DELTA.into()
495+
},
496+
payment_params: params.clone(),
497+
}));
480498
break
481499
}
482500
}
@@ -522,7 +540,7 @@ impl OutboundPayments {
522540
}))?;
523541

524542
let res = if let Some((payment_hash, payment_secret, retry_strategy)) = initial_send_info {
525-
let onion_session_privs = self.add_new_pending_payment(payment_hash, *payment_secret, payment_id, &route, Some(retry_strategy), Some(route_params.clone()), entropy_source, best_block_height)?;
543+
let onion_session_privs = self.add_new_pending_payment(payment_hash, *payment_secret, payment_id, &route, Some(retry_strategy), Some(route_params.payment_params.clone()), entropy_source, best_block_height)?;
526544
self.pay_route_internal(&route, payment_hash, payment_secret, None, payment_id, None, onion_session_privs, node_signer, best_block_height, send_payment_along_path)
527545
} else {
528546
self.retry_payment_with_route(&route, payment_id, entropy_source, node_signer, best_block_height, send_payment_along_path)
@@ -672,7 +690,7 @@ impl OutboundPayments {
672690

673691
pub(super) fn add_new_pending_payment<ES: Deref>(
674692
&self, payment_hash: PaymentHash, payment_secret: Option<PaymentSecret>, payment_id: PaymentId,
675-
route: &Route, retry_strategy: Option<Retry>, route_params: Option<RouteParameters>,
693+
route: &Route, retry_strategy: Option<Retry>, payment_params: Option<PaymentParameters>,
676694
entropy_source: &ES, best_block_height: u32
677695
) -> Result<Vec<[u8; 32]>, PaymentSendFailure> where ES::Target: EntropySource {
678696
let mut onion_session_privs = Vec::with_capacity(route.paths.len());
@@ -687,7 +705,7 @@ impl OutboundPayments {
687705
let payment = entry.insert(PendingOutboundPayment::Retryable {
688706
retry_strategy,
689707
attempts: PaymentAttempts::new(),
690-
route_params,
708+
payment_params,
691709
session_privs: HashSet::new(),
692710
pending_amt_msat: 0,
693711
pending_fee_msat: Some(0),
@@ -965,6 +983,7 @@ impl OutboundPayments {
965983
let mut all_paths_failed = false;
966984
let mut full_failure_ev = None;
967985
let mut pending_retry_ev = None;
986+
let mut retry = None;
968987
let attempts_remaining = if let hash_map::Entry::Occupied(mut payment) = outbounds.entry(*payment_id) {
969988
if !payment.get_mut().remove(&session_priv_bytes, Some(&path)) {
970989
log_trace!(logger, "Received duplicative fail for HTLC with payment_hash {}", log_bytes!(payment_hash.0));
@@ -978,6 +997,33 @@ impl OutboundPayments {
978997
if let Some(scid) = short_channel_id {
979998
payment.get_mut().insert_previously_failed_scid(scid);
980999
}
1000+
1001+
// We want to move towards only using the `PaymentParameters` in the outbound payments
1002+
// map. However, for backwards-compatibility, we still need to support passing the
1003+
// `PaymentParameters` data that was shoved in the HTLC (and given to us via
1004+
// `payment_params`) back to the user.
1005+
let path_last_hop = path.last().expect("Outbound payments must have had a valid path");
1006+
if let Some(params) = payment.get_mut().payment_parameters() {
1007+
if params.final_cltv_expiry_delta.is_none() {
1008+
// This should be rare, but a user could provide None for the payment data, and
1009+
// we need it when we go to retry the payment, so fill it in.
1010+
params.final_cltv_expiry_delta = Some(path_last_hop.cltv_expiry_delta);
1011+
}
1012+
retry = Some(RouteParameters {
1013+
payment_params: params.clone(),
1014+
final_value_msat: path_last_hop.fee_msat,
1015+
final_cltv_expiry_delta: params.final_cltv_expiry_delta.unwrap(),
1016+
});
1017+
} else if let Some(params) = payment_params {
1018+
retry = Some(RouteParameters {
1019+
payment_params: params.clone(),
1020+
final_value_msat: path_last_hop.fee_msat,
1021+
final_cltv_expiry_delta:
1022+
if let Some(delta) = params.final_cltv_expiry_delta { delta }
1023+
else { path_last_hop.cltv_expiry_delta },
1024+
});
1025+
}
1026+
9811027
if payment.get().remaining_parts() == 0 {
9821028
all_paths_failed = true;
9831029
if payment.get().abandoned() {
@@ -994,16 +1040,6 @@ impl OutboundPayments {
9941040
return
9951041
};
9961042
core::mem::drop(outbounds);
997-
let mut retry = if let Some(payment_params_data) = payment_params {
998-
let path_last_hop = path.last().expect("Outbound payments must have had a valid path");
999-
Some(RouteParameters {
1000-
payment_params: payment_params_data.clone(),
1001-
final_value_msat: path_last_hop.fee_msat,
1002-
final_cltv_expiry_delta:
1003-
if let Some(delta) = payment_params_data.final_cltv_expiry_delta { delta }
1004-
else { path_last_hop.cltv_expiry_delta },
1005-
})
1006-
} else { None };
10071043
log_trace!(logger, "Failing outbound payment HTLC with payment_hash {}", log_bytes!(payment_hash.0));
10081044

10091045
let path_failure = {
@@ -1115,13 +1151,13 @@ impl_writeable_tlv_based_enum_upgradable!(PendingOutboundPayment,
11151151
(0, session_privs, required),
11161152
(1, pending_fee_msat, option),
11171153
(2, payment_hash, required),
1118-
(not_written, retry_strategy, (static_value, None)),
1154+
(3, payment_params, option),
11191155
(4, payment_secret, option),
1120-
(not_written, attempts, (static_value, PaymentAttempts::new())),
11211156
(6, total_msat, required),
1122-
(not_written, route_params, (static_value, None)),
11231157
(8, pending_amt_msat, required),
11241158
(10, starting_block_height, required),
1159+
(not_written, retry_strategy, (static_value, None)),
1160+
(not_written, attempts, (static_value, PaymentAttempts::new())),
11251161
},
11261162
(3, Abandoned) => {
11271163
(0, session_privs, required),
@@ -1212,7 +1248,7 @@ mod tests {
12121248

12131249
let err = if on_retry {
12141250
outbound_payments.add_new_pending_payment(PaymentHash([0; 32]), None, PaymentId([0; 32]),
1215-
&Route { paths: vec![], payment_params: None }, Some(Retry::Attempts(1)), Some(route_params.clone()),
1251+
&Route { paths: vec![], payment_params: None }, Some(Retry::Attempts(1)), Some(route_params.payment_params.clone()),
12161252
&&keys_manager, 0).unwrap();
12171253
outbound_payments.pay_internal(
12181254
PaymentId([0; 32]), None, route_params, &&router, vec![], InFlightHtlcs::new(),

lightning/src/ln/payment_tests.rs

Lines changed: 158 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2417,3 +2417,161 @@ fn no_extra_retries_on_back_to_back_fail() {
24172417
_ => panic!("Unexpected event"),
24182418
}
24192419
}
2420+
2421+
#[test]
2422+
fn test_simple_partial_retry() {
2423+
// In the first version of the in-`ChannelManager` payment retries, retries were sent for the
2424+
// full amount of the payment, rather than only the missing amount. Here we simply test for
2425+
// this by sending a payment with two parts, failing one, and retrying the second. Note that
2426+
// `TestRouter` will check that the `RouteParameters` (which contain the amount) matches the
2427+
// request.
2428+
let chanmon_cfgs = create_chanmon_cfgs(3);
2429+
let node_cfgs = create_node_cfgs(3, &chanmon_cfgs);
2430+
let node_chanmgrs = create_node_chanmgrs(3, &node_cfgs, &[None, None, None]);
2431+
let nodes = create_network(3, &node_cfgs, &node_chanmgrs);
2432+
2433+
let chan_1_scid = create_announced_chan_between_nodes_with_value(&nodes, 0, 1, 10_000_000, 0).0.contents.short_channel_id;
2434+
let chan_2_scid = create_announced_chan_between_nodes_with_value(&nodes, 1, 2, 10_000_000, 0).0.contents.short_channel_id;
2435+
2436+
let amt_msat = 200_000_000;
2437+
let (_, payment_hash, _, payment_secret) = get_route_and_payment_hash!(&nodes[0], nodes[2], amt_msat);
2438+
#[cfg(feature = "std")]
2439+
let payment_expiry_secs = SystemTime::UNIX_EPOCH.elapsed().unwrap().as_secs() + 60 * 60;
2440+
#[cfg(not(feature = "std"))]
2441+
let payment_expiry_secs = 60 * 60;
2442+
let mut invoice_features = InvoiceFeatures::empty();
2443+
invoice_features.set_variable_length_onion_required();
2444+
invoice_features.set_payment_secret_required();
2445+
invoice_features.set_basic_mpp_optional();
2446+
let payment_params = PaymentParameters::from_node_id(nodes[1].node.get_our_node_id(), TEST_FINAL_CLTV)
2447+
.with_expiry_time(payment_expiry_secs as u64)
2448+
.with_features(invoice_features);
2449+
let route_params = RouteParameters {
2450+
payment_params,
2451+
final_value_msat: amt_msat,
2452+
final_cltv_expiry_delta: TEST_FINAL_CLTV,
2453+
};
2454+
2455+
let mut route = Route {
2456+
paths: vec![
2457+
vec![RouteHop {
2458+
pubkey: nodes[1].node.get_our_node_id(),
2459+
node_features: nodes[1].node.node_features(),
2460+
short_channel_id: chan_1_scid,
2461+
channel_features: nodes[1].node.channel_features(),
2462+
fee_msat: 0, // nodes[1] will fail the payment as we don't pay its fee
2463+
cltv_expiry_delta: 100,
2464+
}, RouteHop {
2465+
pubkey: nodes[2].node.get_our_node_id(),
2466+
node_features: nodes[2].node.node_features(),
2467+
short_channel_id: chan_2_scid,
2468+
channel_features: nodes[2].node.channel_features(),
2469+
fee_msat: 100_000_000,
2470+
cltv_expiry_delta: 100,
2471+
}],
2472+
vec![RouteHop {
2473+
pubkey: nodes[1].node.get_our_node_id(),
2474+
node_features: nodes[1].node.node_features(),
2475+
short_channel_id: chan_1_scid,
2476+
channel_features: nodes[1].node.channel_features(),
2477+
fee_msat: 100_000,
2478+
cltv_expiry_delta: 100,
2479+
}, RouteHop {
2480+
pubkey: nodes[2].node.get_our_node_id(),
2481+
node_features: nodes[2].node.node_features(),
2482+
short_channel_id: chan_2_scid,
2483+
channel_features: nodes[2].node.channel_features(),
2484+
fee_msat: 100_000_000,
2485+
cltv_expiry_delta: 100,
2486+
}]
2487+
],
2488+
payment_params: Some(PaymentParameters::from_node_id(nodes[2].node.get_our_node_id(), TEST_FINAL_CLTV)),
2489+
};
2490+
nodes[0].router.expect_find_route(route_params.clone(), Ok(route.clone()));
2491+
let mut second_payment_params = route_params.payment_params.clone();
2492+
second_payment_params.previously_failed_channels = vec![chan_2_scid];
2493+
// On retry, we'll only be asked for one path (or 100k sats)
2494+
route.paths.remove(0);
2495+
nodes[0].router.expect_find_route(RouteParameters {
2496+
payment_params: second_payment_params,
2497+
final_value_msat: amt_msat / 2, final_cltv_expiry_delta: TEST_FINAL_CLTV,
2498+
}, Ok(route.clone()));
2499+
2500+
nodes[0].node.send_payment_with_retry(payment_hash, &Some(payment_secret), PaymentId(payment_hash.0), route_params, Retry::Attempts(1)).unwrap();
2501+
let htlc_updates = SendEvent::from_node(&nodes[0]);
2502+
check_added_monitors!(nodes[0], 1);
2503+
assert_eq!(htlc_updates.msgs.len(), 1);
2504+
2505+
nodes[1].node.handle_update_add_htlc(&nodes[0].node.get_our_node_id(), &htlc_updates.msgs[0]);
2506+
nodes[1].node.handle_commitment_signed(&nodes[0].node.get_our_node_id(), &htlc_updates.commitment_msg);
2507+
check_added_monitors!(nodes[1], 1);
2508+
let (bs_first_raa, bs_first_cs) = get_revoke_commit_msgs!(nodes[1], nodes[0].node.get_our_node_id());
2509+
2510+
nodes[0].node.handle_revoke_and_ack(&nodes[1].node.get_our_node_id(), &bs_first_raa);
2511+
check_added_monitors!(nodes[0], 1);
2512+
let second_htlc_updates = SendEvent::from_node(&nodes[0]);
2513+
2514+
nodes[0].node.handle_commitment_signed(&nodes[1].node.get_our_node_id(), &bs_first_cs);
2515+
check_added_monitors!(nodes[0], 1);
2516+
let as_first_raa = get_event_msg!(nodes[0], MessageSendEvent::SendRevokeAndACK, nodes[1].node.get_our_node_id());
2517+
2518+
nodes[1].node.handle_update_add_htlc(&nodes[0].node.get_our_node_id(), &second_htlc_updates.msgs[0]);
2519+
nodes[1].node.handle_commitment_signed(&nodes[0].node.get_our_node_id(), &second_htlc_updates.commitment_msg);
2520+
check_added_monitors!(nodes[1], 1);
2521+
let bs_second_raa = get_event_msg!(nodes[1], MessageSendEvent::SendRevokeAndACK, nodes[0].node.get_our_node_id());
2522+
2523+
nodes[1].node.handle_revoke_and_ack(&nodes[0].node.get_our_node_id(), &as_first_raa);
2524+
check_added_monitors!(nodes[1], 1);
2525+
let bs_fail_update = get_htlc_update_msgs!(nodes[1], nodes[0].node.get_our_node_id());
2526+
2527+
nodes[0].node.handle_revoke_and_ack(&nodes[1].node.get_our_node_id(), &bs_second_raa);
2528+
check_added_monitors!(nodes[0], 1);
2529+
2530+
nodes[0].node.handle_update_fail_htlc(&nodes[1].node.get_our_node_id(), &bs_fail_update.update_fail_htlcs[0]);
2531+
nodes[0].node.handle_commitment_signed(&nodes[1].node.get_our_node_id(), &bs_fail_update.commitment_signed);
2532+
check_added_monitors!(nodes[0], 1);
2533+
let (as_second_raa, as_third_cs) = get_revoke_commit_msgs!(nodes[0], nodes[1].node.get_our_node_id());
2534+
2535+
nodes[1].node.handle_revoke_and_ack(&nodes[0].node.get_our_node_id(), &as_second_raa);
2536+
check_added_monitors!(nodes[1], 1);
2537+
2538+
nodes[1].node.handle_commitment_signed(&nodes[0].node.get_our_node_id(), &as_third_cs);
2539+
check_added_monitors!(nodes[1], 1);
2540+
2541+
let bs_third_raa = get_event_msg!(nodes[1], MessageSendEvent::SendRevokeAndACK, nodes[0].node.get_our_node_id());
2542+
2543+
nodes[0].node.handle_revoke_and_ack(&nodes[1].node.get_our_node_id(), &bs_third_raa);
2544+
check_added_monitors!(nodes[0], 1);
2545+
2546+
let mut events = nodes[0].node.get_and_clear_pending_events();
2547+
assert_eq!(events.len(), 2);
2548+
match events[0] {
2549+
Event::PaymentPathFailed { payment_hash: ev_payment_hash, payment_failed_permanently, .. } => {
2550+
assert_eq!(payment_hash, ev_payment_hash);
2551+
assert_eq!(payment_failed_permanently, false);
2552+
},
2553+
_ => panic!("Unexpected event"),
2554+
}
2555+
match events[1] {
2556+
Event::PendingHTLCsForwardable { .. } => {},
2557+
_ => panic!("Unexpected event"),
2558+
}
2559+
2560+
nodes[0].node.process_pending_htlc_forwards();
2561+
let retry_htlc_updates = SendEvent::from_node(&nodes[0]);
2562+
check_added_monitors!(nodes[0], 1);
2563+
2564+
nodes[1].node.handle_update_add_htlc(&nodes[0].node.get_our_node_id(), &retry_htlc_updates.msgs[0]);
2565+
commitment_signed_dance!(nodes[1], nodes[0], &retry_htlc_updates.commitment_msg, false, true);
2566+
2567+
expect_pending_htlcs_forwardable!(nodes[1]);
2568+
check_added_monitors!(nodes[1], 1);
2569+
2570+
let bs_forward_update = get_htlc_update_msgs!(nodes[1], nodes[2].node.get_our_node_id());
2571+
nodes[2].node.handle_update_add_htlc(&nodes[1].node.get_our_node_id(), &bs_forward_update.update_add_htlcs[0]);
2572+
nodes[2].node.handle_update_add_htlc(&nodes[1].node.get_our_node_id(), &bs_forward_update.update_add_htlcs[1]);
2573+
commitment_signed_dance!(nodes[2], nodes[1], &bs_forward_update.commitment_signed, false);
2574+
2575+
expect_pending_htlcs_forwardable!(nodes[2]);
2576+
expect_payment_claimable!(nodes[2], payment_hash, payment_secret, amt_msat);
2577+
}

0 commit comments

Comments
 (0)