Skip to content

Commit d3c3447

Browse files
committed
Attributable failures
1 parent 6dc2d3d commit d3c3447

File tree

4 files changed

+455
-80
lines changed

4 files changed

+455
-80
lines changed

lightning/src/ln/channel.rs

Lines changed: 30 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+
timestamp: Option<Duration>,
326328
}
327329

328330
/// See AwaitingRemoteRevoke ChannelState for more info
@@ -6095,10 +6097,16 @@ impl<SP: Deref> FundedChannel<SP> where
60956097
false
60966098
} else { true }
60976099
});
6100+
let now = duration_since_epoch();
60986101
pending_outbound_htlcs.retain(|htlc| {
60996102
if let &OutboundHTLCState::AwaitingRemovedRemoteRevoke(ref outcome) = &htlc.state {
61006103
log_trace!(logger, " ...removing outbound AwaitingRemovedRemoteRevoke {}", &htlc.payment_hash);
6101-
if let OutboundHTLCOutcome::Failure(reason) = outcome.clone() { // We really want take() here, but, again, non-mut ref :(
6104+
if let OutboundHTLCOutcome::Failure(mut reason) = outcome.clone() { // We really want take() here, but, again, non-mut ref :(
6105+
if let (Some(timestamp), Some(now)) = (htlc.timestamp, now) {
6106+
let hold_time = u32::try_from((now - timestamp).as_millis()).unwrap_or(u32::MAX);
6107+
reason.set_hold_time(hold_time);
6108+
}
6109+
61026110
revoked_htlcs.push((htlc.source.clone(), htlc.payment_hash, reason));
61036111
} else {
61046112
finalized_claimed_htlcs.push(htlc.source.clone());
@@ -8667,6 +8675,7 @@ impl<SP: Deref> FundedChannel<SP> where
86678675
return Ok(None);
86688676
}
86698677

8678+
let timestamp = duration_since_epoch();
86708679
self.context.pending_outbound_htlcs.push(OutboundHTLCOutput {
86718680
htlc_id: self.context.next_holder_htlc_id,
86728681
amount_msat,
@@ -8676,6 +8685,7 @@ impl<SP: Deref> FundedChannel<SP> where
86768685
source,
86778686
blinding_point,
86788687
skimmed_fee_msat,
8688+
timestamp,
86798689
});
86808690

86818691
let res = msgs::UpdateAddHTLC {
@@ -10673,6 +10683,7 @@ impl<'a, 'b, 'c, ES: Deref, SP: Deref> ReadableArgs<(&'a ES, &'b SP, &'c Channel
1067310683
},
1067410684
skimmed_fee_msat: None,
1067510685
blinding_point: None,
10686+
timestamp: None,
1067610687
});
1067710688
}
1067810689

@@ -11191,6 +11202,18 @@ impl<'a, 'b, 'c, ES: Deref, SP: Deref> ReadableArgs<(&'a ES, &'b SP, &'c Channel
1119111202
}
1119211203
}
1119311204

11205+
fn duration_since_epoch() -> Option<Duration> {
11206+
#[cfg(not(feature = "std"))]
11207+
let now = None;
11208+
11209+
#[cfg(feature = "std")]
11210+
let now = Some(std::time::SystemTime::now()
11211+
.duration_since(std::time::SystemTime::UNIX_EPOCH)
11212+
.expect("SystemTime::now() should come after SystemTime::UNIX_EPOCH"));
11213+
11214+
now
11215+
}
11216+
1119411217
#[cfg(test)]
1119511218
mod tests {
1119611219
use std::cmp;
@@ -11426,6 +11449,7 @@ mod tests {
1142611449
},
1142711450
skimmed_fee_msat: None,
1142811451
blinding_point: None,
11452+
timestamp: None,
1142911453
});
1143011454

