Skip to content

Commit ecadae9

Browse files
committed
Add a test for timeout'ing HTLCs which claim to be a part of an MPP
This is a key test for our automatic HTLC time-out logic, as it ensures we don't allow an HTLC which indicates we should wait for additional HTLCs before responding to cause us to force-close a channel due to HTLC near-timeout.
1 parent c019922 commit ecadae9

File tree

3 files changed

+147
-124
lines changed

3 files changed

+147
-124
lines changed

lightning/src/ln/channelmanager.rs

Lines changed: 76 additions & 83 deletions
Original file line numberDiff line numberDiff line change
@@ -1225,6 +1225,80 @@ impl<ChanSigner: ChannelKeys, M: Deref, T: Deref, K: Deref, F: Deref> ChannelMan
12251225
})
12261226
}
12271227

1228+
// Only public for testing, this should otherwise never be called direcly
1229+
pub(crate) fn send_payment_along_path(&self, path: &Vec<RouteHop>, payment_hash: &PaymentHash, payment_secret: &Option<PaymentSecret>, total_value: u64, cur_height: u32) -> Result<(), APIError> {
1230+
log_trace!(self, "Attempting to send payment for path with next hop {}", path.first().unwrap().short_channel_id);
1231+
let (session_priv, prng_seed) = self.keys_manager.get_onion_rand();
1232+
1233+
let onion_keys = onion_utils::construct_onion_keys(&self.secp_ctx, &path, &session_priv)
1234+
.map_err(|_| APIError::RouteError{err: "Pubkey along hop was maliciously selected"})?;
1235+
let (onion_payloads, htlc_msat, htlc_cltv) = onion_utils::build_onion_payloads(path, total_value, payment_secret, cur_height)?;
1236+
if onion_utils::route_size_insane(&onion_payloads) {
1237+
return Err(APIError::RouteError{err: "Route size too large considering onion data"});
1238+
}
1239+
let onion_packet = onion_utils::construct_onion_packet(onion_payloads, onion_keys, prng_seed, payment_hash);
1240+
1241+
let _ = self.total_consistency_lock.read().unwrap();
1242+
1243+
let err: Result<(), _> = loop {
1244+
let mut channel_lock = self.channel_state.lock().unwrap();
1245+
let id = match channel_lock.short_to_id.get(&path.first().unwrap().short_channel_id) {
1246+
None => return Err(APIError::ChannelUnavailable{err: "No channel available with first hop!"}),
1247+
Some(id) => id.clone(),
1248+
};
1249+
1250+
let channel_state = &mut *channel_lock;
1251+
if let hash_map::Entry::Occupied(mut chan) = channel_state.by_id.entry(id) {
1252+
match {
1253+
if chan.get().get_their_node_id() != path.first().unwrap().pubkey {
1254+
return Err(APIError::RouteError{err: "Node ID mismatch on first hop!"});
1255+
}
1256+
if !chan.get().is_live() {
1257+
return Err(APIError::ChannelUnavailable{err: "Peer for first hop currently disconnected/pending monitor update!"});
1258+
}
1259+
break_chan_entry!(self, chan.get_mut().send_htlc_and_commit(htlc_msat, payment_hash.clone(), htlc_cltv, HTLCSource::OutboundRoute {
1260+
path: path.clone(),
1261+
session_priv: session_priv.clone(),
1262+
first_hop_htlc_msat: htlc_msat,
1263+
}, onion_packet), channel_state, chan)
1264+
} {
1265+
Some((update_add, commitment_signed, monitor_update)) => {
1266+
if let Err(e) = self.monitor.update_monitor(chan.get().get_funding_txo().unwrap(), monitor_update) {
1267+
maybe_break_monitor_err!(self, e, channel_state, chan, RAACommitmentOrder::CommitmentFirst, false, true);
1268+
// Note that MonitorUpdateFailed here indicates (per function docs)
1269+
// that we will resend the commitment update once monitor updating
1270+
// is restored. Therefore, we must return an error indicating that
1271+
// it is unsafe to retry the payment wholesale, which we do in the
1272+
// send_payment check for MonitorUpdateFailed, below.
1273+
return Err(APIError::MonitorUpdateFailed);
1274+
}
1275+
1276+
channel_state.pending_msg_events.push(events::MessageSendEvent::UpdateHTLCs {
1277+
node_id: path.first().unwrap().pubkey,
1278+
updates: msgs::CommitmentUpdate {
1279+
update_add_htlcs: vec![update_add],
1280+
update_fulfill_htlcs: Vec::new(),
1281+
update_fail_htlcs: Vec::new(),
1282+
update_fail_malformed_htlcs: Vec::new(),
1283+
update_fee: None,
1284+
commitment_signed,
1285+
},
1286+
});
1287+
},
1288+
None => {},
1289+
}
1290+
} else { unreachable!(); }
1291+
return Ok(());
1292+
};
1293+
1294+
match handle_error!(self, err, path.first().unwrap().pubkey) {
1295+
Ok(_) => unreachable!(),
1296+
Err(e) => {
1297+
Err(APIError::ChannelUnavailable { err: e.err })
1298+
},
1299+
}
1300+
}
1301+
12281302
/// Sends a payment along a given route.
12291303
///
12301304
/// Value parameters are provided via the last hop in route, see documentation for RouteHop
@@ -1297,89 +1371,8 @@ impl<ChanSigner: ChannelKeys, M: Deref, T: Deref, K: Deref, F: Deref> ChannelMan
12971371

