Repository: lightningdevkit/rust-lightning
Pull request #3650: Attributable failures prefactor
Author: login: joostjager
Labels: name: weekly goal
description: Someone wants to land this this week
Created: 2025-03-06T14:59:16Z | Merged: 2025-03-11T15:06:36Z

Preparatory non-functional commits extracted from #3611 


Diff:
diff --git a/lightning/src/ln/channel.rs b/lightning/src/ln/channel.rs
index a09b3bfa77a..8625eb6914a 100644
--- a/lightning/src/ln/channel.rs
+++ b/lightning/src/ln/channel.rs
@@ -35,7 +35,7 @@ use crate::ln::interactivetxs::{
 	TX_COMMON_FIELDS_WEIGHT,
 };
 use crate::ln::msgs;
-use crate::ln::msgs::{ClosingSigned, ClosingSignedFeeRange, DecodeError};
+use crate::ln::msgs::{ClosingSigned, ClosingSignedFeeRange, DecodeError, OnionErrorPacket};
 use crate::ln::script::{self, ShutdownScript};
 use crate::ln::channel_state::{ChannelShutdownState, CounterpartyForwardingInfo, InboundHTLCDetails, InboundHTLCStateDetails, OutboundHTLCDetails, OutboundHTLCStateDetails};
 use crate::ln::channelmanager::{self, OpenChannelMessage, PendingHTLCStatus, HTLCSource, SentHTLCId, HTLCFailureMsg, PendingHTLCInfo, RAACommitmentOrder, PaymentClaimDetails, BREAKDOWN_TIMEOUT, MIN_CLTV_EXPIRY_DELTA, MAX_LOCAL_BREAKDOWN_TIMEOUT};
@@ -50,7 +50,7 @@ use crate::ln::chan_utils::{
 #[cfg(splicing)]
 use crate::ln::chan_utils::FUNDING_TRANSACTION_WITNESS_WEIGHT;
 use crate::ln::chan_utils;
-use crate::ln::onion_utils::HTLCFailReason;
+use crate::ln::onion_utils::{HTLCFailReason};
 use crate::chain::BestBlock;
 use crate::chain::chaininterface::{FeeEstimator, ConfirmationTarget, LowerBoundedFeeEstimator, fee_for_weight};
 use crate::chain::channelmonitor::{ChannelMonitor, ChannelMonitorUpdate, ChannelMonitorUpdateStep, LATENCY_GRACE_PERIOD_BLOCKS};
@@ -4933,7 +4933,7 @@ trait FailHTLCContents {
 impl FailHTLCContents for msgs::OnionErrorPacket {
 	type Message = msgs::UpdateFailHTLC;
 	fn to_message(self, htlc_id: u64, channel_id: ChannelId) -> Self::Message {
-		msgs::UpdateFailHTLC { htlc_id, channel_id, reason: self }
+		msgs::UpdateFailHTLC { htlc_id, channel_id, reason: self.data }
 	}
 	fn to_inbound_htlc_state(self) -> InboundHTLCState {
 		InboundHTLCState::LocalRemoved(InboundHTLCRemovalReason::FailRelay(self))
@@ -6136,7 +6136,7 @@ impl<SP: Deref> FundedChannel<SP> where
 										require_commitment = true;
 										match fail_msg {
 											HTLCFailureMsg::Relay(msg) => {
-												htlc.state = InboundHTLCState::LocalRemoved(InboundHTLCRemovalReason::FailRelay(msg.reason.clone()));
+												htlc.state = InboundHTLCState::LocalRemoved(InboundHTLCRemovalReason::FailRelay(msg.clone().into()));
 												update_fail_htlcs.push(msg)
 											},
 											HTLCFailureMsg::Malformed(msg) => {
@@ -6844,7 +6844,7 @@ impl<SP: Deref> FundedChannel<SP> where
 						update_fail_htlcs.push(msgs::UpdateFailHTLC {
 							channel_id: self.context.channel_id(),
 							htlc_id: htlc.htlc_id,
-							reason: err_packet.clone()
+							reason: err_packet.data.clone(),
 						});
 					},
 					&InboundHTLCRemovalReason::FailMalformed((ref sha256_of_onion, ref failure_code)) => {
@@ -10142,11 +10142,6 @@ fn get_initial_channel_type(config: &UserConfig, their_features: &InitFeatures)
 const SERIALIZATION_VERSION: u8 = 4;
 const MIN_SERIALIZATION_VERSION: u8 = 4;
 
-impl_writeable_tlv_based_enum_legacy!(InboundHTLCRemovalReason,;
-	(0, FailRelay),
-	(1, FailMalformed),
-	(2, Fulfill),
-);
 
 impl Writeable for ChannelUpdateStatus {
 	fn write<W: Writer>(&self, writer: &mut W) -> Result<(), io::Error> {
@@ -10276,7 +10271,20 @@ impl<SP: Deref> Writeable for FundedChannel<SP> where SP::Target: SignerProvider
 				},
 				&InboundHTLCState::LocalRemoved(ref removal_reason) => {
 					4u8.write(writer)?;
-					removal_reason.write(writer)?;
+					match removal_reason {
+						InboundHTLCRemovalReason::FailRelay(msgs::OnionErrorPacket { data }) => {
+							0u8.write(writer)?;
+							data.write(writer)?;
+						},
+						InboundHTLCRemovalReason::FailMalformed((hash, code)) => {
+							1u8.write(writer)?;
+							(hash, code).write(writer)?;
+						},
+						InboundHTLCRemovalReason::Fulfill(preimage) => {
+							2u8.write(writer)?;
+							preimage.write(writer)?;
+						},
+					}
 				},
 			}
 		}
@@ -10355,7 +10363,7 @@ impl<SP: Deref> Writeable for FundedChannel<SP> where SP::Target: SignerProvider
 				&HTLCUpdateAwaitingACK::FailHTLC { ref htlc_id, ref err_packet } => {
 					2u8.write(writer)?;
 					htlc_id.write(writer)?;
-					err_packet.write(writer)?;
+					err_packet.data.write(writer)?;
 				}
 				&HTLCUpdateAwaitingACK::FailMalformedHTLC {
 					htlc_id, failure_code, sha256_of_onion
@@ -10364,10 +10372,9 @@ impl<SP: Deref> Writeable for FundedChannel<SP> where SP::Target: SignerProvider
 					// `::FailHTLC` variant and write the real malformed error as an optional TLV.
 					malformed_htlcs.push((htlc_id, failure_code, sha256_of_onion));
 
-					let dummy_err_packet = msgs::OnionErrorPacket { data: Vec::new() };
 					2u8.write(writer)?;
 					htlc_id.write(writer)?;
-					dummy_err_packet.write(writer)?;
+					Vec::<u8>::new().write(writer)?;
 				}
 			}
 		}
@@ -10613,7 +10620,17 @@ impl<'a, 'b, 'c, ES: Deref, SP: Deref> ReadableArgs<(&'a ES, &'b SP, &'c Channel
 						InboundHTLCState::AwaitingAnnouncedRemoteRevoke(resolution)
 					},
 					3 => InboundHTLCState::Committed,
-					4 => InboundHTLCState::LocalRemoved(Readable::read(reader)?),
+					4 => {
+						let reason = match <u8 as Readable>::read(reader)? {
+							0 => InboundHTLCRemovalReason::FailRelay(msgs::OnionErrorPacket {
+								data: Readable::read(reader)?,
+							}),
+							1 => InboundHTLCRemovalReason::FailMalformed(Readable::read(reader)?),
+							2 => InboundHTLCRemovalReason::Fulfill(Readable::read(reader)?),
+							_ => return Err(DecodeError::InvalidValue),
+						};
+						InboundHTLCState::LocalRemoved(reason)
+					},
 					_ => return Err(DecodeError::InvalidValue),
 				},
 			});
@@ -10669,7 +10686,9 @@ impl<'a, 'b, 'c, ES: Deref, SP: Deref> ReadableArgs<(&'a ES, &'b SP, &'c Channel
 				},
 				2 => HTLCUpdateAwaitingACK::FailHTLC {
 					htlc_id: Readable::read(reader)?,
-					err_packet: Readable::read(reader)?,
+					err_packet: OnionErrorPacket {
+						data: Readable::read(reader)?,
+					},
 				},
 				_ => return Err(DecodeError::InvalidValue),
 			});
diff --git a/lightning/src/ln/channelmanager.rs b/lightning/src/ln/channelmanager.rs
index c69e6c6b02b..4574b2e7f8a 100644
--- a/lightning/src/ln/channelmanager.rs
+++ b/lightning/src/ln/channelmanager.rs
@@ -4470,11 +4470,12 @@ where
 		} else {
 			(err_code, &res.0[..])
 		};
+		let failure = HTLCFailReason::reason(err_code, err_data.to_vec())
+		.get_encrypted_failure_packet(shared_secret, &None);
 		HTLCFailureMsg::Relay(msgs::UpdateFailHTLC {
 			channel_id: msg.channel_id,
 			htlc_id: msg.htlc_id,
-			reason: HTLCFailReason::reason(err_code, err_data.to_vec())
-				.get_encrypted_failure_packet(shared_secret, &None),
+			reason: failure.data,
 		})
 	}
 
@@ -4498,11 +4499,12 @@ where
 							}
 						))
 					}
+					let failure = HTLCFailReason::reason($err_code, $data.to_vec())
+						.get_encrypted_failure_packet(&shared_secret, &None);
 					return PendingHTLCStatus::Fail(HTLCFailureMsg::Relay(msgs::UpdateFailHTLC {
 						channel_id: msg.channel_id,
 						htlc_id: msg.htlc_id,
-						reason: HTLCFailReason::reason($err_code, $data.to_vec())
-							.get_encrypted_failure_packet(&shared_secret, &None),
+						reason: failure.data,
 					}));
 				}
 			}