1143111455
// Make sure when Node A calculates their local commitment transaction, none of the HTLCs pass
@@ -11810,6 +11834,7 @@ mod tests {
1181011834
source: dummy_htlc_source.clone(),
1181111835
skimmed_fee_msat: None,
1181211836
blinding_point: None,
11837+
timestamp: None,
1181311838
};
1181411839
let mut pending_outbound_htlcs = vec![dummy_outbound_output.clone(); 10];
1181511840
for (idx, htlc) in pending_outbound_htlcs.iter_mut().enumerate() {
@@ -12132,6 +12157,7 @@ mod tests {
1213212157
source: HTLCSource::dummy(),
1213312158
skimmed_fee_msat: None,
1213412159
blinding_point: None,
12160+
timestamp: None,
1213512161
};
1213612162
out.payment_hash.0 = Sha256::hash(&<Vec<u8>>::from_hex("0202020202020202020202020202020202020202020202020202020202020202").unwrap()).to_byte_array();
1213712163
out
@@ -12146,6 +12172,7 @@ mod tests {
1214612172
source: HTLCSource::dummy(),
1214712173
skimmed_fee_msat: None,
1214812174
blinding_point: None,
12175+
timestamp: None,
1214912176
};
1215012177
out.payment_hash.0 = Sha256::hash(&<Vec<u8>>::from_hex("0303030303030303030303030303030303030303030303030303030303030303").unwrap()).to_byte_array();
1215112178
out
@@ -12558,6 +12585,7 @@ mod tests {
1255812585
source: HTLCSource::dummy(),
1255912586
skimmed_fee_msat: None,
1256012587
blinding_point: None,
12588+
timestamp: None,
1256112589
};
1256212590
out.payment_hash.0 = Sha256::hash(&<Vec<u8>>::from_hex("0505050505050505050505050505050505050505050505050505050505050505").unwrap()).to_byte_array();
1256312591
out
@@ -12572,6 +12600,7 @@ mod tests {
1257212600
source: HTLCSource::dummy(),
1257312601
skimmed_fee_msat: None,
1257412602
blinding_point: None,
12603+
timestamp: None,
1257512604
};
1257612605
out.payment_hash.0 = Sha256::hash(&<Vec<u8>>::from_hex("0505050505050505050505050505050505050505050505050505050505050505").unwrap()).to_byte_array();
1257712606
out

lightning/src/ln/functional_tests.rs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ use crate::chain::chaininterface::LowerBoundedFeeEstimator;
1717
use crate::chain::channelmonitor;
1818
use crate::chain::channelmonitor::{Balance, ChannelMonitorUpdateStep, CLTV_CLAIM_BUFFER, LATENCY_GRACE_PERIOD_BLOCKS, ANTI_REORG_DELAY, COUNTERPARTY_CLAIMABLE_WITHIN_BLOCKS_PINNABLE};
1919
use crate::chain::transaction::OutPoint;
20+
use crate::ln::onion_utils::ATTRIBUTION_DATA_LEN;
2021
use crate::sign::{ecdsa::EcdsaChannelSigner, EntropySource, OutputSpender, SignerProvider};
2122
use crate::events::bump_transaction::WalletSource;
2223
use crate::events::{Event, FundingInfo, PathFailure, PaymentPurpose, ClosureReason, HTLCDestination, PaymentFailureReason};
@@ -7060,7 +7061,7 @@ pub fn test_update_fulfill_htlc_bolt2_update_fail_htlc_before_commitment() {
70607061
channel_id: chan.2,
70617062
htlc_id: 0,
70627063
reason: Vec::new(),
7063-
attribution_data: None,
7064+
attribution_data: Some([0; ATTRIBUTION_DATA_LEN])
70647065
};
70657066

70667067
nodes[0].node.handle_update_fail_htlc(nodes[1].node.get_our_node_id(), &update_msg);

lightning/src/ln/onion_route_tests.rs

Lines changed: 32 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,7 @@ use crate::ln::functional_test_utils::*;
5050
use crate::ln::onion_utils::{construct_trampoline_onion_keys, construct_trampoline_onion_packet};
5151

5252
use super::msgs::OnionErrorPacket;
53+
use super::onion_utils::{add_hmacs, ATTRIBUTION_DATA_LEN};
5354

5455
fn run_onion_failure_test<F1,F2>(_name: &str, test_case: u8, nodes: &Vec<Node>, route: &Route, payment_hash: &PaymentHash, payment_secret: &PaymentSecret, callback_msg: F1, callback_node: F2, expected_retryable: bool, expected_error_code: Option<u16>, expected_channel_update: Option<NetworkUpdate>, expected_short_channel_id: Option<u64>, expected_htlc_destination: Option<HTLCDestination>)
5556
where F1: for <'a> FnMut(&'a mut msgs::UpdateAddHTLC),
@@ -411,17 +412,19 @@ fn test_onion_failure() {
411412
// and tamper returning error message
412413
let session_priv = SecretKey::from_slice(&[3; 32]).unwrap();
413414
let onion_keys = onion_utils::construct_onion_keys(&Secp256k1::new(), &route.paths[0], &session_priv).unwrap();
414-
let failure = onion_utils::build_failure_packet(onion_keys[0].shared_secret.as_ref(), NODE|2, &[0;0]);
415+
let failure = onion_utils::build_failure_packet(onion_keys[0].shared_secret.as_ref(), NODE|2, &[0;0], &[0; 4]);
415416
msg.reason = failure.data;
417+
msg.attribution_data = failure.attribution_data;
416418
}, ||{}, true, Some(NODE|2), Some(NetworkUpdate::NodeFailure{node_id: route.paths[0].hops[0].pubkey, is_permanent: false}), Some(route.paths[0].hops[0].short_channel_id), Some(next_hop_failure.clone()));
417419

