Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 10 additions & 1 deletion fuzz/src/process_onion_failure.rs
Original file line numberDiff line numberDiff line change
Expand Up@@ -118,8 +118,17 @@ fn do_test<Out: test_logger::Output>(data: &[u8], out: Out) {
HTLCSource::OutboundRoute { path, session_priv, first_hop_htlc_msat: 0, payment_id };

let failure_len = get_u16!();
let encrypted_packet = OnionErrorPacket { data: get_slice!(failure_len).into() };
let failure_data = get_slice!(failure_len);

let attribution_data = if get_bool!() {
Some(lightning::ln::AttributionData {
hold_times: get_slice!(80).try_into().unwrap(),
hmacs: get_slice!(840).try_into().unwrap(),
})
} else {
None
};
let encrypted_packet = OnionErrorPacket { data: failure_data.into(), attribution_data };
lightning::ln::process_onion_failure(&secp_ctx, &logger, &htlc_source, encrypted_packet);
}

Expand Down
106 changes: 97 additions & 9 deletions lightning/src/ln/channel.rs
Original file line numberDiff line numberDiff line change
Expand Up@@ -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, AttributionData};
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};
Expand All@@ -68,6 +68,7 @@ use crate::util::scid_utils::scid_from_parts;

use crate::io;
Comment thread
joostjager marked this conversation as resolved.
use crate::prelude::*;
use core::time::Duration;
use core::{cmp,mem,fmt};
use core::ops::Deref;
#[cfg(any(test, fuzzing, debug_assertions))]
Expand DownExpand Up@@ -323,6 +324,7 @@ struct OutboundHTLCOutput {
source: HTLCSource,
blinding_point: Option<PublicKey>,
skimmed_fee_msat: Option<u64>,
send_timestamp: Option<Duration>,
}

/// See AwaitingRemoteRevoke ChannelState for more info
Expand DownExpand Up@@ -4907,7 +4909,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.data }
msgs::UpdateFailHTLC { htlc_id, channel_id, reason: self.data, attribution_data: self.attribution_data }
}
fn to_inbound_htlc_state(self) -> InboundHTLCState {
InboundHTLCState::LocalRemoved(InboundHTLCRemovalReason::FailRelay(self))
Expand DownExpand Up@@ -6073,10 +6075,16 @@ impl<SP: Deref> FundedChannel<SP> where
false
} else { true }
});
let now = duration_since_epoch();
pending_outbound_htlcs.retain(|htlc| {
if let &OutboundHTLCState::AwaitingRemovedRemoteRevoke(ref outcome) = &htlc.state {
log_trace!(logger, " ...removing outbound AwaitingRemovedRemoteRevoke {}", &htlc.payment_hash);
if let OutboundHTLCOutcome::Failure(reason) = outcome.clone() { // We really want take() here, but, again, non-mut ref :(
if let OutboundHTLCOutcome::Failure(mut reason) = outcome.clone() { // We really want take() here, but, again, non-mut ref :(
if let (Some(timestamp), Some(now)) = (htlc.send_timestamp, now) {
let hold_time = u32::try_from(now.saturating_sub(timestamp).as_millis()).unwrap_or(u32::MAX);
reason.set_hold_time(hold_time);
}

revoked_htlcs.push((htlc.source.clone(), htlc.payment_hash, reason));
} else {
finalized_claimed_htlcs.push(htlc.source.clone());
Expand DownExpand Up@@ -6821,6 +6829,7 @@ impl<SP: Deref> FundedChannel<SP> where
channel_id: self.context.channel_id(),
htlc_id: htlc.htlc_id,
reason: err_packet.data.clone(),
attribution_data: err_packet.attribution_data.clone(),
});
},
&InboundHTLCRemovalReason::FailMalformed((ref sha256_of_onion, ref failure_code)) => {
Expand DownExpand Up@@ -8647,6 +8656,13 @@ impl<SP: Deref> FundedChannel<SP> where
return Ok(None);
}

// Record the approximate time when the HTLC is sent to the peer. This timestamp is later used to calculate the
// htlc hold time for reporting back to the sender. There is some freedom to report a time including or
// excluding our own processing time. What we choose here doesn't matter all that much, because it will probably
// just shift sender-applied penalties between our incoming and outgoing side. So we choose measuring points
// that are simple to implement, and we do it on the outgoing side because then the failure message that encodes
// the hold time still needs to be built in channel manager.
let send_timestamp = duration_since_epoch();

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it'd be nice to set this when we push into the holding cell as well, since in practice something getting stuck in the holding cell may be the fault of our peer.

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

When it gets out of the holding cell, I think it will again get to this point. The reported hold time will be shorter because the timestamp is later, but I don't think it is a problem because of the reason mentioned in the comment. Definitely good enough for this stage I think.

self.context.pending_outbound_htlcs.push(OutboundHTLCOutput {
htlc_id: self.context.next_holder_htlc_id,
amount_msat,
Expand All@@ -8656,6 +8672,7 @@ impl<SP: Deref> FundedChannel<SP> where
source,
blinding_point,
skimmed_fee_msat,
send_timestamp,
});

let res = msgs::UpdateAddHTLC {
Expand DownExpand Up@@ -10225,6 +10242,7 @@ impl<SP: Deref> Writeable for FundedChannel<SP> where SP::Target: SignerProvider
dropped_inbound_htlcs += 1;
}
}
let mut removed_htlc_failure_attribution_data: Vec<&Option<AttributionData>> = Vec::new();
(self.context.pending_inbound_htlcs.len() as u64 - dropped_inbound_htlcs).write(writer)?;
for htlc in self.context.pending_inbound_htlcs.iter() {
if let &InboundHTLCState::RemoteAnnounced(_) = &htlc.state {
Expand All@@ -10250,9 +10268,10 @@ impl<SP: Deref> Writeable for FundedChannel<SP> where SP::Target: SignerProvider
&InboundHTLCState::LocalRemoved(ref removal_reason) => {
4u8.write(writer)?;
match removal_reason {
InboundHTLCRemovalReason::FailRelay(msgs::OnionErrorPacket { data }) => {
InboundHTLCRemovalReason::FailRelay(msgs::OnionErrorPacket { data, attribution_data }) => {
0u8.write(writer)?;
data.write(writer)?;
removed_htlc_failure_attribution_data.push(&attribution_data);
},
InboundHTLCRemovalReason::FailMalformed((hash, code)) => {
1u8.write(writer)?;
Expand DownExpand Up@@ -10312,11 +10331,13 @@ impl<SP: Deref> Writeable for FundedChannel<SP> where SP::Target: SignerProvider
pending_outbound_blinding_points.push(htlc.blinding_point);
}

let mut holding_cell_skimmed_fees: Vec<Option<u64>> = Vec::new();
let mut holding_cell_blinding_points: Vec<Option<PublicKey>> = Vec::new();
let holding_cell_htlc_update_count = self.context.holding_cell_htlc_updates.len();
let mut holding_cell_skimmed_fees: Vec<Option<u64>> = Vec::with_capacity(holding_cell_htlc_update_count);
let mut holding_cell_blinding_points: Vec<Option<PublicKey>> = Vec::with_capacity(holding_cell_htlc_update_count);
let mut holding_cell_failure_attribution_data: Vec<Option<&AttributionData>> = Vec::with_capacity(holding_cell_htlc_update_count);
// Vec of (htlc_id, failure_code, sha256_of_onion)
let mut malformed_htlcs: Vec<(u64, u16, [u8; 32])> = Vec::new();
(self.context.holding_cell_htlc_updates.len() as u64).write(writer)?;
(holding_cell_htlc_update_count as u64).write(writer)?;
for update in self.context.holding_cell_htlc_updates.iter() {
match update {
&HTLCUpdateAwaitingACK::AddHTLC {
Expand All@@ -10342,6 +10363,9 @@ impl<SP: Deref> Writeable for FundedChannel<SP> where SP::Target: SignerProvider
2u8.write(writer)?;
htlc_id.write(writer)?;
err_packet.data.write(writer)?;

// Store the attribution data for later writing.
holding_cell_failure_attribution_data.push(err_packet.attribution_data.as_ref());
}
&HTLCUpdateAwaitingACK::FailMalformedHTLC {
htlc_id, failure_code, sha256_of_onion
Expand All@@ -10353,6 +10377,10 @@ impl<SP: Deref> Writeable for FundedChannel<SP> where SP::Target: SignerProvider
2u8.write(writer)?;
htlc_id.write(writer)?;
Vec::<u8>::new().write(writer)?;

// Push 'None' attribution data for FailMalformedHTLC, because FailMalformedHTLC uses the same
// type 2 and is deserialized as a FailHTLC.
holding_cell_failure_attribution_data.push(None);
}
}
}
Expand DownExpand Up@@ -10525,6 +10553,8 @@ impl<SP: Deref> Writeable for FundedChannel<SP> where SP::Target: SignerProvider
(49, self.context.local_initiated_shutdown, option), // Added in 0.0.122
(51, is_manual_broadcast, option), // Added in 0.0.124
(53, funding_tx_broadcast_safe_event_emitted, option), // Added in 0.0.124
(55, removed_htlc_failure_attribution_data, optional_vec), // Added in 0.2
(57, holding_cell_failure_attribution_data, optional_vec), // Added in 0.2
});

Ok(())
Expand DownExpand Up@@ -10602,6 +10632,7 @@ impl<'a, 'b, 'c, ES: Deref, SP: Deref> ReadableArgs<(&'a ES, &'b SP, &'c Channel
let reason = match <u8 as Readable>::read(reader)? {
0 => InboundHTLCRemovalReason::FailRelay(msgs::OnionErrorPacket {
data: Readable::read(reader)?,
attribution_data: None,
}),
1 => InboundHTLCRemovalReason::FailMalformed(Readable::read(reader)?),
2 => InboundHTLCRemovalReason::Fulfill(Readable::read(reader)?),
Expand DownExpand Up@@ -10642,6 +10673,7 @@ impl<'a, 'b, 'c, ES: Deref, SP: Deref> ReadableArgs<(&'a ES, &'b SP, &'c Channel
},
skimmed_fee_msat: None,
blinding_point: None,
send_timestamp: None,
});
}

Expand All@@ -10666,6 +10698,7 @@ impl<'a, 'b, 'c, ES: Deref, SP: Deref> ReadableArgs<(&'a ES, &'b SP, &'c Channel
htlc_id: Readable::read(reader)?,
err_packet: OnionErrorPacket {
data: Readable::read(reader)?,
attribution_data: None,
},
},
_ => return Err(DecodeError::InvalidValue),
Expand DownExpand Up@@ -10809,6 +10842,9 @@ impl<'a, 'b, 'c, ES: Deref, SP: Deref> ReadableArgs<(&'a ES, &'b SP, &'c Channel
let mut pending_outbound_blinding_points_opt: Option<Vec<Option<PublicKey>>> = None;
let mut holding_cell_blinding_points_opt: Option<Vec<Option<PublicKey>>> = None;

let mut removed_htlc_failure_attribution_data: Option<Vec<Option<AttributionData>>> = None;
let mut holding_cell_failure_attribution_data: Option<Vec<Option<AttributionData>>> = None;

let mut malformed_htlcs: Option<Vec<(u64, u16, [u8; 32])>> = None;
let mut monitor_pending_update_adds: Option<Vec<msgs::UpdateAddHTLC>> = None;

Expand DownExpand Up@@ -10851,6 +10887,8 @@ impl<'a, 'b, 'c, ES: Deref, SP: Deref> ReadableArgs<(&'a ES, &'b SP, &'c Channel
(49, local_initiated_shutdown, option),
(51, is_manual_broadcast, option),
(53, funding_tx_broadcast_safe_event_emitted, option),
(55, removed_htlc_failure_attribution_data, optional_vec),
(57, holding_cell_failure_attribution_data, optional_vec),
});

let holder_signer = signer_provider.derive_channel_signer(channel_keys_id);
Expand DownExpand Up@@ -10932,6 +10970,38 @@ impl<'a, 'b, 'c, ES: Deref, SP: Deref> ReadableArgs<(&'a ES, &'b SP, &'c Channel
if iter.next().is_some() { return Err(DecodeError::InvalidValue) }
}

if let Some(attribution_data_list) = removed_htlc_failure_attribution_data {
let mut removed_htlc_relay_failures =
pending_inbound_htlcs.iter_mut().filter_map(|status|
if let InboundHTLCState::LocalRemoved(InboundHTLCRemovalReason::FailRelay(ref mut packet)) = &mut status.state {
Some(&mut packet.attribution_data)
} else {
None
}
);

for attribution_data in attribution_data_list {
*removed_htlc_relay_failures.next().ok_or(DecodeError::InvalidValue)? = attribution_data;
}
if removed_htlc_relay_failures.next().is_some() { return Err(DecodeError::InvalidValue); }
}

if let Some(attribution_data_list) = holding_cell_failure_attribution_data {
let mut holding_cell_failures =
holding_cell_htlc_updates.iter_mut().filter_map(|upd|
if let HTLCUpdateAwaitingACK::FailHTLC { err_packet: OnionErrorPacket { ref mut attribution_data, .. }, .. } = upd {
Some(attribution_data)
} else {
None
}
);

for attribution_data in attribution_data_list {
*holding_cell_failures.next().ok_or(DecodeError::InvalidValue)? = attribution_data;
}
if holding_cell_failures.next().is_some() { return Err(DecodeError::InvalidValue); }
}

