Skip to content

Commit cf1cd5d

Browse files
committed
Track htlc hold time
Record a timestamp when the htlc is sent out and record the hold duration alongside the failure reason.
1 parent 92e39b2 commit cf1cd5d

File tree

2 files changed

+52
-4
lines changed

2 files changed

+52
-4
lines changed

lightning/src/ln/channel.rs

Lines changed: 36 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,7 @@ use crate::util::scid_utils::scid_from_parts;
6868

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

328330
/// See AwaitingRemoteRevoke ChannelState for more info
@@ -6073,10 +6075,16 @@ impl<SP: Deref> FundedChannel<SP> where
60736075
false
60746076
} else { true }
60756077
});
6078+
let now = duration_since_epoch();
60766079
pending_outbound_htlcs.retain(|htlc| {
60776080
if let &OutboundHTLCState::AwaitingRemovedRemoteRevoke(ref outcome) = &htlc.state {
60786081
log_trace!(logger, " ...removing outbound AwaitingRemovedRemoteRevoke {}", &htlc.payment_hash);
6079-
if let OutboundHTLCOutcome::Failure(reason) = outcome.clone() { // We really want take() here, but, again, non-mut ref :(
6082+
if let OutboundHTLCOutcome::Failure(mut reason) = outcome.clone() { // We really want take() here, but, again, non-mut ref :(
6083+
if let (Some(timestamp), Some(now)) = (htlc.send_timestamp, now) {
6084+
let hold_time = u32::try_from(now.saturating_sub(timestamp).as_millis()).unwrap_or(u32::MAX);
6085+
reason.set_hold_time(hold_time);
6086+
}
6087+
60806088
revoked_htlcs.push((htlc.source.clone(), htlc.payment_hash, reason));
60816089
} else {
60826090
finalized_claimed_htlcs.push(htlc.source.clone());
@@ -8648,6 +8656,13 @@ impl<SP: Deref> FundedChannel<SP> where
86488656
return Ok(None);
86498657
}
86508658

8659+
// Record the approximate time when the HTLC is sent to the peer. This timestamp is later used to calculate the
8660+
// htlc hold time for reporting back to the sender. There is some freedom to report a time including or
8661+
// excluding our own processing time. What we choose here doesn't matter all that much, because it will probably
8662+
// just shift sender-applied penalties between our incoming and outgoing side. So we choose measuring points
8663+
// that are simple to implement, and we do it on the outgoing side because then the failure message that encodes
8664+
// the hold time still needs to be built in channel manager.
8665+
let timestamp = duration_since_epoch();
86518666
self.context.pending_outbound_htlcs.push(OutboundHTLCOutput {
86528667
htlc_id: self.context.next_holder_htlc_id,
86538668
amount_msat,
@@ -8657,6 +8672,7 @@ impl<SP: Deref> FundedChannel<SP> where
86578672
source,
86588673
blinding_point,
86598674
skimmed_fee_msat,
8675+
send_timestamp: timestamp,
86608676
});
86618677

86628678
let res = msgs::UpdateAddHTLC {
@@ -10657,6 +10673,7 @@ impl<'a, 'b, 'c, ES: Deref, SP: Deref> ReadableArgs<(&'a ES, &'b SP, &'c Channel
1065710673
},
1065810674
skimmed_fee_msat: None,
1065910675
blinding_point: None,
10676+
send_timestamp: None,
1066010677
});
1066110678
}
1066210679

@@ -11175,6 +11192,18 @@ impl<'a, 'b, 'c, ES: Deref, SP: Deref> ReadableArgs<(&'a ES, &'b SP, &'c Channel
1117511192
}
1117611193
}
1117711194

11195+
fn duration_since_epoch() -> Option<Duration> {
11196+
#[cfg(not(feature = "std"))]
11197+
let now = None;
11198+
11199+
#[cfg(feature = "std")]
11200+
let now = Some(std::time::SystemTime::now()
11201+
.duration_since(std::time::SystemTime::UNIX_EPOCH)
11202+
.expect("SystemTime::now() should come after SystemTime::UNIX_EPOCH"));
11203+
11204+
now
11205+
}
11206+
1117811207
#[cfg(test)]
1117911208
mod tests {
1118011209
use std::cmp;
@@ -11410,6 +11439,7 @@ mod tests {
1141011439
},
1141111440
skimmed_fee_msat: None,
1141211441
blinding_point: None,
11442+
send_timestamp: None,
1141311443
});
1141411444