418420
// final node failure
419421
run_onion_failure_test_with_fail_intercept("temporary_node_failure", 200, &nodes, &route, &payment_hash, &payment_secret, |_msg| {}, |msg| {
420422
// and tamper returning error message
421423
let session_priv = SecretKey::from_slice(&[3; 32]).unwrap();
422424
let onion_keys = onion_utils::construct_onion_keys(&Secp256k1::new(), &route.paths[0], &session_priv).unwrap();
423-
let failure = onion_utils::build_failure_packet(onion_keys[1].shared_secret.as_ref(), NODE|2, &[0;0]);
425+
let failure = onion_utils::build_failure_packet(onion_keys[1].shared_secret.as_ref(), NODE|2, &[0;0], &[0; 4]);
424426
msg.reason = failure.data;
427+
msg.attribution_data = failure.attribution_data;
425428
}, ||{
426429
nodes[2].node.fail_htlc_backwards(&payment_hash);
427430
}, true, Some(NODE|2), Some(NetworkUpdate::NodeFailure{node_id: route.paths[0].hops[1].pubkey, is_permanent: false}), Some(route.paths[0].hops[1].short_channel_id), None);
@@ -433,16 +436,18 @@ fn test_onion_failure() {
433436
}, |msg| {
434437
let session_priv = SecretKey::from_slice(&[3; 32]).unwrap();
435438
let onion_keys = onion_utils::construct_onion_keys(&Secp256k1::new(), &route.paths[0], &session_priv).unwrap();
436-
let failure = onion_utils::build_failure_packet(onion_keys[0].shared_secret.as_ref(), PERM|NODE|2, &[0;0]);
439+
let failure = onion_utils::build_failure_packet(onion_keys[0].shared_secret.as_ref(), PERM|NODE|2, &[0;0], &[0; 4]);
437440
msg.reason = failure.data;
441+
msg.attribution_data = failure.attribution_data;
438442
}, ||{}, true, Some(PERM|NODE|2), Some(NetworkUpdate::NodeFailure{node_id: route.paths[0].hops[0].pubkey, is_permanent: true}), Some(route.paths[0].hops[0].short_channel_id), Some(next_hop_failure.clone()));
439443