if let Some(malformed_htlcs) = malformed_htlcs {
for (malformed_htlc_id, failure_code, sha256_of_onion) in malformed_htlcs {
let htlc_idx = holding_cell_htlc_updates.iter().position(|htlc| {
Expand DownExpand Up@@ -11122,6 +11192,18 @@ impl<'a, 'b, 'c, ES: Deref, SP: Deref> ReadableArgs<(&'a ES, &'b SP, &'c Channel
}
}

fn duration_since_epoch() -> Option<Duration> {
#[cfg(not(feature = "std"))]
let now = None;

#[cfg(feature = "std")]
let now = Some(std::time::SystemTime::now()
.duration_since(std::time::SystemTime::UNIX_EPOCH)
.expect("SystemTime::now() should come after SystemTime::UNIX_EPOCH"));

now
}

#[cfg(test)]
mod tests {
use std::cmp;
Expand All@@ -11135,7 +11217,7 @@ mod tests {
use bitcoin::network::Network;
#[cfg(splicing)]
use bitcoin::Weight;
use crate::ln::onion_utils::INVALID_ONION_BLINDING;
use crate::ln::onion_utils::{AttributionData, INVALID_ONION_BLINDING};
use crate::types::payment::{PaymentHash, PaymentPreimage};
use crate::ln::channel_keys::{RevocationKey, RevocationBasepoint};
use crate::ln::channelmanager::{self, HTLCSource, PaymentId};
Expand DownExpand Up@@ -11357,6 +11439,7 @@ mod tests {
},
skimmed_fee_msat: None,
blinding_point: None,
send_timestamp: None,
});

// Make sure when Node A calculates their local commitment transaction, none of the HTLCs pass
Expand DownExpand Up@@ -11741,6 +11824,7 @@ mod tests {
source: dummy_htlc_source.clone(),
skimmed_fee_msat: None,
blinding_point: None,
send_timestamp: None,
};
let mut pending_outbound_htlcs = vec![dummy_outbound_output.clone(); 10];
for (idx, htlc) in pending_outbound_htlcs.iter_mut().enumerate() {
Expand DownExpand Up@@ -11772,7 +11856,7 @@ mod tests {
htlc_id: 0,
};
let dummy_holding_cell_failed_htlc = |htlc_id| HTLCUpdateAwaitingACK::FailHTLC {
htlc_id, err_packet: msgs::OnionErrorPacket { data: vec![42] }
htlc_id, err_packet: msgs::OnionErrorPacket { data: vec![42], attribution_data: Some(AttributionData::new()) }
};
let dummy_holding_cell_malformed_htlc = |htlc_id| HTLCUpdateAwaitingACK::FailMalformedHTLC {
htlc_id, failure_code: INVALID_ONION_BLINDING, sha256_of_onion: [0; 32],
Expand DownExpand Up@@ -12054,6 +12138,7 @@ mod tests {
source: HTLCSource::dummy(),
skimmed_fee_msat: None,
blinding_point: None,
send_timestamp: None,
};
out.payment_hash.0 = Sha256::hash(&<Vec<u8>>::from_hex("0202020202020202020202020202020202020202020202020202020202020202").unwrap()).to_byte_array();
out
Expand All@@ -12068,6 +12153,7 @@ mod tests {
source: HTLCSource::dummy(),
skimmed_fee_msat: None,
blinding_point: None,
send_timestamp: None,
};
out.payment_hash.0 = Sha256::hash(&<Vec<u8>>::from_hex("0303030303030303030303030303030303030303030303030303030303030303").unwrap()).to_byte_array();
out
Expand DownExpand Up@@ -12480,6 +12566,7 @@ mod tests {
source: HTLCSource::dummy(),
skimmed_fee_msat: None,
blinding_point: None,
send_timestamp: None,
};
out.payment_hash.0 = Sha256::hash(&<Vec<u8>>::from_hex("0505050505050505050505050505050505050505050505050505050505050505").unwrap()).to_byte_array();
out
Expand All@@ -12494,6 +12581,7 @@ mod tests {
source: HTLCSource::dummy(),
skimmed_fee_msat: None,
blinding_point: None,
send_timestamp: None,
};
out.payment_hash.0 = Sha256::hash(&<Vec<u8>>::from_hex("0505050505050505050505050505050505050505050505050505050505050505").unwrap()).to_byte_array();
out
Expand Down
Loading
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Add copy buttons to all
 blocks\n(function() {\n function addCopyButtons() {\n document.querySelectorAll('pre code').forEach(function(codeBlock) {\n if (codeBlock.parentElement.hasAttribute('data-copy-added')) return;\n codeBlock.parentElement.setAttribute('data-copy-added', 'true');\n \n var btn = document.createElement('button');\n btn.textContent = 'Copy';\n btn.style.cssText = 'position:absolute;top:4px;right:4px;padding:2px 8px;font-size:11px;background:#4ecdc4;border:none;border-radius:4px;color:#1a1a2e;cursor:pointer;opacity:0.7;transition:opacity 0.2s;';\n btn.onmouseover = function() { this.style.opacity = '1'; };\n btn.onmouseout = function() { this.style.opacity = '0.7'; };\n btn.onclick = function() {\n navigator.clipboard.writeText(codeBlock.textContent).then(function() {\n btn.textContent = 'Copied!';\n setTimeout(function() { btn.textContent = 'Copy'; }, 1500);\n });\n };\n codeBlock.parentElement.style.position = 'relative';\n codeBlock.parentElement.appendChild(btn);\n });\n }\n \n addCopyButtons();\n \n // Re-run on dynamic content\n var observer = new MutationObserver(addCopyButtons);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Add Copy Buttons to Code Blocks");
}
} catch(__e) { console.warn('[Userscript:Add Copy Buttons to Code Blocks]', __e); }
})();
(function(){
try {
var __m = "github.com";
var __re = new RegExp('^' + "github\\.com" + '
Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 10 additions & 1 deletion fuzz/src/process_onion_failure.rs
Original file line numberDiff line numberDiff line change
Expand Up@@ -118,8 +118,17 @@ fn do_test<Out: test_logger::Output>(data: &[u8], out: Out) {
HTLCSource::OutboundRoute { path, session_priv, first_hop_htlc_msat: 0, payment_id };

let failure_len = get_u16!();
let encrypted_packet = OnionErrorPacket { data: get_slice!(failure_len).into() };
let failure_data = get_slice!(failure_len);

let attribution_data = if get_bool!() {
Some(lightning::ln::AttributionData {
hold_times: get_slice!(80).try_into().unwrap(),
hmacs: get_slice!(840).try_into().unwrap(),
})
} else {
None
};
let encrypted_packet = OnionErrorPacket { data: failure_data.into(), attribution_data };
lightning::ln::process_onion_failure(&secp_ctx, &logger, &htlc_source, encrypted_packet);
}

Expand Down
106 changes: 97 additions & 9 deletions lightning/src/ln/channel.rs
Original file line numberDiff line numberDiff line change
Expand Up@@ -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, AttributionData};
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};
Expand All@@ -68,6 +68,7 @@ use crate::util::scid_utils::scid_from_parts;

use crate::io;
Comment thread
joostjager marked this conversation as resolved.
use crate::prelude::*;
use core::time::Duration;
use core::{cmp,mem,fmt};
use core::ops::Deref;
#[cfg(any(test, fuzzing, debug_assertions))]
Expand DownExpand Up@@ -323,6 +324,7 @@ struct OutboundHTLCOutput {
source: HTLCSource,
blinding_point: Option<PublicKey>,
skimmed_fee_msat: Option<u64>,
send_timestamp: Option<Duration>,
}

/// See AwaitingRemoteRevoke ChannelState for more info
Expand DownExpand Up@@ -4907,7 +4909,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.data }
msgs::UpdateFailHTLC { htlc_id, channel_id, reason: self.data, attribution_data: self.attribution_data }
}
fn to_inbound_htlc_state(self) -> InboundHTLCState {
InboundHTLCState::LocalRemoved(InboundHTLCRemovalReason::FailRelay(self))
Expand DownExpand Up@@ -6073,10 +6075,16 @@ impl<SP: Deref> FundedChannel<SP> where
false
} else { true }
});
let now = duration_since_epoch();
pending_outbound_htlcs.retain(|htlc| {
if let &OutboundHTLCState::AwaitingRemovedRemoteRevoke(ref outcome) = &htlc.state {
log_trace!(logger, " ...removing outbound AwaitingRemovedRemoteRevoke {}", &htlc.payment_hash);
if let OutboundHTLCOutcome::Failure(reason) = outcome.clone() { // We really want take() here, but, again, non-mut ref :(
if let OutboundHTLCOutcome::Failure(mut reason) = outcome.clone() { // We really want take() here, but, again, non-mut ref :(
if let (Some(timestamp), Some(now)) = (htlc.send_timestamp, now) {
let hold_time = u32::try_from(now.saturating_sub(timestamp).as_millis()).unwrap_or(u32::MAX);
reason.set_hold_time(hold_time);
}

revoked_htlcs.push((htlc.source.clone(), htlc.payment_hash, reason));
} else {
finalized_claimed_htlcs.push(htlc.source.clone());
Expand DownExpand Up@@ -6821,6 +6829,7 @@ impl<SP: Deref> FundedChannel<SP> where
channel_id: self.context.channel_id(),
htlc_id: htlc.htlc_id,
reason: err_packet.data.clone(),
attribution_data: err_packet.attribution_data.clone(),
});
},
&InboundHTLCRemovalReason::FailMalformed((ref sha256_of_onion, ref failure_code)) => {
Expand DownExpand Up@@ -8647,6 +8656,13 @@ impl<SP: Deref> FundedChannel<SP> where
return Ok(None);
}

// Record the approximate time when the HTLC is sent to the peer. This timestamp is later used to calculate the
// htlc hold time for reporting back to the sender. There is some freedom to report a time including or
// excluding our own processing time. What we choose here doesn't matter all that much, because it will probably
// just shift sender-applied penalties between our incoming and outgoing side. So we choose measuring points
// that are simple to implement, and we do it on the outgoing side because then the failure message that encodes
// the hold time still needs to be built in channel manager.
let send_timestamp = duration_since_epoch();

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it'd be nice to set this when we push into the holding cell as well, since in practice something getting stuck in the holding cell may be the fault of our peer.

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

When it gets out of the holding cell, I think it will again get to this point. The reported hold time will be shorter because the timestamp is later, but I don't think it is a problem because of the reason mentioned in the comment. Definitely good enough for this stage I think.

self.context.pending_outbound_htlcs.push(OutboundHTLCOutput {
htlc_id: self.context.next_holder_htlc_id,
amount_msat,
Expand All@@ -8656,6 +8672,7 @@ impl<SP: Deref> FundedChannel<SP> where
source,
blinding_point,
skimmed_fee_msat,
send_timestamp,
});

let res = msgs::UpdateAddHTLC {
Expand DownExpand Up@@ -10225,6 +10242,7 @@ impl<SP: Deref> Writeable for FundedChannel<SP> where SP::Target: SignerProvider
dropped_inbound_htlcs += 1;
}
}
let mut removed_htlc_failure_attribution_data: Vec<&Option<AttributionData>> = Vec::new();
(self.context.pending_inbound_htlcs.len() as u64 - dropped_inbound_htlcs).write(writer)?;
for htlc in self.context.pending_inbound_htlcs.iter() {
if let &InboundHTLCState::RemoteAnnounced(_) = &htlc.state {
Expand All@@ -10250,9 +10268,10 @@ impl<SP: Deref> Writeable for FundedChannel<SP> where SP::Target: SignerProvider
&InboundHTLCState::LocalRemoved(ref removal_reason) => {
4u8.write(writer)?;
match removal_reason {
InboundHTLCRemovalReason::FailRelay(msgs::OnionErrorPacket { data }) => {
InboundHTLCRemovalReason::FailRelay(msgs::OnionErrorPacket { data, attribution_data }) => {
0u8.write(writer)?;
data.write(writer)?;
removed_htlc_failure_attribution_data.push(&attribution_data);
},
InboundHTLCRemovalReason::FailMalformed((hash, code)) => {
1u8.write(writer)?;
Expand DownExpand Up@@ -10312,11 +10331,13 @@ impl<SP: Deref> Writeable for FundedChannel<SP> where SP::Target: SignerProvider
pending_outbound_blinding_points.push(htlc.blinding_point);
}

let mut holding_cell_skimmed_fees: Vec<Option<u64>> = Vec::new();
let mut holding_cell_blinding_points: Vec<Option<PublicKey>> = Vec::new();
let holding_cell_htlc_update_count = self.context.holding_cell_htlc_updates.len();
let mut holding_cell_skimmed_fees: Vec<Option<u64>> = Vec::with_capacity(holding_cell_htlc_update_count);
let mut holding_cell_blinding_points: Vec<Option<PublicKey>> = Vec::with_capacity(holding_cell_htlc_update_count);
let mut holding_cell_failure_attribution_data: Vec<Option<&AttributionData>> = Vec::with_capacity(holding_cell_htlc_update_count);
// Vec of (htlc_id, failure_code, sha256_of_onion)
let mut malformed_htlcs: Vec<(u64, u16, [u8; 32])> = Vec::new();
(self.context.holding_cell_htlc_updates.len() as u64).write(writer)?;
(holding_cell_htlc_update_count as u64).write(writer)?;
for update in self.context.holding_cell_htlc_updates.iter() {
match update {
&HTLCUpdateAwaitingACK::AddHTLC {
Expand All@@ -10342,6 +10363,9 @@ impl<SP: Deref> Writeable for FundedChannel<SP> where SP::Target: SignerProvider
2u8.write(writer)?;
htlc_id.write(writer)?;
err_packet.data.write(writer)?;

// Store the attribution data for later writing.
holding_cell_failure_attribution_data.push(err_packet.attribution_data.as_ref());
}
&HTLCUpdateAwaitingACK::FailMalformedHTLC {
htlc_id, failure_code, sha256_of_onion
Expand All@@ -10353,6 +10377,10 @@ impl<SP: Deref> Writeable for FundedChannel<SP> where SP::Target: SignerProvider
2u8.write(writer)?;
htlc_id.write(writer)?;
Vec::<u8>::new().write(writer)?;

// Push 'None' attribution data for FailMalformedHTLC, because FailMalformedHTLC uses the same
// type 2 and is deserialized as a FailHTLC.
holding_cell_failure_attribution_data.push(None);
}
}
}
Expand DownExpand Up@@ -10525,6 +10553,8 @@ impl<SP: Deref> Writeable for FundedChannel<SP> where SP::Target: SignerProvider
(49, self.context.local_initiated_shutdown, option), // Added in 0.0.122
(51, is_manual_broadcast, option), // Added in 0.0.124
(53, funding_tx_broadcast_safe_event_emitted, option), // Added in 0.0.124
(55, removed_htlc_failure_attribution_data, optional_vec), // Added in 0.2
(57, holding_cell_failure_attribution_data, optional_vec), // Added in 0.2
});

Ok(())
Expand DownExpand Up@@ -10602,6 +10632,7 @@ impl<'a, 'b, 'c, ES: Deref, SP: Deref> ReadableArgs<(&'a ES, &'b SP, &'c Channel
let reason = match <u8 as Readable>::read(reader)? {
0 => InboundHTLCRemovalReason::FailRelay(msgs::OnionErrorPacket {
data: Readable::read(reader)?,
attribution_data: None,
}),
1 => InboundHTLCRemovalReason::FailMalformed(Readable::read(reader)?),
2 => InboundHTLCRemovalReason::Fulfill(Readable::read(reader)?),
Expand DownExpand Up@@ -10642,6 +10673,7 @@ impl<'a, 'b, 'c, ES: Deref, SP: Deref> ReadableArgs<(&'a ES, &'b SP, &'c Channel
},
skimmed_fee_msat: None,
blinding_point: None,
send_timestamp: None,
});
}

Expand All@@ -10666,6 +10698,7 @@ impl<'a, 'b, 'c, ES: Deref, SP: Deref> ReadableArgs<(&'a ES, &'b SP, &'c Channel
htlc_id: Readable::read(reader)?,
err_packet: OnionErrorPacket {
data: Readable::read(reader)?,
attribution_data: None,
},
},
_ => return Err(DecodeError::InvalidValue),
Expand DownExpand Up@@ -10809,6 +10842,9 @@ impl<'a, 'b, 'c, ES: Deref, SP: Deref> ReadableArgs<(&'a ES, &'b SP, &'c Channel
let mut pending_outbound_blinding_points_opt: Option<Vec<Option<PublicKey>>> = None;
let mut holding_cell_blinding_points_opt: Option<Vec<Option<PublicKey>>> = None;

let mut removed_htlc_failure_attribution_data: Option<Vec<Option<AttributionData>>> = None;
let mut holding_cell_failure_attribution_data: Option<Vec<Option<AttributionData>>> = None;

let mut malformed_htlcs: Option<Vec<(u64, u16, [u8; 32])>> = None;
let mut monitor_pending_update_adds: Option<Vec<msgs::UpdateAddHTLC>> = None;

Expand DownExpand Up@@ -10851,6 +10887,8 @@ impl<'a, 'b, 'c, ES: Deref, SP: Deref> ReadableArgs<(&'a ES, &'b SP, &'c Channel
(49, local_initiated_shutdown, option),
(51, is_manual_broadcast, option),
(53, funding_tx_broadcast_safe_event_emitted, option),
(55, removed_htlc_failure_attribution_data, optional_vec),
(57, holding_cell_failure_attribution_data, optional_vec),
});

let holder_signer = signer_provider.derive_channel_signer(channel_keys_id);
Expand DownExpand Up@@ -10932,6 +10970,38 @@ impl<'a, 'b, 'c, ES: Deref, SP: Deref> ReadableArgs<(&'a ES, &'b SP, &'c Channel
if iter.next().is_some() { return Err(DecodeError::InvalidValue) }
}

if let Some(attribution_data_list) = removed_htlc_failure_attribution_data {
let mut removed_htlc_relay_failures =
pending_inbound_htlcs.iter_mut().filter_map(|status|
if let InboundHTLCState::LocalRemoved(InboundHTLCRemovalReason::FailRelay(ref mut packet)) = &mut status.state {
Some(&mut packet.attribution_data)
} else {
None
}
);

for attribution_data in attribution_data_list {
*removed_htlc_relay_failures.next().ok_or(DecodeError::InvalidValue)? = attribution_data;
}
if removed_htlc_relay_failures.next().is_some() { return Err(DecodeError::InvalidValue); }
}

if let Some(attribution_data_list) = holding_cell_failure_attribution_data {
let mut holding_cell_failures =
holding_cell_htlc_updates.iter_mut().filter_map(|upd|
if let HTLCUpdateAwaitingACK::FailHTLC { err_packet: OnionErrorPacket { ref mut attribution_data, .. }, .. } = upd {
Some(attribution_data)
} else {
None
}
);

for attribution_data in attribution_data_list {
*holding_cell_failures.next().ok_or(DecodeError::InvalidValue)? = attribution_data;
}
if holding_cell_failures.next().is_some() { return Err(DecodeError::InvalidValue); }
}

