Skip to content

Commit d5dd5ea

Browse files
Add test coverage for serialization of malformed HTLCs.
in Channel and ChannelManager.
1 parent a245a15 commit d5dd5ea

File tree

3 files changed

+100
-10
lines changed

3 files changed

+100
-10
lines changed

lightning/src/ln/channel.rs

Lines changed: 28 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -8041,7 +8041,8 @@ mod tests {
80418041
use bitcoin::blockdata::transaction::{Transaction, TxOut};
80428042
use bitcoin::blockdata::opcodes;
80438043
use bitcoin::network::constants::Network;
8044-
use crate::ln::{PaymentHash, PaymentPreimage};
8044+
use crate::ln::onion_utils::INVALID_ONION_BLINDING;
8045+
use crate::ln::{PaymentHash, PaymentPreimage};
80458046
use crate::ln::channel_keys::{RevocationKey, RevocationBasepoint};
80468047
use crate::ln::channelmanager::{self, HTLCSource, PaymentId};
80478048
use crate::ln::channel::InitFeatures;
@@ -8574,8 +8575,9 @@ mod tests {
85748575
}
85758576

85768577
#[test]
8577-
fn blinding_point_skimmed_fee_ser() {
8578-
// Ensure that channel blinding points and skimmed fees are (de)serialized properly.
8578+
fn blinding_point_skimmed_fee_malformed_ser() {
8579+
// Ensure that channel blinding points, skimmed fees, and malformed HTLCs are (de)serialized
8580+
// properly.
85798581
let feeest = LowerBoundedFeeEstimator::new(&TestFeeEstimator{fee_est: 15000});
85808582
let secp_ctx = Secp256k1::new();
85818583
let seed = [42; 32];
@@ -8640,13 +8642,18 @@ mod tests {
86408642
payment_preimage: PaymentPreimage([42; 32]),
86418643
htlc_id: 0,
86428644
};
8643-
let mut holding_cell_htlc_updates = Vec::with_capacity(10);
8644-
for i in 0..10 {
8645-
if i % 3 == 0 {
8645+
let dummy_holding_cell_malformed_htlc = HTLCUpdateAwaitingACK::FailMalformedHTLC {
8646+
htlc_id: 0,
8647+
failure_code: INVALID_ONION_BLINDING,
8648+
sha256_of_onion: [0; 32],
8649+
};
8650+
let mut holding_cell_htlc_updates = Vec::with_capacity(12);
8651+
for i in 0..12 {
8652+
if i % 4 == 0 {
86468653
holding_cell_htlc_updates.push(dummy_holding_cell_add_htlc.clone());
8647-
} else if i % 3 == 1 {
8654+
} else if i % 4 == 1 {
86488655
holding_cell_htlc_updates.push(dummy_holding_cell_claim_htlc.clone());
8649-
} else {
8656+
} else if i % 4 == 2 {
86508657
let mut dummy_add = dummy_holding_cell_add_htlc.clone();
86518658
if let HTLCUpdateAwaitingACK::AddHTLC {
86528659
ref mut blinding_point, ref mut skimmed_fee_msat, ..
@@ -8655,6 +8662,8 @@ mod tests {
86558662
*skimmed_fee_msat = Some(42);
86568663
} else { panic!() }
86578664
holding_cell_htlc_updates.push(dummy_add);
8665+
} else {
8666+
holding_cell_htlc_updates.push(dummy_holding_cell_malformed_htlc.clone());
86588667
}
86598668
}
86608669
chan.context.holding_cell_htlc_updates = holding_cell_htlc_updates.clone();
@@ -8666,6 +8675,17 @@ mod tests {
86668675
let features = channelmanager::provided_channel_type_features(&config);
86678676
let decoded_chan = Channel::read(&mut reader, (&&keys_provider, &&keys_provider, 0, &features)).unwrap();
86688677
assert_eq!(decoded_chan.context.pending_outbound_htlcs, pending_outbound_htlcs);
8678+
8679+
// As part of maintaining support for downgrades, malformed holding cell HTLCs are serialized in
8680+
// `Channel` TLVs and thus will be shuffled to the end of `holding_cell_htlc_updates` on read.
8681+
holding_cell_htlc_updates.sort_by(|a, b| {
8682+
let a_is_malformed = if let HTLCUpdateAwaitingACK::FailMalformedHTLC { .. } = a { true } else { false };
8683+
let b_is_malformed = if let HTLCUpdateAwaitingACK::FailMalformedHTLC { .. } = b { true } else { false };
8684+
if a_is_malformed && !b_is_malformed { return core::cmp::Ordering::Greater }
8685+
else if !a_is_malformed && b_is_malformed { return core::cmp::Ordering::Less }
8686+
core::cmp::Ordering::Equal
8687+
});
8688+
86698689
assert_eq!(decoded_chan.context.holding_cell_htlc_updates, holding_cell_htlc_updates);
86708690
}
86718691

lightning/src/ln/channelmanager.rs

Lines changed: 71 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -111,6 +111,7 @@ use crate::ln::script::ShutdownScript;
111111

112112
/// Information about where a received HTLC('s onion) has indicated the HTLC should go.
113113
#[derive(Clone)] // See Channel::revoke_and_ack for why, tl;dr: Rust bug
114+
#[cfg_attr(test, derive(Debug, PartialEq))]
114115
pub enum PendingHTLCRouting {
115116
/// An HTLC which should be forwarded on to another node.
116117
Forward {
@@ -189,7 +190,7 @@ pub enum PendingHTLCRouting {
189190
}
190191

191192
/// Information used to forward or fail this HTLC that is being forwarded within a blinded path.
192-
#[derive(Clone, Copy, Hash, PartialEq, Eq)]
193+
#[derive(Clone, Copy, Debug, Hash, PartialEq, Eq)]
193194
pub struct BlindedForward {
194195
/// The `blinding_point` that was set in the inbound [`msgs::UpdateAddHTLC`], or in the inbound
195196
/// onion payload if we're the introduction node. Useful for calculating the next hop's
@@ -213,6 +214,7 @@ impl PendingHTLCRouting {
213214
/// Information about an incoming HTLC, including the [`PendingHTLCRouting`] describing where it
214215
/// should go next.
215216
#[derive(Clone)] // See Channel::revoke_and_ack for why, tl;dr: Rust bug
217+
#[cfg_attr(test, derive(Debug, PartialEq))]
216218
pub struct PendingHTLCInfo {
217219
/// Further routing details based on whether the HTLC is being forwarded or received.
218220
pub routing: PendingHTLCRouting,
@@ -267,6 +269,7 @@ pub(super) enum PendingHTLCStatus {
267269
Fail(HTLCFailureMsg),
268270
}
269271

272+
#[cfg_attr(test, derive(Clone, Debug, PartialEq))]
270273
pub(super) struct PendingAddHTLCInfo {
271274
pub(super) forward_info: PendingHTLCInfo,
272275

@@ -282,6 +285,7 @@ pub(super) struct PendingAddHTLCInfo {
282285
prev_user_channel_id: u128,
283286
}
284287

288+
#[cfg_attr(test, derive(Clone, Debug, PartialEq))]
285289
pub(super) enum HTLCForwardInfo {
286290
AddHTLC(PendingAddHTLCInfo),
287291
FailHTLC {
@@ -11048,12 +11052,14 @@ mod tests {
1104811052
use crate::events::{Event, HTLCDestination, MessageSendEvent, MessageSendEventsProvider, ClosureReason};
1104911053
use crate::ln::{PaymentPreimage, PaymentHash, PaymentSecret};
1105011054
use crate::ln::ChannelId;
11051-
use crate::ln::channelmanager::{create_recv_pending_htlc_info, inbound_payment, PaymentId, PaymentSendFailure, RecipientOnionFields, InterceptId};
11055+
use crate::ln::channelmanager::{create_recv_pending_htlc_info, HTLCForwardInfo, inbound_payment, PaymentId, PaymentSendFailure, RecipientOnionFields, InterceptId};
1105211056
use crate::ln::functional_test_utils::*;
1105311057
use crate::ln::msgs::{self, ErrorAction};
1105411058
use crate::ln::msgs::ChannelMessageHandler;
11059+
use crate::prelude::*;
1105511060
use crate::routing::router::{PaymentParameters, RouteParameters, find_route};
1105611061
use crate::util::errors::APIError;
11062+
use crate::util::ser::Writeable;
1105711063
use crate::util::test_utils;
1105811064
use crate::util::config::{ChannelConfig, ChannelConfigUpdate};
1105911065
use crate::sign::EntropySource;
@@ -12328,6 +12334,69 @@ mod tests {
1232812334
check_spends!(txn[0], funding_tx);
1232912335
}
1233012336
}
12337+
12338+
#[test]
12339+
fn test_malformed_forward_htlcs_ser() {
12340+
// Ensure that `HTLCForwardInfo::FailMalformedHTLC`s are (de)serialized properly.
12341+
let chanmon_cfg = create_chanmon_cfgs(1);
12342+
let node_cfg = create_node_cfgs(1, &chanmon_cfg);
12343+
let persister;
12344+
let chain_monitor;
12345+
let chanmgrs = create_node_chanmgrs(1, &node_cfg, &[None]);
12346+
let deserialized_chanmgr;
12347+
let mut nodes = create_network(1, &node_cfg, &chanmgrs);
12348+
12349+
let dummy_failed_htlc = HTLCForwardInfo::FailHTLC {
12350+
htlc_id: 0, err_packet: msgs::OnionErrorPacket { data: vec![0] },
12351+
};
12352+
12353+
let dummy_malformed_htlc = HTLCForwardInfo::FailMalformedHTLC {
12354+
htlc_id: 0, failure_code: 0x4000, sha256_of_onion: [0; 32]
12355+
};
12356+
12357+
let dummy_htlcs_1 = vec![
12358+
dummy_failed_htlc.clone(), dummy_failed_htlc.clone(), dummy_malformed_htlc.clone(),
12359+
dummy_malformed_htlc.clone(), dummy_failed_htlc.clone(), dummy_malformed_htlc.clone()
12360+
];
12361+
let dummy_htlcs_2 = vec![
12362+
dummy_malformed_htlc.clone(), dummy_failed_htlc.clone(), dummy_malformed_htlc.clone(),
12363+
dummy_failed_htlc.clone()
12364+
];
12365+
12366+
let (scid_1, scid_2) = (42, 43);
12367+
let mut forward_htlcs = HashMap::new();
12368+
forward_htlcs.insert(scid_1, dummy_htlcs_1.clone());
12369+
forward_htlcs.insert(scid_2, dummy_htlcs_2.clone());
12370+
12371+
let mut chanmgr_fwd_htlcs = nodes[0].node.forward_htlcs.lock().unwrap();
12372+
*chanmgr_fwd_htlcs = forward_htlcs.clone();
12373+
core::mem::drop(chanmgr_fwd_htlcs);
12374+
12375+
reload_node!(nodes[0], nodes[0].node.encode(), &[], persister, chain_monitor, deserialized_chanmgr);
12376+
12377+
// As part of maintaining support for downgrades, malformed HTLCs are serialized in TLVs and
12378+
// thus will be shuffled to the end in `forward_htlcs` on read.
12379+
let sort_htlcs: fn(&HTLCForwardInfo, &HTLCForwardInfo) -> _ = |a, b| {
12380+
let a_is_malformed = if let HTLCForwardInfo::FailMalformedHTLC { .. } = a { true } else { false };
12381+
let b_is_malformed = if let HTLCForwardInfo::FailMalformedHTLC { .. } = b { true } else { false };
12382+
if a_is_malformed && !b_is_malformed { return core::cmp::Ordering::Greater }
12383+
else if !a_is_malformed && b_is_malformed { return core::cmp::Ordering::Less }
12384+
core::cmp::Ordering::Equal
12385+
};
12386+
for htlcs in forward_htlcs.values_mut() {
12387+
htlcs.sort_by(sort_htlcs);
12388+
}
12389+
12390+
let mut deserialized_fwd_htlcs = nodes[0].node.forward_htlcs.lock().unwrap();
12391+
for scid in [scid_1, scid_2].iter() {
12392+
let deserialized_htlcs = deserialized_fwd_htlcs.remove(scid).unwrap();
12393+
assert_eq!(forward_htlcs.remove(scid).unwrap(), deserialized_htlcs);
12394+
}
12395+
assert!(deserialized_fwd_htlcs.is_empty());
12396+
core::mem::drop(deserialized_fwd_htlcs);
12397+
12398+
expect_pending_htlcs_forwardable!(nodes[0]);
12399+
}
1233112400
}
1233212401

1233312402
#[cfg(ldk_bench)]

lightning/src/ln/msgs.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1674,6 +1674,7 @@ mod fuzzy_internal_msgs {
16741674
// These types aren't intended to be pub, but are exposed for direct fuzzing (as we deserialize
16751675
// them from untrusted input):
16761676
#[derive(Clone)]
1677+
#[cfg_attr(test, derive(Debug, PartialEq))]
16771678
pub struct FinalOnionHopData {
16781679
pub payment_secret: PaymentSecret,
16791680
/// The total value, in msat, of the payment as received by the ultimate recipient.

0 commit comments

Comments
 (0)