Skip to content

Commit 928bfb1

Browse files
committed
Move pending payment tracking to after the new HTLC flies
If we attempt to send a payment, but the HTLC cannot be send due to local channel limits, we'll provide the user an error but end up with an entry in our pending payment map. This will result in a memory leak as we'll never reclaim the pending payment map entry.
1 parent bb4ff74 commit 928bfb1

File tree

2 files changed

+54
-17
lines changed

2 files changed

+54
-17
lines changed

lightning/src/ln/channelmanager.rs

Lines changed: 27 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -402,7 +402,7 @@ struct PendingInboundPayment {
402402

403403
/// Stores the session_priv for each part of a payment that is still pending. For versions 0.0.102
404404
/// and later, also stores information for retrying the payment.
405-
enum PendingOutboundPayment {
405+
pub(crate) enum PendingOutboundPayment {
406406
Legacy {
407407
session_privs: HashSet<[u8; 32]>,
408408
},
@@ -1951,16 +1951,6 @@ impl<Signer: Sign, M: Deref, T: Deref, K: Deref, F: Deref, L: Deref> ChannelMana
19511951
let onion_packet = onion_utils::construct_onion_packet(onion_payloads, onion_keys, prng_seed, payment_hash);
19521952

19531953
let _persistence_guard = PersistenceNotifierGuard::notify_on_drop(&self.total_consistency_lock, &self.persistence_notifier);
1954-
let mut pending_outbounds = self.pending_outbound_payments.lock().unwrap();
1955-
let payment = pending_outbounds.entry(payment_id).or_insert_with(|| PendingOutboundPayment::Retryable {
1956-
session_privs: HashSet::new(),
1957-
pending_amt_msat: 0,
1958-
payment_hash: *payment_hash,
1959-
payment_secret: *payment_secret,
1960-
starting_block_height: self.best_block.read().unwrap().height(),
1961-
total_msat: total_value,
1962-
});
1963-
assert!(payment.insert(session_priv_bytes, path.last().unwrap().fee_msat));
19641954

19651955
let err: Result<(), _> = loop {
19661956
let mut channel_lock = self.channel_state.lock().unwrap();
@@ -1978,12 +1968,27 @@ impl<Signer: Sign, M: Deref, T: Deref, K: Deref, F: Deref, L: Deref> ChannelMana
19781968
if !chan.get().is_live() {
19791969
return Err(APIError::ChannelUnavailable{err: "Peer for first hop currently disconnected/pending monitor update!".to_owned()});
19801970
}
1981-
break_chan_entry!(self, chan.get_mut().send_htlc_and_commit(htlc_msat, payment_hash.clone(), htlc_cltv, HTLCSource::OutboundRoute {
1982-
path: path.clone(),
1983-
session_priv: session_priv.clone(),
1984-
first_hop_htlc_msat: htlc_msat,
1985-
payment_id,
1986-
}, onion_packet, &self.logger), channel_state, chan)
1971+
let send_res = break_chan_entry!(self, chan.get_mut().send_htlc_and_commit(
1972+
htlc_msat, payment_hash.clone(), htlc_cltv, HTLCSource::OutboundRoute {
1973+
path: path.clone(),
1974+
session_priv: session_priv.clone(),
1975+
first_hop_htlc_msat: htlc_msat,
1976+
payment_id,
1977+
}, onion_packet, &self.logger),
1978+
channel_state, chan);
1979+
1980+
let mut pending_outbounds = self.pending_outbound_payments.lock().unwrap();
1981+
let payment = pending_outbounds.entry(payment_id).or_insert_with(|| PendingOutboundPayment::Retryable {
1982+
session_privs: HashSet::new(),
1983+
pending_amt_msat: 0,
1984+
payment_hash: *payment_hash,
1985+
payment_secret: *payment_secret,
1986+
starting_block_height: self.best_block.read().unwrap().height(),
1987+
total_msat: total_value,
1988+
});
1989+
assert!(payment.insert(session_priv_bytes, path.last().unwrap().fee_msat));
1990+
1991+
send_res
19871992
} {
19881993
Some((update_add, commitment_signed, monitor_update)) => {
19891994
if let Err(e) = self.chain_monitor.update_channel(chan.get().get_funding_txo().unwrap(), monitor_update) {
@@ -4383,6 +4388,11 @@ impl<Signer: Sign, M: Deref, T: Deref, K: Deref, F: Deref, L: Deref> ChannelMana
43834388
self.process_pending_events(&event_handler);
43844389
events.into_inner()
43854390
}
4391+
4392+
#[cfg(test)]
4393+
pub fn has_pending_payments(&self) -> bool {
4394+
!self.pending_outbound_payments.lock().unwrap().is_empty()
4395+
}
43864396
}
43874397

43884398
impl<Signer: Sign, M: Deref, T: Deref, K: Deref, F: Deref, L: Deref> MessageSendEventsProvider for ChannelManager<Signer, M, T, K, F, L>

lightning/src/ln/payment_tests.rs

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -225,3 +225,30 @@ fn retry_expired_payment() {
225225
panic!("Unexpected error");
226226
}
227227
}
228+
229+
#[test]
230+
fn no_pending_leak_on_initial_send_failure() {
231+
// In an earlier version of our payment tracking, we'd have a retry entry even when the initial
232+
// HTLC for payment failed to send due to local channel errors (e.g. peer disconnected). In this
233+
// case, the user wouldn't have a PaymentId to retry the payment with, but we'd think we have a
234+
// pending payment forever and never time it out.
235+
// Here we test exactly that - retrying a payment when a peer was disconnected on the first
236+
// try, and then check that no pending payment is being tracked.
237+
let chanmon_cfgs = create_chanmon_cfgs(2);
238+
let node_cfgs = create_node_cfgs(2, &chanmon_cfgs);
239+
let node_chanmgrs = create_node_chanmgrs(2, &node_cfgs, &[None, None]);
240+
let mut nodes = create_network(2, &node_cfgs, &node_chanmgrs);
241+
242+
create_announced_chan_between_nodes(&nodes, 0, 1, InitFeatures::known(), InitFeatures::known());
243+
244+
let (route, payment_hash, _, payment_secret) = get_route_and_payment_hash!(nodes[0], nodes[1], 100_000);
245+
246+
nodes[0].node.peer_disconnected(&nodes[1].node.get_our_node_id(), false);
247+
nodes[1].node.peer_disconnected(&nodes[1].node.get_our_node_id(), false);
248+
249+
unwrap_send_err!(nodes[0].node.send_payment(&route, payment_hash, &Some(payment_secret)),
250+
true, APIError::ChannelUnavailable { ref err },
251+
assert_eq!(err, "Peer for first hop currently disconnected/pending monitor update!"));
252+
253+
assert!(!nodes[0].node.has_pending_payments());
254+
}

0 commit comments

Comments
 (0)