if let Some(malformed_htlcs) = malformed_htlcs {
for (malformed_htlc_id, failure_code, sha256_of_onion) in malformed_htlcs {
let htlc_idx = holding_cell_htlc_updates.iter().position(|htlc| {
Expand DownExpand Up@@ -11122,6 +11192,18 @@ impl<'a, 'b, 'c, ES: Deref, SP: Deref> ReadableArgs<(&'a ES, &'b SP, &'c Channel
}
}

fn duration_since_epoch() -> Option<Duration> {
#[cfg(not(feature = "std"))]
let now = None;

#[cfg(feature = "std")]
let now = Some(std::time::SystemTime::now()
.duration_since(std::time::SystemTime::UNIX_EPOCH)
.expect("SystemTime::now() should come after SystemTime::UNIX_EPOCH"));

now
}

#[cfg(test)]
mod tests {
use std::cmp;
Expand All@@ -11135,7 +11217,7 @@ mod tests {
use bitcoin::network::Network;
#[cfg(splicing)]
use bitcoin::Weight;
use crate::ln::onion_utils::INVALID_ONION_BLINDING;
use crate::ln::onion_utils::{AttributionData, INVALID_ONION_BLINDING};
use crate::types::payment::{PaymentHash, PaymentPreimage};
use crate::ln::channel_keys::{RevocationKey, RevocationBasepoint};
use crate::ln::channelmanager::{self, HTLCSource, PaymentId};
Expand DownExpand Up@@ -11357,6 +11439,7 @@ mod tests {
},
skimmed_fee_msat: None,
blinding_point: None,
send_timestamp: None,
});

// Make sure when Node A calculates their local commitment transaction, none of the HTLCs pass
Expand DownExpand Up@@ -11741,6 +11824,7 @@ mod tests {
source: dummy_htlc_source.clone(),
skimmed_fee_msat: None,
blinding_point: None,
send_timestamp: None,
};
let mut pending_outbound_htlcs = vec![dummy_outbound_output.clone(); 10];
for (idx, htlc) in pending_outbound_htlcs.iter_mut().enumerate() {
Expand DownExpand Up@@ -11772,7 +11856,7 @@ mod tests {
htlc_id: 0,
};
let dummy_holding_cell_failed_htlc = |htlc_id| HTLCUpdateAwaitingACK::FailHTLC {
htlc_id, err_packet: msgs::OnionErrorPacket { data: vec![42] }
htlc_id, err_packet: msgs::OnionErrorPacket { data: vec![42], attribution_data: Some(AttributionData::new()) }
};
let dummy_holding_cell_malformed_htlc = |htlc_id| HTLCUpdateAwaitingACK::FailMalformedHTLC {
htlc_id, failure_code: INVALID_ONION_BLINDING, sha256_of_onion: [0; 32],
Expand DownExpand Up@@ -12054,6 +12138,7 @@ mod tests {
source: HTLCSource::dummy(),
skimmed_fee_msat: None,
blinding_point: None,
send_timestamp: None,
};
out.payment_hash.0 = Sha256::hash(&<Vec<u8>>::from_hex("0202020202020202020202020202020202020202020202020202020202020202").unwrap()).to_byte_array();
out
Expand All@@ -12068,6 +12153,7 @@ mod tests {
source: HTLCSource::dummy(),
skimmed_fee_msat: None,
blinding_point: None,
send_timestamp: None,
};
out.payment_hash.0 = Sha256::hash(&<Vec<u8>>::from_hex("0303030303030303030303030303030303030303030303030303030303030303").unwrap()).to_byte_array();
out
Expand DownExpand Up@@ -12480,6 +12566,7 @@ mod tests {
source: HTLCSource::dummy(),
skimmed_fee_msat: None,
blinding_point: None,
send_timestamp: None,
};
out.payment_hash.0 = Sha256::hash(&<Vec<u8>>::from_hex("0505050505050505050505050505050505050505050505050505050505050505").unwrap()).to_byte_array();
out
Expand All@@ -12494,6 +12581,7 @@ mod tests {
source: HTLCSource::dummy(),
skimmed_fee_msat: None,
blinding_point: None,
send_timestamp: None,
};
out.payment_hash.0 = Sha256::hash(&<Vec<u8>>::from_hex("0505050505050505050505050505050505050505050505050505050505050505").unwrap()).to_byte_array();
out
Expand Down
Loading
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Force GitHub README to respect dark mode\n(function() {\n var style = document.createElement('style');\n style.textContent = '\n .markdown-body {\n color-scheme: dark light;\n }\n .markdown-body pre { background: #161b22 !important; }\n .markdown-body code { background: rgba(110, 118, 129, 0.4) !important; }\n .markdown-body table th, .markdown-body table td { border-color: #30363d !important; }\n .markdown-body img { background: #0d1117; }\n .markdown-body blockquote { border-left-color: #8b949e; }\n .markdown-body hr { border-color: #30363d; }\n ';\n document.head.appendChild(style);\n})();", "GitHub Dark Mode README Fix"); } } catch(__e) { console.warn('[Userscript:GitHub Dark Mode README Fix]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 10 additions & 1 deletion fuzz/src/process_onion_failure.rs
Original file line numberDiff line numberDiff line change
Expand Up@@ -118,8 +118,17 @@ fn do_test<Out: test_logger::Output>(data: &[u8], out: Out) {
HTLCSource::OutboundRoute { path, session_priv, first_hop_htlc_msat: 0, payment_id };

let failure_len = get_u16!();
let encrypted_packet = OnionErrorPacket { data: get_slice!(failure_len).into() };
let failure_data = get_slice!(failure_len);

let attribution_data = if get_bool!() {
Some(lightning::ln::AttributionData {
hold_times: get_slice!(80).try_into().unwrap(),
hmacs: get_slice!(840).try_into().unwrap(),
})
} else {
None
};
let encrypted_packet = OnionErrorPacket { data: failure_data.into(), attribution_data };
lightning::ln::process_onion_failure(&secp_ctx, &logger, &htlc_source, encrypted_packet);
}

Expand Down
106 changes: 97 additions & 9 deletions lightning/src/ln/channel.rs
Original file line numberDiff line numberDiff line change
Expand Up@@ -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, AttributionData};
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};
Expand All@@ -68,6 +68,7 @@ use crate::util::scid_utils::scid_from_parts;

use crate::io;
Comment thread
joostjager marked this conversation as resolved.
use crate::prelude::*;
use core::time::Duration;
use core::{cmp,mem,fmt};
use core::ops::Deref;
#[cfg(any(test, fuzzing, debug_assertions))]
Expand DownExpand Up@@ -323,6 +324,7 @@ struct OutboundHTLCOutput {
source: HTLCSource,
blinding_point: Option<PublicKey>,
skimmed_fee_msat: Option<u64>,
send_timestamp: Option<Duration>,
}

/// See AwaitingRemoteRevoke ChannelState for more info
Expand DownExpand Up@@ -4907,7 +4909,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.data }
msgs::UpdateFailHTLC { htlc_id, channel_id, reason: self.data, attribution_data: self.attribution_data }
}
fn to_inbound_htlc_state(self) -> InboundHTLCState {
InboundHTLCState::LocalRemoved(InboundHTLCRemovalReason::FailRelay(self))
Expand DownExpand Up@@ -6073,10 +6075,16 @@ impl<SP: Deref> FundedChannel<SP> where
false
} else { true }
});
let now = duration_since_epoch();
pending_outbound_htlcs.retain(|htlc| {
if let &OutboundHTLCState::AwaitingRemovedRemoteRevoke(ref outcome) = &htlc.state {
log_trace!(logger, " ...removing outbound AwaitingRemovedRemoteRevoke {}", &htlc.payment_hash);
if let OutboundHTLCOutcome::Failure(reason) = outcome.clone() { // We really want take() here, but, again, non-mut ref :(
if let OutboundHTLCOutcome::Failure(mut reason) = outcome.clone() { // We really want take() here, but, again, non-mut ref :(
if let (Some(timestamp), Some(now)) = (htlc.send_timestamp, now) {
let hold_time = u32::try_from(now.saturating_sub(timestamp).as_millis()).unwrap_or(u32::MAX);
reason.set_hold_time(hold_time);
}

revoked_htlcs.push((htlc.source.clone(), htlc.payment_hash, reason));
} else {
finalized_claimed_htlcs.push(htlc.source.clone());
Expand DownExpand Up@@ -6821,6 +6829,7 @@ impl<SP: Deref> FundedChannel<SP> where
channel_id: self.context.channel_id(),
htlc_id: htlc.htlc_id,
reason: err_packet.data.clone(),
attribution_data: err_packet.attribution_data.clone(),
});
},
&InboundHTLCRemovalReason::FailMalformed((ref sha256_of_onion, ref failure_code)) => {
Expand DownExpand Up@@ -8647,6 +8656,13 @@ impl<SP: Deref> FundedChannel<SP> where
return Ok(None);
}

// Record the approximate time when the HTLC is sent to the peer. This timestamp is later used to calculate the
// htlc hold time for reporting back to the sender. There is some freedom to report a time including or
// excluding our own processing time. What we choose here doesn't matter all that much, because it will probably
// just shift sender-applied penalties between our incoming and outgoing side. So we choose measuring points
// that are simple to implement, and we do it on the outgoing side because then the failure message that encodes
// the hold time still needs to be built in channel manager.
let send_timestamp = duration_since_epoch();

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it'd be nice to set this when we push into the holding cell as well, since in practice something getting stuck in the holding cell may be the fault of our peer.

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

When it gets out of the holding cell, I think it will again get to this point. The reported hold time will be shorter because the timestamp is later, but I don't think it is a problem because of the reason mentioned in the comment. Definitely good enough for this stage I think.

self.context.pending_outbound_htlcs.push(OutboundHTLCOutput {
htlc_id: self.context.next_holder_htlc_id,
amount_msat,
Expand All@@ -8656,6 +8672,7 @@ impl<SP: Deref> FundedChannel<SP> where
source,
blinding_point,
skimmed_fee_msat,
send_timestamp,
});

let res = msgs::UpdateAddHTLC {
Expand DownExpand Up@@ -10225,6 +10242,7 @@ impl<SP: Deref> Writeable for FundedChannel<SP> where SP::Target: SignerProvider
dropped_inbound_htlcs += 1;
}
}
let mut removed_htlc_failure_attribution_data: Vec<&Option<AttributionData>> = Vec::new();
(self.context.pending_inbound_htlcs.len() as u64 - dropped_inbound_htlcs).write(writer)?;
for htlc in self.context.pending_inbound_htlcs.iter() {
if let &InboundHTLCState::RemoteAnnounced(_) = &htlc.state {
Expand All@@ -10250,9 +10268,10 @@ impl<SP: Deref> Writeable for FundedChannel<SP> where SP::Target: SignerProvider
&InboundHTLCState::LocalRemoved(ref removal_reason) => {
4u8.write(writer)?;
match removal_reason {
InboundHTLCRemovalReason::FailRelay(msgs::OnionErrorPacket { data }) => {
InboundHTLCRemovalReason::FailRelay(msgs::OnionErrorPacket { data, attribution_data }) => {
0u8.write(writer)?;
data.write(writer)?;
removed_htlc_failure_attribution_data.push(&attribution_data);
},
InboundHTLCRemovalReason::FailMalformed((hash, code)) => {
1u8.write(writer)?;
Expand DownExpand Up@@ -10312,11 +10331,13 @@ impl<SP: Deref> Writeable for FundedChannel<SP> where SP::Target: SignerProvider
pending_outbound_blinding_points.push(htlc.blinding_point);
}

let mut holding_cell_skimmed_fees: Vec<Option<u64>> = Vec::new();
let mut holding_cell_blinding_points: Vec<Option<PublicKey>> = Vec::new();
let holding_cell_htlc_update_count = self.context.holding_cell_htlc_updates.len();
let mut holding_cell_skimmed_fees: Vec<Option<u64>> = Vec::with_capacity(holding_cell_htlc_update_count);
let mut holding_cell_blinding_points: Vec<Option<PublicKey>> = Vec::with_capacity(holding_cell_htlc_update_count);
let mut holding_cell_failure_attribution_data: Vec<Option<&AttributionData>> = Vec::with_capacity(holding_cell_htlc_update_count);
// Vec of (htlc_id, failure_code, sha256_of_onion)
let mut malformed_htlcs: Vec<(u64, u16, [u8; 32])> = Vec::new();
(self.context.holding_cell_htlc_updates.len() as u64).write(writer)?;
(holding_cell_htlc_update_count as u64).write(writer)?;
for update in self.context.holding_cell_htlc_updates.iter() {
match update {
&HTLCUpdateAwaitingACK::AddHTLC {
Expand All@@ -10342,6 +10363,9 @@ impl<SP: Deref> Writeable for FundedChannel<SP> where SP::Target: SignerProvider
2u8.write(writer)?;
htlc_id.write(writer)?;
err_packet.data.write(writer)?;

// Store the attribution data for later writing.
holding_cell_failure_attribution_data.push(err_packet.attribution_data.as_ref());
}
&HTLCUpdateAwaitingACK::FailMalformedHTLC {
htlc_id, failure_code, sha256_of_onion
Expand All@@ -10353,6 +10377,10 @@ impl<SP: Deref> Writeable for FundedChannel<SP> where SP::Target: SignerProvider
2u8.write(writer)?;
htlc_id.write(writer)?;
Vec::<u8>::new().write(writer)?;

// Push 'None' attribution data for FailMalformedHTLC, because FailMalformedHTLC uses the same
// type 2 and is deserialized as a FailHTLC.
holding_cell_failure_attribution_data.push(None);
}
}
}
Expand DownExpand Up@@ -10525,6 +10553,8 @@ impl<SP: Deref> Writeable for FundedChannel<SP> where SP::Target: SignerProvider
(49, self.context.local_initiated_shutdown, option), // Added in 0.0.122
(51, is_manual_broadcast, option), // Added in 0.0.124
(53, funding_tx_broadcast_safe_event_emitted, option), // Added in 0.0.124
(55, removed_htlc_failure_attribution_data, optional_vec), // Added in 0.2
(57, holding_cell_failure_attribution_data, optional_vec), // Added in 0.2
});

Ok(())
Expand DownExpand Up@@ -10602,6 +10632,7 @@ impl<'a, 'b, 'c, ES: Deref, SP: Deref> ReadableArgs<(&'a ES, &'b SP, &'c Channel
let reason = match <u8 as Readable>::read(reader)? {
0 => InboundHTLCRemovalReason::FailRelay(msgs::OnionErrorPacket {
data: Readable::read(reader)?,
attribution_data: None,
}),
1 => InboundHTLCRemovalReason::FailMalformed(Readable::read(reader)?),
2 => InboundHTLCRemovalReason::Fulfill(Readable::read(reader)?),
Expand DownExpand Up@@ -10642,6 +10673,7 @@ impl<'a, 'b, 'c, ES: Deref, SP: Deref> ReadableArgs<(&'a ES, &'b SP, &'c Channel
},
skimmed_fee_msat: None,
blinding_point: None,
send_timestamp: None,
});
}

Expand All@@ -10666,6 +10698,7 @@ impl<'a, 'b, 'c, ES: Deref, SP: Deref> ReadableArgs<(&'a ES, &'b SP, &'c Channel
htlc_id: Readable::read(reader)?,
err_packet: OnionErrorPacket {
data: Readable::read(reader)?,
attribution_data: None,
},
},
_ => return Err(DecodeError::InvalidValue),
Expand DownExpand Up@@ -10809,6 +10842,9 @@ impl<'a, 'b, 'c, ES: Deref, SP: Deref> ReadableArgs<(&'a ES, &'b SP, &'c Channel
let mut pending_outbound_blinding_points_opt: Option<Vec<Option<PublicKey>>> = None;
let mut holding_cell_blinding_points_opt: Option<Vec<Option<PublicKey>>> = None;

let mut removed_htlc_failure_attribution_data: Option<Vec<Option<AttributionData>>> = None;
let mut holding_cell_failure_attribution_data: Option<Vec<Option<AttributionData>>> = None;

let mut malformed_htlcs: Option<Vec<(u64, u16, [u8; 32])>> = None;
let mut monitor_pending_update_adds: Option<Vec<msgs::UpdateAddHTLC>> = None;

Expand DownExpand Up@@ -10851,6 +10887,8 @@ impl<'a, 'b, 'c, ES: Deref, SP: Deref> ReadableArgs<(&'a ES, &'b SP, &'c Channel
(49, local_initiated_shutdown, option),
(51, is_manual_broadcast, option),
(53, funding_tx_broadcast_safe_event_emitted, option),
(55, removed_htlc_failure_attribution_data, optional_vec),
(57, holding_cell_failure_attribution_data, optional_vec),
});

let holder_signer = signer_provider.derive_channel_signer(channel_keys_id);
Expand DownExpand Up@@ -10932,6 +10970,38 @@ impl<'a, 'b, 'c, ES: Deref, SP: Deref> ReadableArgs<(&'a ES, &'b SP, &'c Channel
if iter.next().is_some() { return Err(DecodeError::InvalidValue) }
}

if let Some(attribution_data_list) = removed_htlc_failure_attribution_data {
let mut removed_htlc_relay_failures =
pending_inbound_htlcs.iter_mut().filter_map(|status|
if let InboundHTLCState::LocalRemoved(InboundHTLCRemovalReason::FailRelay(ref mut packet)) = &mut status.state {
Some(&mut packet.attribution_data)
} else {
None
}
);

for attribution_data in attribution_data_list {
*removed_htlc_relay_failures.next().ok_or(DecodeError::InvalidValue)? = attribution_data;
}
if removed_htlc_relay_failures.next().is_some() { return Err(DecodeError::InvalidValue); }
}

if let Some(attribution_data_list) = holding_cell_failure_attribution_data {
let mut holding_cell_failures =
holding_cell_htlc_updates.iter_mut().filter_map(|upd|
if let HTLCUpdateAwaitingACK::FailHTLC { err_packet: OnionErrorPacket { ref mut attribution_data, .. }, .. } = upd {
Some(attribution_data)
} else {
None
}
);

for attribution_data in attribution_data_list {
*holding_cell_failures.next().ok_or(DecodeError::InvalidValue)? = attribution_data;
}
if holding_cell_failures.next().is_some() { return Err(DecodeError::InvalidValue); }
}

