Skip to content

Commit 91f7361

Browse files
Add test coverage for serialization of malformed HTLCs.
in Channel and ChannelManager.
1 parent ea6a93b commit 91f7361

File tree

3 files changed

+100
-10
lines changed

3 files changed

+100
-10
lines changed

lightning/src/ln/channel.rs

+28-8
Original file line numberDiff line numberDiff line change
@@ -8281,7 +8281,8 @@ mod tests {
82818281
use bitcoin::blockdata::transaction::{Transaction, TxOut};
82828282
use bitcoin::blockdata::opcodes;
82838283
use bitcoin::network::constants::Network;
8284-
use crate::ln::{PaymentHash, PaymentPreimage};
8284+
use crate::ln::onion_utils::INVALID_ONION_BLINDING;
8285+
use crate::ln::{PaymentHash, PaymentPreimage};
82858286
use crate::ln::channel_keys::{RevocationKey, RevocationBasepoint};
82868287
use crate::ln::channelmanager::{self, HTLCSource, PaymentId};
82878288
use crate::ln::channel::InitFeatures;
@@ -8817,8 +8818,9 @@ mod tests {
88178818
}
88188819

88198820
#[test]
8820-
fn blinding_point_skimmed_fee_ser() {
8821-
// Ensure that channel blinding points and skimmed fees are (de)serialized properly.
8821+
fn blinding_point_skimmed_fee_malformed_ser() {
8822+
// Ensure that channel blinding points, skimmed fees, and malformed HTLCs are (de)serialized
8823+
// properly.
88228824
let feeest = LowerBoundedFeeEstimator::new(&TestFeeEstimator{fee_est: 15000});
88238825
let secp_ctx = Secp256k1::new();
88248826
let seed = [42; 32];
@@ -8883,13 +8885,18 @@ mod tests {
88838885
payment_preimage: PaymentPreimage([42; 32]),
88848886
htlc_id: 0,
88858887
};
8886-
let mut holding_cell_htlc_updates = Vec::with_capacity(10);
8887-
for i in 0..10 {
8888-
if i % 3 == 0 {
8888+
let dummy_holding_cell_malformed_htlc = HTLCUpdateAwaitingACK::FailMalformedHTLC {
8889+
htlc_id: 0,
8890+
failure_code: INVALID_ONION_BLINDING,
8891+
sha256_of_onion: [0; 32],
8892+
};
8893+
let mut holding_cell_htlc_updates = Vec::with_capacity(12);
8894+
for i in 0..12 {
8895+
if i % 4 == 0 {
88898896
holding_cell_htlc_updates.push(dummy_holding_cell_add_htlc.clone());
8890-
} else if i % 3 == 1 {
8897+
} else if i % 4 == 1 {
88918898
holding_cell_htlc_updates.push(dummy_holding_cell_claim_htlc.clone());
8892-
} else {
8899+
} else if i % 4 == 2 {
88938900
let mut dummy_add = dummy_holding_cell_add_htlc.clone();
88948901
if let HTLCUpdateAwaitingACK::AddHTLC {
88958902
ref mut blinding_point, ref mut skimmed_fee_msat, ..
@@ -8898,6 +8905,8 @@ mod tests {
88988905
*skimmed_fee_msat = Some(42);
88998906
} else { panic!() }
89008907
holding_cell_htlc_updates.push(dummy_add);
8908+
} else {
8909+
holding_cell_htlc_updates.push(dummy_holding_cell_malformed_htlc.clone());
89018910
}
89028911
}
89038912
chan.context.holding_cell_htlc_updates = holding_cell_htlc_updates.clone();
@@ -8909,6 +8918,17 @@ mod tests {
89098918
let features = channelmanager::provided_channel_type_features(&config);
89108919
let decoded_chan = Channel::read(&mut reader, (&&keys_provider, &&keys_provider, 0, &features)).unwrap();
89118920
assert_eq!(decoded_chan.context.pending_outbound_htlcs, pending_outbound_htlcs);
8921+
8922+
// As part of maintaining support for downgrades, malformed holding cell HTLCs are serialized in
8923+
// `Channel` TLVs and thus will be shuffled to the end of `holding_cell_htlc_updates` on read.
8924+
holding_cell_htlc_updates.sort_by(|a, b| {
8925+
let a_is_malformed = if let HTLCUpdateAwaitingACK::FailMalformedHTLC { .. } = a { true } else { false };
8926+
let b_is_malformed = if let HTLCUpdateAwaitingACK::FailMalformedHTLC { .. } = b { true } else { false };
8927+
if a_is_malformed && !b_is_malformed { return core::cmp::Ordering::Greater }
8928+
else if !a_is_malformed && b_is_malformed { return core::cmp::Ordering::Less }
8929+
core::cmp::Ordering::Equal
8930+
});
8931+
89128932
assert_eq!(decoded_chan.context.holding_cell_htlc_updates, holding_cell_htlc_updates);
89138933
}
89148934

lightning/src/ln/channelmanager.rs

+71-2
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 {
@@ -11063,12 +11067,14 @@ mod tests {
1106311067
use crate::events::{Event, HTLCDestination, MessageSendEvent, MessageSendEventsProvider, ClosureReason};
1106411068
use crate::ln::{PaymentPreimage, PaymentHash, PaymentSecret};
1106511069
use crate::ln::ChannelId;
11066-
use crate::ln::channelmanager::{create_recv_pending_htlc_info, inbound_payment, PaymentId, PaymentSendFailure, RecipientOnionFields, InterceptId};
11070+
use crate::ln::channelmanager::{create_recv_pending_htlc_info, HTLCForwardInfo, inbound_payment, PaymentId, PaymentSendFailure, RecipientOnionFields, InterceptId};
1106711071
use crate::ln::functional_test_utils::*;
1106811072
use crate::ln::msgs::{self, ErrorAction};
1106911073
use crate::ln::msgs::ChannelMessageHandler;
11074+
use crate::prelude::*;
1107011075
use crate::routing::router::{PaymentParameters, RouteParameters, find_route};
1107111076
use crate::util::errors::APIError;
11077+
use crate::util::ser::Writeable;
1107211078
use crate::util::test_utils;
1107311079
use crate::util::config::{ChannelConfig, ChannelConfigUpdate};
1107411080
use crate::sign::EntropySource;
@@ -12343,6 +12349,69 @@ mod tests {
1234312349
check_spends!(txn[0], funding_tx);
1234412350
}
1234512351
}
12352+
12353+
#[test]
12354+
fn test_malformed_forward_htlcs_ser() {
12355+
// Ensure that `HTLCForwardInfo::FailMalformedHTLC`s are (de)serialized properly.
12356+
let chanmon_cfg = create_chanmon_cfgs(1);
12357+
let node_cfg = create_node_cfgs(1, &chanmon_cfg);
12358+
let persister;
12359+
let chain_monitor;
12360+
let chanmgrs = create_node_chanmgrs(1, &node_cfg, &[None]);
12361+
let deserialized_chanmgr;
12362+
let mut nodes = create_network(1, &node_cfg, &chanmgrs);
12363+
12364+
let dummy_failed_htlc = HTLCForwardInfo::FailHTLC {
12365+
htlc_id: 0, err_packet: msgs::OnionErrorPacket { data: vec![0] },
12366+
};
12367+
12368+
let dummy_malformed_htlc = HTLCForwardInfo::FailMalformedHTLC {
12369+
htlc_id: 0, failure_code: 0x4000, sha256_of_onion: [0; 32]
12370+
};
12371+
12372+
let dummy_htlcs_1 = vec![
12373+
dummy_failed_htlc.clone(), dummy_failed_htlc.clone(), dummy_malformed_htlc.clone(),
12374+
dummy_malformed_htlc.clone(), dummy_failed_htlc.clone(), dummy_malformed_htlc.clone()
12375+
];
12376+
let dummy_htlcs_2 = vec![
12377+
dummy_malformed_htlc.clone(), dummy_failed_htlc.clone(), dummy_malformed_htlc.clone(),
12378+
dummy_failed_htlc.clone()
12379+
];
12380+
12381+
let (scid_1, scid_2) = (42, 43);
12382+
let mut forward_htlcs = HashMap::new();
12383+
forward_htlcs.insert(scid_1, dummy_htlcs_1.clone());
12384+
forward_htlcs.insert(scid_2, dummy_htlcs_2.clone());
12385+
12386+
let mut chanmgr_fwd_htlcs = nodes[0].node.forward_htlcs.lock().unwrap();
12387+
*chanmgr_fwd_htlcs = forward_htlcs.clone();
12388+
core::mem::drop(chanmgr_fwd_htlcs);
12389+
12390+
reload_node!(nodes[0], nodes[0].node.encode(), &[], persister, chain_monitor, deserialized_chanmgr);
12391+
12392+
// As part of maintaining support for downgrades, malformed HTLCs are serialized in TLVs and
12393+
// thus will be shuffled to the end in `forward_htlcs` on read.
12394+
let sort_htlcs: fn(&HTLCForwardInfo, &HTLCForwardInfo) -> _ = |a, b| {
12395+
let a_is_malformed = if let HTLCForwardInfo::FailMalformedHTLC { .. } = a { true } else { false };
12396+
let b_is_malformed = if let HTLCForwardInfo::FailMalformedHTLC { .. } = b { true } else { false };
12397+
if a_is_malformed && !b_is_malformed { return core::cmp::Ordering::Greater }
12398+
else if !a_is_malformed && b_is_malformed { return core::cmp::Ordering::Less }
12399+
core::cmp::Ordering::Equal
12400+
};
12401+
for htlcs in forward_htlcs.values_mut() {
12402+
htlcs.sort_by(sort_htlcs);
12403+
}
12404+
12405+
let mut deserialized_fwd_htlcs = nodes[0].node.forward_htlcs.lock().unwrap();
12406+
for scid in [scid_1, scid_2].iter() {
12407+
let deserialized_htlcs = deserialized_fwd_htlcs.remove(scid).unwrap();
12408+
assert_eq!(forward_htlcs.remove(scid).unwrap(), deserialized_htlcs);
12409+
}
12410+
assert!(deserialized_fwd_htlcs.is_empty());
12411+
core::mem::drop(deserialized_fwd_htlcs);
12412+
12413+
expect_pending_htlcs_forwardable!(nodes[0]);
12414+
}
1234612415
}
1234712416

1234812417
#[cfg(ldk_bench)]

lightning/src/ln/msgs.rs

+1
Original file line numberDiff line numberDiff line change
@@ -1678,6 +1678,7 @@ mod fuzzy_internal_msgs {
16781678
// These types aren't intended to be pub, but are exposed for direct fuzzing (as we deserialize
16791679
// them from untrusted input):
16801680
#[derive(Clone)]
1681+
#[cfg_attr(test, derive(Debug, PartialEq))]
16811682
pub struct FinalOnionHopData {
16821683
pub payment_secret: PaymentSecret,
16831684
/// The total value, in msat, of the payment as received by the ultimate recipient.

0 commit comments

Comments
 (0)