12981372
let cur_height = self.latest_block_height.load(Ordering::Acquire) as u32 + 1;
12991373
let mut results = Vec::new();
1300-
'path_loop: for path in route.paths.iter() {
1301-
macro_rules! check_res_push {
1302-
($res: expr) => { match $res {
1303-
Ok(r) => r,
1304-
Err(e) => {
1305-
results.push(Err(e));
1306-
continue 'path_loop;
1307-
},
1308-
}
1309-
}
1310-
}
1311-
1312-
log_trace!(self, "Attempting to send payment for path with next hop {}", path.first().unwrap().short_channel_id);
1313-
let (session_priv, prng_seed) = self.keys_manager.get_onion_rand();
1314-
1315-
let onion_keys = check_res_push!(onion_utils::construct_onion_keys(&self.secp_ctx, &path, &session_priv)
1316-
.map_err(|_| APIError::RouteError{err: "Pubkey along hop was maliciously selected"}));
1317-
let (onion_payloads, htlc_msat, htlc_cltv) = check_res_push!(onion_utils::build_onion_payloads(&path, total_value, payment_secret, cur_height));
1318-
if onion_utils::route_size_insane(&onion_payloads) {
1319-
check_res_push!(Err(APIError::RouteError{err: "Route size too large considering onion data"}));
1320-
}
1321-
let onion_packet = onion_utils::construct_onion_packet(onion_payloads, onion_keys, prng_seed, &payment_hash);
1322-
1323-
let _ = self.total_consistency_lock.read().unwrap();
1324-
1325-
let err: Result<(), _> = loop {
1326-
let mut channel_lock = self.channel_state.lock().unwrap();
1327-
let id = match channel_lock.short_to_id.get(&path.first().unwrap().short_channel_id) {
1328-
None => check_res_push!(Err(APIError::ChannelUnavailable{err: "No channel available with first hop!"})),
1329-
Some(id) => id.clone(),
1330-
};
1331-
1332-
let channel_state = &mut *channel_lock;
1333-
if let hash_map::Entry::Occupied(mut chan) = channel_state.by_id.entry(id) {
1334-
match {
1335-
if chan.get().get_their_node_id() != path.first().unwrap().pubkey {
1336-
check_res_push!(Err(APIError::RouteError{err: "Node ID mismatch on first hop!"}));
1337-
}
1338-
if !chan.get().is_live() {
1339-
check_res_push!(Err(APIError::ChannelUnavailable{err: "Peer for first hop currently disconnected/pending monitor update!"}));
1340-
}
1341-
break_chan_entry!(self, chan.get_mut().send_htlc_and_commit(htlc_msat, payment_hash.clone(), htlc_cltv, HTLCSource::OutboundRoute {
1342-
path: path.clone(),
1343-
session_priv: session_priv.clone(),
1344-
first_hop_htlc_msat: htlc_msat,
1345-
}, onion_packet), channel_state, chan)
1346-
} {
1347-
Some((update_add, commitment_signed, monitor_update)) => {
1348-
if let Err(e) = self.monitor.update_monitor(chan.get().get_funding_txo().unwrap(), monitor_update) {
1349-
maybe_break_monitor_err!(self, e, channel_state, chan, RAACommitmentOrder::CommitmentFirst, false, true);
1350-
// Note that MonitorUpdateFailed here indicates (per function docs)
1351-
// that we will resend the commitment update once monitor updating
1352-
// is restored. Therefore, we must return an error indicating that
1353-
// it is unsafe to retry the payment wholesale, which we do in the
1354-
// next check for MonitorUpdateFailed, below.
1355-
check_res_push!(Err(APIError::MonitorUpdateFailed));
1356-
}
1357-
1358-
channel_state.pending_msg_events.push(events::MessageSendEvent::UpdateHTLCs {
1359-
node_id: path.first().unwrap().pubkey,
1360-
updates: msgs::CommitmentUpdate {
1361-
update_add_htlcs: vec![update_add],
1362-
update_fulfill_htlcs: Vec::new(),
1363-
update_fail_htlcs: Vec::new(),
1364-
update_fail_malformed_htlcs: Vec::new(),
1365-
update_fee: None,
1366-
commitment_signed,
1367-
},
1368-
});
1369-
},
1370-
None => {},
1371-
}
1372-
} else { unreachable!(); }
1373-
results.push(Ok(()));
1374-
continue 'path_loop;
1375-
};
1376-
1377-
match handle_error!(self, err, path.first().unwrap().pubkey) {
1378-
Ok(_) => unreachable!(),
1379-
Err(e) => {
1380-
check_res_push!(Err(APIError::ChannelUnavailable { err: e.err }));
1381-
},
1382-
}
1374+
for path in route.paths.iter() {
1375+
results.push(self.send_payment_along_path(&path, &payment_hash, payment_secret, total_value, cur_height));
13831376
}
13841377
let mut has_ok = false;
13851378
let mut has_err = false;

