Skip to content

Commit 7e78fa6

Browse files
committed
Handle double-HTLC-claims without failing the backwards channel
When receiving an update_fulfill_htlc message, we immediately forward the claim backwards along the payment path before waiting for a full commitment_signed dance. This is great, but can cause duplicative claims if a node sends an update_fulfill_htlc message, disconnects, reconnects, and then has to re-send its update_fulfill_htlc message again. While there was code to handle this, it treated it as a channel error on the inbound channel, which is incorrect - this is an expected, albeit incredibly rare, condition. Instead, we handle these double-claims correctly, simply ignoring them. With debug_assertions enabled, we also check that the previous close of the same HTLC was a fulfill, and that we are not moving from a HTLC failure to an HTLC claim after its too late. A test is also added, which hits all three failure cases in `Channel::get_update_fulfill_htlc`. Found by the chanmon_consistency fuzzer.
1 parent fecac81 commit 7e78fa6

File tree

2 files changed

+184
-9
lines changed

2 files changed

+184
-9
lines changed

lightning/src/ln/chanmon_update_fail_tests.rs

Lines changed: 116 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2215,3 +2215,119 @@ fn channel_holding_cell_serialize() {
22152215
do_channel_holding_cell_serialize(true, false);
22162216
do_channel_holding_cell_serialize(false, true); // last arg doesn't matter
22172217
}
2218+
2219+
#[derive(PartialEq)]
2220+
enum HTLCStatusAtDupClaim {
2221+
Received,
2222+
HoldingCell,
2223+
Cleared,
2224+
}
2225+
fn do_test_reconnect_dup_htlc_claims(htlc_status: HTLCStatusAtDupClaim, second_fails: bool) {
2226+
// When receiving an update_fulfill_htlc message, we immediately forward the claim backwards
2227+
// along the payment path before waiting for a full commitment_signed dance. This is great, but
2228+
// can cause duplicative claims if a node sends an update_fulfill_htlc message, disconnects,
2229+
// reconnects, and then has to re-send its update_fulfill_htlc message again.
2230+
// In previous code, we didn't handle the double-claim correctly, spuriously closing the
2231+
// channel on which the inbound HTLC was received.
2232+
let chanmon_cfgs = create_chanmon_cfgs(3);
2233+
let node_cfgs = create_node_cfgs(3, &chanmon_cfgs);
2234+
let node_chanmgrs = create_node_chanmgrs(3, &node_cfgs, &[None, None, None]);
2235+
let mut nodes = create_network(3, &node_cfgs, &node_chanmgrs);
2236+
2237+
create_announced_chan_between_nodes(&nodes, 0, 1, InitFeatures::known(), InitFeatures::known());
2238+
let chan_2 = create_announced_chan_between_nodes(&nodes, 1, 2, InitFeatures::known(), InitFeatures::known()).2;
2239+
2240+
let (payment_preimage, payment_hash, _) = route_payment(&nodes[0], &[&nodes[1], &nodes[2]], 100_000);
2241+
2242+
let mut as_raa = None;
2243+
if htlc_status == HTLCStatusAtDupClaim::HoldingCell {
2244+
// In order to get the HTLC claim into the holding cell at nodes[1], we need nodes[1] to be
2245+
// awaiting a remote revoke_and_ack from nodes[0].
2246+
let (_, second_payment_hash, second_payment_secret) = get_payment_preimage_hash!(nodes[1]);
2247+
let route = get_route(&nodes[0].node.get_our_node_id(), &nodes[0].net_graph_msg_handler.network_graph.read().unwrap(),
2248+
&nodes[1].node.get_our_node_id(), Some(InvoiceFeatures::known()), None, &Vec::new(), 100_000, TEST_FINAL_CLTV, nodes[1].logger).unwrap();
2249+
nodes[0].node.send_payment(&route, second_payment_hash, &Some(second_payment_secret)).unwrap();
2250+
check_added_monitors!(nodes[0], 1);
2251+
2252+
let send_event = SendEvent::from_event(nodes[0].node.get_and_clear_pending_msg_events().remove(0));
2253+
nodes[1].node.handle_update_add_htlc(&nodes[0].node.get_our_node_id(), &send_event.msgs[0]);
2254+
nodes[1].node.handle_commitment_signed(&nodes[0].node.get_our_node_id(), &send_event.commitment_msg);
2255+
check_added_monitors!(nodes[1], 1);
2256+
2257+
let (bs_raa, bs_cs) = get_revoke_commit_msgs!(nodes[1], nodes[0].node.get_our_node_id());
2258+
nodes[0].node.handle_revoke_and_ack(&nodes[1].node.get_our_node_id(), &bs_raa);
2259+
check_added_monitors!(nodes[0], 1);
2260+
nodes[0].node.handle_commitment_signed(&nodes[1].node.get_our_node_id(), &bs_cs);
2261+
check_added_monitors!(nodes[0], 1);
2262+
2263+
as_raa = Some(get_event_msg!(nodes[0], MessageSendEvent::SendRevokeAndACK, nodes[1].node.get_our_node_id()));
2264+
}
2265+
2266+
let fulfill_msg = msgs::UpdateFulfillHTLC {
2267+
channel_id: chan_2,
2268+
htlc_id: 0,
2269+
payment_preimage,
2270+
};
2271+
if second_fails {
2272+
assert!(nodes[2].node.fail_htlc_backwards(&payment_hash));
2273+
expect_pending_htlcs_forwardable!(nodes[2]);
2274+
check_added_monitors!(nodes[2], 1);
2275+
get_htlc_update_msgs!(nodes[2], nodes[1].node.get_our_node_id());
2276+
} else {
2277+
assert!(nodes[2].node.claim_funds(payment_preimage));
2278+
check_added_monitors!(nodes[2], 1);
2279+
let cs_updates = get_htlc_update_msgs!(nodes[2], nodes[1].node.get_our_node_id());
2280+
assert_eq!(cs_updates.update_fulfill_htlcs.len(), 1);
2281+
// Check that the message we're about to deliver matches the one generated:
2282+
assert_eq!(fulfill_msg, cs_updates.update_fulfill_htlcs[0]);
2283+
}
2284+
nodes[1].node.handle_update_fulfill_htlc(&nodes[2].node.get_our_node_id(), &fulfill_msg);
2285+
check_added_monitors!(nodes[1], 1);
2286+
2287+
let mut bs_updates = None;
2288+
if htlc_status != HTLCStatusAtDupClaim::HoldingCell {
2289+
bs_updates = Some(get_htlc_update_msgs!(nodes[1], nodes[0].node.get_our_node_id()));
2290+
assert_eq!(bs_updates.as_ref().unwrap().update_fulfill_htlcs.len(), 1);
2291+
nodes[0].node.handle_update_fulfill_htlc(&nodes[1].node.get_our_node_id(), &bs_updates.as_ref().unwrap().update_fulfill_htlcs[0]);
2292+
expect_payment_sent!(nodes[0], payment_preimage);
2293+
if htlc_status == HTLCStatusAtDupClaim::Cleared {
2294+
commitment_signed_dance!(nodes[0], nodes[1], &bs_updates.as_ref().unwrap().commitment_signed, false);
2295+
}
2296+
} else {
2297+
assert!(nodes[1].node.get_and_clear_pending_msg_events().is_empty());
2298+
}
2299+
2300+
nodes[1].node.peer_disconnected(&nodes[2].node.get_our_node_id(), false);
2301+
nodes[2].node.peer_disconnected(&nodes[1].node.get_our_node_id(), false);
2302+
2303+
if second_fails {
2304+
reconnect_nodes(&nodes[1], &nodes[2], (false, false), (0, 0), (0, 0), (1, 0), (0, 0), (0, 0), (false, false));
2305+
expect_pending_htlcs_forwardable!(nodes[1]);
2306+
} else {
2307+
reconnect_nodes(&nodes[1], &nodes[2], (false, false), (0, 0), (1, 0), (0, 0), (0, 0), (0, 0), (false, false));
2308+
}
2309+
2310+
if htlc_status == HTLCStatusAtDupClaim::HoldingCell {
2311+
nodes[1].node.handle_revoke_and_ack(&nodes[0].node.get_our_node_id(), &as_raa.unwrap());
2312+
check_added_monitors!(nodes[1], 1);
2313+
expect_pending_htlcs_forwardable_ignore!(nodes[1]); // We finally receive the second payment, but don't claim it
2314+
2315+
bs_updates = Some(get_htlc_update_msgs!(nodes[1], nodes[0].node.get_our_node_id()));
2316+
assert_eq!(bs_updates.as_ref().unwrap().update_fulfill_htlcs.len(), 1);
2317+
nodes[0].node.handle_update_fulfill_htlc(&nodes[1].node.get_our_node_id(), &bs_updates.as_ref().unwrap().update_fulfill_htlcs[0]);
2318+
expect_payment_sent!(nodes[0], payment_preimage);
2319+
}
2320+
if htlc_status != HTLCStatusAtDupClaim::Cleared {
2321+
commitment_signed_dance!(nodes[0], nodes[1], &bs_updates.as_ref().unwrap().commitment_signed, false);
2322+
}
2323+
}
2324+
2325+
#[test]
2326+
fn test_reconnect_dup_htlc_claims() {
2327+
do_test_reconnect_dup_htlc_claims(HTLCStatusAtDupClaim::Received, false);
2328+
do_test_reconnect_dup_htlc_claims(HTLCStatusAtDupClaim::HoldingCell, false);
2329+
do_test_reconnect_dup_htlc_claims(HTLCStatusAtDupClaim::Cleared, false);
2330+
do_test_reconnect_dup_htlc_claims(HTLCStatusAtDupClaim::Received, true);
2331+
do_test_reconnect_dup_htlc_claims(HTLCStatusAtDupClaim::HoldingCell, true);
2332+
do_test_reconnect_dup_htlc_claims(HTLCStatusAtDupClaim::Cleared, true);
2333+
}

