Skip to content

Commit 3b45250

Browse files
committed
Reload pending payments from ChannelMonitor HTLC data on reload
If we go to send a payment, add the HTLC(s) to the channel(s), commit the ChannelMonitor updates to disk, and then crash, we'll come back up with no pending payments but HTLC(s) ready to be claim/failed. This makes it rather impractical to write a payment sender/retryer, as you cannot guarantee atomicity - you cannot guarantee you'll have retry data persisted even if the HTLC(s) are actually pending. Because ChannelMonitors are *the* atomically-persisted data in LDK, we lean on their current HTLC data to figure out what HTLC(s) are a part of an outbound payment, rebuilding the pending payments list on reload.
1 parent 74ca73f commit 3b45250

File tree

2 files changed

+157
-2
lines changed

2 files changed

+157
-2
lines changed

lightning/src/chain/channelmonitor.rs

Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1515,6 +1515,99 @@ impl<Signer: Sign> ChannelMonitor<Signer> {
15151515

15161516
res
15171517
}
1518+
1519+
/// Gets the set of outbound HTLCs which are pending resolution in this channel.
1520+
/// This is used to reconstruct pending outbound payments on restart in the ChannelManager.
1521+
pub(crate) fn get_pending_htlcs(&self) -> HashMap<HTLCSource, HTLCOutputInCommitment> {
1522+
let mut res = HashMap::new();
1523+
let us = self.inner.lock().unwrap();
1524+
1525+
macro_rules! walk_htlcs {
1526+
($holder_commitment: expr, $htlc_iter: expr) => {
1527+
for (htlc, source) in $htlc_iter {
1528+
if us.htlcs_resolved_on_chain.iter().any(|v| Some(v.input_idx) == htlc.transaction_output_index) {
1529+
// We should assert that funding_spend_confirmed is_some() here, but we
1530+
// have some unit tests which violate HTLC transaction CSVs entirely and
1531+
// would fail.
1532+
} else if htlc.offered == $holder_commitment {
1533+
// If the payment was outbound, check if there's an HTLCUpdate
1534+
// indicating we have spent this HTLC with a timeout, claiming it back
1535+
// and awaiting confirmations on it.
1536+
let htlc_update_confd = us.onchain_events_awaiting_threshold_conf.iter().find_map(|event| {
1537+
if let OnchainEvent::HTLCUpdate { input_idx: Some(input_idx), .. } = event.event {
1538+
// If the HTLC was timed out, we wait for ANTI_REORG_DELAY blocks
1539+
// before considering it "no longer pending" - this matches when we
1540+
// provide the ChannelManager an HTLC failure event.
1541+
if Some(input_idx) == htlc.transaction_output_index &&
1542+
us.best_block.height() >= event.height + ANTI_REORG_DELAY - 1
1543+
{ Some(()) } else { None }
1544+
} else if let OnchainEvent::HTLCSpendConfirmation { input_idx, .. } = event.event {
1545+
// If the HTLC was fulfilled with a preimage, we consider the HTLC
1546+
// immediately non-pending, matching when we provide ChannelManager
1547+
// the preimage.
1548+
if Some(input_idx) == htlc.transaction_output_index {
1549+
Some(())
1550+
} else { None }
1551+
} else { None }
1552+
});
1553+
if htlc_update_confd.is_none() {
1554+
res.insert(source.clone(), htlc.clone());
1555+
}
1556+
}
1557+
}
1558+
}
1559+
}
1560+
1561+
// We're only concerned with the confirmation count of HTLC transactions, and don't
1562+
// actually care how many confirmations a commitment transaction may or may not have. Thus,
1563+
// we look for both a FundingSpendConfirmation event, or at funding_spend_confirmed.
1564+
let confirmed_txid = us.funding_spend_confirmed.or_else(|| {
1565+
us.onchain_events_awaiting_threshold_conf.iter().find_map(|event| {
1566+
if let OnchainEvent::FundingSpendConfirmation { .. } = event.event {
1567+
Some(event.txid)
1568+
} else { None }
1569+
})
1570+
});
1571+
if let Some(txid) = confirmed_txid {
1572+
if Some(txid) == us.current_counterparty_commitment_txid || Some(txid) == us.prev_counterparty_commitment_txid {
1573+
walk_htlcs!(false, us.counterparty_claimable_outpoints.get(&txid).unwrap().iter().find_map(|(a, b)| {
1574+
if let &Some(ref source) = b {
1575+
Some((a, &**source))
1576+
} else { None }
1577+
}));
1578+
} else if txid == us.current_holder_commitment_tx.txid {
1579+
walk_htlcs!(true, us.current_holder_commitment_tx.htlc_outputs.iter().find_map(|(a, _, c)| {
1580+
if let Some(source) = c { Some((a, source)) } else { None }
1581+
}));
1582+
} else if let Some(prev_commitment) = &us.prev_holder_signed_commitment_tx {
1583+
if txid == prev_commitment.txid {
1584+
walk_htlcs!(true, prev_commitment.htlc_outputs.iter().find_map(|(a, _, c)| {
1585+
if let Some(source) = c { Some((a, source)) } else { None }
1586+
}));
1587+
}
1588+
}
1589+
} else {
1590+
macro_rules! check_htlc_fails {
1591+
($txid: expr, $commitment_tx: expr) => {
1592+
if let Some(ref latest_outpoints) = us.counterparty_claimable_outpoints.get($txid) {
1593+
for &(ref htlc, ref source_option) in latest_outpoints.iter() {
1594+
if let &Some(ref source) = source_option {
1595+
res.insert((**source).clone(), htlc.clone());
1596+
}
1597+
}
1598+
}
1599+
}
1600+
}
1601+
if let Some(ref txid) = us.current_counterparty_commitment_txid {
1602+
check_htlc_fails!(txid, "current");
1603+
}
1604+
if let Some(ref txid) = us.prev_counterparty_commitment_txid {
1605+
check_htlc_fails!(txid, "previous");
1606+
}
1607+
}
1608+
1609+
res
1610+
}
15181611
}
15191612

