Skip to content

Commit 2de839e

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 the user not being able to retry their payment as we'll consider it still "in-flight".
1 parent bb4ff74 commit 2de839e

File tree

2 files changed

+53
-17
lines changed

2 files changed

+53
-17
lines changed

lightning/src/ln/channelmanager.rs

Lines changed: 26 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
},
@@ -557,6 +557,9 @@ pub struct ChannelManager<Signer: Sign, M: Deref, T: Deref, K: Deref, F: Deref,
557557
/// See `PendingOutboundPayment` documentation for more info.
558558
///
559559
/// Locked *after* channel_state.
560+
#[cfg(test)] // Exposed so that we can check there aren't dangling entries
561+
pub(crate) pending_outbound_payments: Mutex<HashMap<PaymentId, PendingOutboundPayment>>,
562+
#[cfg(not(test))]
560563
pending_outbound_payments: Mutex<HashMap<PaymentId, PendingOutboundPayment>>,
561564

562565
our_network_key: SecretKey,
@@ -1951,16 +1954,6 @@ impl<Signer: Sign, M: Deref, T: Deref, K: Deref, F: Deref, L: Deref> ChannelMana
19511954
let onion_packet = onion_utils::construct_onion_packet(onion_payloads, onion_keys, prng_seed, payment_hash);
19521955

19531956
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));
19641957

19651958
let err: Result<(), _> = loop {
19661959
let mut channel_lock = self.channel_state.lock().unwrap();
@@ -1978,12 +1971,28 @@ impl<Signer: Sign, M: Deref, T: Deref, K: Deref, F: Deref, L: Deref> ChannelMana
19781971
if !chan.get().is_live() {
19791972
return Err(APIError::ChannelUnavailable{err: "Peer for first hop currently disconnected/pending monitor update!".to_owned()});
19801973
}
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)
1974+
let send_res =
1975+
break_chan_entry!(self, chan.get_mut().send_htlc_and_commit(
1976+
htlc_msat, payment_hash.clone(), htlc_cltv, HTLCSource::OutboundRoute {
1977+
path: path.clone(),
1978+
session_priv: session_priv.clone(),
1979+
first_hop_htlc_msat: htlc_msat,
1980+
payment_id,
1981+
}, onion_packet, &self.logger),
1982+
channel_state, chan);
1983+
1984+
let mut pending_outbounds = self.pending_outbound_payments.lock().unwrap();
1985+
let payment = pending_outbounds.entry(payment_id).or_insert_with(|| PendingOutboundPayment::Retryable {
1986+
session_privs: HashSet::new(),
1987+
pending_amt_msat: 0,
1988+
payment_hash: *payment_hash,
1989+
payment_secret: *payment_secret,
1990+
starting_block_height: self.best_block.read().unwrap().height(),
1991+
total_msat: total_value,
1992+
});
1993+
assert!(payment.insert(session_priv_bytes, path.last().unwrap().fee_msat));
1994+
1995+
send_res
19871996
} {
19881997
Some((update_add, commitment_signed, monitor_update)) => {
19891998
if let Err(e) = self.chain_monitor.update_channel(chan.get().get_funding_txo().unwrap(), monitor_update) {

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 retry_on_init_fail() {
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 (eg 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 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.pending_outbound_payments.lock().unwrap().is_empty());
254+
}

0 commit comments

Comments
 (0)