lightning/src/ln/channel.rs

Lines changed: 68 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -444,6 +444,15 @@ pub(super) struct Channel<Signer: Sign> {
444444
///
445445
/// See-also <https://github.com/lightningnetwork/lnd/issues/4006>
446446
pub workaround_lnd_bug_4006: Option<msgs::FundingLocked>,
447+
448+
#[cfg(any(test, feature = "fuzztarget"))]
449+
// When we receive an HTLC fulfill on an outbound path, we may immediately fulfill the
450+
// corresponding HTLC on the inbound path. If, then, the outbound path channel is
451+
// disconnected and reconnected (before we've exchange commitment_signed and revoke_and_ack
452+
// messages), they may re-broadcast their update_fulfill_htlc, causing a duplicate claim. This
453+
// is fine, but as a sanity check in our failure to generate the second claim, we check here
454+
// that the original was a claim, and that we aren't now trying to fulfill a failed HTLC.
455+
historical_inbound_htlc_fulfills: HashSet<u64>,
447456
}
448457

449458
#[cfg(any(test, feature = "fuzztarget"))]
@@ -645,6 +654,9 @@ impl<Signer: Sign> Channel<Signer> {
645654
next_remote_commitment_tx_fee_info_cached: Mutex::new(None),
646655

647656
workaround_lnd_bug_4006: None,
657+
658+
#[cfg(any(test, feature = "fuzztarget"))]
659+
historical_inbound_htlc_fulfills: HashSet::new(),
648660
})
649661
}
650662