@@ -5881,7 +5883,7 @@ where
 				let failure = match htlc_fail {
 					HTLCFailureMsg::Relay(fail_htlc) => HTLCForwardInfo::FailHTLC {
 						htlc_id: fail_htlc.htlc_id,
-						err_packet: fail_htlc.reason,
+						err_packet: fail_htlc.into(),
 					},
 					HTLCFailureMsg::Malformed(fail_malformed_htlc) => HTLCForwardInfo::FailMalformedHTLC {
 						htlc_id: fail_malformed_htlc.htlc_id,
@@ -13224,7 +13226,7 @@ impl Writeable for HTLCForwardInfo {
 				FAIL_HTLC_VARIANT_ID.write(w)?;
 				write_tlv_fields!(w, {
 					(0, htlc_id, required),
-					(2, err_packet, required),
+					(2, err_packet.data, required),
 				});
 			},
 			Self::FailMalformedHTLC { htlc_id, failure_code, sha256_of_onion } => {
@@ -13232,11 +13234,10 @@ impl Writeable for HTLCForwardInfo {
 				// packet so older versions have something to fail back with, but serialize the real data as
 				// optional TLVs for the benefit of newer versions.
 				FAIL_HTLC_VARIANT_ID.write(w)?;
-				let dummy_err_packet = msgs::OnionErrorPacket { data: Vec::new() };
 				write_tlv_fields!(w, {
 					(0, htlc_id, required),
 					(1, failure_code, required),
-					(2, dummy_err_packet, required),
+					(2, Vec::<u8>::new(), required),
 					(3, sha256_of_onion, required),
 				});
 			},
@@ -13266,7 +13267,9 @@ impl Readable for HTLCForwardInfo {
 				} else {
 					Self::FailHTLC {
 						htlc_id: _init_tlv_based_struct_field!(htlc_id, required),
-						err_packet: _init_tlv_based_struct_field!(err_packet, required),
+						err_packet: crate::ln::msgs::OnionErrorPacket {
+							data: _init_tlv_based_struct_field!(err_packet, required),
+						},
 					}
 				}
 			},
@@ -16273,7 +16276,7 @@ mod tests {
 		let mut nodes = create_network(1, &node_cfg, &chanmgrs);
 
 		let dummy_failed_htlc = |htlc_id| {
-			HTLCForwardInfo::FailHTLC { htlc_id, err_packet: msgs::OnionErrorPacket { data: vec![42] }, }
+			HTLCForwardInfo::FailHTLC { htlc_id, err_packet: msgs::OnionErrorPacket { data: vec![42] } }
 		};
 		let dummy_malformed_htlc = |htlc_id| {
 			HTLCForwardInfo::FailMalformedHTLC { htlc_id, failure_code: 0x4000, sha256_of_onion: [0; 32] }
diff --git a/lightning/src/ln/functional_tests.rs b/lightning/src/ln/functional_tests.rs
index d46fc721c13..a65646e9b61 100644
--- a/lightning/src/ln/functional_tests.rs
+++ b/lightning/src/ln/functional_tests.rs
@@ -7057,7 +7057,7 @@ pub fn test_update_fulfill_htlc_bolt2_update_fail_htlc_before_commitment() {
 	let update_msg = msgs::UpdateFailHTLC{
 		channel_id: chan.2,
 		htlc_id: 0,
-		reason: msgs::OnionErrorPacket { data: Vec::new()},
+		reason: Vec::new(),
 	};
 
 	nodes[0].node.handle_update_fail_htlc(nodes[1].node.get_our_node_id(), &update_msg);
diff --git a/lightning/src/ln/msgs.rs b/lightning/src/ln/msgs.rs
index 85cbd3b8b1b..4d2102171eb 100644
--- a/lightning/src/ln/msgs.rs
+++ b/lightning/src/ln/msgs.rs
@@ -766,7 +766,7 @@ pub struct UpdateFailHTLC {
 	pub channel_id: ChannelId,
 	/// The HTLC ID
 	pub htlc_id: u64,
-	pub(crate) reason: OnionErrorPacket,
+	pub(crate) reason: Vec<u8>,
 }
 
 /// An [`update_fail_malformed_htlc`] message to be sent to or received from a peer.
@@ -2355,6 +2355,14 @@ pub(crate) struct OnionErrorPacket {
 	pub(crate) data: Vec<u8>,
 }
 
+impl From<UpdateFailHTLC> for OnionErrorPacket {
+	fn from(msg: UpdateFailHTLC) -> Self {
+		OnionErrorPacket {
+			data: msg.reason,
+		}
+	}
+}
+
 impl fmt::Display for DecodeError {
 	fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
 		match *self {
@@ -3001,13 +3009,6 @@ impl_writeable_msg!(PeerStorageRetrieval, {
 	data
 }, {});
 
-// Note that this is written as a part of ChannelManager objects, and thus cannot change its
-// serialization format in a way which assumes we know the total serialized length/message end
-// position.
-impl_writeable!(OnionErrorPacket, {
-	data
-});
-
 // Note that this is written as a part of ChannelManager objects, and thus cannot change its
 // serialization format in a way which assumes we know the total serialized length/message end
 // position.
@@ -3933,7 +3934,7 @@ mod tests {
 	use crate::ln::types::ChannelId;
 	use crate::types::payment::{PaymentPreimage, PaymentHash, PaymentSecret};
 	use crate::types::features::{ChannelFeatures, ChannelTypeFeatures, InitFeatures, NodeFeatures};
-	use crate::ln::msgs::{self, FinalOnionHopData, OnionErrorPacket, CommonOpenChannelFields, CommonAcceptChannelFields, OutboundTrampolinePayload, TrampolineOnionPacket, InboundOnionForwardPayload, InboundOnionReceivePayload};
+	use crate::ln::msgs::{self, FinalOnionHopData, CommonOpenChannelFields, CommonAcceptChannelFields, OutboundTrampolinePayload, TrampolineOnionPacket, InboundOnionForwardPayload, InboundOnionReceivePayload};
 	use crate::ln::msgs::SocketAddress;
 	use crate::routing::gossip::{NodeAlias, NodeId};
 	use crate::util::ser::{BigSize, FixedLengthReader, Hostname, LengthReadable, Readable, ReadableArgs, TransactionU16LenLimited, Writeable};
@@ -4932,13 +4933,10 @@ mod tests {
 
 	#[test]
 	fn encoding_update_fail_htlc() {
-		let reason = OnionErrorPacket {
-			data: [1; 32].to_vec(),
-		};
 		let update_fail_htlc = msgs::UpdateFailHTLC {
 			channel_id: ChannelId::from_bytes([2; 32]),
 			htlc_id: 2316138423780173,
-			reason
+			reason: [1; 32].to_vec()
 		};
 		let encoded_value = update_fail_htlc.encode();
 		let target_value = <Vec<u8>>::from_hex("020202020202020202020202020202020202020202020202020202020202020200083a840000034d00200101010101010101010101010101010101010101010101010101010101010101").unwrap();
diff --git a/lightning/src/ln/onion_payment.rs b/lightning/src/ln/onion_payment.rs
index c3594a9b0d1..46661df6807 100644
--- a/lightning/src/ln/onion_payment.rs
+++ b/lightning/src/ln/onion_payment.rs
@@ -407,7 +407,7 @@ where
 	).map_err(|e| {
 		let (err_code, err_data) = match e {
 			HTLCFailureMsg::Malformed(m) => (m.failure_code, Vec::new()),
-			HTLCFailureMsg::Relay(r) => (0x4000 | 22, r.reason.data),
+			HTLCFailureMsg::Relay(r) => (0x4000 | 22, r.reason),
 		};
 		let msg = "Failed to decode update add htlc onion";
 		InboundHTLCErr { msg, err_code, err_data }
@@ -512,11 +512,12 @@ where
 		}
 
 		log_info!(logger, "Failed to accept/forward incoming HTLC: {}", message);
+		let failure = HTLCFailReason::reason(err_code, data.to_vec())
+			.get_encrypted_failure_packet(&shared_secret, &trampoline_shared_secret);
 		return Err(HTLCFailureMsg::Relay(msgs::UpdateFailHTLC {
 			channel_id: msg.channel_id,
 			htlc_id: msg.htlc_id,
-			reason: HTLCFailReason::reason(err_code, data.to_vec())
-				.get_encrypted_failure_packet(&shared_secret, &trampoline_shared_secret),
+			reason: failure.data,
 		}));
 	};
 
diff --git a/lightning/src/ln/onion_route_tests.rs b/lightning/src/ln/onion_route_tests.rs
index f23cfaacdb0..127d4a45588 100644
--- a/lightning/src/ln/onion_route_tests.rs
+++ b/lightning/src/ln/onion_route_tests.rs
@@ -28,7 +28,7 @@ use crate::ln::msgs::{
 	OutboundOnionPayload, OutboundTrampolinePayload, MessageSendEvent,
 };
 use crate::ln::wire::Encode;
-use crate::util::ser::{Writeable, Writer, BigSize};
+use crate::util::ser::{BigSize, Writeable, Writer};
 use crate::util::test_utils;
 use crate::util::config::{UserConfig, ChannelConfig, MaxDustHTLCExposure};
 use crate::util::errors::APIError;
@@ -49,6 +49,8 @@ use crate::blinded_path::BlindedHop;
 use crate::ln::functional_test_utils::*;
 use crate::ln::onion_utils::{construct_trampoline_onion_keys, construct_trampoline_onion_packet};
 
+use super::msgs::OnionErrorPacket;
+
 fn run_onion_failure_test<F1,F2>(_name: &str, test_case: u8, nodes: &Vec<Node>, route: &Route, payment_hash: &PaymentHash, payment_secret: &PaymentSecret, callback_msg: F1, callback_node: F2, expected_retryable: bool, expected_error_code: Option<u16>, expected_channel_update: Option<NetworkUpdate>, expected_short_channel_id: Option<u64>, expected_htlc_destination: Option<HTLCDestination>)
 	where F1: for <'a> FnMut(&'a mut msgs::UpdateAddHTLC),
 				F2: FnMut(),
@@ -409,7 +411,8 @@ fn test_onion_failure() {
 		// and tamper returning error message
 		let session_priv = SecretKey::from_slice(&[3; 32]).unwrap();
 		let onion_keys = onion_utils::construct_onion_keys(&Secp256k1::new(), &route.paths[0], &session_priv).unwrap();
-		msg.reason = onion_utils::build_first_hop_failure_packet(onion_keys[0].shared_secret.as_ref(), NODE|2, &[0;0]);
+		let failure = onion_utils::build_failure_packet(onion_keys[0].shared_secret.as_ref(), NODE|2, &[0;0]);
+		msg.reason = failure.data;
 	}, ||{}, true, Some(NODE|2), Some(NetworkUpdate::NodeFailure{node_id: route.paths[0].hops[0].pubkey, is_permanent: false}), Some(route.paths[0].hops[0].short_channel_id), Some(next_hop_failure.clone()));
 
 	// final node failure
@@ -417,7 +420,8 @@ fn test_onion_failure() {
 		// and tamper returning error message
 		let session_priv = SecretKey::from_slice(&[3; 32]).unwrap();
 		let onion_keys = onion_utils::construct_onion_keys(&Secp256k1::new(), &route.paths[0], &session_priv).unwrap();
-		msg.reason = onion_utils::build_first_hop_failure_packet(onion_keys[1].shared_secret.as_ref(), NODE|2, &[0;0]);
+		let failure = onion_utils::build_failure_packet(onion_keys[1].shared_secret.as_ref(), NODE|2, &[0;0]);
+		msg.reason = failure.data;
 	}, ||{
 		nodes[2].node.fail_htlc_backwards(&payment_hash);
 	}, true, Some(NODE|2), Some(NetworkUpdate::NodeFailure{node_id: route.paths[0].hops[1].pubkey, is_permanent: false}), Some(route.paths[0].hops[1].short_channel_id), None);
@@ -429,14 +433,16 @@ fn test_onion_failure() {
 	}, |msg| {
 		let session_priv = SecretKey::from_slice(&[3; 32]).unwrap();
 		let onion_keys = onion_utils::construct_onion_keys(&Secp256k1::new(), &route.paths[0], &session_priv).unwrap();
-		msg.reason = onion_utils::build_first_hop_failure_packet(onion_keys[0].shared_secret.as_ref(), PERM|NODE|2, &[0;0]);
+		let failure = onion_utils::build_failure_packet(onion_keys[0].shared_secret.as_ref(), PERM|NODE|2, &[0;0]);
+		msg.reason = failure.data;
 	}, ||{}, true, Some(PERM|NODE|2), Some(NetworkUpdate::NodeFailure{node_id: route.paths[0].hops[0].pubkey, is_permanent: true}), Some(route.paths[0].hops[0].short_channel_id), Some(next_hop_failure.clone()));
 
 	// final node failure
 	run_onion_failure_test_with_fail_intercept("permanent_node_failure", 200, &nodes, &route, &payment_hash, &payment_secret, |_msg| {}, |msg| {
 		let session_priv = SecretKey::from_slice(&[3; 32]).unwrap();
 		let onion_keys = onion_utils::construct_onion_keys(&Secp256k1::new(), &route.paths[0], &session_priv).unwrap();
-		msg.reason = onion_utils::build_first_hop_failure_packet(onion_keys[1].shared_secret.as_ref(), PERM|NODE|2, &[0;0]);
+		let failure = onion_utils::build_failure_packet(onion_keys[1].shared_secret.as_ref(), PERM|NODE|2, &[0;0]);
+		msg.reason = failure.data;
 	}, ||{
 		nodes[2].node.fail_htlc_backwards(&payment_hash);
 	}, false, Some(PERM|NODE|2), Some(NetworkUpdate::NodeFailure{node_id: route.paths[0].hops[1].pubkey, is_permanent: true}), Some(route.paths[0].hops[1].short_channel_id), None);
@@ -448,7 +454,8 @@ fn test_onion_failure() {
 	}, |msg| {
 		let session_priv = SecretKey::from_slice(&[3; 32]).unwrap();
 		let onion_keys = onion_utils::construct_onion_keys(&Secp256k1::new(), &route.paths[0], &session_priv).unwrap();
-		msg.reason = onion_utils::build_first_hop_failure_packet(onion_keys[0].shared_secret.as_ref(), PERM|NODE|3, &[0;0]);
+		let failure = onion_utils::build_failure_packet(onion_keys[0].shared_secret.as_ref(), PERM|NODE|3, &[0;0]);
+		msg.reason = failure.data;
 	}, ||{
 		nodes[2].node.fail_htlc_backwards(&payment_hash);
 	}, true, Some(PERM|NODE|3), Some(NetworkUpdate::NodeFailure{node_id: route.paths[0].hops[0].pubkey, is_permanent: true}), Some(route.paths[0].hops[0].short_channel_id), Some(next_hop_failure.clone()));
@@ -457,7 +464,8 @@ fn test_onion_failure() {
 	run_onion_failure_test_with_fail_intercept("required_node_feature_missing", 200, &nodes, &route, &payment_hash, &payment_secret, |_msg| {}, |msg| {
 		let session_priv = SecretKey::from_slice(&[3; 32]).unwrap();
 		let onion_keys = onion_utils::construct_onion_keys(&Secp256k1::new(), &route.paths[0], &session_priv).unwrap();
-		msg.reason = onion_utils::build_first_hop_failure_packet(onion_keys[1].shared_secret.as_ref(), PERM|NODE|3, &[0;0]);
+		let failure = onion_utils::build_failure_packet(onion_keys[1].shared_secret.as_ref(), PERM|NODE|3, &[0;0]);
+		msg.reason = failure.data;
 	}, ||{
 		nodes[2].node.fail_htlc_backwards(&payment_hash);
 	}, false, Some(PERM|NODE|3), Some(NetworkUpdate::NodeFailure{node_id: route.paths[0].hops[1].pubkey, is_permanent: true}), Some(route.paths[0].hops[1].short_channel_id), None);
@@ -487,7 +495,8 @@ fn test_onion_failure() {
 	}, |msg| {
 		let session_priv = SecretKey::from_slice(&[3; 32]).unwrap();
 		let onion_keys = onion_utils::construct_onion_keys(&Secp256k1::new(), &route.paths[0], &session_priv).unwrap();
-		msg.reason = onion_utils::build_first_hop_failure_packet(onion_keys[0].shared_secret.as_ref(), UPDATE|7, &err_data);
+		let failure = onion_utils::build_failure_packet(onion_keys[0].shared_secret.as_ref(), UPDATE|7, &err_data);
+		msg.reason = failure.data;
 	}, ||{}, true, Some(UPDATE|7),
 	Some(NetworkUpdate::ChannelFailure { short_channel_id, is_permanent: false }),
 	Some(short_channel_id), Some(next_hop_failure.clone()));
@@ -499,7 +508,8 @@ fn test_onion_failure() {
 	}, |msg| {
 		let session_priv = SecretKey::from_slice(&[3; 32]).unwrap();
 		let onion_keys = onion_utils::construct_onion_keys(&Secp256k1::new(), &route.paths[0], &session_priv).unwrap();
-		msg.reason = onion_utils::build_first_hop_failure_packet(onion_keys[0].shared_secret.as_ref(), UPDATE|7, &err_data_without_type);
+		let failure = onion_utils::build_failure_packet(onion_keys[0].shared_secret.as_ref(), UPDATE|7, &err_data_without_type);
+		msg.reason = failure.data;
 	}, ||{}, true, Some(UPDATE|7),
 	Some(NetworkUpdate::ChannelFailure { short_channel_id, is_permanent: false }),
 	Some(short_channel_id), Some(next_hop_failure.clone()));
@@ -510,7 +520,8 @@ fn test_onion_failure() {
 	}, |msg| {
 		let session_priv = SecretKey::from_slice(&[3; 32]).unwrap();
 		let onion_keys = onion_utils::construct_onion_keys(&Secp256k1::new(), &route.paths[0], &session_priv).unwrap();
-		msg.reason = onion_utils::build_first_hop_failure_packet(onion_keys[0].shared_secret.as_ref(), PERM|8, &[0;0]);
+		let failure = onion_utils::build_failure_packet(onion_keys[0].shared_secret.as_ref(), PERM|8, &[0;0]);
+		msg.reason = failure.data;
 		// short_channel_id from the processing node
 	}, ||{}, true, Some(PERM|8), Some(NetworkUpdate::ChannelFailure{short_channel_id, is_permanent: true}), Some(short_channel_id), Some(next_hop_failure.clone()));
 
@@ -520,7 +531,8 @@ fn test_onion_failure() {
 	}, |msg| {
 		let session_priv = SecretKey::from_slice(&[3; 32]).unwrap();
 		let onion_keys = onion_utils::construct_onion_keys(&Secp256k1::new(), &route.paths[0], &session_priv).unwrap();
-		msg.reason = onion_utils::build_first_hop_failure_packet(onion_keys[0].shared_secret.as_ref(), PERM|9, &[0;0]);
+		let failure = onion_utils::build_failure_packet(onion_keys[0].shared_secret.as_ref(), PERM|9, &[0;0]);
+		msg.reason = failure.data;
 		// short_channel_id from the processing node
 	}, ||{}, true, Some(PERM|9), Some(NetworkUpdate::ChannelFailure{short_channel_id, is_permanent: true}), Some(short_channel_id), Some(next_hop_failure.clone()));
 
@@ -652,7 +664,8 @@ fn test_onion_failure() {
 		// Tamper returning error message
 		let session_priv = SecretKey::from_slice(&[3; 32]).unwrap();
 		let onion_keys = onion_utils::construct_onion_keys(&Secp256k1::new(), &route.paths[0], &session_priv).unwrap();
-		msg.reason = onion_utils::build_first_hop_failure_packet(onion_keys[1].shared_secret.as_ref(), 23, &[0;0]);
+		let failure = onion_utils::build_failure_packet(onion_keys[1].shared_secret.as_ref(), 23, &[0;0]);
+		msg.reason = failure.data;
 	}, ||{
 		nodes[2].node.fail_htlc_backwards(&payment_hash);
 	}, true, Some(23), None, None, None);
