Skip to content

Commit d016f39

Browse files
committed
Attributable failures
1 parent 87c5cae commit d016f39

File tree

9 files changed

+572
-89
lines changed

9 files changed

+572
-89
lines changed

lightning/src/ln/channel.rs

Lines changed: 88 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,7 @@ use crate::ln::chan_utils::{
5050
#[cfg(splicing)]
5151
use crate::ln::chan_utils::FUNDING_TRANSACTION_WITNESS_WEIGHT;
5252
use crate::ln::chan_utils;
53-
use crate::ln::onion_utils::{HTLCFailReason};
53+
use crate::ln::onion_utils::{HTLCFailReason, ATTRIBUTION_DATA_LEN};
5454
use crate::chain::BestBlock;
5555
use crate::chain::chaininterface::{FeeEstimator, ConfirmationTarget, LowerBoundedFeeEstimator, fee_for_weight};
5656
use crate::chain::channelmonitor::{ChannelMonitor, ChannelMonitorUpdate, ChannelMonitorUpdateStep, LATENCY_GRACE_PERIOD_BLOCKS};
@@ -68,6 +68,7 @@ use crate::util::scid_utils::scid_from_parts;
6868

6969
use crate::io;
7070
use crate::prelude::*;
71+
use core::time::Duration;
7172
use core::{cmp,mem,fmt};
7273
use core::ops::Deref;
7374
#[cfg(any(test, fuzzing, debug_assertions))]
@@ -323,6 +324,7 @@ struct OutboundHTLCOutput {
323324
source: HTLCSource,
324325
blinding_point: Option<PublicKey>,
325326
skimmed_fee_msat: Option<u64>,
327+
timestamp: Option<Duration>,
326328
}
327329

328330
/// See AwaitingRemoteRevoke ChannelState for more info
@@ -4929,7 +4931,7 @@ trait FailHTLCContents {
49294931
impl FailHTLCContents for msgs::OnionErrorPacket {
49304932
type Message = msgs::UpdateFailHTLC;
49314933
fn to_message(self, htlc_id: u64, channel_id: ChannelId) -> Self::Message {
4932-
msgs::UpdateFailHTLC { htlc_id, channel_id, reason: self.data }
4934+
msgs::UpdateFailHTLC { htlc_id, channel_id, reason: self.data, attribution_data: self.attribution_data }
49334935
}
49344936
fn to_inbound_htlc_state(self) -> InboundHTLCState {
49354937
InboundHTLCState::LocalRemoved(InboundHTLCRemovalReason::FailRelay(self))
@@ -6095,10 +6097,16 @@ impl<SP: Deref> FundedChannel<SP> where
60956097
false
60966098
} else { true }
60976099
});
6100+
let now = duration_since_epoch();
60986101
pending_outbound_htlcs.retain(|htlc| {
60996102
if let &OutboundHTLCState::AwaitingRemovedRemoteRevoke(ref outcome) = &htlc.state {
61006103
log_trace!(logger, " ...removing outbound AwaitingRemovedRemoteRevoke {}", &htlc.payment_hash);
6101-
if let OutboundHTLCOutcome::Failure(reason) = outcome.clone() { // We really want take() here, but, again, non-mut ref :(
6104+
if let OutboundHTLCOutcome::Failure(mut reason) = outcome.clone() { // We really want take() here, but, again, non-mut ref :(
6105+
if let (Some(timestamp), Some(now)) = (htlc.timestamp, now) {
6106+
let hold_time = u32::try_from((now - timestamp).as_millis()).unwrap_or(u32::MAX);
6107+
reason.set_hold_time(hold_time);
6108+
}
6109+
61026110
revoked_htlcs.push((htlc.source.clone(), htlc.payment_hash, reason));
61036111
} else {
61046112
finalized_claimed_htlcs.push(htlc.source.clone());
@@ -6840,6 +6848,7 @@ impl<SP: Deref> FundedChannel<SP> where
68406848
channel_id: self.context.channel_id(),
68416849
htlc_id: htlc.htlc_id,
68426850
reason: err_packet.data.clone(),
6851+
attribution_data: err_packet.attribution_data,
68436852
});
68446853
},
68456854
&InboundHTLCRemovalReason::FailMalformed((ref sha256_of_onion, ref failure_code)) => {
@@ -8666,6 +8675,7 @@ impl<SP: Deref> FundedChannel<SP> where
86668675
return Ok(None);
86678676
}
86688677

8678+
let timestamp = duration_since_epoch();
86698679
self.context.pending_outbound_htlcs.push(OutboundHTLCOutput {
86708680
htlc_id: self.context.next_holder_htlc_id,
86718681
amount_msat,
@@ -8675,6 +8685,7 @@ impl<SP: Deref> FundedChannel<SP> where
86758685
source,
86768686
blinding_point,
86778687
skimmed_fee_msat,
8688+
timestamp,
86788689
});
86798690

86808691
let res = msgs::UpdateAddHTLC {
@@ -10242,6 +10253,7 @@ impl<SP: Deref> Writeable for FundedChannel<SP> where SP::Target: SignerProvider
1024210253
dropped_inbound_htlcs += 1;
1024310254
}
1024410255
}
10256+
let mut removed_htlc_failure_attribution_data: Vec<&Option<[u8; ATTRIBUTION_DATA_LEN]>> = Vec::new();
1024510257
(self.context.pending_inbound_htlcs.len() as u64 - dropped_inbound_htlcs).write(writer)?;
1024610258
for htlc in self.context.pending_inbound_htlcs.iter() {
1024710259
if let &InboundHTLCState::RemoteAnnounced(_) = &htlc.state {
@@ -10267,9 +10279,10 @@ impl<SP: Deref> Writeable for FundedChannel<SP> where SP::Target: SignerProvider
1026710279
&InboundHTLCState::LocalRemoved(ref removal_reason) => {
1026810280
4u8.write(writer)?;
1026910281
match removal_reason {
10270-
InboundHTLCRemovalReason::FailRelay(msgs::OnionErrorPacket { data }) => {
10282+
InboundHTLCRemovalReason::FailRelay(msgs::OnionErrorPacket { data, attribution_data }) => {
1027110283
0u8.write(writer)?;
1027210284
data.write(writer)?;
10285+
removed_htlc_failure_attribution_data.push(&attribution_data);
1027310286
},
1027410287
InboundHTLCRemovalReason::FailMalformed((hash, code)) => {
1027510288
1u8.write(writer)?;
@@ -10331,10 +10344,11 @@ impl<SP: Deref> Writeable for FundedChannel<SP> where SP::Target: SignerProvider
1033110344

1033210345
let mut holding_cell_skimmed_fees: Vec<Option<u64>> = Vec::new();
1033310346
let mut holding_cell_blinding_points: Vec<Option<PublicKey>> = Vec::new();
10347+
let mut holding_cell_failure_attribution_data: Vec<(u32, [u8; ATTRIBUTION_DATA_LEN])> = Vec::new();
1033410348
// Vec of (htlc_id, failure_code, sha256_of_onion)
1033510349
let mut malformed_htlcs: Vec<(u64, u16, [u8; 32])> = Vec::new();
1033610350
(self.context.holding_cell_htlc_updates.len() as u64).write(writer)?;
10337-
for update in self.context.holding_cell_htlc_updates.iter() {
10351+
for (i, update) in self.context.holding_cell_htlc_updates.iter().enumerate() {
1033810352
match update {
1033910353
&HTLCUpdateAwaitingACK::AddHTLC {
1034010354
ref amount_msat, ref cltv_expiry, ref payment_hash, ref source, ref onion_routing_packet,
@@ -10359,6 +10373,13 @@ impl<SP: Deref> Writeable for FundedChannel<SP> where SP::Target: SignerProvider
1035910373
2u8.write(writer)?;
1036010374
htlc_id.write(writer)?;
1036110375
err_packet.data.write(writer)?;
10376+
10377+
// Store the attribution data for later writing. Include the holding cell htlc update index because
10378+
// FailMalformedHTLC is stored with the same type 2 and we wouldn't be able to distinguish the two
10379+
// when reading back in.
10380+
if let Some(attribution_data ) = err_packet.attribution_data {
10381+
holding_cell_failure_attribution_data.push((i as u32, attribution_data));
10382+
}
1036210383
}
1036310384
&HTLCUpdateAwaitingACK::FailMalformedHTLC {
1036410385
htlc_id, failure_code, sha256_of_onion
@@ -10542,6 +10563,8 @@ impl<SP: Deref> Writeable for FundedChannel<SP> where SP::Target: SignerProvider
1054210563
(49, self.context.local_initiated_shutdown, option), // Added in 0.0.122
1054310564
(51, is_manual_broadcast, option), // Added in 0.0.124
1054410565
(53, funding_tx_broadcast_safe_event_emitted, option), // Added in 0.0.124
10566+
(55, removed_htlc_failure_attribution_data, optional_vec), // Added in 0.2
10567+
(57, holding_cell_failure_attribution_data, optional_vec), // Added in 0.2
1054510568
});
1054610569

1054710570
Ok(())
@@ -10619,6 +10642,7 @@ impl<'a, 'b, 'c, ES: Deref, SP: Deref> ReadableArgs<(&'a ES, &'b SP, &'c Channel
1061910642
let reason = match <u8 as Readable>::read(reader)? {
1062010643
0 => InboundHTLCRemovalReason::FailRelay(msgs::OnionErrorPacket {
1062110644
data: Readable::read(reader)?,
10645+
attribution_data: None,
1062210646
}),
1062310647
1 => InboundHTLCRemovalReason::FailMalformed(Readable::read(reader)?),
1062410648
2 => InboundHTLCRemovalReason::Fulfill(Readable::read(reader)?),
@@ -10659,6 +10683,7 @@ impl<'a, 'b, 'c, ES: Deref, SP: Deref> ReadableArgs<(&'a ES, &'b SP, &'c Channel
1065910683
},
1066010684
skimmed_fee_msat: None,
1066110685
blinding_point: None,
10686+
timestamp: None,
1066210687
});
1066310688
}
1066410689

@@ -10683,6 +10708,7 @@ impl<'a, 'b, 'c, ES: Deref, SP: Deref> ReadableArgs<(&'a ES, &'b SP, &'c Channel
1068310708
htlc_id: Readable::read(reader)?,
1068410709
err_packet: OnionErrorPacket {
1068510710
data: Readable::read(reader)?,
10711+
attribution_data: None,
1068610712
},
1068710713
},
1068810714
_ => return Err(DecodeError::InvalidValue),
@@ -10826,6 +10852,9 @@ impl<'a, 'b, 'c, ES: Deref, SP: Deref> ReadableArgs<(&'a ES, &'b SP, &'c Channel
1082610852
let mut pending_outbound_blinding_points_opt: Option<Vec<Option<PublicKey>>> = None;
1082710853
let mut holding_cell_blinding_points_opt: Option<Vec<Option<PublicKey>>> = None;
1082810854

10855+
let mut removed_htlc_failure_attribution_data: Option<Vec<Option<[u8; ATTRIBUTION_DATA_LEN]>>> = None;
10856+
let mut holding_cell_failure_attribution_data: Option<Vec<(u32, [u8; ATTRIBUTION_DATA_LEN])>> = None;
10857+
1082910858
let mut malformed_htlcs: Option<Vec<(u64, u16, [u8; 32])>> = None;
1083010859
let mut monitor_pending_update_adds: Option<Vec<msgs::UpdateAddHTLC>> = None;
1083110860

@@ -10868,6 +10897,8 @@ impl<'a, 'b, 'c, ES: Deref, SP: Deref> ReadableArgs<(&'a ES, &'b SP, &'c Channel
1086810897
(49, local_initiated_shutdown, option),
1086910898
(51, is_manual_broadcast, option),
1087010899
(53, funding_tx_broadcast_safe_event_emitted, option),
10900+
(55, removed_htlc_failure_attribution_data, optional_vec),
10901+
(57, holding_cell_failure_attribution_data, optional_vec),
1087110902
});
1087210903

1087310904
let holder_signer = signer_provider.derive_channel_signer(channel_keys_id);
@@ -10949,6 +10980,38 @@ impl<'a, 'b, 'c, ES: Deref, SP: Deref> ReadableArgs<(&'a ES, &'b SP, &'c Channel
1094910980
if iter.next().is_some() { return Err(DecodeError::InvalidValue) }
1095010981
}
1095110982

10983+
if let Some(attribution_datas) = removed_htlc_failure_attribution_data {
10984+
let mut removed_htlc_relay_failures =
10985+
pending_inbound_htlcs.iter_mut().filter_map(|status|
10986+
if let InboundHTLCState::LocalRemoved(ref mut reason) = &mut status.state {
10987+
if let InboundHTLCRemovalReason::FailRelay(ref mut packet) = reason {
10988+
Some(&mut packet.attribution_data)
10989+
} else {
10990+
None
10991+
}
10992+
} else {
10993+
None
10994+
}
10995+
);
10996+
10997+
for attribution_data in attribution_datas {
10998+
*removed_htlc_relay_failures.next().ok_or(DecodeError::InvalidValue)? = attribution_data;
10999+
}
11000+
if removed_htlc_relay_failures.next().is_some() { return Err(DecodeError::InvalidValue); }
11001+
}
11002+
11003+
if let Some(attribution_datas) = holding_cell_failure_attribution_data {
11004+
for (i, attribution_data) in attribution_datas {
11005+
let update = holding_cell_htlc_updates.get_mut(i as usize).ok_or(DecodeError::InvalidValue)?;
11006+
11007+
if let HTLCUpdateAwaitingACK::FailHTLC { htlc_id: _, ref mut err_packet } = update {
11008+
err_packet.attribution_data = Some(attribution_data);
11009+
} else {
11010+
return Err(DecodeError::InvalidValue);
11011+
}
11012+
}
11013+
}
11014+
1095211015
if let Some(malformed_htlcs) = malformed_htlcs {
1095311016
for (malformed_htlc_id, failure_code, sha256_of_onion) in malformed_htlcs {
1095411017
let htlc_idx = holding_cell_htlc_updates.iter().position(|htlc| {
@@ -11139,6 +11202,18 @@ impl<'a, 'b, 'c, ES: Deref, SP: Deref> ReadableArgs<(&'a ES, &'b SP, &'c Channel
1113911202
}
1114011203
}
1114111204

11205+
fn duration_since_epoch() -> Option<Duration> {
11206+
#[cfg(not(feature = "std"))]
11207+
let now = None;
11208+
11209+
#[cfg(feature = "std")]
11210+
let now = Some(std::time::SystemTime::now()
11211+
.duration_since(std::time::SystemTime::UNIX_EPOCH)
11212+
.expect("SystemTime::now() should come after SystemTime::UNIX_EPOCH"));
11213+
11214+
now
11215+
}
11216+
1114211217
#[cfg(test)]
1114311218
mod tests {
1114411219
use std::cmp;
@@ -11152,7 +11227,7 @@ mod tests {
1115211227
use bitcoin::network::Network;
1115311228
#[cfg(splicing)]
1115411229
use bitcoin::Weight;
11155-
use crate::ln::onion_utils::INVALID_ONION_BLINDING;
11230+
use crate::ln::onion_utils::{ATTRIBUTION_DATA_LEN, INVALID_ONION_BLINDING};
1115611231
use crate::types::payment::{PaymentHash, PaymentPreimage};
1115711232
use crate::ln::channel_keys::{RevocationKey, RevocationBasepoint};
1115811233
use crate::ln::channelmanager::{self, HTLCSource, PaymentId};
@@ -11374,6 +11449,7 @@ mod tests {
1137411449
},
1137511450
skimmed_fee_msat: None,
1137611451
blinding_point: None,
11452+
timestamp: None,
1137711453
});
1137811454

1137911455
// Make sure when Node A calculates their local commitment transaction, none of the HTLCs pass
@@ -11758,6 +11834,7 @@ mod tests {
1175811834
source: dummy_htlc_source.clone(),
1175911835
skimmed_fee_msat: None,
1176011836
blinding_point: None,
11837+
timestamp: None,
1176111838
};
1176211839
let mut pending_outbound_htlcs = vec![dummy_outbound_output.clone(); 10];
1176311840
for (idx, htlc) in pending_outbound_htlcs.iter_mut().enumerate() {
@@ -11789,7 +11866,7 @@ mod tests {
1178911866
htlc_id: 0,
1179011867
};
1179111868
let dummy_holding_cell_failed_htlc = |htlc_id| HTLCUpdateAwaitingACK::FailHTLC {
11792-
htlc_id, err_packet: msgs::OnionErrorPacket { data: vec![42] }
11869+
htlc_id, err_packet: msgs::OnionErrorPacket { data: vec![42], attribution_data: Some([1; ATTRIBUTION_DATA_LEN]) }
1179311870
};
1179411871
let dummy_holding_cell_malformed_htlc = |htlc_id| HTLCUpdateAwaitingACK::FailMalformedHTLC {
1179511872
htlc_id, failure_code: INVALID_ONION_BLINDING, sha256_of_onion: [0; 32],
@@ -12080,6 +12157,7 @@ mod tests {
1208012157
source: HTLCSource::dummy(),
1208112158
skimmed_fee_msat: None,
1208212159
blinding_point: None,
12160+
timestamp: None,
1208312161
};
1208412162
out.payment_hash.0 = Sha256::hash(&<Vec<u8>>::from_hex("0202020202020202020202020202020202020202020202020202020202020202").unwrap()).to_byte_array();
1208512163
out
@@ -12094,6 +12172,7 @@ mod tests {
1209412172
source: HTLCSource::dummy(),
1209512173
skimmed_fee_msat: None,
1209612174
blinding_point: None,
12175+
timestamp: None,
1209712176
};
1209812177
out.payment_hash.0 = Sha256::hash(&<Vec<u8>>::from_hex("0303030303030303030303030303030303030303030303030303030303030303").unwrap()).to_byte_array();
1209912178
out
@@ -12506,6 +12585,7 @@ mod tests {
1250612585
source: HTLCSource::dummy(),
1250712586
skimmed_fee_msat: None,
1250812587
blinding_point: None,
12588+
timestamp: None,
1250912589
};
1251012590
out.payment_hash.0 = Sha256::hash(&<Vec<u8>>::from_hex("0505050505050505050505050505050505050505050505050505050505050505").unwrap()).to_byte_array();
1251112591
out
@@ -12520,6 +12600,7 @@ mod tests {
1252012600
source: HTLCSource::dummy(),
1252112601
skimmed_fee_msat: None,
1252212602
blinding_point: None,
12603+
timestamp: None,
1252312604
};
1252412605
out.payment_hash.0 = Sha256::hash(&<Vec<u8>>::from_hex("0505050505050505050505050505050505050505050505050505050505050505").unwrap()).to_byte_array();
1252512606
out

lightning/src/ln/channelmanager.rs

Lines changed: 18 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -59,7 +59,7 @@ use crate::types::features::Bolt11InvoiceFeatures;
5959
use crate::routing::router::{BlindedTail, InFlightHtlcs, Path, Payee, PaymentParameters, RouteParameters, RouteParametersConfig, Router, FixedRouter, Route};
6060
use crate::ln::onion_payment::{check_incoming_htlc_cltv, create_recv_pending_htlc_info, create_fwd_pending_htlc_info, decode_incoming_update_add_htlc_onion, HopConnector, InboundHTLCErr, NextPacketDetails};
6161
use crate::ln::msgs;
62-
use crate::ln::onion_utils;
62+
use crate::ln::onion_utils::{self};
6363
use crate::ln::onion_utils::{HTLCFailReason, INVALID_ONION_BLINDING};
6464
use crate::ln::msgs::{BaseMessageHandler, ChannelMessageHandler, CommitmentUpdate, DecodeError, LightningError, MessageSendEvent};
6565
#[cfg(test)]
@@ -4476,6 +4476,7 @@ where
44764476
channel_id: msg.channel_id,
44774477
htlc_id: msg.htlc_id,
44784478
reason: failure.data,
4479+
attribution_data: failure.attribution_data,
44794480
})
44804481
}
44814482

@@ -4501,10 +4502,12 @@ where
45014502
}
45024503
let failure = HTLCFailReason::reason($err_code, $data.to_vec())
45034504
.get_encrypted_failure_packet(&shared_secret, &None);
4505+
45044506
return PendingHTLCStatus::Fail(HTLCFailureMsg::Relay(msgs::UpdateFailHTLC {
45054507
channel_id: msg.channel_id,
45064508
htlc_id: msg.htlc_id,
45074509
reason: failure.data,
4510+
attribution_data: failure.attribution_data,
45084511
}));
45094512
}
45104513
}
@@ -12968,11 +12971,15 @@ impl_writeable_tlv_based!(PendingHTLCInfo, {
1296812971
impl Writeable for HTLCFailureMsg {
1296912972
fn write<W: Writer>(&self, writer: &mut W) -> Result<(), io::Error> {
1297012973
match self {
12971-
HTLCFailureMsg::Relay(msgs::UpdateFailHTLC { channel_id, htlc_id, reason }) => {
12974+
HTLCFailureMsg::Relay(msgs::UpdateFailHTLC { channel_id, htlc_id, reason, attribution_data }) => {
1297212975
0u8.write(writer)?;
1297312976
channel_id.write(writer)?;
1297412977
htlc_id.write(writer)?;
1297512978
reason.write(writer)?;
12979+
12980+
// This code will only ever be hit for legacy data that is re-serialized. It isn't necessary to try
12981+
// writing out attribution data, because it can never be present.
12982+
debug_assert!(attribution_data.is_none());
1297612983
},
1297712984
HTLCFailureMsg::Malformed(msgs::UpdateFailMalformedHTLC {
1297812985
channel_id, htlc_id, sha256_of_onion, failure_code
@@ -12997,6 +13004,7 @@ impl Readable for HTLCFailureMsg {
1299713004
channel_id: Readable::read(reader)?,
1299813005
htlc_id: Readable::read(reader)?,
1299913006
reason: Readable::read(reader)?,
13007+
attribution_data: None,
1300013008
}))
1300113009
},
1300213010
1 => {
@@ -13227,6 +13235,7 @@ impl Writeable for HTLCForwardInfo {
1322713235
write_tlv_fields!(w, {
1322813236
(0, htlc_id, required),
1322913237
(2, err_packet.data, required),
13238+
(5, err_packet.attribution_data, option),
1323013239
});
1323113240
},
1323213241
Self::FailMalformedHTLC { htlc_id, failure_code, sha256_of_onion } => {
@@ -13257,8 +13266,12 @@ impl Readable for HTLCForwardInfo {
1325713266
(1, malformed_htlc_failure_code, option),
1325813267
(2, err_packet, required),
1325913268
(3, sha256_of_onion, option),
13269+
(5, attribution_data, option),
1326013270
});
1326113271
if let Some(failure_code) = malformed_htlc_failure_code {
13272+
if attribution_data.is_some() {
13273+
return Err(DecodeError::InvalidValue);
13274+
}
1326213275
Self::FailMalformedHTLC {
1326313276
htlc_id: _init_tlv_based_struct_field!(htlc_id, required),
1326413277
failure_code,
@@ -13269,6 +13282,7 @@ impl Readable for HTLCForwardInfo {
1326913282
htlc_id: _init_tlv_based_struct_field!(htlc_id, required),
1327013283
err_packet: crate::ln::msgs::OnionErrorPacket {
1327113284
data: _init_tlv_based_struct_field!(err_packet, required),
13285+
attribution_data: _init_tlv_based_struct_field!(attribution_data, option),
1327213286
},
1327313287
}
1327413288
}
@@ -14954,6 +14968,7 @@ mod tests {
1495414968
use bitcoin::secp256k1::ecdh::SharedSecret;
1495514969
use core::sync::atomic::Ordering;
1495614970
use crate::events::{Event, HTLCDestination, ClosureReason};
14971+
use crate::ln::onion_utils::ATTRIBUTION_DATA_LEN;
1495714972
use crate::ln::types::ChannelId;
1495814973
use crate::types::payment::{PaymentPreimage, PaymentHash, PaymentSecret};
1495914974
use crate::ln::channelmanager::{create_recv_pending_htlc_info, inbound_payment, ChannelConfigOverrides, HTLCForwardInfo, InterceptId, PaymentId, RecipientOnionFields};
@@ -16276,7 +16291,7 @@ mod tests {
1627616291
let mut nodes = create_network(1, &node_cfg, &chanmgrs);
1627716292

1627816293
let dummy_failed_htlc = |htlc_id| {
16279-
HTLCForwardInfo::FailHTLC { htlc_id, err_packet: msgs::OnionErrorPacket { data: vec![42] } }
16294+
HTLCForwardInfo::FailHTLC { htlc_id, err_packet: msgs::OnionErrorPacket { data: vec![42], attribution_data: Some([0; ATTRIBUTION_DATA_LEN]) } }
1628016295
};
1628116296
let dummy_malformed_htlc = |htlc_id| {
1628216297
HTLCForwardInfo::FailMalformedHTLC { htlc_id, failure_code: 0x4000, sha256_of_onion: [0; 32] }

0 commit comments

Comments
 (0)