@@ -890,6 +902,9 @@ impl<Signer: Sign> Channel<Signer> {
890902
next_remote_commitment_tx_fee_info_cached: Mutex::new(None),
891903

892904
workaround_lnd_bug_4006: None,
905+
906+
#[cfg(any(test, feature = "fuzztarget"))]
907+
historical_inbound_htlc_fulfills: HashSet::new(),
893908
};
894909

895910
Ok(chan)
@@ -1249,8 +1264,8 @@ impl<Signer: Sign> Channel<Signer> {
12491264
if let &InboundHTLCRemovalReason::Fulfill(_) = reason {
12501265
} else {
12511266
log_warn!(logger, "Have preimage and want to fulfill HTLC with payment hash {} we already failed against channel {}", log_bytes!(htlc.payment_hash.0), log_bytes!(self.channel_id()));
1267+
debug_assert!(false, "Tried to fulfill an HTLC that was already failed");
12521268
}
1253-
debug_assert!(false, "Tried to fulfill an HTLC that was already fail/fulfilled");
12541269
return Ok((None, None));
12551270
},
12561271
_ => {
@@ -1263,7 +1278,11 @@ impl<Signer: Sign> Channel<Signer> {
12631278
}
12641279
}
12651280
if pending_idx == core::usize::MAX {
1266-
return Err(ChannelError::Ignore("Unable to find a pending HTLC which matched the given HTLC ID".to_owned()));
1281+
#[cfg(any(test, feature = "fuzztarget"))]
1282+
// If we failed to find an HTLC to fulfill, make sure it was previously fulfilled and
1283+
// this is simply a duplicate claim, not previously failed and we lost funds.
1284+
debug_assert!(self.historical_inbound_htlc_fulfills.contains(&htlc_id_arg));
1285+
return Ok((None, None));
12671286
}
12681287