@@ -670,8 +683,12 @@ fn test_onion_failure() {
 			let mut hmac = HmacEngine::<Sha256>::new(&um);
 			hmac.input(&decoded_err_packet.encode()[32..]);
 			decoded_err_packet.hmac = Hmac::from_engine(hmac).to_byte_array();
-			msg.reason = onion_utils::encrypt_failure_packet(
-				&onion_keys[1].shared_secret.as_ref(), &decoded_err_packet.encode()[..])
+			let mut onion_error = OnionErrorPacket {
+				data: decoded_err_packet.encode(),
+			};
+			onion_utils::test_crypt_failure_packet(
+				&onion_keys[1].shared_secret.as_ref(), &mut onion_error);
+			msg.reason = onion_error.data;
 		}, || nodes[2].node.fail_htlc_backwards(&payment_hash), false, None,
 		Some(NetworkUpdate::NodeFailure { node_id: route.paths[0].hops[1].pubkey, is_permanent: true }),
 		Some(channels[1].0.contents.short_channel_id), None);
@@ -693,8 +710,12 @@ fn test_onion_failure() {
 			let mut hmac = HmacEngine::<Sha256>::new(&um);
 			hmac.input(&decoded_err_packet.encode()[32..]);
 			decoded_err_packet.hmac = Hmac::from_engine(hmac).to_byte_array();
-			msg.reason = onion_utils::encrypt_failure_packet(
-				&onion_keys[0].shared_secret.as_ref(), &decoded_err_packet.encode()[..])
+			let mut onion_error = OnionErrorPacket{
+				data: decoded_err_packet.encode(),
+			};
+			onion_utils::test_crypt_failure_packet(
+				&onion_keys[0].shared_secret.as_ref(), &mut onion_error);
+			msg.reason = onion_error.data;
 		}, || {}, true, Some(0x1000|7),
 		Some(NetworkUpdate::ChannelFailure {
 			short_channel_id: channels[1].0.contents.short_channel_id,
@@ -717,8 +738,12 @@ fn test_onion_failure() {
 			let mut hmac = HmacEngine::<Sha256>::new(&um);
 			hmac.input(&decoded_err_packet.encode()[32..]);
 			decoded_err_packet.hmac = Hmac::from_engine(hmac).to_byte_array();
-			msg.reason = onion_utils::encrypt_failure_packet(
-				&onion_keys[1].shared_secret.as_ref(), &decoded_err_packet.encode()[..])
+			let mut onion_error = OnionErrorPacket{
+				data: decoded_err_packet.encode(),
+			};
+			onion_utils::test_crypt_failure_packet(
+				&onion_keys[1].shared_secret.as_ref(), &mut onion_error);
+			msg.reason = onion_error.data;
 		}, || nodes[2].node.fail_htlc_backwards(&payment_hash), true, Some(0x1000|7),
 		Some(NetworkUpdate::ChannelFailure {
 			short_channel_id: channels[1].0.contents.short_channel_id,
diff --git a/lightning/src/ln/onion_utils.rs b/lightning/src/ln/onion_utils.rs
index bc4121cfd3d..41701b1ade3 100644
--- a/lightning/src/ln/onion_utils.rs
+++ b/lightning/src/ln/onion_utils.rs
@@ -7,6 +7,7 @@
 // You may not use this file except in accordance with one or both of these
 // licenses.
 
+use super::msgs::OnionErrorPacket;
 use crate::blinded_path::BlindedHop;
 use crate::crypto::chacha20::ChaCha20;
 use crate::crypto::streams::ChaChaReader;
@@ -869,23 +870,25 @@ fn construct_onion_packet_with_init_noise<HD: Writeable, P: Packet>(
 	Ok(P::new(onion_keys.first().unwrap().ephemeral_pubkey, packet_data, hmac_res))
 }
 
-/// Encrypts a failure packet. raw_packet can either be a
-/// msgs::DecodedOnionErrorPacket.encode() result or a msgs::OnionErrorPacket.data element.
-pub(super) fn encrypt_failure_packet(
-	shared_secret: &[u8], raw_packet: &[u8],
-) -> msgs::OnionErrorPacket {
+/// Encrypts/decrypts a failure packet.
+fn crypt_failure_packet(shared_secret: &[u8], packet: &mut OnionErrorPacket) {
 	let ammag = gen_ammag_from_shared_secret(&shared_secret);
+	process_chacha(&ammag, &mut packet.data);
+}
 
-	let mut packet_crypted = Vec::with_capacity(raw_packet.len());
-	packet_crypted.resize(raw_packet.len(), 0);
-	let mut chacha = ChaCha20::new(&ammag, &[0u8; 8]);
-	chacha.process(&raw_packet, &mut packet_crypted[..]);
-	msgs::OnionErrorPacket { data: packet_crypted }
+#[cfg(test)]
+pub(super) fn test_crypt_failure_packet(shared_secret: &[u8], packet: &mut OnionErrorPacket) {
+	crypt_failure_packet(shared_secret, packet)
 }
 
-pub(super) fn build_failure_packet(
+fn process_chacha(key: &[u8; 32], packet: &mut [u8]) {
+	let mut chacha = ChaCha20::new(key, &[0u8; 8]);
+	chacha.process_in_place(packet);
+}
+
+fn build_unencrypted_failure_packet(
 	shared_secret: &[u8], failure_type: u16, failure_data: &[u8],
-) -> msgs::DecodedOnionErrorPacket {
+) -> OnionErrorPacket {
 	assert_eq!(shared_secret.len(), 32);
 	assert!(failure_data.len() <= 256 - 2);
 
@@ -909,15 +912,18 @@ pub(super) fn build_failure_packet(
 	hmac.input(&packet.encode()[32..]);
 	packet.hmac = Hmac::from_engine(hmac).to_byte_array();
 
-	packet
+	OnionErrorPacket { data: packet.encode() }
 }
 
-#[cfg(test)]
-pub(super) fn build_first_hop_failure_packet(
+pub(super) fn build_failure_packet(
 	shared_secret: &[u8], failure_type: u16, failure_data: &[u8],
-) -> msgs::OnionErrorPacket {
-	let failure_packet = build_failure_packet(shared_secret, failure_type, failure_data);
-	encrypt_failure_packet(shared_secret, &failure_packet.encode()[..])
+) -> OnionErrorPacket {
+	let mut onion_error_packet =
+		build_unencrypted_failure_packet(shared_secret, failure_type, failure_data);
+
+	crypt_failure_packet(shared_secret, &mut onion_error_packet);
+
+	onion_error_packet
 }
 
 pub(crate) struct DecodedOnionFailure {
@@ -931,18 +937,12 @@ pub(crate) struct DecodedOnionFailure {
 	pub(crate) onion_error_data: Option<Vec<u8>>,
 }
 
-/// Decrypt the error packet in-place.
-fn decrypt_onion_error_packet(packet: &mut Vec<u8>, shared_secret: SharedSecret) {
-	let ammag = gen_ammag_from_shared_secret(shared_secret.as_ref());
-	let mut chacha = ChaCha20::new(&ammag, &[0u8; 8]);
-	chacha.process_in_place(packet);
-}
-
 /// Process failure we got back from upstream on a payment we sent (implying htlc_source is an
 /// OutboundRoute).
 #[inline]
 pub(super) fn process_onion_failure<T: secp256k1::Signing, L: Deref>(
-	secp_ctx: &Secp256k1<T>, logger: &L, htlc_source: &HTLCSource, mut encrypted_packet: Vec<u8>,
+	secp_ctx: &Secp256k1<T>, logger: &L, htlc_source: &HTLCSource,
+	mut encrypted_packet: OnionErrorPacket,
 ) -> DecodedOnionFailure
 where
 	L::Target: Logger,
@@ -975,7 +975,11 @@ where
 	const UPDATE: u16 = 0x1000;
 
 	// Handle packed channel/node updates for passing back for the route handler
-	let callback = |shared_secret, _, _, route_hop_opt: Option<&RouteHop>, route_hop_idx| {
+	let callback = |shared_secret: SharedSecret,
+	                _,
+	                _,
+	                route_hop_opt: Option<&RouteHop>,
+	                route_hop_idx| {
 		if res.is_some() {
 			return;
 		}
@@ -1017,9 +1021,9 @@ where
 					{
 						// Actually parse the onion error data in tests so we can check that blinded hops fail
 						// back correctly.
-						decrypt_onion_error_packet(&mut encrypted_packet, shared_secret);
+						crypt_failure_packet(shared_secret.as_ref(), &mut encrypted_packet);
 						let err_packet = msgs::DecodedOnionErrorPacket::read(&mut Cursor::new(
-							&encrypted_packet,
+							&encrypted_packet.data,
 						))
 						.unwrap();
 						error_code_ret = Some(u16::from_be_bytes(
@@ -1042,18 +1046,18 @@ where
 		let amt_to_forward = htlc_msat - route_hop.fee_msat;
 		htlc_msat = amt_to_forward;
 
-		decrypt_onion_error_packet(&mut encrypted_packet, shared_secret);
+		crypt_failure_packet(shared_secret.as_ref(), &mut encrypted_packet);
 
 		let um = gen_um_from_shared_secret(shared_secret.as_ref());
 		let mut hmac = HmacEngine::<Sha256>::new(&um);
-		hmac.input(&encrypted_packet[32..]);
+		hmac.input(&encrypted_packet.data[32..]);
 
-		if !fixed_time_eq(&Hmac::from_engine(hmac).to_byte_array(), &encrypted_packet[..32]) {
+		if !fixed_time_eq(&Hmac::from_engine(hmac).to_byte_array(), &encrypted_packet.data[..32]) {
 			return;
 		}
 
 		let err_packet =
-			match msgs::DecodedOnionErrorPacket::read(&mut Cursor::new(&encrypted_packet)) {
+			match msgs::DecodedOnionErrorPacket::read(&mut Cursor::new(&encrypted_packet.data)) {
 				Ok(p) => p,
 				Err(_) => {
 					log_warn!(logger, "Unreadable failure from {}", route_hop.pubkey);
@@ -1295,7 +1299,14 @@ impl Readable for HTLCFailReason {
 
 impl_writeable_tlv_based_enum!(HTLCFailReasonRepr,
 	(0, LightningError) => {
-		(0, err, required),
+		(0, data, (legacy, Vec<u8>, |us|
+			if let &HTLCFailReasonRepr::LightningError { err: msgs::OnionErrorPacket { ref data, .. } } = us {
+				Some(data)
+			} else {
+				None
+			})
+		),
+		(_unused, err, (static_value, msgs::OnionErrorPacket { data: data.ok_or(DecodeError::InvalidValue)? })),
 	},
 	(1, Reason) => {
 		(0, failure_code, required),
@@ -1352,7 +1363,9 @@ impl HTLCFailReason {
 	}
 
 	pub(super) fn from_msg(msg: &msgs::UpdateFailHTLC) -> Self {
-		Self(HTLCFailReasonRepr::LightningError { err: msg.reason.clone() })
+		Self(HTLCFailReasonRepr::LightningError {
+			err: OnionErrorPacket { data: msg.reason.clone() },
+		})
 	}
 
 	/// Encrypted a failure packet using a shared secret.
@@ -1366,27 +1379,22 @@ impl HTLCFailReason {
 		match self.0 {
 			HTLCFailReasonRepr::Reason { ref failure_code, ref data } => {
 				if let Some(secondary_shared_secret) = secondary_shared_secret {
-					let inner_packet =
-						build_failure_packet(secondary_shared_secret, *failure_code, &data[..])
-							.encode();
-					let encrypted_inner_packet =
-						encrypt_failure_packet(secondary_shared_secret, &inner_packet);
-					encrypt_failure_packet(
-						incoming_packet_shared_secret,
-						&encrypted_inner_packet.data[..],
-					)
+					let mut packet =
+						build_failure_packet(secondary_shared_secret, *failure_code, &data[..]);
+
+					crypt_failure_packet(incoming_packet_shared_secret, &mut packet);
+
+					packet
 				} else {
-					let packet = build_failure_packet(
-						incoming_packet_shared_secret,
-						*failure_code,
-						&data[..],
-					)
-					.encode();
-					encrypt_failure_packet(incoming_packet_shared_secret, &packet)
+					build_failure_packet(incoming_packet_shared_secret, *failure_code, &data[..])
 				}
 			},
 			HTLCFailReasonRepr::LightningError { ref err } => {
-				encrypt_failure_packet(incoming_packet_shared_secret, &err.data)
+				let mut err = err.clone();
+
+				crypt_failure_packet(incoming_packet_shared_secret, &mut err);
+
+				err
 			},
 		}
 	}
@@ -1399,7 +1407,7 @@ impl HTLCFailReason {
 	{
 		match self.0 {
 			HTLCFailReasonRepr::LightningError { ref err } => {
-				process_onion_failure(secp_ctx, logger, &htlc_source, err.data.clone())
+				process_onion_failure(secp_ctx, logger, &htlc_source, err.clone())
 			},
 			#[allow(unused)]
 			HTLCFailReasonRepr::Reason { ref failure_code, ref data, .. } => {
@@ -2287,45 +2295,33 @@ mod tests {
 		// Returning Errors test vectors from BOLT 4
 
 		let onion_keys = build_test_onion_keys();
-		let onion_error =
-			super::build_failure_packet(onion_keys[4].shared_secret.as_ref(), 0x2002, &[0; 0]);
-		let hex = "4c2fc8bc08510334b6833ad9c3e79cd1b52ae59dfe5c2a4b23ead50f09f7ee0b0002200200fe0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000";
-		assert_eq!(onion_error.encode(), <Vec<u8>>::from_hex(hex).unwrap());
-
-		let onion_packet_1 = super::encrypt_failure_packet(
+		let mut onion_error = super::build_unencrypted_failure_packet(
 			onion_keys[4].shared_secret.as_ref(),
-			&onion_error.encode()[..],
+			0x2002,
+			&[0; 0],
 		);
+		let hex = "4c2fc8bc08510334b6833ad9c3e79cd1b52ae59dfe5c2a4b23ead50f09f7ee0b0002200200fe0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000";
+		assert_eq!(onion_error.data, <Vec<u8>>::from_hex(hex).unwrap());
+
+		super::crypt_failure_packet(onion_keys[4].shared_secret.as_ref(), &mut onion_error);
 		let hex = "a5e6bd0c74cb347f10cce367f949098f2457d14c046fd8a22cb96efb30b0fdcda8cb9168b50f2fd45edd73c1b0c8b33002df376801ff58aaa94000bf8a86f92620f343baef38a580102395ae3abf9128d1047a0736ff9b83d456740ebbb4aeb3aa9737f18fb4afb4aa074fb26c4d702f42968888550a3bded8c05247e045b866baef0499f079fdaeef6538f31d44deafffdfd3afa2fb4ca9082b8f1c465371a9894dd8c243fb4847e004f5256b3e90e2edde4c9fb3082ddfe4d1e734cacd96ef0706bf63c9984e22dc98851bcccd1c3494351feb458c9c6af41c0044bea3c47552b1d992ae542b17a2d0bba1a096c78d169034ecb55b6e3a7263c26017f033031228833c1daefc0dedb8cf7c3e37c9c37ebfe42f3225c326e8bcfd338804c145b16e34e4";
-		assert_eq!(onion_packet_1.data, <Vec<u8>>::from_hex(hex).unwrap());
+		assert_eq!(onion_error.data, <Vec<u8>>::from_hex(hex).unwrap());
 
-		let onion_packet_2 = super::encrypt_failure_packet(
-			onion_keys[3].shared_secret.as_ref(),
-			&onion_packet_1.data[..],
-		);
+		super::crypt_failure_packet(onion_keys[3].shared_secret.as_ref(), &mut onion_error);
 		let hex = "c49a1ce81680f78f5f2000cda36268de34a3f0a0662f55b4e837c83a8773c22aa081bab1616a0011585323930fa5b9fae0c85770a2279ff59ec427ad1bbff9001c0cd1497004bd2a0f68b50704cf6d6a4bf3c8b6a0833399a24b3456961ba00736785112594f65b6b2d44d9f5ea4e49b5e1ec2af978cbe31c67114440ac51a62081df0ed46d4a3df295da0b0fe25c0115019f03f15ec86fabb4c852f83449e812f141a9395b3f70b766ebbd4ec2fae2b6955bd8f32684c15abfe8fd3a6261e52650e8807a92158d9f1463261a925e4bfba44bd20b166d532f0017185c3a6ac7957adefe45559e3072c8dc35abeba835a8cb01a71a15c736911126f27d46a36168ca5ef7dccd4e2886212602b181463e0dd30185c96348f9743a02aca8ec27c0b90dca270";
-		assert_eq!(onion_packet_2.data, <Vec<u8>>::from_hex(hex).unwrap());
+		assert_eq!(onion_error.data, <Vec<u8>>::from_hex(hex).unwrap());
 
-		let onion_packet_3 = super::encrypt_failure_packet(
-			onion_keys[2].shared_secret.as_ref(),
-			&onion_packet_2.data[..],
-		);
+		super::crypt_failure_packet(onion_keys[2].shared_secret.as_ref(), &mut onion_error);
 		let hex = "a5d3e8634cfe78b2307d87c6d90be6fe7855b4f2cc9b1dfb19e92e4b79103f61ff9ac25f412ddfb7466e74f81b3e545563cdd8f5524dae873de61d7bdfccd496af2584930d2b566b4f8d3881f8c043df92224f38cf094cfc09d92655989531524593ec6d6caec1863bdfaa79229b5020acc034cd6deeea1021c50586947b9b8e6faa83b81fbfa6133c0af5d6b07c017f7158fa94f0d206baf12dda6b68f785b773b360fd0497e16cc402d779c8d48d0fa6315536ef0660f3f4e1865f5b38ea49c7da4fd959de4e83ff3ab686f059a45c65ba2af4a6a79166aa0f496bf04d06987b6d2ea205bdb0d347718b9aeff5b61dfff344993a275b79717cd815b6ad4c0beb568c4ac9c36ff1c315ec1119a1993c4b61e6eaa0375e0aaf738ac691abd3263bf937e3";
-		assert_eq!(onion_packet_3.data, <Vec<u8>>::from_hex(hex).unwrap());
+		assert_eq!(onion_error.data, <Vec<u8>>::from_hex(hex).unwrap());
 
-		let onion_packet_4 = super::encrypt_failure_packet(
-			onion_keys[1].shared_secret.as_ref(),
-			&onion_packet_3.data[..],
-		);
+		super::crypt_failure_packet(onion_keys[1].shared_secret.as_ref(), &mut onion_error);
 		let hex = "aac3200c4968f56b21f53e5e374e3a2383ad2b1b6501bbcc45abc31e59b26881b7dfadbb56ec8dae8857add94e6702fb4c3a4de22e2e669e1ed926b04447fc73034bb730f4932acd62727b75348a648a1128744657ca6a4e713b9b646c3ca66cac02cdab44dd3439890ef3aaf61708714f7375349b8da541b2548d452d84de7084bb95b3ac2345201d624d31f4d52078aa0fa05a88b4e20202bd2b86ac5b52919ea305a8949de95e935eed0319cf3cf19ebea61d76ba92532497fcdc9411d06bcd4275094d0a4a3c5d3a945e43305a5a9256e333e1f64dbca5fcd4e03a39b9012d197506e06f29339dfee3331995b21615337ae060233d39befea925cc262873e0530408e6990f1cbd233a150ef7b004ff6166c70c68d9f8c853c1abca640b8660db2921";
-		assert_eq!(onion_packet_4.data, <Vec<u8>>::from_hex(hex).unwrap());
+		assert_eq!(onion_error.data, <Vec<u8>>::from_hex(hex).unwrap());
 
-		let onion_packet_5 = super::encrypt_failure_packet(
-			onion_keys[0].shared_secret.as_ref(),
-			&onion_packet_4.data[..],
-		);
+		super::crypt_failure_packet(onion_keys[0].shared_secret.as_ref(), &mut onion_error);
 		let hex = "9c5add3963fc7f6ed7f148623c84134b5647e1306419dbe2174e523fa9e2fbed3a06a19f899145610741c83ad40b7712aefaddec8c6baf7325d92ea4ca4d1df8bce517f7e54554608bf2bd8071a4f52a7a2f7ffbb1413edad81eeea5785aa9d990f2865dc23b4bc3c301a94eec4eabebca66be5cf638f693ec256aec514620cc28ee4a94bd9565bc4d4962b9d3641d4278fb319ed2b84de5b665f307a2db0f7fbb757366067d88c50f7e829138fde4f78d39b5b5802f1b92a8a820865af5cc79f9f30bc3f461c66af95d13e5e1f0381c184572a91dee1c849048a647a1158cf884064deddbf1b0b88dfe2f791428d0ba0f6fb2f04e14081f69165ae66d9297c118f0907705c9c4954a199bae0bb96fad763d690e7daa6cfda59ba7f2c8d11448b604d12d";
-		assert_eq!(onion_packet_5.data, <Vec<u8>>::from_hex(hex).unwrap());
+		assert_eq!(onion_error.data, <Vec<u8>>::from_hex(hex).unwrap());
 
 		let logger: Arc<TestLogger> = Arc::new(TestLogger::new());
 		let ctx_full = Secp256k1::new();
@@ -2339,7 +2335,7 @@ mod tests {
 
 		// Assert that the original failure can be retrieved and that all hmacs check out.
 		let decrypted_failure =
-			process_onion_failure(&ctx_full, &logger, &htlc_source, onion_packet_5.data);
+			process_onion_failure(&ctx_full, &logger, &htlc_source, onion_error);
 
 		assert_eq!(decrypted_failure.onion_error_code, Some(0x2002));
 	}
@@ -2348,10 +2344,11 @@ mod tests {
 	fn test_non_attributable_failure_packet_onion() {
 		// Create a failure packet with bogus data.
 		let packet = vec![1u8; 292];
+		let onion_error_packet = OnionErrorPacket { data: packet };
 
 		// In the current protocol, it is unfortunately not possible to identify the failure source.
 		let logger: TestLogger = TestLogger::new();
-		let decrypted_failure = test_failure_attribution(&logger, &packet);
+		let decrypted_failure = test_failure_attribution(&logger, onion_error_packet);
 		assert_eq!(decrypted_failure.short_channel_id, None);
 
 		logger.assert_log_contains(
@@ -2376,11 +2373,12 @@ mod tests {
 		let hmac = Hmac::from_engine(hmac).to_byte_array();
 		packet[..32].copy_from_slice(&hmac);
 
-		let packet = encrypt_failure_packet(shared_secret, &packet);
+		let mut onion_error_packet = OnionErrorPacket { data: packet.to_vec() };
+		crypt_failure_packet(shared_secret, &mut onion_error_packet);
 
 		// For the unreadable failure, it is still expected that the failing channel can be identified.
 		let logger: TestLogger = TestLogger::new();
-		let decrypted_failure = test_failure_attribution(&logger, &packet.data);
+		let decrypted_failure = test_failure_attribution(&logger, onion_error_packet);
 		assert_eq!(decrypted_failure.short_channel_id, Some(0));
 
 		logger.assert_log_contains("lightning::ln::onion_utils", "Unreadable failure", 1);
@@ -2401,10 +2399,11 @@ mod tests {
 		hmac.input(&packet.encode()[32..]);
 		packet.hmac = Hmac::from_engine(hmac).to_byte_array();
 
-		let packet = encrypt_failure_packet(shared_secret, &packet.encode()[..]);
+		let mut onion_error_packet = OnionErrorPacket { data: packet.encode() };
+		crypt_failure_packet(shared_secret, &mut onion_error_packet);
 
 		let logger = TestLogger::new();
-		let decrypted_failure = test_failure_attribution(&logger, &packet.data);
+		let decrypted_failure = test_failure_attribution(&logger, onion_error_packet);
 		assert_eq!(decrypted_failure.short_channel_id, Some(0));
 
 		logger.assert_log_contains(
@@ -2414,7 +2413,9 @@ mod tests {
 		);
 	}
 
-	fn test_failure_attribution(logger: &TestLogger, packet: &[u8]) -> DecodedOnionFailure {
+	fn test_failure_attribution(
+		logger: &TestLogger, packet: OnionErrorPacket,
+	) -> DecodedOnionFailure {
 		let ctx_full = Secp256k1::new();
 		let path = build_test_path();
 		let htlc_source = HTLCSource::OutboundRoute {
@@ -2424,8 +2425,7 @@ mod tests {
 			payment_id: PaymentId([1; 32]),
 		};
 
-		let decrypted_failure =
-			process_onion_failure(&ctx_full, &logger, &htlc_source, packet.into());
+		let decrypted_failure = process_onion_failure(&ctx_full, &logger, &htlc_source, packet);
 
 		decrypted_failure
 	}


Reviews:
user: login: arik-so
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-06T23:43:30Z
user: login: joostjager
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-07T07:22:36Z
user: login: joostjager
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-07T07:34:36Z
user: login: arik-so
state: DISMISSED
author_association: CONTRIBUTOR
submitted_at: 2025-03-10T06:43:32Z
user: login: joostjager
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-10T07:27:27Z
user: login: arik-so
body: re-acking
state: DISMISSED
author_association: CONTRIBUTOR
submitted_at: 2025-03-10T07:58:28Z
user: login: TheBlueMatt
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-03-10T13:09:34Z
user: login: joostjager
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-10T13:21:02Z
user: login: joostjager
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-10T13:27:28Z
user: login: joostjager
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-10T13:31:51Z
user: login: joostjager
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-10T13:34:15Z
user: login: TheBlueMatt
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-03-10T14:23:17Z
user: login: joostjager
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-10T15:27:43Z
user: login: TheBlueMatt
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-03-10T18:19:30Z
user: login: joostjager
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-10T19:28:24Z
user: login: joostjager
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-10T19:29:26Z
user: login: TheBlueMatt
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-03-10T20:13:52Z
user: login: joostjager
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-10T20:32:54Z
user: login: TheBlueMatt
body: This LGTM once fixups are squashed.
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-03-10T21:26:40Z
user: login: arik-so
state: APPROVED
author_association: CONTRIBUTOR
submitted_at: 2025-03-11T06:54:45Z

Review comments:
diff_hunk: @@ -670,8 +672,12 @@ fn test_onion_failure() {
 			let mut hmac = HmacEngine::<Sha256>::new(&um);
 			hmac.input(&decoded_err_packet.encode()[32..]);
 			decoded_err_packet.hmac = Hmac::from_engine(hmac).to_byte_array();
-			msg.reason = onion_utils::encrypt_failure_packet(
-				&onion_keys[1].shared_secret.as_ref(), &decoded_err_packet.encode()[..])
+			let mut onion_error = OnionErrorPacket{
+				data: decoded_err_packet.encode(),
path: lightning/src/ln/onion_route_tests.rs
user: login: arik-so
body: while I get why we do this, part of me wonders how prudent it is to mutate the inner property of an argument? It seemed to be somewhat more of an expectation when passing a Vec<u8> directly.
created_at: 2025-03-06T23:26:59Z
author_association: CONTRIBUTOR
diff_hunk: @@ -932,17 +932,17 @@ pub(crate) struct DecodedOnionFailure {
 }
 
 /// Decrypt the error packet in-place.
-fn decrypt_onion_error_packet(packet: &mut Vec<u8>, shared_secret: SharedSecret) {
-	let ammag = gen_ammag_from_shared_secret(shared_secret.as_ref());
-	let mut chacha = ChaCha20::new(&ammag, &[0u8; 8]);
-	chacha.process_in_place(packet);
+fn decrypt_onion_error_packet(packet: &mut OnionErrorPacket, shared_secret: SharedSecret) {
+	// Decrypt the packet.
path: lightning/src/ln/onion_utils.rs
user: login: arik-so
body: at this point, do we even still need this method?
created_at: 2025-03-06T23:29:39Z
author_association: CONTRIBUTOR
diff_hunk: @@ -670,8 +672,12 @@ fn test_onion_failure() {
 			let mut hmac = HmacEngine::<Sha256>::new(&um);
 			hmac.input(&decoded_err_packet.encode()[32..]);
 			decoded_err_packet.hmac = Hmac::from_engine(hmac).to_byte_array();
-			msg.reason = onion_utils::encrypt_failure_packet(
-				&onion_keys[1].shared_secret.as_ref(), &decoded_err_packet.encode()[..])
+			let mut onion_error = OnionErrorPacket{
+				data: decoded_err_packet.encode(),
path: lightning/src/ln/onion_route_tests.rs
user: login: joostjager
body: My thought was that what `crypt_failure_packet` does exactly to the packet in its inner properties doesn't matter all that much to the caller. It just crypts in-place. Ofc now with just a single property it looks a bit unnecessary, but when `attribution_data` is added and also crypted, it makes more sense?

I suppose the alternative is returning a copy. Also open to that, but I think it's less efficient?
created_at: 2025-03-07T07:22:36Z
author_association: CONTRIBUTOR
diff_hunk: @@ -932,17 +932,17 @@ pub(crate) struct DecodedOnionFailure {
 }
 
 /// Decrypt the error packet in-place.
-fn decrypt_onion_error_packet(packet: &mut Vec<u8>, shared_secret: SharedSecret) {
-	let ammag = gen_ammag_from_shared_secret(shared_secret.as_ref());
-	let mut chacha = ChaCha20::new(&ammag, &[0u8; 8]);
-	chacha.process_in_place(packet);
+fn decrypt_onion_error_packet(packet: &mut OnionErrorPacket, shared_secret: SharedSecret) {
+	// Decrypt the packet.
path: lightning/src/ln/onion_utils.rs
user: login: joostjager
body: no, it doesn't do much anymore, and also won't with the follow up pr. removed.
created_at: 2025-03-07T07:34:36Z
author_association: CONTRIBUTOR
diff_hunk: @@ -670,8 +672,12 @@ fn test_onion_failure() {
 			let mut hmac = HmacEngine::<Sha256>::new(&um);
 			hmac.input(&decoded_err_packet.encode()[32..]);
 			decoded_err_packet.hmac = Hmac::from_engine(hmac).to_byte_array();
-			msg.reason = onion_utils::encrypt_failure_packet(
-				&onion_keys[1].shared_secret.as_ref(), &decoded_err_packet.encode()[..])
+			let mut onion_error = OnionErrorPacket{
+				data: decoded_err_packet.encode(),
path: lightning/src/ln/onion_route_tests.rs
user: login: arik-so
body: I guess given the type is called `Packet`, an inner mutation shouldn't be too jarring after all
created_at: 2025-03-10T06:43:02Z
author_association: CONTRIBUTOR
diff_hunk: @@ -670,8 +672,12 @@ fn test_onion_failure() {
 			let mut hmac = HmacEngine::<Sha256>::new(&um);
 			hmac.input(&decoded_err_packet.encode()[32..]);
 			decoded_err_packet.hmac = Hmac::from_engine(hmac).to_byte_array();
-			msg.reason = onion_utils::encrypt_failure_packet(
-				&onion_keys[1].shared_secret.as_ref(), &decoded_err_packet.encode()[..])
+			let mut onion_error = OnionErrorPacket{
+				data: decoded_err_packet.encode(),
path: lightning/src/ln/onion_route_tests.rs
user: login: arik-so
body: btw, minor nit, but add a space between `OnionErrorPacket` and `{`
created_at: 2025-03-10T06:43:26Z
author_association: CONTRIBUTOR
diff_hunk: @@ -670,8 +672,12 @@ fn test_onion_failure() {
 			let mut hmac = HmacEngine::<Sha256>::new(&um);
 			hmac.input(&decoded_err_packet.encode()[32..]);
 			decoded_err_packet.hmac = Hmac::from_engine(hmac).to_byte_array();
-			msg.reason = onion_utils::encrypt_failure_packet(
-				&onion_keys[1].shared_secret.as_ref(), &decoded_err_packet.encode()[..])
+			let mut onion_error = OnionErrorPacket{
+				data: decoded_err_packet.encode(),
path: lightning/src/ln/onion_route_tests.rs
user: login: joostjager
body: added
created_at: 2025-03-10T07:27:27Z
author_association: CONTRIBUTOR
diff_hunk: @@ -909,15 +907,17 @@ pub(super) fn build_failure_packet(
 	hmac.input(&packet.encode()[32..]);
 	packet.hmac = Hmac::from_engine(hmac).to_byte_array();
 
-	packet
+	OnionErrorPacket { data: packet.encode() }
path: lightning/src/ln/onion_utils.rs
user: login: TheBlueMatt
body: Bleh, it seems pretty weird that we now have `OnionErrorPacket`s flying around (at least outside of `onion_utils.rs`) that are both decrypted and encrypted. I think the reason it was written the way it was previously was specifically to avoid that - holding the encoded bytes until we get around to encrypting, then making it an `OnionErrorPacket`. It seems much too easy now to double-en/decrypt or forget to en/decrypt.
created_at: 2025-03-10T12:52:09Z
author_association: COLLABORATOR
diff_hunk: @@ -4435,11 +4435,12 @@ where
 		} else {
 			(err_code, &res.0[..])
 		};
+		let failure = HTLCFailReason::reason(err_code, err_data.to_vec())
+		.get_encrypted_failure_packet(shared_secret, &None);
 		HTLCFailureMsg::Relay(msgs::UpdateFailHTLC {
 			channel_id: msg.channel_id,
 			htlc_id: msg.htlc_id,
-			reason: HTLCFailReason::reason(err_code, err_data.to_vec())
-				.get_encrypted_failure_packet(shared_secret, &None),
+			reason: failure.data.clone(),
path: lightning/src/ln/channelmanager.rs
user: login: TheBlueMatt
body: This shouldn't need `clone`
created_at: 2025-03-10T12:54:28Z
author_association: COLLABORATOR
diff_hunk: @@ -2355,6 +2355,14 @@ pub(crate) struct OnionErrorPacket {
 	pub(crate) data: Vec<u8>,
 }
 
+impl From<&UpdateFailHTLC> for OnionErrorPacket {
path: lightning/src/ln/msgs.rs
user: login: TheBlueMatt
body: Should be able to drop the `&` here. That will avoid hiding the `clone` behind an `into`, which is somewhat nonstandard.
created_at: 2025-03-10T13:08:46Z
author_association: COLLABORATOR
diff_hunk: @@ -909,15 +907,17 @@ pub(super) fn build_failure_packet(
 	hmac.input(&packet.encode()[32..]);
 	packet.hmac = Hmac::from_engine(hmac).to_byte_array();
 
-	packet
+	OnionErrorPacket { data: packet.encode() }
path: lightning/src/ln/onion_utils.rs
user: login: joostjager
body: I think a similar situation already existed for intermediate nodes? In https://github.com/lightningdevkit/rust-lightning/blob/4c43a5b3df2afc7ffb74df84168aafdf47b965a2/lightning/src/ln/onion_utils.rs#L1389, an `OnionErrorPacket` received from downstream is encrypted into again an `OnionErrorPacket`.

The way I see it is that there's "data" in the packet that needs to be encrypted, and it doesn't matter whether that data is 0, 1, 2, etc times encrypted already.
created_at: 2025-03-10T13:21:02Z
author_association: CONTRIBUTOR
diff_hunk: @@ -4435,11 +4435,12 @@ where
 		} else {
 			(err_code, &res.0[..])
 		};
+		let failure = HTLCFailReason::reason(err_code, err_data.to_vec())
+		.get_encrypted_failure_packet(shared_secret, &None);
 		HTLCFailureMsg::Relay(msgs::UpdateFailHTLC {
 			channel_id: msg.channel_id,
 			htlc_id: msg.htlc_id,
-			reason: HTLCFailReason::reason(err_code, err_data.to_vec())
-				.get_encrypted_failure_packet(shared_secret, &None),
+			reason: failure.data.clone(),
path: lightning/src/ln/channelmanager.rs
user: login: joostjager
body: Leftover apparently, removed.
created_at: 2025-03-10T13:27:28Z
author_association: CONTRIBUTOR
diff_hunk: @@ -2355,6 +2355,14 @@ pub(crate) struct OnionErrorPacket {
 	pub(crate) data: Vec<u8>,
 }
 
+impl From<&UpdateFailHTLC> for OnionErrorPacket {
path: lightning/src/ln/msgs.rs
user: login: joostjager
body: I see. I assume this will move the clone up the call stack, if necessary (in this case in one location that was necessary because the value couldn't be moved). Fixed.
created_at: 2025-03-10T13:31:50Z
author_association: CONTRIBUTOR
diff_hunk: @@ -909,15 +907,17 @@ pub(super) fn build_failure_packet(
 	hmac.input(&packet.encode()[32..]);
 	packet.hmac = Hmac::from_engine(hmac).to_byte_array();
 
-	packet
+	OnionErrorPacket { data: packet.encode() }
path: lightning/src/ln/onion_utils.rs
user: login: joostjager
body: Open to alternatives of course. Were you thinking of pulling the encrypt step into `build_failure_packet` too?
created_at: 2025-03-10T13:34:14Z
author_association: CONTRIBUTOR
diff_hunk: @@ -909,15 +907,17 @@ pub(super) fn build_failure_packet(
 	hmac.input(&packet.encode()[32..]);
 	packet.hmac = Hmac::from_engine(hmac).to_byte_array();
 
-	packet
+	OnionErrorPacket { data: packet.encode() }
path: lightning/src/ln/onion_utils.rs
user: login: TheBlueMatt
body: Yea, certainly not saying the old code was perfect and indeed its already the case that things are encrypted multiple times. I guess I just saw `OnionErrorPacket` as "encrypted thing ready to go over the wire/received on the wire". Certainly stuff internal to `onion_utils` doesn't matter quite as much, but once it leaves `onion_utils.rs` it looks like it'd be really easy to have code that just forgets to encrypt/decrypt (ie the callsite would just read `build_failure_packet` and the fact that its unencrypted isn't obvious).

Pulling the encrypt step into `build_failure_packet` seems like one perfectly good way to address it.
created_at: 2025-03-10T14:23:17Z
author_association: COLLABORATOR
diff_hunk: @@ -909,15 +907,17 @@ pub(super) fn build_failure_packet(
 	hmac.input(&packet.encode()[32..]);
 	packet.hmac = Hmac::from_engine(hmac).to_byte_array();
 
-	packet
+	OnionErrorPacket { data: packet.encode() }
path: lightning/src/ln/onion_utils.rs
user: login: joostjager
body: Done. Just kept a non-public unencrypted version of build_failure_packet for the bolt test vector test.
created_at: 2025-03-10T15:27:42Z
author_association: CONTRIBUTOR
diff_hunk: @@ -869,23 +870,20 @@ fn construct_onion_packet_with_init_noise<HD: Writeable, P: Packet>(
 	Ok(P::new(onion_keys.first().unwrap().ephemeral_pubkey, packet_data, hmac_res))
 }
 
-/// Encrypts a failure packet. raw_packet can either be a
-/// msgs::DecodedOnionErrorPacket.encode() result or a msgs::OnionErrorPacket.data element.
-pub(super) fn encrypt_failure_packet(
-	shared_secret: &[u8], raw_packet: &[u8],
-) -> msgs::OnionErrorPacket {
+/// Encrypts/decrypts a failure packet.
+pub(super) fn crypt_failure_packet(shared_secret: &[u8], packet: &mut OnionErrorPacket) {
path: lightning/src/ln/onion_utils.rs
user: login: TheBlueMatt
body: Does this need to remain public now?
created_at: 2025-03-10T18:19:14Z
author_association: COLLABORATOR
diff_hunk: @@ -869,23 +870,20 @@ fn construct_onion_packet_with_init_noise<HD: Writeable, P: Packet>(
 	Ok(P::new(onion_keys.first().unwrap().ephemeral_pubkey, packet_data, hmac_res))
 }
 
-/// Encrypts a failure packet. raw_packet can either be a
-/// msgs::DecodedOnionErrorPacket.encode() result or a msgs::OnionErrorPacket.data element.
-pub(super) fn encrypt_failure_packet(
-	shared_secret: &[u8], raw_packet: &[u8],
-) -> msgs::OnionErrorPacket {
+/// Encrypts/decrypts a failure packet.
+pub(super) fn crypt_failure_packet(shared_secret: &[u8], packet: &mut OnionErrorPacket) {
 	let ammag = gen_ammag_from_shared_secret(&shared_secret);
+	process_chacha(&ammag, &mut packet.data);
+}
 
-	let mut packet_crypted = Vec::with_capacity(raw_packet.len());
-	packet_crypted.resize(raw_packet.len(), 0);
-	let mut chacha = ChaCha20::new(&ammag, &[0u8; 8]);
-	chacha.process(&raw_packet, &mut packet_crypted[..]);
-	msgs::OnionErrorPacket { data: packet_crypted }
+pub(super) fn process_chacha(key: &[u8; 32], packet: &mut [u8]) {
path: lightning/src/ln/onion_utils.rs
user: login: TheBlueMatt
body: Same here, I'm a bit skeptical we need to keep this method public given its 2 LoC
created_at: 2025-03-10T18:19:28Z
author_association: COLLABORATOR
diff_hunk: @@ -869,23 +870,20 @@ fn construct_onion_packet_with_init_noise<HD: Writeable, P: Packet>(
 	Ok(P::new(onion_keys.first().unwrap().ephemeral_pubkey, packet_data, hmac_res))
 }
 
-/// Encrypts a failure packet. raw_packet can either be a
-/// msgs::DecodedOnionErrorPacket.encode() result or a msgs::OnionErrorPacket.data element.
-pub(super) fn encrypt_failure_packet(
-	shared_secret: &[u8], raw_packet: &[u8],
-) -> msgs::OnionErrorPacket {
+/// Encrypts/decrypts a failure packet.
+pub(super) fn crypt_failure_packet(shared_secret: &[u8], packet: &mut OnionErrorPacket) {
 	let ammag = gen_ammag_from_shared_secret(&shared_secret);
+	process_chacha(&ammag, &mut packet.data);
+}
 
-	let mut packet_crypted = Vec::with_capacity(raw_packet.len());
-	packet_crypted.resize(raw_packet.len(), 0);
-	let mut chacha = ChaCha20::new(&ammag, &[0u8; 8]);
-	chacha.process(&raw_packet, &mut packet_crypted[..]);
-	msgs::OnionErrorPacket { data: packet_crypted }
+pub(super) fn process_chacha(key: &[u8; 32], packet: &mut [u8]) {
path: lightning/src/ln/onion_utils.rs
user: login: joostjager
body: Can be private indeed, changed.
created_at: 2025-03-10T19:28:24Z
author_association: CONTRIBUTOR
diff_hunk: @@ -869,23 +870,20 @@ fn construct_onion_packet_with_init_noise<HD: Writeable, P: Packet>(
 	Ok(P::new(onion_keys.first().unwrap().ephemeral_pubkey, packet_data, hmac_res))
 }
 
-/// Encrypts a failure packet. raw_packet can either be a
-/// msgs::DecodedOnionErrorPacket.encode() result or a msgs::OnionErrorPacket.data element.
-pub(super) fn encrypt_failure_packet(
-	shared_secret: &[u8], raw_packet: &[u8],
-) -> msgs::OnionErrorPacket {
+/// Encrypts/decrypts a failure packet.
+pub(super) fn crypt_failure_packet(shared_secret: &[u8], packet: &mut OnionErrorPacket) {
path: lightning/src/ln/onion_utils.rs
user: login: joostjager
body: It's used in `onion_route_tests.rs` - I think there is no other way to make it available there, or is there?
created_at: 2025-03-10T19:29:26Z
author_association: CONTRIBUTOR
diff_hunk: @@ -869,23 +870,20 @@ fn construct_onion_packet_with_init_noise<HD: Writeable, P: Packet>(
 	Ok(P::new(onion_keys.first().unwrap().ephemeral_pubkey, packet_data, hmac_res))
 }
 
-/// Encrypts a failure packet. raw_packet can either be a
-/// msgs::DecodedOnionErrorPacket.encode() result or a msgs::OnionErrorPacket.data element.
-pub(super) fn encrypt_failure_packet(
-	shared_secret: &[u8], raw_packet: &[u8],
-) -> msgs::OnionErrorPacket {
+/// Encrypts/decrypts a failure packet.
+pub(super) fn crypt_failure_packet(shared_secret: &[u8], packet: &mut OnionErrorPacket) {
path: lightning/src/ln/onion_utils.rs
user: login: TheBlueMatt
body: Often we do a test wrapper like the below.  For four LoC we could even just copy them into the tests, though.

```
#[cfg(test)]
pub fn test_crypt_failure_packet(shared_secret, packet) { crypt_failure_packet(shared_secret, packet) }
```
created_at: 2025-03-10T20:13:49Z
author_association: COLLABORATOR
diff_hunk: @@ -869,23 +870,20 @@ fn construct_onion_packet_with_init_noise<HD: Writeable, P: Packet>(
 	Ok(P::new(onion_keys.first().unwrap().ephemeral_pubkey, packet_data, hmac_res))
 }
 
-/// Encrypts a failure packet. raw_packet can either be a
-/// msgs::DecodedOnionErrorPacket.encode() result or a msgs::OnionErrorPacket.data element.
-pub(super) fn encrypt_failure_packet(
-	shared_secret: &[u8], raw_packet: &[u8],
-) -> msgs::OnionErrorPacket {
+/// Encrypts/decrypts a failure packet.
+pub(super) fn crypt_failure_packet(shared_secret: &[u8], packet: &mut OnionErrorPacket) {
path: lightning/src/ln/onion_utils.rs
user: login: joostjager
body: Well, it's about testing that function also, so wouldn't want to copy it.

Created the test wrapper.
created_at: 2025-03-10T20:32:54Z
author_association: CONTRIBUTOR

PR comments:
user: login: ldk-reviews-bot
created_at: 2025-03-06T14:59:19Z
body: 👋 Thanks for assigning @arik-so as a reviewer!
I'll wait for their review and will help manage the review process.
Once they submit their review, I'll check if a second reviewer would be helpful.
author_association: NONE
user: login: joostjager
created_at: 2025-03-06T22:08:49Z
body: Rebased
author_association: CONTRIBUTOR
user: login: ldk-reviews-bot
created_at: 2025-03-06T23:43:33Z
body: 👋 The first review has been submitted!

Do you think this PR is ready for a second reviewer? If so, [click here to assign a second reviewer](https://ldk-reviews-bot.bluematt.me//assign-second-reviewer/lightningdevkit/rust-lightning/3650).
author_association: NONE
user: login: codecov[bot]
created_at: 2025-03-07T07:42:27Z
body: ## [Codecov](https://app.codecov.io/gh/lightningdevkit/rust-lightning/pull/3650?dropdown=coverage&src=pr&el=h1&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=lightningdevkit) Report
Attention: Patch coverage is `86.33540%` with `22 lines` in your changes missing coverage. Please review.
> Project coverage is 89.22%. Comparing base [(`4c43a5b`)](https://app.codecov.io/gh/lightningdevkit/rust-lightning/commit/4c43a5b3df2afc7ffb74df84168aafdf47b965a2?dropdown=coverage&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=lightningdevkit) to head [(`1426197`)](https://app.codecov.io/gh/lightningdevkit/rust-lightning/commit/1426197828384463e972cad43ee031f3984cad93?dropdown=coverage&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=lightningdevkit).
> Report is 5 commits behind head on main.

| [Files with missing lines](https://app.codecov.io/gh/lightningdevkit/rust-lightning/pull/3650?dropdown=coverage&src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=lightningdevkit) | Patch % | Lines |
|---|---|---|
| [lightning/src/ln/channel.rs](https://app.codecov.io/gh/lightningdevkit/rust-lightning/pull/3650?src=pr&el=tree&filepath=lightning%2Fsrc%2Fln%2Fchannel.rs&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=lightningdevkit#diff-bGlnaHRuaW5nL3NyYy9sbi9jaGFubmVsLnJz) | 31.81% | [2 Missing and 13 partials :warning: ](https://app.codecov.io/gh/lightningdevkit/rust-lightning/pull/3650?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=lightningdevkit) |
| [lightning/src/ln/onion\_utils.rs](https://app.codecov.io/gh/lightningdevkit/rust-lightning/pull/3650?src=pr&el=tree&filepath=lightning%2Fsrc%2Fln%2Fonion_utils.rs&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=lightningdevkit#diff-bGlnaHRuaW5nL3NyYy9sbi9vbmlvbl91dGlscy5ycw==) | 92.30% | [4 Missing and 2 partials :warning: ](https://app.codecov.io/gh/lightningdevkit/rust-lightning/pull/3650?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=lightningdevkit) |
| [lightning/src/ln/onion\_payment.rs](https://app.codecov.io/gh/lightningdevkit/rust-lightning/pull/3650?src=pr&el=tree&filepath=lightning%2Fsrc%2Fln%2Fonion_payment.rs&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=lightningdevkit#diff-bGlnaHRuaW5nL3NyYy9sbi9vbmlvbl9wYXltZW50LnJz) | 75.00% | [1 Missing :warning: ](https://app.codecov.io/gh/lightningdevkit/rust-lightning/pull/3650?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=lightningdevkit) |

<details><summary>Additional details and impacted files</summary>


```diff
@@            Coverage Diff             @@
##             main    #3650      +/-   ##
==========================================
- Coverage   89.24%   89.22%   -0.02%     
==========================================
  Files         155      155              
  Lines      119280   119314      +34     
  Branches   119280   119314      +34     
==========================================
+ Hits       106446   106463      +17     
- Misses      10240    10248       +8     
- Partials     2594     2603       +9     
```

</details>

[:umbrella: View full report in Codecov by Sentry](https://app.codecov.io/gh/lightningdevkit/rust-lightning/pull/3650?dropdown=coverage&src=pr&el=continue&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=lightningdevkit).   
:loudspeaker: Have feedback on the report? [Share it here](https://about.codecov.io/codecov-pr-comment-feedback/?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=lightningdevkit).

<details><summary>🚀 New features to boost your workflow: </summary>

- ❄ [Test Analytics](https://docs.codecov.com/docs/test-analytics): Detect flaky tests, report on failures, and find test suite problems.
</details>
author_association: NONE
user: login: ldk-reviews-bot
created_at: 2025-03-08T15:00:02Z
body: 🔔 1st Reminder

Hey @arik-so! This PR has been waiting for your review.
Please take a look when you have a chance. If you're unable to review, please let us know so we can find another reviewer.
author_association: NONE