15201613
/// Compares a broadcasted commitment transaction's HTLCs with those in the latest state,

lightning/src/ln/channelmanager.rs

Lines changed: 64 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -145,7 +145,7 @@ pub(super) enum HTLCForwardInfo {
145145
}
146146

147147
/// Tracks the inbound corresponding to an outbound HTLC
148-
#[derive(Clone, PartialEq)]
148+
#[derive(Clone, Hash, PartialEq, Eq)]
149149
pub(crate) struct HTLCPreviousHopData {
150150
short_channel_id: u64,
151151
htlc_id: u64,
@@ -189,7 +189,8 @@ impl Readable for PaymentId {
189189
}
190190
}
191191
/// Tracks the inbound corresponding to an outbound HTLC
192-
#[derive(Clone, PartialEq)]
192+
#[allow(clippy::derive_hash_xor_eq)] // Our Hash is faithful to the data, we just don't have SecretKey::hash
193+
#[derive(Clone, PartialEq, Eq)]
193194
pub(crate) enum HTLCSource {
194195
PreviousHopData(HTLCPreviousHopData),
195196
OutboundRoute {
@@ -202,6 +203,24 @@ pub(crate) enum HTLCSource {
202203
payment_secret: Option<PaymentSecret>,
203204
},
204205
}
206+
impl core::hash::Hash for HTLCSource {
207+
fn hash<H: core::hash::Hasher>(&self, hasher: &mut H) {
208+
match self {
209+
HTLCSource::PreviousHopData(prev_hop_data) => {
210+
0u8.hash(hasher);
211+
prev_hop_data.hash(hasher);
212+
},
213+
HTLCSource::OutboundRoute { path, session_priv, payment_id, payment_secret, first_hop_htlc_msat } => {
214+
1u8.hash(hasher);
215+
path.hash(hasher);
216+
session_priv[..].hash(hasher);
217+
payment_id.hash(hasher);
218+
payment_secret.hash(hasher);
219+
first_hop_htlc_msat.hash(hasher);
220+
},
221+
}
222+
}
223+
}
205224
#[cfg(test)]
206225
impl HTLCSource {
207226
pub fn dummy() -> Self {
@@ -5875,6 +5894,49 @@ impl<'a, Signer: Sign, M: Deref, T: Deref, K: Deref, F: Deref, L: Deref>
58755894
outbounds.insert(id, PendingOutboundPayment::Legacy { session_privs });
58765895
}
58775896
pending_outbound_payments = Some(outbounds);
5897+
} else {
5898+
// If we're tracking pending payments, ensure we haven't lost any by looking at the
5899+
// ChannelMonitor data for any channels for which we do not have authorative state
5900+
// (i.e. those for which we just force-closed above or we otherwise don't have a
5901+
// corresponding `Channel` at all).
5902+
// This avoids several edge-cases where we would otherwise "forget" about pending
5903+
// payments which are still in-flight via their on-chain state.
5904+
// We only rebuild the pending payments map if we were most recently serialized by
5905+
// 0.0.102+
5906+
for (_, monitor) in args.channel_monitors {
5907+
if by_id.get(&monitor.get_funding_txo().0.to_channel_id()).is_none() {
5908+
for (htlc_source, htlc) in monitor.get_pending_htlcs() {
5909+
if let HTLCSource::OutboundRoute { payment_id, session_priv, path, payment_secret, .. } = htlc_source {
5910+
if path.is_empty() {
5911+
log_error!(args.logger, "Got an empty path for a pending payment");
5912+
return Err(DecodeError::InvalidValue);
5913+
}
5914+
let path_amt = path.last().unwrap().fee_msat;
5915+
let mut session_priv_bytes = [0; 32];
5916+
session_priv_bytes[..].copy_from_slice(&session_priv[..]);
5917+
match pending_outbound_payments.as_mut().unwrap().entry(payment_id) {
5918+
hash_map::Entry::Occupied(mut entry) => {
5919+
let readded = entry.get_mut().insert(session_priv_bytes, path_amt);
5920+
log_info!(args.logger, "{} a pending payment path for {} msat for session priv {} on an existing pending payment with payment hash {}",
5921+
if readded { "Added" } else { "Had" }, path_amt, log_bytes!(session_priv_bytes), log_bytes!(htlc.payment_hash.0));
5922+
},
5923+
hash_map::Entry::Vacant(entry) => {
5924+
entry.insert(PendingOutboundPayment::Retryable {
5925+
session_privs: [session_priv_bytes].iter().map(|a| *a).collect(),
5926+
payment_hash: htlc.payment_hash,
5927+
payment_secret,
5928+
pending_amt_msat: path_amt,
5929+
total_msat: path_amt,
5930+
starting_block_height: best_block_height,
5931+
});
5932+
log_info!(args.logger, "Added a pending payment for {} msat with payment hash {} for path with session priv {}",
5933+
path_amt, log_bytes!(htlc.payment_hash.0), log_bytes!(session_priv_bytes));
5934+
}
5935+
}
5936+
}
5937+
}
5938+
}
5939+
}
58785940
}
58795941

58805942
let mut secp_ctx = Secp256k1::new();

0 commit comments

Comments
 (0)