if let Some(malformed_htlcs) = malformed_htlcs {
for (malformed_htlc_id, failure_code, sha256_of_onion) in malformed_htlcs {
let htlc_idx = holding_cell_htlc_updates.iter().position(|htlc| {
Expand DownExpand Up@@ -11122,6 +11192,18 @@ impl<'a, 'b, 'c, ES: Deref, SP: Deref> ReadableArgs<(&'a ES, &'b SP, &'c Channel
}
}

fn duration_since_epoch() -> Option<Duration> {
#[cfg(not(feature = "std"))]
let now = None;

#[cfg(feature = "std")]
let now = Some(std::time::SystemTime::now()
.duration_since(std::time::SystemTime::UNIX_EPOCH)
.expect("SystemTime::now() should come after SystemTime::UNIX_EPOCH"));

now
}

#[cfg(test)]
mod tests {
use std::cmp;
Expand All@@ -11135,7 +11217,7 @@ mod tests {
use bitcoin::network::Network;
#[cfg(splicing)]
use bitcoin::Weight;
use crate::ln::onion_utils::INVALID_ONION_BLINDING;
use crate::ln::onion_utils::{AttributionData, INVALID_ONION_BLINDING};
use crate::types::payment::{PaymentHash, PaymentPreimage};
use crate::ln::channel_keys::{RevocationKey, RevocationBasepoint};
use crate::ln::channelmanager::{self, HTLCSource, PaymentId};
Expand DownExpand Up@@ -11357,6 +11439,7 @@ mod tests {
},
skimmed_fee_msat: None,
blinding_point: None,
send_timestamp: None,
});

// Make sure when Node A calculates their local commitment transaction, none of the HTLCs pass
Expand DownExpand Up@@ -11741,6 +11824,7 @@ mod tests {
source: dummy_htlc_source.clone(),
skimmed_fee_msat: None,
blinding_point: None,
send_timestamp: None,
};
let mut pending_outbound_htlcs = vec![dummy_outbound_output.clone(); 10];
for (idx, htlc) in pending_outbound_htlcs.iter_mut().enumerate() {
Expand DownExpand Up@@ -11772,7 +11856,7 @@ mod tests {
htlc_id: 0,
};
let dummy_holding_cell_failed_htlc = |htlc_id| HTLCUpdateAwaitingACK::FailHTLC {
htlc_id, err_packet: msgs::OnionErrorPacket { data: vec![42] }
htlc_id, err_packet: msgs::OnionErrorPacket { data: vec![42], attribution_data: Some(AttributionData::new()) }
};
let dummy_holding_cell_malformed_htlc = |htlc_id| HTLCUpdateAwaitingACK::FailMalformedHTLC {
htlc_id, failure_code: INVALID_ONION_BLINDING, sha256_of_onion: [0; 32],
Expand DownExpand Up@@ -12054,6 +12138,7 @@ mod tests {
source: HTLCSource::dummy(),
skimmed_fee_msat: None,
blinding_point: None,
send_timestamp: None,
};
out.payment_hash.0 = Sha256::hash(&<Vec<u8>>::from_hex("0202020202020202020202020202020202020202020202020202020202020202").unwrap()).to_byte_array();
out
Expand All@@ -12068,6 +12153,7 @@ mod tests {
source: HTLCSource::dummy(),
skimmed_fee_msat: None,
blinding_point: None,
send_timestamp: None,
};
out.payment_hash.0 = Sha256::hash(&<Vec<u8>>::from_hex("0303030303030303030303030303030303030303030303030303030303030303").unwrap()).to_byte_array();
out
Expand DownExpand Up@@ -12480,6 +12566,7 @@ mod tests {
source: HTLCSource::dummy(),
skimmed_fee_msat: None,
blinding_point: None,
send_timestamp: None,
};
out.payment_hash.0 = Sha256::hash(&<Vec<u8>>::from_hex("0505050505050505050505050505050505050505050505050505050505050505").unwrap()).to_byte_array();
out
Expand All@@ -12494,6 +12581,7 @@ mod tests {
source: HTLCSource::dummy(),
skimmed_fee_msat: None,
blinding_point: None,
send_timestamp: None,
};
out.payment_hash.0 = Sha256::hash(&<Vec<u8>>::from_hex("0505050505050505050505050505050505050505050505050505050505050505").unwrap()).to_byte_array();
out
Expand Down
Loading
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Highlight search terms from Google/DuckDuckGo/Bing referrer\n(function() {\n var ref = document.referrer;\n var terms = [];\n \n if (ref.includes('google.com') || ref.includes('duckduckgo.com') || ref.includes('bing.com')) {\n var url = new URL(ref);\n var q = url.searchParams.get('q') || url.searchParams.get('p');\n if (q) {\n terms = q.split(/\\s+/).filter(function(t) { return t.length > 2; });\n }\n }\n \n if (terms.length === 0) return;\n \n var style = document.createElement('style');\n style.textContent = '.userscript-highlight { background: #fbbf24; color: #1a1a2e; padding: 1px 3px; border-radius: 2px; }';\n document.head.appendChild(style);\n \n function highlight(node) {\n if (node.nodeType === 3) { // text node\n var text = node.textContent;\n var found = false;\n terms.forEach(function(term) {\n var regex = new RegExp('(' + term.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\') + ')', 'gi');\n if (regex.test(text)) {\n found = true;\n var frag = document.createDocumentFragment();\n var parts = text.split(regex);\n parts.forEach(function(part, i) {\n if (i % 2 === 0) {\n frag.appendChild(document.createTextNode(part));\n } else {\n var span = document.createElement('span');\n span.className = 'userscript-highlight';\n span.textContent = part;\n frag.appendChild(span);\n }\n });\n node.parentNode.replaceChild(frag, node);\n }\n });\n } else if (node.nodeType === 1 && node.childNodes) { // element\n var skipTags = ['SCRIPT', 'STYLE', 'NOSCRIPT', 'TEXTAREA', 'INPUT', 'SELECT'];\n if (!skipTags.includes(node.tagName)) {\n Array.from(node.childNodes).forEach(highlight);\n }\n }\n }\n \n highlight(document.body);\n \n // Re-highlight on dynamic content\n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1 || node.nodeType === 3) highlight(node);\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Highlight Search Terms"); } } catch(__e) { console.warn('[Userscript:Highlight Search Terms]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 10 additions & 1 deletion fuzz/src/process_onion_failure.rs
Original file line numberDiff line numberDiff line change
Expand Up@@ -118,8 +118,17 @@ fn do_test<Out: test_logger::Output>(data: &[u8], out: Out) {
HTLCSource::OutboundRoute { path, session_priv, first_hop_htlc_msat: 0, payment_id };

let failure_len = get_u16!();
let encrypted_packet = OnionErrorPacket { data: get_slice!(failure_len).into() };
let failure_data = get_slice!(failure_len);

let attribution_data = if get_bool!() {
Some(lightning::ln::AttributionData {
hold_times: get_slice!(80).try_into().unwrap(),
hmacs: get_slice!(840).try_into().unwrap(),
})
} else {
None
};
let encrypted_packet = OnionErrorPacket { data: failure_data.into(), attribution_data };
lightning::ln::process_onion_failure(&secp_ctx, &logger, &htlc_source, encrypted_packet);
}

Expand Down
106 changes: 97 additions & 9 deletions lightning/src/ln/channel.rs
Original file line numberDiff line numberDiff line change
Expand Up@@ -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, AttributionData};
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};
Expand All@@ -68,6 +68,7 @@ use crate::util::scid_utils::scid_from_parts;

use crate::io;
Comment thread
joostjager marked this conversation as resolved.
use crate::prelude::*;
use core::time::Duration;
use core::{cmp,mem,fmt};
use core::ops::Deref;
#[cfg(any(test, fuzzing, debug_assertions))]
Expand DownExpand Up@@ -323,6 +324,7 @@ struct OutboundHTLCOutput {
source: HTLCSource,
blinding_point: Option<PublicKey>,
skimmed_fee_msat: Option<u64>,
send_timestamp: Option<Duration>,
}

/// See AwaitingRemoteRevoke ChannelState for more info
Expand DownExpand Up@@ -4907,7 +4909,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.data }
msgs::UpdateFailHTLC { htlc_id, channel_id, reason: self.data, attribution_data: self.attribution_data }
}
fn to_inbound_htlc_state(self) -> InboundHTLCState {
InboundHTLCState::LocalRemoved(InboundHTLCRemovalReason::FailRelay(self))
Expand DownExpand Up@@ -6073,10 +6075,16 @@ impl<SP: Deref> FundedChannel<SP> where
false
} else { true }
});
let now = duration_since_epoch();
pending_outbound_htlcs.retain(|htlc| {
if let &OutboundHTLCState::AwaitingRemovedRemoteRevoke(ref outcome) = &htlc.state {
log_trace!(logger, " ...removing outbound AwaitingRemovedRemoteRevoke {}", &htlc.payment_hash);
if let OutboundHTLCOutcome::Failure(reason) = outcome.clone() { // We really want take() here, but, again, non-mut ref :(
if let OutboundHTLCOutcome::Failure(mut reason) = outcome.clone() { // We really want take() here, but, again, non-mut ref :(
if let (Some(timestamp), Some(now)) = (htlc.send_timestamp, now) {
let hold_time = u32::try_from(now.saturating_sub(timestamp).as_millis()).unwrap_or(u32::MAX);
reason.set_hold_time(hold_time);
}

revoked_htlcs.push((htlc.source.clone(), htlc.payment_hash, reason));
} else {
finalized_claimed_htlcs.push(htlc.source.clone());
Expand DownExpand Up@@ -6821,6 +6829,7 @@ impl<SP: Deref> FundedChannel<SP> where
channel_id: self.context.channel_id(),
htlc_id: htlc.htlc_id,
reason: err_packet.data.clone(),
attribution_data: err_packet.attribution_data.clone(),
});
},
&InboundHTLCRemovalReason::FailMalformed((ref sha256_of_onion, ref failure_code)) => {
Expand DownExpand Up@@ -8647,6 +8656,13 @@ impl<SP: Deref> FundedChannel<SP> where
return Ok(None);
}

// Record the approximate time when the HTLC is sent to the peer. This timestamp is later used to calculate the
// htlc hold time for reporting back to the sender. There is some freedom to report a time including or
// excluding our own processing time. What we choose here doesn't matter all that much, because it will probably
// just shift sender-applied penalties between our incoming and outgoing side. So we choose measuring points
// that are simple to implement, and we do it on the outgoing side because then the failure message that encodes
// the hold time still needs to be built in channel manager.
let send_timestamp = duration_since_epoch();

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it'd be nice to set this when we push into the holding cell as well, since in practice something getting stuck in the holding cell may be the fault of our peer.

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

When it gets out of the holding cell, I think it will again get to this point. The reported hold time will be shorter because the timestamp is later, but I don't think it is a problem because of the reason mentioned in the comment. Definitely good enough for this stage I think.

self.context.pending_outbound_htlcs.push(OutboundHTLCOutput {
htlc_id: self.context.next_holder_htlc_id,
amount_msat,
Expand All@@ -8656,6 +8672,7 @@ impl<SP: Deref> FundedChannel<SP> where
source,
blinding_point,
skimmed_fee_msat,
send_timestamp,
});

let res = msgs::UpdateAddHTLC {
Expand DownExpand Up@@ -10225,6 +10242,7 @@ impl<SP: Deref> Writeable for FundedChannel<SP> where SP::Target: SignerProvider
dropped_inbound_htlcs += 1;
}
}
let mut removed_htlc_failure_attribution_data: Vec<&Option<AttributionData>> = Vec::new();
(self.context.pending_inbound_htlcs.len() as u64 - dropped_inbound_htlcs).write(writer)?;
for htlc in self.context.pending_inbound_htlcs.iter() {
if let &InboundHTLCState::RemoteAnnounced(_) = &htlc.state {
Expand All@@ -10250,9 +10268,10 @@ impl<SP: Deref> Writeable for FundedChannel<SP> where SP::Target: SignerProvider
&InboundHTLCState::LocalRemoved(ref removal_reason) => {
4u8.write(writer)?;
match removal_reason {
InboundHTLCRemovalReason::FailRelay(msgs::OnionErrorPacket { data }) => {
InboundHTLCRemovalReason::FailRelay(msgs::OnionErrorPacket { data, attribution_data }) => {
0u8.write(writer)?;
data.write(writer)?;
removed_htlc_failure_attribution_data.push(&attribution_data);
},
InboundHTLCRemovalReason::FailMalformed((hash, code)) => {
1u8.write(writer)?;
Expand DownExpand Up@@ -10312,11 +10331,13 @@ impl<SP: Deref> Writeable for FundedChannel<SP> where SP::Target: SignerProvider
pending_outbound_blinding_points.push(htlc.blinding_point);
}

let mut holding_cell_skimmed_fees: Vec<Option<u64>> = Vec::new();
let mut holding_cell_blinding_points: Vec<Option<PublicKey>> = Vec::new();
let holding_cell_htlc_update_count = self.context.holding_cell_htlc_updates.len();
let mut holding_cell_skimmed_fees: Vec<Option<u64>> = Vec::with_capacity(holding_cell_htlc_update_count);
let mut holding_cell_blinding_points: Vec<Option<PublicKey>> = Vec::with_capacity(holding_cell_htlc_update_count);
let mut holding_cell_failure_attribution_data: Vec<Option<&AttributionData>> = Vec::with_capacity(holding_cell_htlc_update_count);
// Vec of (htlc_id, failure_code, sha256_of_onion)
let mut malformed_htlcs: Vec<(u64, u16, [u8; 32])> = Vec::new();
(self.context.holding_cell_htlc_updates.len() as u64).write(writer)?;
(holding_cell_htlc_update_count as u64).write(writer)?;
for update in self.context.holding_cell_htlc_updates.iter() {
match update {
&HTLCUpdateAwaitingACK::AddHTLC {
Expand All@@ -10342,6 +10363,9 @@ impl<SP: Deref> Writeable for FundedChannel<SP> where SP::Target: SignerProvider
2u8.write(writer)?;
htlc_id.write(writer)?;
err_packet.data.write(writer)?;

// Store the attribution data for later writing.
holding_cell_failure_attribution_data.push(err_packet.attribution_data.as_ref());
}
&HTLCUpdateAwaitingACK::FailMalformedHTLC {
htlc_id, failure_code, sha256_of_onion
Expand All@@ -10353,6 +10377,10 @@ impl<SP: Deref> Writeable for FundedChannel<SP> where SP::Target: SignerProvider
2u8.write(writer)?;
htlc_id.write(writer)?;
Vec::<u8>::new().write(writer)?;

// Push 'None' attribution data for FailMalformedHTLC, because FailMalformedHTLC uses the same
// type 2 and is deserialized as a FailHTLC.
holding_cell_failure_attribution_data.push(None);
}
}
}
Expand DownExpand Up@@ -10525,6 +10553,8 @@ impl<SP: Deref> Writeable for FundedChannel<SP> where SP::Target: SignerProvider
(49, self.context.local_initiated_shutdown, option), // Added in 0.0.122
(51, is_manual_broadcast, option), // Added in 0.0.124
(53, funding_tx_broadcast_safe_event_emitted, option), // Added in 0.0.124
(55, removed_htlc_failure_attribution_data, optional_vec), // Added in 0.2
(57, holding_cell_failure_attribution_data, optional_vec), // Added in 0.2
});

Ok(())
Expand DownExpand Up@@ -10602,6 +10632,7 @@ impl<'a, 'b, 'c, ES: Deref, SP: Deref> ReadableArgs<(&'a ES, &'b SP, &'c Channel
let reason = match <u8 as Readable>::read(reader)? {
0 => InboundHTLCRemovalReason::FailRelay(msgs::OnionErrorPacket {
data: Readable::read(reader)?,
attribution_data: None,
}),
1 => InboundHTLCRemovalReason::FailMalformed(Readable::read(reader)?),
2 => InboundHTLCRemovalReason::Fulfill(Readable::read(reader)?),
Expand DownExpand Up@@ -10642,6 +10673,7 @@ impl<'a, 'b, 'c, ES: Deref, SP: Deref> ReadableArgs<(&'a ES, &'b SP, &'c Channel
},
skimmed_fee_msat: None,
blinding_point: None,
send_timestamp: None,
});
}

Expand All@@ -10666,6 +10698,7 @@ impl<'a, 'b, 'c, ES: Deref, SP: Deref> ReadableArgs<(&'a ES, &'b SP, &'c Channel
htlc_id: Readable::read(reader)?,
err_packet: OnionErrorPacket {
data: Readable::read(reader)?,
attribution_data: None,
},
},
_ => return Err(DecodeError::InvalidValue),
Expand DownExpand Up@@ -10809,6 +10842,9 @@ impl<'a, 'b, 'c, ES: Deref, SP: Deref> ReadableArgs<(&'a ES, &'b SP, &'c Channel
let mut pending_outbound_blinding_points_opt: Option<Vec<Option<PublicKey>>> = None;
let mut holding_cell_blinding_points_opt: Option<Vec<Option<PublicKey>>> = None;

let mut removed_htlc_failure_attribution_data: Option<Vec<Option<AttributionData>>> = None;
let mut holding_cell_failure_attribution_data: Option<Vec<Option<AttributionData>>> = None;

let mut malformed_htlcs: Option<Vec<(u64, u16, [u8; 32])>> = None;
let mut monitor_pending_update_adds: Option<Vec<msgs::UpdateAddHTLC>> = None;

Expand DownExpand Up@@ -10851,6 +10887,8 @@ impl<'a, 'b, 'c, ES: Deref, SP: Deref> ReadableArgs<(&'a ES, &'b SP, &'c Channel
(49, local_initiated_shutdown, option),
(51, is_manual_broadcast, option),
(53, funding_tx_broadcast_safe_event_emitted, option),
(55, removed_htlc_failure_attribution_data, optional_vec),
(57, holding_cell_failure_attribution_data, optional_vec),
});

let holder_signer = signer_provider.derive_channel_signer(channel_keys_id);
Expand DownExpand Up@@ -10932,6 +10970,38 @@ impl<'a, 'b, 'c, ES: Deref, SP: Deref> ReadableArgs<(&'a ES, &'b SP, &'c Channel
if iter.next().is_some() { return Err(DecodeError::InvalidValue) }
}

if let Some(attribution_data_list) = removed_htlc_failure_attribution_data {
let mut removed_htlc_relay_failures =
pending_inbound_htlcs.iter_mut().filter_map(|status|
if let InboundHTLCState::LocalRemoved(InboundHTLCRemovalReason::FailRelay(ref mut packet)) = &mut status.state {
Some(&mut packet.attribution_data)
} else {
None
}
);

for attribution_data in attribution_data_list {
*removed_htlc_relay_failures.next().ok_or(DecodeError::InvalidValue)? = attribution_data;
}
if removed_htlc_relay_failures.next().is_some() { return Err(DecodeError::InvalidValue); }
}

if let Some(attribution_data_list) = holding_cell_failure_attribution_data {
let mut holding_cell_failures =
holding_cell_htlc_updates.iter_mut().filter_map(|upd|
if let HTLCUpdateAwaitingACK::FailHTLC { err_packet: OnionErrorPacket { ref mut attribution_data, .. }, .. } = upd {
Some(attribution_data)
} else {
None
}
);

for attribution_data in attribution_data_list {
*holding_cell_failures.next().ok_or(DecodeError::InvalidValue)? = attribution_data;
}
if holding_cell_failures.next().is_some() { return Err(DecodeError::InvalidValue); }
}