lightning/src/ln/functional_test_utils.rs

Lines changed: 46 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -786,49 +786,57 @@ macro_rules! expect_payment_failed {
786786
pub fn send_along_route_with_secret<'a, 'b, 'c>(origin_node: &Node<'a, 'b, 'c>, route: Route, expected_paths: &[&[&Node<'a, 'b, 'c>]], recv_value: u64, our_payment_hash: PaymentHash, our_payment_secret: Option<PaymentSecret>) {
787787
origin_node.node.send_payment(&route, our_payment_hash, &our_payment_secret).unwrap();
788788
check_added_monitors!(origin_node, expected_paths.len());
789+
pass_along_route(origin_node, expected_paths, recv_value, our_payment_hash, our_payment_secret);
790+
}
789791

790-
let mut events = origin_node.node.get_and_clear_pending_msg_events();
791-
assert_eq!(events.len(), expected_paths.len());
792-
for (path_idx, (ev, expected_route)) in events.drain(..).zip(expected_paths.iter()).enumerate() {
793-
let mut payment_event = SendEvent::from_event(ev);
794-
let mut prev_node = origin_node;
795-
796-
for (idx, &node) in expected_route.iter().enumerate() {
797-
assert_eq!(node.node.get_our_node_id(), payment_event.node_id);
798-
799-
node.node.handle_update_add_htlc(&prev_node.node.get_our_node_id(), &payment_event.msgs[0]);
800-
check_added_monitors!(node, 0);
801-
commitment_signed_dance!(node, prev_node, payment_event.commitment_msg, false);
802-
803-
expect_pending_htlcs_forwardable!(node);
804-
805-
if idx == expected_route.len() - 1 {
806-
let events_2 = node.node.get_and_clear_pending_events();
807-
// Once we've gotten through all the HTLCs, the last one should result in a
808-
// PaymentReceived (but each previous one should not!).
809-
if path_idx == expected_paths.len() - 1 {
810-
assert_eq!(events_2.len(), 1);
811-
match events_2[0] {
812-
Event::PaymentReceived { ref payment_hash, ref payment_secret, amt } => {
813-
assert_eq!(our_payment_hash, *payment_hash);
814-
assert_eq!(our_payment_secret, *payment_secret);
815-
assert_eq!(amt, recv_value);
816-
},
817-
_ => panic!("Unexpected event"),
818-
}
819-
} else {
820-
assert!(events_2.is_empty());
792+
pub fn pass_along_path<'a, 'b, 'c>(origin_node: &Node<'a, 'b, 'c>, expected_path: &[&Node<'a, 'b, 'c>], recv_value: u64, our_payment_hash: PaymentHash, our_payment_secret: Option<PaymentSecret>, ev: MessageSendEvent, payment_received_expected: bool) {
793+
let mut payment_event = SendEvent::from_event(ev);
794+
let mut prev_node = origin_node;
795+
796+
for (idx, &node) in expected_path.iter().enumerate() {
797+
assert_eq!(node.node.get_our_node_id(), payment_event.node_id);
798+
799+
node.node.handle_update_add_htlc(&prev_node.node.get_our_node_id(), &payment_event.msgs[0]);
800+
check_added_monitors!(node, 0);
801+
commitment_signed_dance!(node, prev_node, payment_event.commitment_msg, false);
802+
803+
expect_pending_htlcs_forwardable!(node);
804+
805+
if idx == expected_path.len() - 1 {
806+
let events_2 = node.node.get_and_clear_pending_events();
807+
if payment_received_expected {
808+
assert_eq!(events_2.len(), 1);
809+
match events_2[0] {
810+
Event::PaymentReceived { ref payment_hash, ref payment_secret, amt } => {
811+
assert_eq!(our_payment_hash, *payment_hash);
812+
assert_eq!(our_payment_secret, *payment_secret);
813+
assert_eq!(amt, recv_value);
814+
},
815+
_ => panic!("Unexpected event"),
821816
}
822817
} else {
823-
let mut events_2 = node.node.get_and_clear_pending_msg_events();
824-
assert_eq!(events_2.len(), 1);
825-
check_added_monitors!(node, 1);
826-
payment_event = SendEvent::from_event(events_2.remove(0));
827-
assert_eq!(payment_event.msgs.len(), 1);
818+
assert!(events_2.is_empty());
828819
}
829-
830-
prev_node = node;
820+
} else {
821+
let mut events_2 = node.node.get_and_clear_pending_msg_events();
822+
assert_eq!(events_2.len(), 1);
823+
check_added_monitors!(node, 1);
824+
payment_event = SendEvent::from_event(events_2.remove(0));
825+
assert_eq!(payment_event.msgs.len(), 1);
831826
}
827+
828+
prev_node = node;
829+
}
830+
}
831+
832+
pub fn pass_along_route<'a, 'b, 'c>(origin_node: &Node<'a, 'b, 'c>, expected_route: &[&[&Node<'a, 'b, 'c>]], recv_value: u64, our_payment_hash: PaymentHash, our_payment_secret: Option<PaymentSecret>) {
833+
let mut events = origin_node.node.get_and_clear_pending_msg_events();
834+
assert_eq!(events.len(), expected_route.len());
835+
for (path_idx, (ev, expected_path)) in events.drain(..).zip(expected_route.iter()).enumerate() {
836+
// Once we've gotten through all the HTLCs, the last one should result in a
837+
// PaymentReceived (but each previous one should not!), .
838+
let expect_payment = path_idx == expected_route.len() - 1;
839+
pass_along_path(origin_node, expected_path, recv_value, our_payment_hash.clone(), our_payment_secret, ev, expect_payment);
832840
}
833841
}
834842

lightning/src/ln/functional_tests.rs

Lines changed: 25 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3618,8 +3618,7 @@ fn test_drop_messages_peer_disconnect_dual_htlc() {
36183618
claim_payment(&nodes[0], &[&nodes[1]], payment_preimage_2, 1_000_000);
36193619
}
36203620

3621-
#[test]
3622-
fn test_htlc_timeout() {
3621+
fn do_test_htlc_timeout(send_partial_mpp: bool) {
36233622
// If the user fails to claim/fail an HTLC within the HTLC CLTV timeout we fail it for them
36243623
// to avoid our counterparty failing the channel.
36253624
let chanmon_cfgs = create_chanmon_cfgs(2);
@@ -3628,7 +3627,24 @@ fn test_htlc_timeout() {
36283627
let nodes = create_network(2, &node_cfgs, &node_chanmgrs);
36293628

36303629
create_announced_chan_between_nodes(&nodes, 0, 1, InitFeatures::supported(), InitFeatures::supported());
3631-
let (_, our_payment_hash) = route_payment(&nodes[0], &[&nodes[1]], 100000);
3630+
3631+
let our_payment_hash = if send_partial_mpp {
3632+
let route = nodes[0].router.get_route(&nodes[1].node.get_our_node_id(), None, &Vec::new(), 100000, TEST_FINAL_CLTV).unwrap();
3633+
let (_, our_payment_hash) = get_payment_preimage_hash!(&nodes[0]);
3634+
let payment_secret = PaymentSecret([0xdb; 32]);
3635+
// Use the utility function send_payment_along_path to send the payment with MPP data which
3636+
// indicates there are more HTLCs coming.
3637+
nodes[0].node.send_payment_along_path(&route.paths[0], &our_payment_hash, &Some(payment_secret), 200000, CHAN_CONFIRM_DEPTH).unwrap();
3638+
check_added_monitors!(nodes[0], 1);
3639+
let mut events = nodes[0].node.get_and_clear_pending_msg_events();
3640+
assert_eq!(events.len(), 1);
3641+
// Now do the relevant commitment_signed/RAA dances along the path, noting that the final
3642+
// hop should *not* yet generate any PaymentReceived event(s).
3643+
pass_along_path(&nodes[0], &[&nodes[1]], 100000, our_payment_hash, Some(payment_secret), events.drain(..).next().unwrap(), false);
3644+
our_payment_hash
3645+
} else {
3646+
route_payment(&nodes[0], &[&nodes[1]], 100000).1
3647+
};
36323648

36333649
let mut header = BlockHeader { version: 0x20000000, prev_blockhash: Default::default(), merkle_root: Default::default(), time: 42, bits: 42, nonce: 42 };
36343650
nodes[0].block_notifier.block_connected_checked(&header, 101, &[], &[]);
@@ -3656,6 +3672,12 @@ fn test_htlc_timeout() {
36563672
expect_payment_failed!(nodes[0], our_payment_hash, true, 0x4000 | 15, &expected_failure_data[..]);
36573673
}
36583674

3675+
#[test]
3676+
fn test_htlc_timeout() {
3677+
do_test_htlc_timeout(true);
3678+
do_test_htlc_timeout(false);
3679+
}
3680+
36593681
#[test]
36603682
fn test_invalid_channel_announcement() {
36613683
//Test BOLT 7 channel_announcement msg requirement for final node, gather data to build customed channel_announcement msgs

0 commit comments

Comments
 (0)