440444
// final node failure
441445
run_onion_failure_test_with_fail_intercept("permanent_node_failure", 200, &nodes, &route, &payment_hash, &payment_secret, |_msg| {}, |msg| {
442446
let session_priv = SecretKey::from_slice(&[3; 32]).unwrap();
443447
let onion_keys = onion_utils::construct_onion_keys(&Secp256k1::new(), &route.paths[0], &session_priv).unwrap();
444-
let failure = onion_utils::build_failure_packet(onion_keys[1].shared_secret.as_ref(), PERM|NODE|2, &[0;0]);
448+
let failure = onion_utils::build_failure_packet(onion_keys[1].shared_secret.as_ref(), PERM|NODE|2, &[0;0], &[0; 4]);
445449
msg.reason = failure.data;
450+
msg.attribution_data = failure.attribution_data;
446451
}, ||{
447452
nodes[2].node.fail_htlc_backwards(&payment_hash);
448453
}, false, Some(PERM|NODE|2), Some(NetworkUpdate::NodeFailure{node_id: route.paths[0].hops[1].pubkey, is_permanent: true}), Some(route.paths[0].hops[1].short_channel_id), None);
@@ -454,8 +459,9 @@ fn test_onion_failure() {
454459
}, |msg| {
455460
let session_priv = SecretKey::from_slice(&[3; 32]).unwrap();
456461
let onion_keys = onion_utils::construct_onion_keys(&Secp256k1::new(), &route.paths[0], &session_priv).unwrap();
457-
let failure = onion_utils::build_failure_packet(onion_keys[0].shared_secret.as_ref(), PERM|NODE|3, &[0;0]);
462+
let failure = onion_utils::build_failure_packet(onion_keys[0].shared_secret.as_ref(), PERM|NODE|3, &[0;0], &[0; 4]);
458463
msg.reason = failure.data;
464+
msg.attribution_data = failure.attribution_data;
459465
}, ||{
460466
nodes[2].node.fail_htlc_backwards(&payment_hash);
461467
}, true, Some(PERM|NODE|3), Some(NetworkUpdate::NodeFailure{node_id: route.paths[0].hops[0].pubkey, is_permanent: true}), Some(route.paths[0].hops[0].short_channel_id), Some(next_hop_failure.clone()));
@@ -464,8 +470,9 @@ fn test_onion_failure() {
464470
run_onion_failure_test_with_fail_intercept("required_node_feature_missing", 200, &nodes, &route, &payment_hash, &payment_secret, |_msg| {}, |msg| {
465471
let session_priv = SecretKey::from_slice(&[3; 32]).unwrap();
466472
let onion_keys = onion_utils::construct_onion_keys(&Secp256k1::new(), &route.paths[0], &session_priv).unwrap();
467-
let failure = onion_utils::build_failure_packet(onion_keys[1].shared_secret.as_ref(), PERM|NODE|3, &[0;0]);
473+
let failure = onion_utils::build_failure_packet(onion_keys[1].shared_secret.as_ref(), PERM|NODE|3, &[0;0], &[0; 4]);
468474
msg.reason = failure.data;
475+
msg.attribution_data = failure.attribution_data;
469476
}, ||{
470477
nodes[2].node.fail_htlc_backwards(&payment_hash);
471478
}, false, Some(PERM|NODE|3), Some(NetworkUpdate::NodeFailure{node_id: route.paths[0].hops[1].pubkey, is_permanent: true}), Some(route.paths[0].hops[1].short_channel_id), None);
@@ -495,8 +502,9 @@ fn test_onion_failure() {
495502
}, |msg| {
496503
let session_priv = SecretKey::from_slice(&[3; 32]).unwrap();
497504
let onion_keys = onion_utils::construct_onion_keys(&Secp256k1::new(), &route.paths[0], &session_priv).unwrap();
498-
let failure = onion_utils::build_failure_packet(onion_keys[0].shared_secret.as_ref(), UPDATE|7, &err_data);
505+
let failure = onion_utils::build_failure_packet(onion_keys[0].shared_secret.as_ref(), UPDATE|7, &err_data, &[0; 4]);
499506
msg.reason = failure.data;
507+
msg.attribution_data = failure.attribution_data;
500508
}, ||{}, true, Some(UPDATE|7),
501509
Some(NetworkUpdate::ChannelFailure { short_channel_id, is_permanent: false }),
502510
Some(short_channel_id), Some(next_hop_failure.clone()));
@@ -508,8 +516,9 @@ fn test_onion_failure() {
508516
}, |msg| {
509517
let session_priv = SecretKey::from_slice(&[3; 32]).unwrap();
510518
let onion_keys = onion_utils::construct_onion_keys(&Secp256k1::new(), &route.paths[0], &session_priv).unwrap();
511-
let failure = onion_utils::build_failure_packet(onion_keys[0].shared_secret.as_ref(), UPDATE|7, &err_data_without_type);
519+
let failure = onion_utils::build_failure_packet(onion_keys[0].shared_secret.as_ref(), UPDATE|7, &err_data_without_type, &[0; 4]);
512520
msg.reason = failure.data;
521+
msg.attribution_data = failure.attribution_data;
513522
}, ||{}, true, Some(UPDATE|7),
514523
Some(NetworkUpdate::ChannelFailure { short_channel_id, is_permanent: false }),
515524
Some(short_channel_id), Some(next_hop_failure.clone()));
@@ -520,8 +529,9 @@ fn test_onion_failure() {
520529
}, |msg| {
521530
let session_priv = SecretKey::from_slice(&[3; 32]).unwrap();
522531
let onion_keys = onion_utils::construct_onion_keys(&Secp256k1::new(), &route.paths[0], &session_priv).unwrap();
523-
let failure = onion_utils::build_failure_packet(onion_keys[0].shared_secret.as_ref(), PERM|8, &[0;0]);
532+
let failure = onion_utils::build_failure_packet(onion_keys[0].shared_secret.as_ref(), PERM|8, &[0;0], &[0; 4]);
524533
msg.reason = failure.data;
534+
msg.attribution_data = failure.attribution_data;
525535
// short_channel_id from the processing node
526536
}, ||{}, true, Some(PERM|8), Some(NetworkUpdate::ChannelFailure{short_channel_id, is_permanent: true}), Some(short_channel_id), Some(next_hop_failure.clone()));
527537