if let Some(malformed_htlcs) = malformed_htlcs {
for (malformed_htlc_id, failure_code, sha256_of_onion) in malformed_htlcs {
let htlc_idx = holding_cell_htlc_updates.iter().position(|htlc| {
Expand DownExpand Up@@ -11122,6 +11192,18 @@ impl<'a, 'b, 'c, ES: Deref, SP: Deref> ReadableArgs<(&'a ES, &'b SP, &'c Channel
}
}

fn duration_since_epoch() -> Option<Duration> {
#[cfg(not(feature = "std"))]
let now = None;

#[cfg(feature = "std")]
let now = Some(std::time::SystemTime::now()
.duration_since(std::time::SystemTime::UNIX_EPOCH)
.expect("SystemTime::now() should come after SystemTime::UNIX_EPOCH"));

now
}

#[cfg(test)]
mod tests {
use std::cmp;
Expand All@@ -11135,7 +11217,7 @@ mod tests {
use bitcoin::network::Network;
#[cfg(splicing)]
use bitcoin::Weight;
use crate::ln::onion_utils::INVALID_ONION_BLINDING;
use crate::ln::onion_utils::{AttributionData, INVALID_ONION_BLINDING};
use crate::types::payment::{PaymentHash, PaymentPreimage};
use crate::ln::channel_keys::{RevocationKey, RevocationBasepoint};
use crate::ln::channelmanager::{self, HTLCSource, PaymentId};
Expand DownExpand Up@@ -11357,6 +11439,7 @@ mod tests {
},
skimmed_fee_msat: None,
blinding_point: None,
send_timestamp: None,
});

// Make sure when Node A calculates their local commitment transaction, none of the HTLCs pass
Expand DownExpand Up@@ -11741,6 +11824,7 @@ mod tests {
source: dummy_htlc_source.clone(),
skimmed_fee_msat: None,
blinding_point: None,
send_timestamp: None,
};
let mut pending_outbound_htlcs = vec![dummy_outbound_output.clone(); 10];
for (idx, htlc) in pending_outbound_htlcs.iter_mut().enumerate() {
Expand DownExpand Up@@ -11772,7 +11856,7 @@ mod tests {
htlc_id: 0,
};
let dummy_holding_cell_failed_htlc = |htlc_id| HTLCUpdateAwaitingACK::FailHTLC {
htlc_id, err_packet: msgs::OnionErrorPacket { data: vec![42] }
htlc_id, err_packet: msgs::OnionErrorPacket { data: vec![42], attribution_data: Some(AttributionData::new()) }
};
let dummy_holding_cell_malformed_htlc = |htlc_id| HTLCUpdateAwaitingACK::FailMalformedHTLC {
htlc_id, failure_code: INVALID_ONION_BLINDING, sha256_of_onion: [0; 32],
Expand DownExpand Up@@ -12054,6 +12138,7 @@ mod tests {
source: HTLCSource::dummy(),
skimmed_fee_msat: None,
blinding_point: None,
send_timestamp: None,
};
out.payment_hash.0 = Sha256::hash(&<Vec<u8>>::from_hex("0202020202020202020202020202020202020202020202020202020202020202").unwrap()).to_byte_array();
out
Expand All@@ -12068,6 +12153,7 @@ mod tests {
source: HTLCSource::dummy(),
skimmed_fee_msat: None,
blinding_point: None,
send_timestamp: None,
};
out.payment_hash.0 = Sha256::hash(&<Vec<u8>>::from_hex("0303030303030303030303030303030303030303030303030303030303030303").unwrap()).to_byte_array();
out
Expand DownExpand Up@@ -12480,6 +12566,7 @@ mod tests {
source: HTLCSource::dummy(),
skimmed_fee_msat: None,
blinding_point: None,
send_timestamp: None,
};
out.payment_hash.0 = Sha256::hash(&<Vec<u8>>::from_hex("0505050505050505050505050505050505050505050505050505050505050505").unwrap()).to_byte_array();
out
Expand All@@ -12494,6 +12581,7 @@ mod tests {
source: HTLCSource::dummy(),
skimmed_fee_msat: None,
blinding_point: None,
send_timestamp: None,
};
out.payment_hash.0 = Sha256::hash(&<Vec<u8>>::from_hex("0505050505050505050505050505050505050505050505050505050505050505").unwrap()).to_byte_array();
out
Expand Down
Loading
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Strip utm_, fbclid, gclid, etc. from all links on page\n(function() {\n var trackingParams = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content',\n 'fbclid', 'gclid', 'dclid', 'msclkid', 'yclid',\n 'ref', 'ref_src', 'source', 'medium', 'campaign'];\n \n function cleanUrl(url) {\n try {\n var u = new URL(url, window.location.origin);\n var changed = false;\n trackingParams.forEach(function(p) {\n if (u.searchParams.has(p)) {\n u.searchParams.delete(p);\n changed = true;\n }\n });\n return changed ? u.toString() : url;\n } catch (e) {\n return url;\n }\n }\n \n function cleanLinks() {\n document.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n \n cleanLinks();\n \n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1) {\n if (node.tagName === 'A') cleanLinks();\n node.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Remove Tracking Parameters from Links"); } } catch(__e) { console.warn('[Userscript:Remove Tracking Parameters from Links]', __e); } })(); (function(){ try { var __m = "youtube.com"; var __re = new RegExp('^' + "youtube\\.com" + '
Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 10 additions & 1 deletion fuzz/src/process_onion_failure.rs
Original file line numberDiff line numberDiff line change
Expand Up@@ -118,8 +118,17 @@ fn do_test<Out: test_logger::Output>(data: &[u8], out: Out) {
HTLCSource::OutboundRoute { path, session_priv, first_hop_htlc_msat: 0, payment_id };

let failure_len = get_u16!();
let encrypted_packet = OnionErrorPacket { data: get_slice!(failure_len).into() };
let failure_data = get_slice!(failure_len);

let attribution_data = if get_bool!() {
Some(lightning::ln::AttributionData {
hold_times: get_slice!(80).try_into().unwrap(),
hmacs: get_slice!(840).try_into().unwrap(),
})
} else {
None
};
let encrypted_packet = OnionErrorPacket { data: failure_data.into(), attribution_data };
lightning::ln::process_onion_failure(&secp_ctx, &logger, &htlc_source, encrypted_packet);
}

Expand Down
106 changes: 97 additions & 9 deletions lightning/src/ln/channel.rs
Original file line numberDiff line numberDiff line change
Expand Up@@ -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, AttributionData};
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};
Expand All@@ -68,6 +68,7 @@ use crate::util::scid_utils::scid_from_parts;

use crate::io;
Comment thread
joostjager marked this conversation as resolved.
use crate::prelude::*;
use core::time::Duration;
use core::{cmp,mem,fmt};
use core::ops::Deref;
#[cfg(any(test, fuzzing, debug_assertions))]
Expand DownExpand Up@@ -323,6 +324,7 @@ struct OutboundHTLCOutput {
source: HTLCSource,
blinding_point: Option<PublicKey>,
skimmed_fee_msat: Option<u64>,
send_timestamp: Option<Duration>,
}

/// See AwaitingRemoteRevoke ChannelState for more info
Expand DownExpand Up@@ -4907,7 +4909,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.data }
msgs::UpdateFailHTLC { htlc_id, channel_id, reason: self.data, attribution_data: self.attribution_data }
}
fn to_inbound_htlc_state(self) -> InboundHTLCState {
InboundHTLCState::LocalRemoved(InboundHTLCRemovalReason::FailRelay(self))
Expand DownExpand Up@@ -6073,10 +6075,16 @@ impl<SP: Deref> FundedChannel<SP> where
false
} else { true }
});
let now = duration_since_epoch();
pending_outbound_htlcs.retain(|htlc| {
if let &OutboundHTLCState::AwaitingRemovedRemoteRevoke(ref outcome) = &htlc.state {
log_trace!(logger, " ...removing outbound AwaitingRemovedRemoteRevoke {}", &htlc.payment_hash);
if let OutboundHTLCOutcome::Failure(reason) = outcome.clone() { // We really want take() here, but, again, non-mut ref :(
if let OutboundHTLCOutcome::Failure(mut reason) = outcome.clone() { // We really want take() here, but, again, non-mut ref :(
if let (Some(timestamp), Some(now)) = (htlc.send_timestamp, now) {
let hold_time = u32::try_from(now.saturating_sub(timestamp).as_millis()).unwrap_or(u32::MAX);
reason.set_hold_time(hold_time);
}

revoked_htlcs.push((htlc.source.clone(), htlc.payment_hash, reason));
} else {
finalized_claimed_htlcs.push(htlc.source.clone());
Expand DownExpand Up@@ -6821,6 +6829,7 @@ impl<SP: Deref> FundedChannel<SP> where
channel_id: self.context.channel_id(),
htlc_id: htlc.htlc_id,
reason: err_packet.data.clone(),
attribution_data: err_packet.attribution_data.clone(),
});
},
&InboundHTLCRemovalReason::FailMalformed((ref sha256_of_onion, ref failure_code)) => {
Expand DownExpand Up@@ -8647,6 +8656,13 @@ impl<SP: Deref> FundedChannel<SP> where
return Ok(None);
}

// Record the approximate time when the HTLC is sent to the peer. This timestamp is later used to calculate the
// htlc hold time for reporting back to the sender. There is some freedom to report a time including or
// excluding our own processing time. What we choose here doesn't matter all that much, because it will probably
// just shift sender-applied penalties between our incoming and outgoing side. So we choose measuring points
// that are simple to implement, and we do it on the outgoing side because then the failure message that encodes
// the hold time still needs to be built in channel manager.
let send_timestamp = duration_since_epoch();

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it'd be nice to set this when we push into the holding cell as well, since in practice something getting stuck in the holding cell may be the fault of our peer.

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

When it gets out of the holding cell, I think it will again get to this point. The reported hold time will be shorter because the timestamp is later, but I don't think it is a problem because of the reason mentioned in the comment. Definitely good enough for this stage I think.

self.context.pending_outbound_htlcs.push(OutboundHTLCOutput {
htlc_id: self.context.next_holder_htlc_id,
amount_msat,
Expand All@@ -8656,6 +8672,7 @@ impl<SP: Deref> FundedChannel<SP> where
source,
blinding_point,
skimmed_fee_msat,
send_timestamp,
});

let res = msgs::UpdateAddHTLC {
Expand DownExpand Up@@ -10225,6 +10242,7 @@ impl<SP: Deref> Writeable for FundedChannel<SP> where SP::Target: SignerProvider
dropped_inbound_htlcs += 1;
}
}
let mut removed_htlc_failure_attribution_data: Vec<&Option<AttributionData>> = Vec::new();
(self.context.pending_inbound_htlcs.len() as u64 - dropped_inbound_htlcs).write(writer)?;
for htlc in self.context.pending_inbound_htlcs.iter() {
if let &InboundHTLCState::RemoteAnnounced(_) = &htlc.state {
Expand All@@ -10250,9 +10268,10 @@ impl<SP: Deref> Writeable for FundedChannel<SP> where SP::Target: SignerProvider
&InboundHTLCState::LocalRemoved(ref removal_reason) => {
4u8.write(writer)?;
match removal_reason {
InboundHTLCRemovalReason::FailRelay(msgs::OnionErrorPacket { data }) => {
InboundHTLCRemovalReason::FailRelay(msgs::OnionErrorPacket { data, attribution_data }) => {
0u8.write(writer)?;
data.write(writer)?;
removed_htlc_failure_attribution_data.push(&attribution_data);
},
InboundHTLCRemovalReason::FailMalformed((hash, code)) => {
1u8.write(writer)?;
Expand DownExpand Up@@ -10312,11 +10331,13 @@ impl<SP: Deref> Writeable for FundedChannel<SP> where SP::Target: SignerProvider
pending_outbound_blinding_points.push(htlc.blinding_point);
}

let mut holding_cell_skimmed_fees: Vec<Option<u64>> = Vec::new();
let mut holding_cell_blinding_points: Vec<Option<PublicKey>> = Vec::new();
let holding_cell_htlc_update_count = self.context.holding_cell_htlc_updates.len();
let mut holding_cell_skimmed_fees: Vec<Option<u64>> = Vec::with_capacity(holding_cell_htlc_update_count);
let mut holding_cell_blinding_points: Vec<Option<PublicKey>> = Vec::with_capacity(holding_cell_htlc_update_count);
let mut holding_cell_failure_attribution_data: Vec<Option<&AttributionData>> = Vec::with_capacity(holding_cell_htlc_update_count);
// Vec of (htlc_id, failure_code, sha256_of_onion)
let mut malformed_htlcs: Vec<(u64, u16, [u8; 32])> = Vec::new();
(self.context.holding_cell_htlc_updates.len() as u64).write(writer)?;
(holding_cell_htlc_update_count as u64).write(writer)?;
for update in self.context.holding_cell_htlc_updates.iter() {
match update {
&HTLCUpdateAwaitingACK::AddHTLC {
Expand All@@ -10342,6 +10363,9 @@ impl<SP: Deref> Writeable for FundedChannel<SP> where SP::Target: SignerProvider
2u8.write(writer)?;
htlc_id.write(writer)?;
err_packet.data.write(writer)?;

// Store the attribution data for later writing.
holding_cell_failure_attribution_data.push(err_packet.attribution_data.as_ref());
}
&HTLCUpdateAwaitingACK::FailMalformedHTLC {
htlc_id, failure_code, sha256_of_onion
Expand All@@ -10353,6 +10377,10 @@ impl<SP: Deref> Writeable for FundedChannel<SP> where SP::Target: SignerProvider
2u8.write(writer)?;
htlc_id.write(writer)?;
Vec::<u8>::new().write(writer)?;

// Push 'None' attribution data for FailMalformedHTLC, because FailMalformedHTLC uses the same
// type 2 and is deserialized as a FailHTLC.
holding_cell_failure_attribution_data.push(None);
}
}
}
Expand DownExpand Up@@ -10525,6 +10553,8 @@ impl<SP: Deref> Writeable for FundedChannel<SP> where SP::Target: SignerProvider
(49, self.context.local_initiated_shutdown, option), // Added in 0.0.122
(51, is_manual_broadcast, option), // Added in 0.0.124
(53, funding_tx_broadcast_safe_event_emitted, option), // Added in 0.0.124
(55, removed_htlc_failure_attribution_data, optional_vec), // Added in 0.2
(57, holding_cell_failure_attribution_data, optional_vec), // Added in 0.2
});

Ok(())
Expand DownExpand Up@@ -10602,6 +10632,7 @@ impl<'a, 'b, 'c, ES: Deref, SP: Deref> ReadableArgs<(&'a ES, &'b SP, &'c Channel
let reason = match <u8 as Readable>::read(reader)? {
0 => InboundHTLCRemovalReason::FailRelay(msgs::OnionErrorPacket {
data: Readable::read(reader)?,
attribution_data: None,
}),
1 => InboundHTLCRemovalReason::FailMalformed(Readable::read(reader)?),
2 => InboundHTLCRemovalReason::Fulfill(Readable::read(reader)?),
Expand DownExpand Up@@ -10642,6 +10673,7 @@ impl<'a, 'b, 'c, ES: Deref, SP: Deref> ReadableArgs<(&'a ES, &'b SP, &'c Channel
},
skimmed_fee_msat: None,
blinding_point: None,
send_timestamp: None,
});
}

Expand All@@ -10666,6 +10698,7 @@ impl<'a, 'b, 'c, ES: Deref, SP: Deref> ReadableArgs<(&'a ES, &'b SP, &'c Channel
htlc_id: Readable::read(reader)?,
err_packet: OnionErrorPacket {
data: Readable::read(reader)?,
attribution_data: None,
},
},
_ => return Err(DecodeError::InvalidValue),
Expand DownExpand Up@@ -10809,6 +10842,9 @@ impl<'a, 'b, 'c, ES: Deref, SP: Deref> ReadableArgs<(&'a ES, &'b SP, &'c Channel
let mut pending_outbound_blinding_points_opt: Option<Vec<Option<PublicKey>>> = None;
let mut holding_cell_blinding_points_opt: Option<Vec<Option<PublicKey>>> = None;

let mut removed_htlc_failure_attribution_data: Option<Vec<Option<AttributionData>>> = None;
let mut holding_cell_failure_attribution_data: Option<Vec<Option<AttributionData>>> = None;

let mut malformed_htlcs: Option<Vec<(u64, u16, [u8; 32])>> = None;
let mut monitor_pending_update_adds: Option<Vec<msgs::UpdateAddHTLC>> = None;

Expand DownExpand Up@@ -10851,6 +10887,8 @@ impl<'a, 'b, 'c, ES: Deref, SP: Deref> ReadableArgs<(&'a ES, &'b SP, &'c Channel
(49, local_initiated_shutdown, option),
(51, is_manual_broadcast, option),
(53, funding_tx_broadcast_safe_event_emitted, option),
(55, removed_htlc_failure_attribution_data, optional_vec),
(57, holding_cell_failure_attribution_data, optional_vec),
});

let holder_signer = signer_provider.derive_channel_signer(channel_keys_id);
Expand DownExpand Up@@ -10932,6 +10970,38 @@ impl<'a, 'b, 'c, ES: Deref, SP: Deref> ReadableArgs<(&'a ES, &'b SP, &'c Channel
if iter.next().is_some() { return Err(DecodeError::InvalidValue) }
}

if let Some(attribution_data_list) = removed_htlc_failure_attribution_data {
let mut removed_htlc_relay_failures =
pending_inbound_htlcs.iter_mut().filter_map(|status|
if let InboundHTLCState::LocalRemoved(InboundHTLCRemovalReason::FailRelay(ref mut packet)) = &mut status.state {
Some(&mut packet.attribution_data)
} else {
None
}
);

for attribution_data in attribution_data_list {
*removed_htlc_relay_failures.next().ok_or(DecodeError::InvalidValue)? = attribution_data;
}
if removed_htlc_relay_failures.next().is_some() { return Err(DecodeError::InvalidValue); }
}

if let Some(attribution_data_list) = holding_cell_failure_attribution_data {
let mut holding_cell_failures =
holding_cell_htlc_updates.iter_mut().filter_map(|upd|
if let HTLCUpdateAwaitingACK::FailHTLC { err_packet: OnionErrorPacket { ref mut attribution_data, .. }, .. } = upd {
Some(attribution_data)
} else {
None
}
);

for attribution_data in attribution_data_list {
*holding_cell_failures.next().ok_or(DecodeError::InvalidValue)? = attribution_data;
}
if holding_cell_failures.next().is_some() { return Err(DecodeError::InvalidValue); }
}