1141511445
// Make sure when Node A calculates their local commitment transaction, none of the HTLCs pass
@@ -11794,6 +11824,7 @@ mod tests {
1179411824
source: dummy_htlc_source.clone(),
1179511825
skimmed_fee_msat: None,
1179611826
blinding_point: None,
11827+
send_timestamp: None,
1179711828
};
1179811829
let mut pending_outbound_htlcs = vec![dummy_outbound_output.clone(); 10];
1179911830
for (idx, htlc) in pending_outbound_htlcs.iter_mut().enumerate() {
@@ -12107,6 +12138,7 @@ mod tests {
1210712138
source: HTLCSource::dummy(),
1210812139
skimmed_fee_msat: None,
1210912140
blinding_point: None,
12141+
send_timestamp: None,
1211012142
};
1211112143
out.payment_hash.0 = Sha256::hash(&<Vec<u8>>::from_hex("0202020202020202020202020202020202020202020202020202020202020202").unwrap()).to_byte_array();
1211212144
out
@@ -12121,6 +12153,7 @@ mod tests {
1212112153
source: HTLCSource::dummy(),
1212212154
skimmed_fee_msat: None,
1212312155
blinding_point: None,
12156+
send_timestamp: None,
1212412157
};
1212512158
out.payment_hash.0 = Sha256::hash(&<Vec<u8>>::from_hex("0303030303030303030303030303030303030303030303030303030303030303").unwrap()).to_byte_array();
1212612159
out
@@ -12533,6 +12566,7 @@ mod tests {
1253312566
source: HTLCSource::dummy(),
1253412567
skimmed_fee_msat: None,
1253512568
blinding_point: None,
12569+
send_timestamp: None,
1253612570
};
1253712571
out.payment_hash.0 = Sha256::hash(&<Vec<u8>>::from_hex("0505050505050505050505050505050505050505050505050505050505050505").unwrap()).to_byte_array();
1253812572
out
@@ -12547,6 +12581,7 @@ mod tests {
1254712581
source: HTLCSource::dummy(),
1254812582
skimmed_fee_msat: None,
1254912583
blinding_point: None,
12584+
send_timestamp: None,
1255012585
};
1255112586
out.payment_hash.0 = Sha256::hash(&<Vec<u8>>::from_hex("0505050505050505050505050505050505050505050505050505050505050505").unwrap()).to_byte_array();
1255212587
out

lightning/src/ln/onion_utils.rs

Lines changed: 16 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1378,10 +1378,21 @@ pub(super) struct HTLCFailReason(HTLCFailReasonRepr);
13781378
#[derive(Clone)] // See Channel::revoke_and_ack for why, tl;dr: Rust bug
13791379
#[cfg_attr(test, derive(PartialEq))]
13801380
enum HTLCFailReasonRepr {
1381-
LightningError { err: msgs::OnionErrorPacket },
1381+
LightningError { err: msgs::OnionErrorPacket, hold_time: Option<u32> },
13821382
Reason { failure_code: u16, data: Vec<u8> },
13831383
}
13841384

1385+
impl HTLCFailReason {
1386+
pub fn set_hold_time(&mut self, hold_time: u32) {
1387+
match self.0 {
1388+
HTLCFailReasonRepr::LightningError { hold_time: ref mut current_hold_time, .. } => {
1389+
*current_hold_time = Some(hold_time);
1390+
},
1391+
_ => {},
1392+
}
1393+
}
1394+
}
1395+
13851396
impl core::fmt::Debug for HTLCFailReason {
13861397
fn fmt(&self, f: &mut core::fmt::Formatter) -> Result<(), core::fmt::Error> {
13871398
match self.0 {
@@ -1422,6 +1433,7 @@ impl_writeable_tlv_based_enum!(HTLCFailReasonRepr,
14221433
None
14231434
})
14241435
),
1436+
(3, hold_time, option),
14251437
(_unused, err, (static_value, msgs::OnionErrorPacket { data: data.ok_or(DecodeError::InvalidValue)?, attribution_data })),
14261438
},
14271439
(1, Reason) => {
@@ -1484,6 +1496,7 @@ impl HTLCFailReason {
14841496
data: msg.reason.clone(),
14851497
attribution_data: msg.attribution_data.clone(),
14861498
},
1499+
hold_time: None,
14871500
})
14881501
}
14891502

@@ -1508,7 +1521,7 @@ impl HTLCFailReason {
15081521
build_failure_packet(incoming_packet_shared_secret, *failure_code, &data[..])
15091522
}
15101523
},
1511-
HTLCFailReasonRepr::LightningError { ref err } => {
1524+
HTLCFailReasonRepr::LightningError { ref err, .. } => {
15121525
let mut err = err.clone();
15131526

15141527
crypt_failure_packet(incoming_packet_shared_secret, &mut err);
@@ -1525,7 +1538,7 @@ impl HTLCFailReason {
15251538
L::Target: Logger,
15261539
{
15271540
match self.0 {
1528-
HTLCFailReasonRepr::LightningError { ref err } => {
1541+
HTLCFailReasonRepr::LightningError { ref err, .. } => {
15291542
process_onion_failure(secp_ctx, logger, &htlc_source, err.clone())
15301543
},
15311544
#[allow(unused)]

0 commit comments

Comments
 (0)