@@ -531,8 +541,9 @@ fn test_onion_failure() {
531541
}, |msg| {
532542
let session_priv = SecretKey::from_slice(&[3; 32]).unwrap();
533543
let onion_keys = onion_utils::construct_onion_keys(&Secp256k1::new(), &route.paths[0], &session_priv).unwrap();
534-
let failure = onion_utils::build_failure_packet(onion_keys[0].shared_secret.as_ref(), PERM|9, &[0;0]);
544+
let failure = onion_utils::build_failure_packet(onion_keys[0].shared_secret.as_ref(), PERM|9, &[0;0], &[0; 4]);
535545
msg.reason = failure.data;
546+
msg.attribution_data = failure.attribution_data;
536547
// short_channel_id from the processing node
537548
}, ||{}, true, Some(PERM|9), Some(NetworkUpdate::ChannelFailure{short_channel_id, is_permanent: true}), Some(short_channel_id), Some(next_hop_failure.clone()));
538549

@@ -664,8 +675,9 @@ fn test_onion_failure() {
664675
// Tamper returning error message
665676
let session_priv = SecretKey::from_slice(&[3; 32]).unwrap();
666677
let onion_keys = onion_utils::construct_onion_keys(&Secp256k1::new(), &route.paths[0], &session_priv).unwrap();
667-
let failure = onion_utils::build_failure_packet(onion_keys[1].shared_secret.as_ref(), 23, &[0;0]);
678+
let failure = onion_utils::build_failure_packet(onion_keys[1].shared_secret.as_ref(), 23, &[0;0], &[0; 4]);
668679
msg.reason = failure.data;
680+
msg.attribution_data = failure.attribution_data;
669681
}, ||{
670682
nodes[2].node.fail_htlc_backwards(&payment_hash);
671683
}, true, Some(23), None, None, None);
@@ -685,11 +697,13 @@ fn test_onion_failure() {
685697
decoded_err_packet.hmac = Hmac::from_engine(hmac).to_byte_array();
686698
let mut onion_error = OnionErrorPacket {
687699
data: decoded_err_packet.encode(),
688-
attribution_data: None,
700+
attribution_data: Some([0; ATTRIBUTION_DATA_LEN]),
689701
};
702+
add_hmacs(&onion_keys[1].shared_secret.as_ref(), &onion_error.data, onion_error.attribution_data.as_mut().unwrap());
690703
onion_utils::test_crypt_failure_packet(
691704
&onion_keys[1].shared_secret.as_ref(), &mut onion_error);
692705
msg.reason = onion_error.data;
706+
msg.attribution_data = onion_error.attribution_data;
693707
}, || nodes[2].node.fail_htlc_backwards(&payment_hash), false, None,
694708
Some(NetworkUpdate::NodeFailure { node_id: route.paths[0].hops[1].pubkey, is_permanent: true }),
695709
Some(channels[1].0.contents.short_channel_id), None);
@@ -713,11 +727,13 @@ fn test_onion_failure() {
713727
decoded_err_packet.hmac = Hmac::from_engine(hmac).to_byte_array();
714728
let mut onion_error = OnionErrorPacket{
715729
data: decoded_err_packet.encode(),
716-
attribution_data: None,
730+
attribution_data: Some([0; ATTRIBUTION_DATA_LEN]),
717731
};
732+
add_hmacs(&onion_keys[0].shared_secret.as_ref(), &onion_error.data, onion_error.attribution_data.as_mut().unwrap());
718733
onion_utils::test_crypt_failure_packet(
719734
&onion_keys[0].shared_secret.as_ref(), &mut onion_error);
720735
msg.reason = onion_error.data;
736+
msg.attribution_data = onion_error.attribution_data;
721737
}, || {}, true, Some(0x1000|7),
722738
Some(NetworkUpdate::ChannelFailure {
723739
short_channel_id: channels[1].0.contents.short_channel_id,
@@ -742,11 +758,13 @@ fn test_onion_failure() {
742758
decoded_err_packet.hmac = Hmac::from_engine(hmac).to_byte_array();
743759
let mut onion_error = OnionErrorPacket{
744760
data: decoded_err_packet.encode(),
745-
attribution_data: None,
761+
attribution_data: Some([0; ATTRIBUTION_DATA_LEN]),
746762
};
763+
add_hmacs(&onion_keys[1].shared_secret.as_ref(), &onion_error.data, onion_error.attribution_data.as_mut().unwrap());
747764
onion_utils::test_crypt_failure_packet(
748765
&onion_keys[1].shared_secret.as_ref(), &mut onion_error);
749766
msg.reason = onion_error.data;
767+
msg.attribution_data = onion_error.attribution_data;
750768
}, || nodes[2].node.fail_htlc_backwards(&payment_hash), true, Some(0x1000|7),
751769
Some(NetworkUpdate::ChannelFailure {
752770
short_channel_id: channels[1].0.contents.short_channel_id,

0 commit comments

Comments
 (0)