12691288
// Now update local state:
@@ -1285,7 +1304,8 @@ impl<Signer: Sign> Channel<Signer> {
12851304
if htlc_id_arg == htlc_id {
12861305
// Make sure we don't leave latest_monitor_update_id incremented here:
12871306
self.latest_monitor_update_id -= 1;
1288-
debug_assert!(false, "Tried to fulfill an HTLC that was already fulfilled");
1307+
#[cfg(any(test, feature = "fuzztarget"))]
1308+
debug_assert!(self.historical_inbound_htlc_fulfills.contains(&htlc_id_arg));
12891309
return Ok((None, None));
12901310
}
12911311
},
@@ -1305,8 +1325,12 @@ impl<Signer: Sign> Channel<Signer> {
13051325
self.holding_cell_htlc_updates.push(HTLCUpdateAwaitingACK::ClaimHTLC {
13061326
payment_preimage: payment_preimage_arg, htlc_id: htlc_id_arg,
13071327
});
1328+
#[cfg(any(test, feature = "fuzztarget"))]
1329+
self.historical_inbound_htlc_fulfills.insert(htlc_id_arg);
13081330
return Ok((None, Some(monitor_update)));
13091331
}
1332+
#[cfg(any(test, feature = "fuzztarget"))]
1333+
self.historical_inbound_htlc_fulfills.insert(htlc_id_arg);
13101334