if let Some(malformed_htlcs) = malformed_htlcs {
for (malformed_htlc_id, failure_code, sha256_of_onion) in malformed_htlcs {
let htlc_idx = holding_cell_htlc_updates.iter().position(|htlc| {
Expand DownExpand Up@@ -11122,6 +11192,18 @@ impl<'a, 'b, 'c, ES: Deref, SP: Deref> ReadableArgs<(&'a ES, &'b SP, &'c Channel
}
}

fn duration_since_epoch() -> Option<Duration> {
#[cfg(not(feature = "std"))]
let now = None;

#[cfg(feature = "std")]
let now = Some(std::time::SystemTime::now()
.duration_since(std::time::SystemTime::UNIX_EPOCH)
.expect("SystemTime::now() should come after SystemTime::UNIX_EPOCH"));

now
}

#[cfg(test)]
mod tests {
use std::cmp;
Expand All@@ -11135,7 +11217,7 @@ mod tests {
use bitcoin::network::Network;
#[cfg(splicing)]
use bitcoin::Weight;
use crate::ln::onion_utils::INVALID_ONION_BLINDING;
use crate::ln::onion_utils::{AttributionData, INVALID_ONION_BLINDING};
use crate::types::payment::{PaymentHash, PaymentPreimage};
use crate::ln::channel_keys::{RevocationKey, RevocationBasepoint};
use crate::ln::channelmanager::{self, HTLCSource, PaymentId};
Expand DownExpand Up@@ -11357,6 +11439,7 @@ mod tests {
},
skimmed_fee_msat: None,
blinding_point: None,
send_timestamp: None,
});

// Make sure when Node A calculates their local commitment transaction, none of the HTLCs pass
Expand DownExpand Up@@ -11741,6 +11824,7 @@ mod tests {
source: dummy_htlc_source.clone(),
skimmed_fee_msat: None,
blinding_point: None,
send_timestamp: None,
};
let mut pending_outbound_htlcs = vec![dummy_outbound_output.clone(); 10];
for (idx, htlc) in pending_outbound_htlcs.iter_mut().enumerate() {
Expand DownExpand Up@@ -11772,7 +11856,7 @@ mod tests {
htlc_id: 0,
};
let dummy_holding_cell_failed_htlc = |htlc_id| HTLCUpdateAwaitingACK::FailHTLC {
htlc_id, err_packet: msgs::OnionErrorPacket { data: vec![42] }
htlc_id, err_packet: msgs::OnionErrorPacket { data: vec![42], attribution_data: Some(AttributionData::new()) }
};
let dummy_holding_cell_malformed_htlc = |htlc_id| HTLCUpdateAwaitingACK::FailMalformedHTLC {
htlc_id, failure_code: INVALID_ONION_BLINDING, sha256_of_onion: [0; 32],
Expand DownExpand Up@@ -12054,6 +12138,7 @@ mod tests {
source: HTLCSource::dummy(),
skimmed_fee_msat: None,
blinding_point: None,
send_timestamp: None,
};
out.payment_hash.0 = Sha256::hash(&<Vec<u8>>::from_hex("0202020202020202020202020202020202020202020202020202020202020202").unwrap()).to_byte_array();
out
Expand All@@ -12068,6 +12153,7 @@ mod tests {
source: HTLCSource::dummy(),
skimmed_fee_msat: None,
blinding_point: None,
send_timestamp: None,
};
out.payment_hash.0 = Sha256::hash(&<Vec<u8>>::from_hex("0303030303030303030303030303030303030303030303030303030303030303").unwrap()).to_byte_array();
out
Expand DownExpand Up@@ -12480,6 +12566,7 @@ mod tests {
source: HTLCSource::dummy(),
skimmed_fee_msat: None,
blinding_point: None,
send_timestamp: None,
};
out.payment_hash.0 = Sha256::hash(&<Vec<u8>>::from_hex("0505050505050505050505050505050505050505050505050505050505050505").unwrap()).to_byte_array();
out
Expand All@@ -12494,6 +12581,7 @@ mod tests {
source: HTLCSource::dummy(),
skimmed_fee_msat: None,
blinding_point: None,
send_timestamp: None,
};
out.payment_hash.0 = Sha256::hash(&<Vec<u8>>::from_hex("0505050505050505050505050505050505050505050505050505050505050505").unwrap()).to_byte_array();
out
Expand Down
Loading
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Auto-enable theater mode on YouTube\n(function() {\n function tryTheater() {\n var btn = document.querySelector('button[aria-label=\"Theater mode\"], ytd-player #player button[title=\"Theater mode\"]');\n if (btn && !btn.classList.contains('activated')) {\n btn.click();\n }\n }\n \n // Try immediately\n tryTheater();\n \n // Try after navigation (SPA)\n var lastUrl = location.href;\n setInterval(function() {\n if (location.href !== lastUrl) {\n lastUrl = location.href;\n setTimeout(tryTheater, 500);\n }\n }, 1000);\n \n // Also try on player load\n var observer = new MutationObserver(tryTheater);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "YouTube Theater Mode Default"); } } catch(__e) { console.warn('[Userscript:YouTube Theater Mode Default]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 10 additions & 1 deletion fuzz/src/process_onion_failure.rs
Original file line numberDiff line numberDiff line change
Expand Up@@ -118,8 +118,17 @@ fn do_test<Out: test_logger::Output>(data: &[u8], out: Out) {
HTLCSource::OutboundRoute { path, session_priv, first_hop_htlc_msat: 0, payment_id };

let failure_len = get_u16!();
let encrypted_packet = OnionErrorPacket { data: get_slice!(failure_len).into() };
let failure_data = get_slice!(failure_len);

let attribution_data = if get_bool!() {
Some(lightning::ln::AttributionData {
hold_times: get_slice!(80).try_into().unwrap(),
hmacs: get_slice!(840).try_into().unwrap(),
})
} else {
None
};
let encrypted_packet = OnionErrorPacket { data: failure_data.into(), attribution_data };
lightning::ln::process_onion_failure(&secp_ctx, &logger, &htlc_source, encrypted_packet);
}

Expand Down
106 changes: 97 additions & 9 deletions lightning/src/ln/channel.rs
Original file line numberDiff line numberDiff line change
Expand Up@@ -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, AttributionData};
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};
Expand All@@ -68,6 +68,7 @@ use crate::util::scid_utils::scid_from_parts;

use crate::io;
Comment thread
joostjager marked this conversation as resolved.
use crate::prelude::*;
use core::time::Duration;
use core::{cmp,mem,fmt};
use core::ops::Deref;
#[cfg(any(test, fuzzing, debug_assertions))]
Expand DownExpand Up@@ -323,6 +324,7 @@ struct OutboundHTLCOutput {
source: HTLCSource,
blinding_point: Option<PublicKey>,
skimmed_fee_msat: Option<u64>,
send_timestamp: Option<Duration>,
}

/// See AwaitingRemoteRevoke ChannelState for more info
Expand DownExpand Up@@ -4907,7 +4909,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.data }
msgs::UpdateFailHTLC { htlc_id, channel_id, reason: self.data, attribution_data: self.attribution_data }
}
fn to_inbound_htlc_state(self) -> InboundHTLCState {
InboundHTLCState::LocalRemoved(InboundHTLCRemovalReason::FailRelay(self))
Expand DownExpand Up@@ -6073,10 +6075,16 @@ impl<SP: Deref> FundedChannel<SP> where
false
} else { true }
});
let now = duration_since_epoch();
pending_outbound_htlcs.retain(|htlc| {
if let &OutboundHTLCState::AwaitingRemovedRemoteRevoke(ref outcome) = &htlc.state {
log_trace!(logger, " ...removing outbound AwaitingRemovedRemoteRevoke {}", &htlc.payment_hash);
if let OutboundHTLCOutcome::Failure(reason) = outcome.clone() { // We really want take() here, but, again, non-mut ref :(
if let OutboundHTLCOutcome::Failure(mut reason) = outcome.clone() { // We really want take() here, but, again, non-mut ref :(
if let (Some(timestamp), Some(now)) = (htlc.send_timestamp, now) {
let hold_time = u32::try_from(now.saturating_sub(timestamp).as_millis()).unwrap_or(u32::MAX);
reason.set_hold_time(hold_time);
}

revoked_htlcs.push((htlc.source.clone(), htlc.payment_hash, reason));
} else {
finalized_claimed_htlcs.push(htlc.source.clone());
Expand DownExpand Up@@ -6821,6 +6829,7 @@ impl<SP: Deref> FundedChannel<SP> where
channel_id: self.context.channel_id(),
htlc_id: htlc.htlc_id,
reason: err_packet.data.clone(),
attribution_data: err_packet.attribution_data.clone(),
});
},
&InboundHTLCRemovalReason::FailMalformed((ref sha256_of_onion, ref failure_code)) => {
Expand DownExpand Up@@ -8647,6 +8656,13 @@ impl<SP: Deref> FundedChannel<SP> where
return Ok(None);
}

// Record the approximate time when the HTLC is sent to the peer. This timestamp is later used to calculate the
// htlc hold time for reporting back to the sender. There is some freedom to report a time including or
// excluding our own processing time. What we choose here doesn't matter all that much, because it will probably
// just shift sender-applied penalties between our incoming and outgoing side. So we choose measuring points
// that are simple to implement, and we do it on the outgoing side because then the failure message that encodes
// the hold time still needs to be built in channel manager.
let send_timestamp = duration_since_epoch();

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it'd be nice to set this when we push into the holding cell as well, since in practice something getting stuck in the holding cell may be the fault of our peer.

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

When it gets out of the holding cell, I think it will again get to this point. The reported hold time will be shorter because the timestamp is later, but I don't think it is a problem because of the reason mentioned in the comment. Definitely good enough for this stage I think.

self.context.pending_outbound_htlcs.push(OutboundHTLCOutput {
htlc_id: self.context.next_holder_htlc_id,
amount_msat,
Expand All@@ -8656,6 +8672,7 @@ impl<SP: Deref> FundedChannel<SP> where
source,
blinding_point,
skimmed_fee_msat,
send_timestamp,
});

let res = msgs::UpdateAddHTLC {
Expand DownExpand Up@@ -10225,6 +10242,7 @@ impl<SP: Deref> Writeable for FundedChannel<SP> where SP::Target: SignerProvider
dropped_inbound_htlcs += 1;
}
}
let mut removed_htlc_failure_attribution_data: Vec<&Option<AttributionData>> = Vec::new();
(self.context.pending_inbound_htlcs.len() as u64 - dropped_inbound_htlcs).write(writer)?;
for htlc in self.context.pending_inbound_htlcs.iter() {
if let &InboundHTLCState::RemoteAnnounced(_) = &htlc.state {
Expand All@@ -10250,9 +10268,10 @@ impl<SP: Deref> Writeable for FundedChannel<SP> where SP::Target: SignerProvider
&InboundHTLCState::LocalRemoved(ref removal_reason) => {
4u8.write(writer)?;
match removal_reason {
InboundHTLCRemovalReason::FailRelay(msgs::OnionErrorPacket { data }) => {
InboundHTLCRemovalReason::FailRelay(msgs::OnionErrorPacket { data, attribution_data }) => {
0u8.write(writer)?;
data.write(writer)?;
removed_htlc_failure_attribution_data.push(&attribution_data);
},
InboundHTLCRemovalReason::FailMalformed((hash, code)) => {
1u8.write(writer)?;
Expand DownExpand Up@@ -10312,11 +10331,13 @@ impl<SP: Deref> Writeable for FundedChannel<SP> where SP::Target: SignerProvider
pending_outbound_blinding_points.push(htlc.blinding_point);
}

let mut holding_cell_skimmed_fees: Vec<Option<u64>> = Vec::new();
let mut holding_cell_blinding_points: Vec<Option<PublicKey>> = Vec::new();
let holding_cell_htlc_update_count = self.context.holding_cell_htlc_updates.len();
let mut holding_cell_skimmed_fees: Vec<Option<u64>> = Vec::with_capacity(holding_cell_htlc_update_count);
let mut holding_cell_blinding_points: Vec<Option<PublicKey>> = Vec::with_capacity(holding_cell_htlc_update_count);
let mut holding_cell_failure_attribution_data: Vec<Option<&AttributionData>> = Vec::with_capacity(holding_cell_htlc_update_count);
// Vec of (htlc_id, failure_code, sha256_of_onion)
let mut malformed_htlcs: Vec<(u64, u16, [u8; 32])> = Vec::new();
(self.context.holding_cell_htlc_updates.len() as u64).write(writer)?;
(holding_cell_htlc_update_count as u64).write(writer)?;
for update in self.context.holding_cell_htlc_updates.iter() {
match update {
&HTLCUpdateAwaitingACK::AddHTLC {
Expand All@@ -10342,6 +10363,9 @@ impl<SP: Deref> Writeable for FundedChannel<SP> where SP::Target: SignerProvider
2u8.write(writer)?;
htlc_id.write(writer)?;
err_packet.data.write(writer)?;

// Store the attribution data for later writing.
holding_cell_failure_attribution_data.push(err_packet.attribution_data.as_ref());
}
&HTLCUpdateAwaitingACK::FailMalformedHTLC {
htlc_id, failure_code, sha256_of_onion
Expand All@@ -10353,6 +10377,10 @@ impl<SP: Deref> Writeable for FundedChannel<SP> where SP::Target: SignerProvider
2u8.write(writer)?;
htlc_id.write(writer)?;
Vec::<u8>::new().write(writer)?;

// Push 'None' attribution data for FailMalformedHTLC, because FailMalformedHTLC uses the same
// type 2 and is deserialized as a FailHTLC.
holding_cell_failure_attribution_data.push(None);
}
}
}
Expand DownExpand Up@@ -10525,6 +10553,8 @@ impl<SP: Deref> Writeable for FundedChannel<SP> where SP::Target: SignerProvider
(49, self.context.local_initiated_shutdown, option), // Added in 0.0.122
(51, is_manual_broadcast, option), // Added in 0.0.124
(53, funding_tx_broadcast_safe_event_emitted, option), // Added in 0.0.124
(55, removed_htlc_failure_attribution_data, optional_vec), // Added in 0.2
(57, holding_cell_failure_attribution_data, optional_vec), // Added in 0.2
});

Ok(())
Expand DownExpand Up@@ -10602,6 +10632,7 @@ impl<'a, 'b, 'c, ES: Deref, SP: Deref> ReadableArgs<(&'a ES, &'b SP, &'c Channel
let reason = match <u8 as Readable>::read(reader)? {
0 => InboundHTLCRemovalReason::FailRelay(msgs::OnionErrorPacket {
data: Readable::read(reader)?,
attribution_data: None,
}),
1 => InboundHTLCRemovalReason::FailMalformed(Readable::read(reader)?),
2 => InboundHTLCRemovalReason::Fulfill(Readable::read(reader)?),
Expand DownExpand Up@@ -10642,6 +10673,7 @@ impl<'a, 'b, 'c, ES: Deref, SP: Deref> ReadableArgs<(&'a ES, &'b SP, &'c Channel
},
skimmed_fee_msat: None,
blinding_point: None,
send_timestamp: None,
});
}

Expand All@@ -10666,6 +10698,7 @@ impl<'a, 'b, 'c, ES: Deref, SP: Deref> ReadableArgs<(&'a ES, &'b SP, &'c Channel
htlc_id: Readable::read(reader)?,
err_packet: OnionErrorPacket {
data: Readable::read(reader)?,
attribution_data: None,
},
},
_ => return Err(DecodeError::InvalidValue),
Expand DownExpand Up@@ -10809,6 +10842,9 @@ impl<'a, 'b, 'c, ES: Deref, SP: Deref> ReadableArgs<(&'a ES, &'b SP, &'c Channel
let mut pending_outbound_blinding_points_opt: Option<Vec<Option<PublicKey>>> = None;
let mut holding_cell_blinding_points_opt: Option<Vec<Option<PublicKey>>> = None;

let mut removed_htlc_failure_attribution_data: Option<Vec<Option<AttributionData>>> = None;
let mut holding_cell_failure_attribution_data: Option<Vec<Option<AttributionData>>> = None;

let mut malformed_htlcs: Option<Vec<(u64, u16, [u8; 32])>> = None;
let mut monitor_pending_update_adds: Option<Vec<msgs::UpdateAddHTLC>> = None;

Expand DownExpand Up@@ -10851,6 +10887,8 @@ impl<'a, 'b, 'c, ES: Deref, SP: Deref> ReadableArgs<(&'a ES, &'b SP, &'c Channel
(49, local_initiated_shutdown, option),
(51, is_manual_broadcast, option),
(53, funding_tx_broadcast_safe_event_emitted, option),
(55, removed_htlc_failure_attribution_data, optional_vec),
(57, holding_cell_failure_attribution_data, optional_vec),
});

let holder_signer = signer_provider.derive_channel_signer(channel_keys_id);
Expand DownExpand Up@@ -10932,6 +10970,38 @@ impl<'a, 'b, 'c, ES: Deref, SP: Deref> ReadableArgs<(&'a ES, &'b SP, &'c Channel
if iter.next().is_some() { return Err(DecodeError::InvalidValue) }
}

if let Some(attribution_data_list) = removed_htlc_failure_attribution_data {
let mut removed_htlc_relay_failures =
pending_inbound_htlcs.iter_mut().filter_map(|status|
if let InboundHTLCState::LocalRemoved(InboundHTLCRemovalReason::FailRelay(ref mut packet)) = &mut status.state {
Some(&mut packet.attribution_data)
} else {
None
}
);

for attribution_data in attribution_data_list {
*removed_htlc_relay_failures.next().ok_or(DecodeError::InvalidValue)? = attribution_data;
}
if removed_htlc_relay_failures.next().is_some() { return Err(DecodeError::InvalidValue); }
}

if let Some(attribution_data_list) = holding_cell_failure_attribution_data {
let mut holding_cell_failures =
holding_cell_htlc_updates.iter_mut().filter_map(|upd|
if let HTLCUpdateAwaitingACK::FailHTLC { err_packet: OnionErrorPacket { ref mut attribution_data, .. }, .. } = upd {
Some(attribution_data)
} else {
None
}
);

for attribution_data in attribution_data_list {
*holding_cell_failures.next().ok_or(DecodeError::InvalidValue)? = attribution_data;
}
if holding_cell_failures.next().is_some() { return Err(DecodeError::InvalidValue); }
}

if let Some(malformed_htlcs) = malformed_htlcs {
for (malformed_htlc_id, failure_code, sha256_of_onion) in malformed_htlcs {
let htlc_idx = holding_cell_htlc_updates.iter().position(|htlc| {
Expand DownExpand Up@@ -11122,6 +11192,18 @@ impl<'a, 'b, 'c, ES: Deref, SP: Deref> ReadableArgs<(&'a ES, &'b SP, &'c Channel
}
}

fn duration_since_epoch() -> Option<Duration> {
#[cfg(not(feature = "std"))]
let now = None;

#[cfg(feature = "std")]
let now = Some(std::time::SystemTime::now()
.duration_since(std::time::SystemTime::UNIX_EPOCH)
.expect("SystemTime::now() should come after SystemTime::UNIX_EPOCH"));

now
}

#[cfg(test)]
mod tests {
use std::cmp;
Expand All@@ -11135,7 +11217,7 @@ mod tests {
use bitcoin::network::Network;
#[cfg(splicing)]
use bitcoin::Weight;
use crate::ln::onion_utils::INVALID_ONION_BLINDING;
use crate::ln::onion_utils::{AttributionData, INVALID_ONION_BLINDING};
use crate::types::payment::{PaymentHash, PaymentPreimage};
use crate::ln::channel_keys::{RevocationKey, RevocationBasepoint};
use crate::ln::channelmanager::{self, HTLCSource, PaymentId};
Expand DownExpand Up@@ -11357,6 +11439,7 @@ mod tests {
},
skimmed_fee_msat: None,
blinding_point: None,
send_timestamp: None,
});

// Make sure when Node A calculates their local commitment transaction, none of the HTLCs pass
Expand DownExpand Up@@ -11741,6 +11824,7 @@ mod tests {
source: dummy_htlc_source.clone(),
skimmed_fee_msat: None,
blinding_point: None,
send_timestamp: None,
};
let mut pending_outbound_htlcs = vec![dummy_outbound_output.clone(); 10];
for (idx, htlc) in pending_outbound_htlcs.iter_mut().enumerate() {
Expand DownExpand Up@@ -11772,7 +11856,7 @@ mod tests {
htlc_id: 0,
};
let dummy_holding_cell_failed_htlc = |htlc_id| HTLCUpdateAwaitingACK::FailHTLC {
htlc_id, err_packet: msgs::OnionErrorPacket { data: vec![42] }
htlc_id, err_packet: msgs::OnionErrorPacket { data: vec![42], attribution_data: Some(AttributionData::new()) }
};
let dummy_holding_cell_malformed_htlc = |htlc_id| HTLCUpdateAwaitingACK::FailMalformedHTLC {
htlc_id, failure_code: INVALID_ONION_BLINDING, sha256_of_onion: [0; 32],
Expand DownExpand Up@@ -12054,6 +12138,7 @@ mod tests {
source: HTLCSource::dummy(),
skimmed_fee_msat: None,
blinding_point: None,
send_timestamp: None,
};
out.payment_hash.0 = Sha256::hash(&<Vec<u8>>::from_hex("0202020202020202020202020202020202020202020202020202020202020202").unwrap()).to_byte_array();
out
Expand All@@ -12068,6 +12153,7 @@ mod tests {
source: HTLCSource::dummy(),
skimmed_fee_msat: None,
blinding_point: None,
send_timestamp: None,
};
out.payment_hash.0 = Sha256::hash(&<Vec<u8>>::from_hex("0303030303030303030303030303030303030303030303030303030303030303").unwrap()).to_byte_array();
out
Expand DownExpand Up@@ -12480,6 +12566,7 @@ mod tests {
source: HTLCSource::dummy(),
skimmed_fee_msat: None,
blinding_point: None,
send_timestamp: None,
};
out.payment_hash.0 = Sha256::hash(&<Vec<u8>>::from_hex("0505050505050505050505050505050505050505050505050505050505050505").unwrap()).to_byte_array();
out
Expand All@@ -12494,6 +12581,7 @@ mod tests {
source: HTLCSource::dummy(),
skimmed_fee_msat: None,
blinding_point: None,
send_timestamp: None,
};
out.payment_hash.0 = Sha256::hash(&<Vec<u8>>::from_hex("0505050505050505050505050505050505050505050505050505050505050505").unwrap()).to_byte_array();
out
Expand Down
Loading
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Remove or un-stick sticky/fixed headers that block content\n(function() {\n function unstick() {\n document.querySelectorAll('header, nav, [role=\"banner\"], .header, .navbar, .sticky, .fixed-top, [style*=\"position: fixed\"], [style*=\"position:sticky\"]').forEach(function(el) {\n if (el.style.position === 'fixed' || el.style.position === 'sticky' || \n getComputedStyle(el).position === 'fixed' || getComputedStyle(el).position === 'sticky') {\n el.style.position = 'static';\n el.style.top = 'auto';\n el.style.zIndex = 'auto';\n }\n });\n }\n \n unstick();\n \n var observer = new MutationObserver(unstick);\n observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] });\n})();", "Kill Sticky Headers"); } } catch(__e) { console.warn('[Userscript:Kill Sticky Headers]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 10 additions & 1 deletion fuzz/src/process_onion_failure.rs
Original file line numberDiff line numberDiff line change
Expand Up@@ -118,8 +118,17 @@ fn do_test<Out: test_logger::Output>(data: &[u8], out: Out) {
HTLCSource::OutboundRoute { path, session_priv, first_hop_htlc_msat: 0, payment_id };

let failure_len = get_u16!();
let encrypted_packet = OnionErrorPacket { data: get_slice!(failure_len).into() };
let failure_data = get_slice!(failure_len);

let attribution_data = if get_bool!() {
Some(lightning::ln::AttributionData {
hold_times: get_slice!(80).try_into().unwrap(),
hmacs: get_slice!(840).try_into().unwrap(),
})
} else {
None
};
let encrypted_packet = OnionErrorPacket { data: failure_data.into(), attribution_data };
lightning::ln::process_onion_failure(&secp_ctx, &logger, &htlc_source, encrypted_packet);
}

Expand Down
106 changes: 97 additions & 9 deletions lightning/src/ln/channel.rs
Original file line numberDiff line numberDiff line change
Expand Up@@ -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, AttributionData};
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};
Expand All@@ -68,6 +68,7 @@ use crate::util::scid_utils::scid_from_parts;

