Skip to content

Commit 89cdc6e

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 90efaa0 commit 89cdc6e

File tree

2 files changed

+118
-2
lines changed

2 files changed

+118
-2
lines changed

lightning/src/chain/channelmonitor.rs

Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1527,6 +1527,77 @@ impl<Signer: Sign> ChannelMonitor<Signer> {
15271527

15281528
res
15291529
}
1530+
1531+
/// Gets the set of outbound HTLCs which are pending resolution in this channel.
1532+
/// This is used to reconstruct pending outbound payments on restart in the ChannelManager.
1533+
pub(crate) fn get_pending_htlcs(&self) -> HashMap<HTLCSource, HTLCOutputInCommitment> {
1534+
let mut res = HashMap::new();
1535+
let us = self.inner.lock().unwrap();
1536+
1537+
macro_rules! walk_htlcs {
1538+
($holder_commitment: expr, $htlc_iter: expr) => {
1539+
for (htlc, source) in $htlc_iter {
1540+
if us.htlcs_resolved_on_chain.iter().any(|v| Some(v.input_idx) == htlc.transaction_output_index) {
1541+
assert!(us.funding_spend_confirmed.is_some());
1542+
} else if htlc.offered == $holder_commitment {
1543+
// If the payment was outbound, check if there's an HTLCUpdate
1544+
// indicating we have spent this HTLC with a timeout, claiming it back
1545+
// and awaiting confirmations on it.
1546+
let htlc_update_confd = us.onchain_events_awaiting_threshold_conf.iter().find_map(|event| {
1547+
if let OnchainEvent::HTLCUpdate { input_idx: Some(input_idx), .. } = event.event {
1548+
if Some(input_idx) == htlc.transaction_output_index &&
1549+
us.best_block.height() >= event.height + ANTI_REORG_DELAY - 1
1550+
{ Some(()) } 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+
if let Some(txid) = us.funding_spend_confirmed {
1562+
if Some(txid) == us.current_counterparty_commitment_txid || Some(txid) == us.prev_counterparty_commitment_txid {
1563+
walk_htlcs!(false, us.counterparty_claimable_outpoints.get(&txid).unwrap().iter().find_map(|(a, b)| {
1564+
if let &Some(ref source) = b {
1565+
Some((a, &**source))
1566+
} else { None }
1567+
}));
1568+
} else if txid == us.current_holder_commitment_tx.txid {
1569+
walk_htlcs!(true, us.current_holder_commitment_tx.htlc_outputs.iter().find_map(|(a, _, c)| {
1570+
if let Some(source) = c { Some((a, source)) } else { None }
1571+
}));
1572+
} else if let Some(prev_commitment) = &us.prev_holder_signed_commitment_tx {
1573+
if txid == prev_commitment.txid {
1574+
walk_htlcs!(true, prev_commitment.htlc_outputs.iter().find_map(|(a, _, c)| {
1575+
if let Some(source) = c { Some((a, source)) } else { None }
1576+
}));
1577+
}
1578+
}
1579+
} else {
1580+
macro_rules! check_htlc_fails {
1581+
($txid: expr, $commitment_tx: expr) => {
1582+
if let Some(ref latest_outpoints) = us.counterparty_claimable_outpoints.get($txid) {
1583+
for &(ref htlc, ref source_option) in latest_outpoints.iter() {
1584+
if let &Some(ref source) = source_option {
1585+
res.insert((**source).clone(), htlc.clone());
1586+
}
1587+
}
1588+
}
1589+
}
1590+
}
1591+
if let Some(ref txid) = us.current_counterparty_commitment_txid {
1592+
check_htlc_fails!(txid, "current");
1593+
}
1594+
if let Some(ref txid) = us.prev_counterparty_commitment_txid {
1595+
check_htlc_fails!(txid, "previous");
1596+
}
1597+
}
1598+
1599+
res
1600+
}
15301601
}
15311602

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

lightning/src/ln/channelmanager.rs

Lines changed: 47 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,7 @@ impl Readable for PaymentId {
189189
}
190190
}
191191
/// Tracks the inbound corresponding to an outbound HTLC
192-
#[derive(Clone, PartialEq)]
192+
#[derive(Clone, PartialEq, Eq)]
193193
pub(crate) enum HTLCSource {
194194
PreviousHopData(HTLCPreviousHopData),
195195
OutboundRoute {
@@ -202,6 +202,23 @@ pub(crate) enum HTLCSource {
202202
payment_secret: Option<PaymentSecret>,
203203
},
204204
}
205+
impl core::hash::Hash for HTLCSource {
206+
fn hash<H: core::hash::Hasher>(&self, hasher: &mut H) {
207+
match self {
208+
HTLCSource::PreviousHopData(prev_hop_data) => {
209+
0u8.hash(hasher);
210+
prev_hop_data.hash(hasher);
211+
},
212+
HTLCSource::OutboundRoute { path, session_priv, payment_id, payment_secret, first_hop_htlc_msat: _ } => {
213+
1u8.hash(hasher);
214+
path.hash(hasher);
215+
session_priv[..].hash(hasher);
216+
payment_id.hash(hasher);
217+
payment_secret.hash(hasher);
218+
},
219+
}
220+
}
221+
}
205222
#[cfg(test)]
206223
impl HTLCSource {
207224
pub fn dummy() -> Self {
@@ -5833,6 +5850,34 @@ impl<'a, Signer: Sign, M: Deref, T: Deref, K: Deref, F: Deref, L: Deref>
58335850
outbounds.insert(id, PendingOutboundPayment::Legacy { session_privs });
58345851
}
58355852
pending_outbound_payments = Some(outbounds);
5853+
} else {
5854+
// We only rebuild the pending payments map if we were most recently serialized by
5855+
// 0.0.102+
5856+
for (_, monitor) in args.channel_monitors {
5857+
for (htlc_source, htlc) in monitor.get_pending_htlcs() {
5858+
if let HTLCSource::OutboundRoute { payment_id, session_priv, path, payment_secret, .. } = htlc_source {
5859+
if path.is_empty() { return Err(DecodeError::InvalidValue); }
5860+
let path_amt = path.last().unwrap().fee_msat;
5861+
let mut session_priv_bytes = [0; 32];
5862+
session_priv_bytes[..].copy_from_slice(&session_priv[..]);
5863+
match pending_outbound_payments.as_mut().unwrap().entry(payment_id) {
5864+
hash_map::Entry::Occupied(mut entry) => {
5865+
entry.get_mut().insert(session_priv_bytes, path_amt);
5866+
},
5867+
hash_map::Entry::Vacant(entry) => {
5868+
entry.insert(PendingOutboundPayment::Retryable {
5869+
session_privs: [session_priv_bytes].iter().map(|a| *a).collect(),
5870+
payment_hash: htlc.payment_hash,
5871+
payment_secret,
5872+
pending_amt_msat: path_amt,
5873+
total_msat: path_amt,
5874+
starting_block_height: best_block_height,
5875+
});
5876+
}
5877+
}
5878+
}
5879+
}
5880+
}
58365881
}
58375882

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

0 commit comments

Comments
 (0)