13111335
{
13121336
let htlc = &mut self.pending_inbound_htlcs[pending_idx];
@@ -1366,8 +1390,11 @@ impl<Signer: Sign> Channel<Signer> {
13661390
if htlc.htlc_id == htlc_id_arg {
13671391
match htlc.state {
13681392
InboundHTLCState::Committed => {},
1369-
InboundHTLCState::LocalRemoved(_) => {
1370-
debug_assert!(false, "Tried to fail an HTLC that was already fail/fulfilled");
1393+
InboundHTLCState::LocalRemoved(ref reason) => {
1394+
if let &InboundHTLCRemovalReason::Fulfill(_) = reason {
1395+
} else {
1396+
debug_assert!(false, "Tried to fail an HTLC that was already failed");
1397+
}
13711398
return Ok(None);
13721399
},
13731400
_ => {
@@ -1379,7 +1406,11 @@ impl<Signer: Sign> Channel<Signer> {
13791406
}
13801407
}
13811408
if pending_idx == core::usize::MAX {
1382-
return Err(ChannelError::Ignore("Unable to find a pending HTLC which matched the given HTLC ID".to_owned()));
1409+
#[cfg(any(test, feature = "fuzztarget"))]
1410+
// If we failed to find an HTLC to fail, make sure it was previously fulfilled and this
1411+
// is simply a duplicate fail, not previously failed and we failed-back too early.
1412+
debug_assert!(self.historical_inbound_htlc_fulfills.contains(&htlc_id_arg));
1413+
return Ok(None);
13831414
}
13841415

13851416
// Now update local state:
@@ -1388,8 +1419,9 @@ impl<Signer: Sign> Channel<Signer> {
13881419
match pending_update {
13891420
&HTLCUpdateAwaitingACK::ClaimHTLC { htlc_id, .. } => {
13901421
if htlc_id_arg == htlc_id {
1391-
debug_assert!(false, "Tried to fail an HTLC that was already fulfilled");
1392-
return Err(ChannelError::Ignore("Unable to find a pending HTLC which matched the given HTLC ID".to_owned()));
1422+
#[cfg(any(test, feature = "fuzztarget"))]
1423+
debug_assert!(self.historical_inbound_htlc_fulfills.contains(&htlc_id_arg));
1424+
return Ok(None);
13931425
}
13941426
},
13951427
&HTLCUpdateAwaitingACK::FailHTLC { htlc_id, .. } => {
@@ -2453,7 +2485,14 @@ impl<Signer: Sign> Channel<Signer> {
24532485
},
24542486
&HTLCUpdateAwaitingACK::FailHTLC { htlc_id, ref err_packet } => {
24552487
match self.get_update_fail_htlc(htlc_id, err_packet.clone(), logger) {
2456-
Ok(update_fail_msg_option) => update_fail_htlcs.push(update_fail_msg_option.unwrap()),
2488+
Ok(update_fail_msg_option) => {
2489+
// If an HTLC failure was previously added to the holding cell (via
2490+
// `get_update_fail_htlc`) then generating the fail message itself
2491+
// must not fail - we should never end up in a state where we
2492+
// double-fail an HTLC or fail-then-claim an HTLC as it indicates
2493+
// we didn't wait for a full revocation before failing.
2494+
update_fail_htlcs.push(update_fail_msg_option.unwrap())
2495+
},
24572496
Err(e) => {
24582497
if let ChannelError::Ignore(_) = e {}
24592498
else {
@@ -4690,6 +4729,13 @@ impl<Signer: Sign> Writeable for Channel<Signer> {
46904729

46914730
self.channel_update_status.write(writer)?;
46924731

4732+
#[cfg(any(test, feature = "fuzztarget"))]
4733+
(self.historical_inbound_htlc_fulfills.len() as u64).write(writer)?;
4734+
#[cfg(any(test, feature = "fuzztarget"))]
4735+
for htlc in self.historical_inbound_htlc_fulfills.iter() {
4736+
htlc.write(writer)?;
4737+
}
4738+
46934739
write_tlv_fields!(writer, {
46944740
(0, self.announcement_sigs, option),
46954741
// minimum_depth and counterparty_selected_channel_reserve_satoshis used to have a
@@ -4882,6 +4928,16 @@ impl<'a, Signer: Sign, K: Deref> ReadableArgs<&'a K> for Channel<Signer>
48824928

48834929
let channel_update_status = Readable::read(reader)?;
48844930

4931+
#[cfg(any(test, feature = "fuzztarget"))]
4932+
let mut historical_inbound_htlc_fulfills = HashSet::new();
4933+
#[cfg(any(test, feature = "fuzztarget"))]
4934+
{
4935+
let htlc_fulfills_len: u64 = Readable::read(reader)?;
4936+
for _ in 0..htlc_fulfills_len {
4937+
assert!(historical_inbound_htlc_fulfills.insert(Readable::read(reader)?));
4938+
}
4939+
}
4940+
48854941
let mut announcement_sigs = None;
48864942
read_tlv_fields!(reader, {
48874943
(0, announcement_sigs, option),
@@ -4973,6 +5029,9 @@ impl<'a, Signer: Sign, K: Deref> ReadableArgs<&'a K> for Channel<Signer>
49735029
next_remote_commitment_tx_fee_info_cached: Mutex::new(None),
49745030

49755031
workaround_lnd_bug_4006: None,
5032+
5033+
#[cfg(any(test, feature = "fuzztarget"))]
5034+
historical_inbound_htlc_fulfills,
49765035
})
49775036
}
49785037
}

0 commit comments

Comments
 (0)