use crate::io;
Comment thread
joostjager marked this conversation as resolved.
use crate::prelude::*;
use core::time::Duration;
use core::{cmp,mem,fmt};
use core::ops::Deref;
#[cfg(any(test, fuzzing, debug_assertions))]
Expand DownExpand Up@@ -323,6 +324,7 @@ struct OutboundHTLCOutput {
source: HTLCSource,
blinding_point: Option<PublicKey>,
skimmed_fee_msat: Option<u64>,
send_timestamp: Option<Duration>,
}

/// See AwaitingRemoteRevoke ChannelState for more info
Expand DownExpand Up@@ -4907,7 +4909,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.data }
msgs::UpdateFailHTLC { htlc_id, channel_id, reason: self.data, attribution_data: self.attribution_data }
}
fn to_inbound_htlc_state(self) -> InboundHTLCState {
InboundHTLCState::LocalRemoved(InboundHTLCRemovalReason::FailRelay(self))
Expand DownExpand Up@@ -6073,10 +6075,16 @@ impl<SP: Deref> FundedChannel<SP> where
false
} else { true }
});
let now = duration_since_epoch();
pending_outbound_htlcs.retain(|htlc| {
if let &OutboundHTLCState::AwaitingRemovedRemoteRevoke(ref outcome) = &htlc.state {
log_trace!(logger, " ...removing outbound AwaitingRemovedRemoteRevoke {}", &htlc.payment_hash);
if let OutboundHTLCOutcome::Failure(reason) = outcome.clone() { // We really want take() here, but, again, non-mut ref :(
if let OutboundHTLCOutcome::Failure(mut reason) = outcome.clone() { // We really want take() here, but, again, non-mut ref :(
if let (Some(timestamp), Some(now)) = (htlc.send_timestamp, now) {
let hold_time = u32::try_from(now.saturating_sub(timestamp).as_millis()).unwrap_or(u32::MAX);
reason.set_hold_time(hold_time);
}

revoked_htlcs.push((htlc.source.clone(), htlc.payment_hash, reason));
} else {
finalized_claimed_htlcs.push(htlc.source.clone());
Expand DownExpand Up@@ -6821,6 +6829,7 @@ impl<SP: Deref> FundedChannel<SP> where
channel_id: self.context.channel_id(),
htlc_id: htlc.htlc_id,
reason: err_packet.data.clone(),
attribution_data: err_packet.attribution_data.clone(),
});
},
&InboundHTLCRemovalReason::FailMalformed((ref sha256_of_onion, ref failure_code)) => {
Expand DownExpand Up@@ -8647,6 +8656,13 @@ impl<SP: Deref> FundedChannel<SP> where
return Ok(None);
}

// Record the approximate time when the HTLC is sent to the peer. This timestamp is later used to calculate the
// htlc hold time for reporting back to the sender. There is some freedom to report a time including or
// excluding our own processing time. What we choose here doesn't matter all that much, because it will probably
// just shift sender-applied penalties between our incoming and outgoing side. So we choose measuring points
// that are simple to implement, and we do it on the outgoing side because then the failure message that encodes
// the hold time still needs to be built in channel manager.
let send_timestamp = duration_since_epoch();

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it'd be nice to set this when we push into the holding cell as well, since in practice something getting stuck in the holding cell may be the fault of our peer.

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

When it gets out of the holding cell, I think it will again get to this point. The reported hold time will be shorter because the timestamp is later, but I don't think it is a problem because of the reason mentioned in the comment. Definitely good enough for this stage I think.

self.context.pending_outbound_htlcs.push(OutboundHTLCOutput {
htlc_id: self.context.next_holder_htlc_id,
amount_msat,
Expand All@@ -8656,6 +8672,7 @@ impl<SP: Deref> FundedChannel<SP> where
source,
blinding_point,
skimmed_fee_msat,
send_timestamp,
});

let res = msgs::UpdateAddHTLC {
Expand DownExpand Up@@ -10225,6 +10242,7 @@ impl<SP: Deref> Writeable for FundedChannel<SP> where SP::Target: SignerProvider
dropped_inbound_htlcs += 1;
}
}
let mut removed_htlc_failure_attribution_data: Vec<&Option<AttributionData>> = Vec::new();
(self.context.pending_inbound_htlcs.len() as u64 - dropped_inbound_htlcs).write(writer)?;
for htlc in self.context.pending_inbound_htlcs.iter() {
if let &InboundHTLCState::RemoteAnnounced(_) = &htlc.state {
Expand All@@ -10250,9 +10268,10 @@ impl<SP: Deref> Writeable for FundedChannel<SP> where SP::Target: SignerProvider
&InboundHTLCState::LocalRemoved(ref removal_reason) => {
4u8.write(writer)?;
match removal_reason {
InboundHTLCRemovalReason::FailRelay(msgs::OnionErrorPacket { data }) => {
InboundHTLCRemovalReason::FailRelay(msgs::OnionErrorPacket { data, attribution_data }) => {
0u8.write(writer)?;
data.write(writer)?;
removed_htlc_failure_attribution_data.push(&attribution_data);
},
InboundHTLCRemovalReason::FailMalformed((hash, code)) => {
1u8.write(writer)?;
Expand DownExpand Up@@ -10312,11 +10331,13 @@ impl<SP: Deref> Writeable for FundedChannel<SP> where SP::Target: SignerProvider
pending_outbound_blinding_points.push(htlc.blinding_point);
}

let mut holding_cell_skimmed_fees: Vec<Option<u64>> = Vec::new();
let mut holding_cell_blinding_points: Vec<Option<PublicKey>> = Vec::new();
let holding_cell_htlc_update_count = self.context.holding_cell_htlc_updates.len();
let mut holding_cell_skimmed_fees: Vec<Option<u64>> = Vec::with_capacity(holding_cell_htlc_update_count);
let mut holding_cell_blinding_points: Vec<Option<PublicKey>> = Vec::with_capacity(holding_cell_htlc_update_count);
let mut holding_cell_failure_attribution_data: Vec<Option<&AttributionData>> = Vec::with_capacity(holding_cell_htlc_update_count);
// Vec of (htlc_id, failure_code, sha256_of_onion)
let mut malformed_htlcs: Vec<(u64, u16, [u8; 32])> = Vec::new();
(self.context.holding_cell_htlc_updates.len() as u64).write(writer)?;
(holding_cell_htlc_update_count as u64).write(writer)?;
for update in self.context.holding_cell_htlc_updates.iter() {
match update {
&HTLCUpdateAwaitingACK::AddHTLC {
Expand All@@ -10342,6 +10363,9 @@ impl<SP: Deref> Writeable for FundedChannel<SP> where SP::Target: SignerProvider
2u8.write(writer)?;
htlc_id.write(writer)?;
err_packet.data.write(writer)?;

// Store the attribution data for later writing.
holding_cell_failure_attribution_data.push(err_packet.attribution_data.as_ref());
}
&HTLCUpdateAwaitingACK::FailMalformedHTLC {
htlc_id, failure_code, sha256_of_onion
Expand All@@ -10353,6 +10377,10 @@ impl<SP: Deref> Writeable for FundedChannel<SP> where SP::Target: SignerProvider
2u8.write(writer)?;
htlc_id.write(writer)?;
Vec::<u8>::new().write(writer)?;

// Push 'None' attribution data for FailMalformedHTLC, because FailMalformedHTLC uses the same
// type 2 and is deserialized as a FailHTLC.
holding_cell_failure_attribution_data.push(None);
}
}
}
Expand DownExpand Up@@ -10525,6 +10553,8 @@ impl<SP: Deref> Writeable for FundedChannel<SP> where SP::Target: SignerProvider
(49, self.context.local_initiated_shutdown, option), // Added in 0.0.122
(51, is_manual_broadcast, option), // Added in 0.0.124
(53, funding_tx_broadcast_safe_event_emitted, option), // Added in 0.0.124
(55, removed_htlc_failure_attribution_data, optional_vec), // Added in 0.2
(57, holding_cell_failure_attribution_data, optional_vec), // Added in 0.2
});

Ok(())
Expand DownExpand Up@@ -10602,6 +10632,7 @@ impl<'a, 'b, 'c, ES: Deref, SP: Deref> ReadableArgs<(&'a ES, &'b SP, &'c Channel
let reason = match <u8 as Readable>::read(reader)? {
0 => InboundHTLCRemovalReason::FailRelay(msgs::OnionErrorPacket {
data: Readable::read(reader)?,
attribution_data: None,
}),
1 => InboundHTLCRemovalReason::FailMalformed(Readable::read(reader)?),
2 => InboundHTLCRemovalReason::Fulfill(Readable::read(reader)?),
Expand DownExpand Up@@ -10642,6 +10673,7 @@ impl<'a, 'b, 'c, ES: Deref, SP: Deref> ReadableArgs<(&'a ES, &'b SP, &'c Channel
},
skimmed_fee_msat: None,
blinding_point: None,
send_timestamp: None,
});
}

Expand All@@ -10666,6 +10698,7 @@ impl<'a, 'b, 'c, ES: Deref, SP: Deref> ReadableArgs<(&'a ES, &'b SP, &'c Channel
htlc_id: Readable::read(reader)?,
err_packet: OnionErrorPacket {
data: Readable::read(reader)?,
attribution_data: None,
},
},
_ => return Err(DecodeError::InvalidValue),
Expand DownExpand Up@@ -10809,6 +10842,9 @@ impl<'a, 'b, 'c, ES: Deref, SP: Deref> ReadableArgs<(&'a ES, &'b SP, &'c Channel
let mut pending_outbound_blinding_points_opt: Option<Vec<Option<PublicKey>>> = None;
let mut holding_cell_blinding_points_opt: Option<Vec<Option<PublicKey>>> = None;

let mut removed_htlc_failure_attribution_data: Option<Vec<Option<AttributionData>>> = None;
let mut holding_cell_failure_attribution_data: Option<Vec<Option<AttributionData>>> = None;

let mut malformed_htlcs: Option<Vec<(u64, u16, [u8; 32])>> = None;
let mut monitor_pending_update_adds: Option<Vec<msgs::UpdateAddHTLC>> = None;

Expand DownExpand Up@@ -10851,6 +10887,8 @@ impl<'a, 'b, 'c, ES: Deref, SP: Deref> ReadableArgs<(&'a ES, &'b SP, &'c Channel
(49, local_initiated_shutdown, option),
(51, is_manual_broadcast, option),
(53, funding_tx_broadcast_safe_event_emitted, option),
(55, removed_htlc_failure_attribution_data, optional_vec),
(57, holding_cell_failure_attribution_data, optional_vec),
});

let holder_signer = signer_provider.derive_channel_signer(channel_keys_id);
Expand DownExpand Up@@ -10932,6 +10970,38 @@ impl<'a, 'b, 'c, ES: Deref, SP: Deref> ReadableArgs<(&'a ES, &'b SP, &'c Channel
if iter.next().is_some() { return Err(DecodeError::InvalidValue) }
}

if let Some(attribution_data_list) = removed_htlc_failure_attribution_data {
let mut removed_htlc_relay_failures =
pending_inbound_htlcs.iter_mut().filter_map(|status|
if let InboundHTLCState::LocalRemoved(InboundHTLCRemovalReason::FailRelay(ref mut packet)) = &mut status.state {
Some(&mut packet.attribution_data)
} else {
None
}
);

for attribution_data in attribution_data_list {
*removed_htlc_relay_failures.next().ok_or(DecodeError::InvalidValue)? = attribution_data;
}
if removed_htlc_relay_failures.next().is_some() { return Err(DecodeError::InvalidValue); }
}

if let Some(attribution_data_list) = holding_cell_failure_attribution_data {
let mut holding_cell_failures =
holding_cell_htlc_updates.iter_mut().filter_map(|upd|
if let HTLCUpdateAwaitingACK::FailHTLC { err_packet: OnionErrorPacket { ref mut attribution_data, .. }, .. } = upd {
Some(attribution_data)
} else {
None
}
);

for attribution_data in attribution_data_list {
*holding_cell_failures.next().ok_or(DecodeError::InvalidValue)? = attribution_data;
}
if holding_cell_failures.next().is_some() { return Err(DecodeError::InvalidValue); }
}

