Skip to content

Commit d7e40a4

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

File tree

3 files changed

+99
-9
lines changed

3 files changed

+99
-9
lines changed

lightning/src/ln/channel.rs

+27-7
Original file line numberDiff line numberDiff line change
@@ -8264,6 +8264,7 @@ mod tests {
82648264
use bitcoin::blockdata::transaction::{Transaction, TxOut};
82658265
use bitcoin::blockdata::opcodes;
82668266
use bitcoin::network::constants::Network;
8267+
use crate::ln::onion_utils::INVALID_ONION_BLINDING;
82678268
use crate::ln::{PaymentHash, PaymentPreimage};
82688269
use crate::ln::channel_keys::{RevocationKey, RevocationBasepoint};
82698270
use crate::ln::channelmanager::{self, HTLCSource, PaymentId};
@@ -8800,8 +8801,9 @@ mod tests {
88008801
}
88018802

88028803
#[test]
8803-
fn blinding_point_skimmed_fee_ser() {
8804-
// Ensure that channel blinding points and skimmed fees are (de)serialized properly.
8804+
fn blinding_point_skimmed_fee_malformed_ser() {
8805+
// Ensure that channel blinding points, skimmed fees, and malformed HTLCs are (de)serialized
8806+
// properly.
88058807
let feeest = LowerBoundedFeeEstimator::new(&TestFeeEstimator{fee_est: 15000});
88068808
let secp_ctx = Secp256k1::new();
88078809
let seed = [42; 32];
@@ -8866,13 +8868,18 @@ mod tests {
88668868
payment_preimage: PaymentPreimage([42; 32]),
88678869
htlc_id: 0,
88688870
};
8869-
let mut holding_cell_htlc_updates = Vec::with_capacity(10);
8870-
for i in 0..10 {
8871-
if i % 3 == 0 {
8871+
let dummy_holding_cell_malformed_htlc = HTLCUpdateAwaitingACK::FailMalformedHTLC {
8872+
htlc_id: 0,
8873+
failure_code: INVALID_ONION_BLINDING,
8874+
sha256_of_onion: [0; 32],
8875+
};
8876+
let mut holding_cell_htlc_updates = Vec::with_capacity(12);
8877+
for i in 0..12 {
8878+
if i % 4 == 0 {
88728879
holding_cell_htlc_updates.push(dummy_holding_cell_add_htlc.clone());
8873-
} else if i % 3 == 1 {
8880+
} else if i % 4 == 1 {
88748881
holding_cell_htlc_updates.push(dummy_holding_cell_claim_htlc.clone());
8875-
} else {
8882+
} else if i % 4 == 2 {
88768883
let mut dummy_add = dummy_holding_cell_add_htlc.clone();
88778884
if let HTLCUpdateAwaitingACK::AddHTLC {
88788885
ref mut blinding_point, ref mut skimmed_fee_msat, ..
@@ -8881,6 +8888,8 @@ mod tests {
88818888
*skimmed_fee_msat = Some(42);
88828889
} else { panic!() }
88838890
holding_cell_htlc_updates.push(dummy_add);
8891+
} else {
8892+
holding_cell_htlc_updates.push(dummy_holding_cell_malformed_htlc.clone());
88848893
}
88858894
}
88868895
chan.context.holding_cell_htlc_updates = holding_cell_htlc_updates.clone();
@@ -8892,6 +8901,17 @@ mod tests {
88928901
let features = channelmanager::provided_channel_type_features(&config);
88938902
let decoded_chan = Channel::read(&mut reader, (&&keys_provider, &&keys_provider, 0, &features)).unwrap();
88948903
assert_eq!(decoded_chan.context.pending_outbound_htlcs, pending_outbound_htlcs);
8904+
8905+
// As part of maintaining support for downgrades, malformed holding cell HTLCs are serialized in
8906+
// `Channel` TLVs and thus will be shuffled to the end of `holding_cell_htlc_updates` on read.
8907+
holding_cell_htlc_updates.sort_by(|a, b| {
8908+
let a_is_malformed = if let HTLCUpdateAwaitingACK::FailMalformedHTLC { .. } = a { true } else { false };
8909+
let b_is_malformed = if let HTLCUpdateAwaitingACK::FailMalformedHTLC { .. } = b { true } else { false };
8910+
if a_is_malformed && !b_is_malformed { return core::cmp::Ordering::Greater }
8911+
else if !a_is_malformed && b_is_malformed { return core::cmp::Ordering::Less }
8912+
core::cmp::Ordering::Equal
8913+
});
8914+
88958915
assert_eq!(decoded_chan.context.holding_cell_htlc_updates, holding_cell_htlc_updates);
88968916
}
88978917

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

1234912418
#[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)