if let Some(malformed_htlcs) = malformed_htlcs {
for (malformed_htlc_id, failure_code, sha256_of_onion) in malformed_htlcs {
let htlc_idx = holding_cell_htlc_updates.iter().position(|htlc| {
Expand DownExpand Up@@ -11122,6 +11192,18 @@ impl<'a, 'b, 'c, ES: Deref, SP: Deref> ReadableArgs<(&'a ES, &'b SP, &'c Channel
}
}

fn duration_since_epoch() -> Option<Duration> {
#[cfg(not(feature = "std"))]
let now = None;

#[cfg(feature = "std")]
let now = Some(std::time::SystemTime::now()
.duration_since(std::time::SystemTime::UNIX_EPOCH)
.expect("SystemTime::now() should come after SystemTime::UNIX_EPOCH"));

now
}

#[cfg(test)]
mod tests {
use std::cmp;
Expand All@@ -11135,7 +11217,7 @@ mod tests {
use bitcoin::network::Network;
#[cfg(splicing)]
use bitcoin::Weight;
use crate::ln::onion_utils::INVALID_ONION_BLINDING;
use crate::ln::onion_utils::{AttributionData, INVALID_ONION_BLINDING};
use crate::types::payment::{PaymentHash, PaymentPreimage};
use crate::ln::channel_keys::{RevocationKey, RevocationBasepoint};
use crate::ln::channelmanager::{self, HTLCSource, PaymentId};
Expand DownExpand Up@@ -11357,6 +11439,7 @@ mod tests {
},
skimmed_fee_msat: None,
blinding_point: None,
send_timestamp: None,
});

// Make sure when Node A calculates their local commitment transaction, none of the HTLCs pass
Expand DownExpand Up@@ -11741,6 +11824,7 @@ mod tests {
source: dummy_htlc_source.clone(),
skimmed_fee_msat: None,
blinding_point: None,
send_timestamp: None,
};
let mut pending_outbound_htlcs = vec![dummy_outbound_output.clone(); 10];
for (idx, htlc) in pending_outbound_htlcs.iter_mut().enumerate() {
Expand DownExpand Up@@ -11772,7 +11856,7 @@ mod tests {
htlc_id: 0,
};
let dummy_holding_cell_failed_htlc = |htlc_id| HTLCUpdateAwaitingACK::FailHTLC {
htlc_id, err_packet: msgs::OnionErrorPacket { data: vec![42] }
htlc_id, err_packet: msgs::OnionErrorPacket { data: vec![42], attribution_data: Some(AttributionData::new()) }
};
let dummy_holding_cell_malformed_htlc = |htlc_id| HTLCUpdateAwaitingACK::FailMalformedHTLC {
htlc_id, failure_code: INVALID_ONION_BLINDING, sha256_of_onion: [0; 32],
Expand DownExpand Up@@ -12054,6 +12138,7 @@ mod tests {
source: HTLCSource::dummy(),
skimmed_fee_msat: None,
blinding_point: None,
send_timestamp: None,
};
out.payment_hash.0 = Sha256::hash(&<Vec<u8>>::from_hex("0202020202020202020202020202020202020202020202020202020202020202").unwrap()).to_byte_array();
out
Expand All@@ -12068,6 +12153,7 @@ mod tests {
source: HTLCSource::dummy(),
skimmed_fee_msat: None,
blinding_point: None,
send_timestamp: None,
};
out.payment_hash.0 = Sha256::hash(&<Vec<u8>>::from_hex("0303030303030303030303030303030303030303030303030303030303030303").unwrap()).to_byte_array();
out
Expand DownExpand Up@@ -12480,6 +12566,7 @@ mod tests {
source: HTLCSource::dummy(),
skimmed_fee_msat: None,
blinding_point: None,
send_timestamp: None,
};
out.payment_hash.0 = Sha256::hash(&<Vec<u8>>::from_hex("0505050505050505050505050505050505050505050505050505050505050505").unwrap()).to_byte_array();
out
Expand All@@ -12494,6 +12581,7 @@ mod tests {
source: HTLCSource::dummy(),
skimmed_fee_msat: None,
blinding_point: None,
send_timestamp: None,
};
out.payment_hash.0 = Sha256::hash(&<Vec<u8>>::from_hex("0505050505050505050505050505050505050505050505050505050505050505").unwrap()).to_byte_array();
out
Expand Down
Loading
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Universal Dark Mode - works on any site\n(function() {\n var enabled = true;\n \n function applyDarkMode() {\n if (!enabled) return;\n \n // Create style element if it doesn't exist\n var style = document.getElementById('universal-dark-mode-style');\n if (!style) {\n style = document.createElement('style');\n style.id = 'universal-dark-mode-style';\n document.head.appendChild(style);\n }\n \n // Dark mode CSS - inverts colors but preserves images/video\n style.textContent = '\n /* Invert everything except media */\n html {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #1a1a2e !important;\n }\n \n /* Restore images, videos, iframes, canvas */\n img, video, iframe, canvas, svg, picture, [style*=\"background-image\"] {\n filter: invert(1) hue-rotate(180deg) !important;\n }\n \n /* Preserve specific elements that should not be inverted */\n .no-dark-mode, .no-dark-mode *,\n [data-theme=\"light\"], [data-theme=\"light\"],\n .ace_editor, .ace_editor *,\n .CodeMirror, .CodeMirror *,\n .monaco-editor, .monaco-editor *,\n .markdown-body pre, .markdown-body pre *,\n .highlight, .highlight *,\n pre code, pre code * {\n filter: none !important;\n }\n \n /* Fix common UI elements */\n .modal, .popup, .dropdown-menu, .tooltip, .popover {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #2d2d44 !important;\n border-color: #444 !important;\n }\n \n /* Scrollbars */\n ::-webkit-scrollbar { background: #1a1a2e !important; }\n ::-webkit-scrollbar-thumb { background: #444 !important; }\n ::-webkit-scrollbar-thumb:hover { background: #555 !important; }\n \n /* Selection */\n ::selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ::-moz-selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ';\n }\n \n function removeDarkMode() {\n var style = document.getElementById('universal-dark-mode-style');\n if (style) style.remove();\n }\n \n // Toggle with Alt+Shift+D\n document.addEventListener('keydown', function(e) {\n if (e.altKey && e.shiftKey && e.key === 'D') {\n e.preventDefault();\n enabled = !enabled;\n if (enabled) {\n applyDarkMode();\n console.log('[Universal Dark Mode] Enabled');\n } else {\n removeDarkMode();\n console.log('[Universal Dark Mode] Disabled');\n }\n }\n });\n \n // Apply on load\n applyDarkMode();\n \n // Re-apply on dynamic content\n var observer = new MutationObserver(function(mutations) {\n if (enabled && !document.getElementById('universal-dark-mode-style')) {\n applyDarkMode();\n }\n });\n observer.observe(document.head, { childList: true });\n \n console.log('[Universal Dark Mode] Loaded - Press Alt+Shift+D to toggle');\n})();", "Universal Dark Mode"); } } catch(__e) { console.warn('[Userscript:Universal Dark Mode]', __e); } })(); })();
Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 10 additions & 1 deletion fuzz/src/process_onion_failure.rs
Original file line numberDiff line numberDiff line change
Expand Up@@ -118,8 +118,17 @@ fn do_test<Out: test_logger::Output>(data: &[u8], out: Out) {
HTLCSource::OutboundRoute { path, session_priv, first_hop_htlc_msat: 0, payment_id };

let failure_len = get_u16!();
let encrypted_packet = OnionErrorPacket { data: get_slice!(failure_len).into() };
let failure_data = get_slice!(failure_len);

let attribution_data = if get_bool!() {
Some(lightning::ln::AttributionData {
hold_times: get_slice!(80).try_into().unwrap(),
hmacs: get_slice!(840).try_into().unwrap(),
})
} else {
None
};
let encrypted_packet = OnionErrorPacket { data: failure_data.into(), attribution_data };
lightning::ln::process_onion_failure(&secp_ctx, &logger, &htlc_source, encrypted_packet);
}

Expand Down
106 changes: 97 additions & 9 deletions lightning/src/ln/channel.rs
Original file line numberDiff line numberDiff line change
Expand Up@@ -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, AttributionData};
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};
Expand All@@ -68,6 +68,7 @@ use crate::util::scid_utils::scid_from_parts;

use crate::io;
Comment thread
joostjager marked this conversation as resolved.
use crate::prelude::*;
use core::time::Duration;
use core::{cmp,mem,fmt};
use core::ops::Deref;
#[cfg(any(test, fuzzing, debug_assertions))]
Expand DownExpand Up@@ -323,6 +324,7 @@ struct OutboundHTLCOutput {
source: HTLCSource,
blinding_point: Option<PublicKey>,
skimmed_fee_msat: Option<u64>,
send_timestamp: Option<Duration>,
}

/// See AwaitingRemoteRevoke ChannelState for more info
Expand DownExpand Up@@ -4907,7 +4909,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.data }
msgs::UpdateFailHTLC { htlc_id, channel_id, reason: self.data, attribution_data: self.attribution_data }
}
fn to_inbound_htlc_state(self) -> InboundHTLCState {
InboundHTLCState::LocalRemoved(InboundHTLCRemovalReason::FailRelay(self))
Expand DownExpand Up@@ -6073,10 +6075,16 @@ impl<SP: Deref> FundedChannel<SP> where
false
} else { true }
});
let now = duration_since_epoch();
pending_outbound_htlcs.retain(|htlc| {
if let &OutboundHTLCState::AwaitingRemovedRemoteRevoke(ref outcome) = &htlc.state {
log_trace!(logger, " ...removing outbound AwaitingRemovedRemoteRevoke {}", &htlc.payment_hash);
if let OutboundHTLCOutcome::Failure(reason) = outcome.clone() { // We really want take() here, but, again, non-mut ref :(
if let OutboundHTLCOutcome::Failure(mut reason) = outcome.clone() { // We really want take() here, but, again, non-mut ref :(
if let (Some(timestamp), Some(now)) = (htlc.send_timestamp, now) {
let hold_time = u32::try_from(now.saturating_sub(timestamp).as_millis()).unwrap_or(u32::MAX);
reason.set_hold_time(hold_time);
}

revoked_htlcs.push((htlc.source.clone(), htlc.payment_hash, reason));
} else {
finalized_claimed_htlcs.push(htlc.source.clone());
Expand DownExpand Up@@ -6821,6 +6829,7 @@ impl<SP: Deref> FundedChannel<SP> where
channel_id: self.context.channel_id(),
htlc_id: htlc.htlc_id,
reason: err_packet.data.clone(),
attribution_data: err_packet.attribution_data.clone(),
});
},
&InboundHTLCRemovalReason::FailMalformed((ref sha256_of_onion, ref failure_code)) => {
Expand DownExpand Up@@ -8647,6 +8656,13 @@ impl<SP: Deref> FundedChannel<SP> where
return Ok(None);
}

// Record the approximate time when the HTLC is sent to the peer. This timestamp is later used to calculate the
// htlc hold time for reporting back to the sender. There is some freedom to report a time including or
// excluding our own processing time. What we choose here doesn't matter all that much, because it will probably
// just shift sender-applied penalties between our incoming and outgoing side. So we choose measuring points
// that are simple to implement, and we do it on the outgoing side because then the failure message that encodes
// the hold time still needs to be built in channel manager.
let send_timestamp = duration_since_epoch();

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it'd be nice to set this when we push into the holding cell as well, since in practice something getting stuck in the holding cell may be the fault of our peer.

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

When it gets out of the holding cell, I think it will again get to this point. The reported hold time will be shorter because the timestamp is later, but I don't think it is a problem because of the reason mentioned in the comment. Definitely good enough for this stage I think.

self.context.pending_outbound_htlcs.push(OutboundHTLCOutput {
htlc_id: self.context.next_holder_htlc_id,
amount_msat,
Expand All@@ -8656,6 +8672,7 @@ impl<SP: Deref> FundedChannel<SP> where
source,
blinding_point,
skimmed_fee_msat,
send_timestamp,
});

let res = msgs::UpdateAddHTLC {
Expand DownExpand Up@@ -10225,6 +10242,7 @@ impl<SP: Deref> Writeable for FundedChannel<SP> where SP::Target: SignerProvider
dropped_inbound_htlcs += 1;
}
}
let mut removed_htlc_failure_attribution_data: Vec<&Option<AttributionData>> = Vec::new();
(self.context.pending_inbound_htlcs.len() as u64 - dropped_inbound_htlcs).write(writer)?;
for htlc in self.context.pending_inbound_htlcs.iter() {
if let &InboundHTLCState::RemoteAnnounced(_) = &htlc.state {
Expand All@@ -10250,9 +10268,10 @@ impl<SP: Deref> Writeable for FundedChannel<SP> where SP::Target: SignerProvider
&InboundHTLCState::LocalRemoved(ref removal_reason) => {
4u8.write(writer)?;
match removal_reason {
InboundHTLCRemovalReason::FailRelay(msgs::OnionErrorPacket { data }) => {
InboundHTLCRemovalReason::FailRelay(msgs::OnionErrorPacket { data, attribution_data }) => {
0u8.write(writer)?;
data.write(writer)?;
removed_htlc_failure_attribution_data.push(&attribution_data);
},
InboundHTLCRemovalReason::FailMalformed((hash, code)) => {
1u8.write(writer)?;
Expand DownExpand Up@@ -10312,11 +10331,13 @@ impl<SP: Deref> Writeable for FundedChannel<SP> where SP::Target: SignerProvider
pending_outbound_blinding_points.push(htlc.blinding_point);
}

let mut holding_cell_skimmed_fees: Vec<Option<u64>> = Vec::new();
let mut holding_cell_blinding_points: Vec<Option<PublicKey>> = Vec::new();
let holding_cell_htlc_update_count = self.context.holding_cell_htlc_updates.len();
let mut holding_cell_skimmed_fees: Vec<Option<u64>> = Vec::with_capacity(holding_cell_htlc_update_count);
let mut holding_cell_blinding_points: Vec<Option<PublicKey>> = Vec::with_capacity(holding_cell_htlc_update_count);
let mut holding_cell_failure_attribution_data: Vec<Option<&AttributionData>> = Vec::with_capacity(holding_cell_htlc_update_count);
// Vec of (htlc_id, failure_code, sha256_of_onion)
let mut malformed_htlcs: Vec<(u64, u16, [u8; 32])> = Vec::new();
(self.context.holding_cell_htlc_updates.len() as u64).write(writer)?;
(holding_cell_htlc_update_count as u64).write(writer)?;
for update in self.context.holding_cell_htlc_updates.iter() {
match update {
&HTLCUpdateAwaitingACK::AddHTLC {
Expand All@@ -10342,6 +10363,9 @@ impl<SP: Deref> Writeable for FundedChannel<SP> where SP::Target: SignerProvider
2u8.write(writer)?;
htlc_id.write(writer)?;
err_packet.data.write(writer)?;

// Store the attribution data for later writing.
holding_cell_failure_attribution_data.push(err_packet.attribution_data.as_ref());
}
&HTLCUpdateAwaitingACK::FailMalformedHTLC {
htlc_id, failure_code, sha256_of_onion
Expand All@@ -10353,6 +10377,10 @@ impl<SP: Deref> Writeable for FundedChannel<SP> where SP::Target: SignerProvider
2u8.write(writer)?;
htlc_id.write(writer)?;
Vec::<u8>::new().write(writer)?;

// Push 'None' attribution data for FailMalformedHTLC, because FailMalformedHTLC uses the same
// type 2 and is deserialized as a FailHTLC.
holding_cell_failure_attribution_data.push(None);
}
}
}
Expand DownExpand Up@@ -10525,6 +10553,8 @@ impl<SP: Deref> Writeable for FundedChannel<SP> where SP::Target: SignerProvider
(49, self.context.local_initiated_shutdown, option), // Added in 0.0.122
(51, is_manual_broadcast, option), // Added in 0.0.124
(53, funding_tx_broadcast_safe_event_emitted, option), // Added in 0.0.124
(55, removed_htlc_failure_attribution_data, optional_vec), // Added in 0.2
(57, holding_cell_failure_attribution_data, optional_vec), // Added in 0.2
});

Ok(())
Expand DownExpand Up@@ -10602,6 +10632,7 @@ impl<'a, 'b, 'c, ES: Deref, SP: Deref> ReadableArgs<(&'a ES, &'b SP, &'c Channel
let reason = match <u8 as Readable>::read(reader)? {
0 => InboundHTLCRemovalReason::FailRelay(msgs::OnionErrorPacket {
data: Readable::read(reader)?,
attribution_data: None,
}),
1 => InboundHTLCRemovalReason::FailMalformed(Readable::read(reader)?),
2 => InboundHTLCRemovalReason::Fulfill(Readable::read(reader)?),
Expand DownExpand Up@@ -10642,6 +10673,7 @@ impl<'a, 'b, 'c, ES: Deref, SP: Deref> ReadableArgs<(&'a ES, &'b SP, &'c Channel
},
skimmed_fee_msat: None,
blinding_point: None,
send_timestamp: None,
});
}

Expand All@@ -10666,6 +10698,7 @@ impl<'a, 'b, 'c, ES: Deref, SP: Deref> ReadableArgs<(&'a ES, &'b SP, &'c Channel
htlc_id: Readable::read(reader)?,
err_packet: OnionErrorPacket {
data: Readable::read(reader)?,
attribution_data: None,
},
},
_ => return Err(DecodeError::InvalidValue),
Expand DownExpand Up@@ -10809,6 +10842,9 @@ impl<'a, 'b, 'c, ES: Deref, SP: Deref> ReadableArgs<(&'a ES, &'b SP, &'c Channel
let mut pending_outbound_blinding_points_opt: Option<Vec<Option<PublicKey>>> = None;
let mut holding_cell_blinding_points_opt: Option<Vec<Option<PublicKey>>> = None;

let mut removed_htlc_failure_attribution_data: Option<Vec<Option<AttributionData>>> = None;
let mut holding_cell_failure_attribution_data: Option<Vec<Option<AttributionData>>> = None;

let mut malformed_htlcs: Option<Vec<(u64, u16, [u8; 32])>> = None;
let mut monitor_pending_update_adds: Option<Vec<msgs::UpdateAddHTLC>> = None;

Expand DownExpand Up@@ -10851,6 +10887,8 @@ impl<'a, 'b, 'c, ES: Deref, SP: Deref> ReadableArgs<(&'a ES, &'b SP, &'c Channel
(49, local_initiated_shutdown, option),
(51, is_manual_broadcast, option),
(53, funding_tx_broadcast_safe_event_emitted, option),
(55, removed_htlc_failure_attribution_data, optional_vec),
(57, holding_cell_failure_attribution_data, optional_vec),
});

let holder_signer = signer_provider.derive_channel_signer(channel_keys_id);
Expand DownExpand Up@@ -10932,6 +10970,38 @@ impl<'a, 'b, 'c, ES: Deref, SP: Deref> ReadableArgs<(&'a ES, &'b SP, &'c Channel
if iter.next().is_some() { return Err(DecodeError::InvalidValue) }
}

if let Some(attribution_data_list) = removed_htlc_failure_attribution_data {
let mut removed_htlc_relay_failures =
pending_inbound_htlcs.iter_mut().filter_map(|status|
if let InboundHTLCState::LocalRemoved(InboundHTLCRemovalReason::FailRelay(ref mut packet)) = &mut status.state {
Some(&mut packet.attribution_data)
} else {
None
}
);

for attribution_data in attribution_data_list {
*removed_htlc_relay_failures.next().ok_or(DecodeError::InvalidValue)? = attribution_data;
}
if removed_htlc_relay_failures.next().is_some() { return Err(DecodeError::InvalidValue); }
}

if let Some(attribution_data_list) = holding_cell_failure_attribution_data {
let mut holding_cell_failures =
holding_cell_htlc_updates.iter_mut().filter_map(|upd|
if let HTLCUpdateAwaitingACK::FailHTLC { err_packet: OnionErrorPacket { ref mut attribution_data, .. }, .. } = upd {
Some(attribution_data)
} else {
None
}
);

for attribution_data in attribution_data_list {
*holding_cell_failures.next().ok_or(DecodeError::InvalidValue)? = attribution_data;
}
if holding_cell_failures.next().is_some() { return Err(DecodeError::InvalidValue); }
}

if let Some(malformed_htlcs) = malformed_htlcs {
for (malformed_htlc_id, failure_code, sha256_of_onion) in malformed_htlcs {
let htlc_idx = holding_cell_htlc_updates.iter().position(|htlc| {
Expand DownExpand Up@@ -11122,6 +11192,18 @@ impl<'a, 'b, 'c, ES: Deref, SP: Deref> ReadableArgs<(&'a ES, &'b SP, &'c Channel
}
}

fn duration_since_epoch() -> Option<Duration> {
#[cfg(not(feature = "std"))]
let now = None;

#[cfg(feature = "std")]
let now = Some(std::time::SystemTime::now()
.duration_since(std::time::SystemTime::UNIX_EPOCH)
.expect("SystemTime::now() should come after SystemTime::UNIX_EPOCH"));

now
}

#[cfg(test)]
mod tests {
use std::cmp;
Expand All@@ -11135,7 +11217,7 @@ mod tests {
use bitcoin::network::Network;
#[cfg(splicing)]
use bitcoin::Weight;
use crate::ln::onion_utils::INVALID_ONION_BLINDING;
use crate::ln::onion_utils::{AttributionData, INVALID_ONION_BLINDING};
use crate::types::payment::{PaymentHash, PaymentPreimage};
use crate::ln::channel_keys::{RevocationKey, RevocationBasepoint};
use crate::ln::channelmanager::{self, HTLCSource, PaymentId};
Expand DownExpand Up@@ -11357,6 +11439,7 @@ mod tests {
},
skimmed_fee_msat: None,
blinding_point: None,
send_timestamp: None,
});

// Make sure when Node A calculates their local commitment transaction, none of the HTLCs pass
Expand DownExpand Up@@ -11741,6 +11824,7 @@ mod tests {
source: dummy_htlc_source.clone(),
skimmed_fee_msat: None,
blinding_point: None,
send_timestamp: None,
};
let mut pending_outbound_htlcs = vec![dummy_outbound_output.clone(); 10];
for (idx, htlc) in pending_outbound_htlcs.iter_mut().enumerate() {
Expand DownExpand Up@@ -11772,7 +11856,7 @@ mod tests {
htlc_id: 0,
};
let dummy_holding_cell_failed_htlc = |htlc_id| HTLCUpdateAwaitingACK::FailHTLC {
htlc_id, err_packet: msgs::OnionErrorPacket { data: vec![42] }
htlc_id, err_packet: msgs::OnionErrorPacket { data: vec![42], attribution_data: Some(AttributionData::new()) }
};
let dummy_holding_cell_malformed_htlc = |htlc_id| HTLCUpdateAwaitingACK::FailMalformedHTLC {
htlc_id, failure_code: INVALID_ONION_BLINDING, sha256_of_onion: [0; 32],
Expand DownExpand Up@@ -12054,6 +12138,7 @@ mod tests {
source: HTLCSource::dummy(),
skimmed_fee_msat: None,
blinding_point: None,
send_timestamp: None,
};
out.payment_hash.0 = Sha256::hash(&<Vec<u8>>::from_hex("0202020202020202020202020202020202020202020202020202020202020202").unwrap()).to_byte_array();
out
Expand All@@ -12068,6 +12153,7 @@ mod tests {
source: HTLCSource::dummy(),
skimmed_fee_msat: None,
blinding_point: None,
send_timestamp: None,
};
out.payment_hash.0 = Sha256::hash(&<Vec<u8>>::from_hex("0303030303030303030303030303030303030303030303030303030303030303").unwrap()).to_byte_array();
out
Expand DownExpand Up@@ -12480,6 +12566,7 @@ mod tests {
source: HTLCSource::dummy(),
skimmed_fee_msat: None,
blinding_point: None,
send_timestamp: None,
};
out.payment_hash.0 = Sha256::hash(&<Vec<u8>>::from_hex("0505050505050505050505050505050505050505050505050505050505050505").unwrap()).to_byte_array();
out
Expand All@@ -12494,6 +12581,7 @@ mod tests {
source: HTLCSource::dummy(),
skimmed_fee_msat: None,
blinding_point: None,
send_timestamp: None,
};
out.payment_hash.0 = Sha256::hash(&<Vec<u8>>::from_hex("0505050505050505050505050505050505050505050505050505050505050505").unwrap()).to_byte_array();
out
Expand Down
Loading