From 43340d647a0693bac4b29798ec46af707d5dc6ff Mon Sep 17 00:00:00 2001 From: Joost Jager Date: Wed, 18 Jun 2025 14:54:44 +0200 Subject: [PATCH 1/7] Rustfmt parts of channel, onion_route_tests and outbound_payment --- lightning/src/ln/channel.rs | 221 ++- lightning/src/ln/onion_route_tests.rs | 2298 +++++++++++++++++++------ lightning/src/ln/outbound_payment.rs | 183 +- 3 files changed, 2031 insertions(+), 671 deletions(-) diff --git a/lightning/src/ln/channel.rs b/lightning/src/ln/channel.rs index fb58b51d4dc..da6a436b5e9 100644 --- a/lightning/src/ln/channel.rs +++ b/lightning/src/ln/channel.rs @@ -7006,29 +7006,48 @@ where /// waiting on this revoke_and_ack. The generation of this new commitment_signed may also fail, /// generating an appropriate error *after* the channel state has been updated based on the /// revoke_and_ack message. - #[rustfmt::skip] - pub fn revoke_and_ack(&mut self, msg: &msgs::RevokeAndACK, - fee_estimator: &LowerBoundedFeeEstimator, logger: &L, hold_mon_update: bool, + pub fn revoke_and_ack( + &mut self, msg: &msgs::RevokeAndACK, fee_estimator: &LowerBoundedFeeEstimator, + logger: &L, hold_mon_update: bool, ) -> Result<(Vec<(HTLCSource, PaymentHash)>, Option), ChannelError> - where F::Target: FeeEstimator, L::Target: Logger, + where + F::Target: FeeEstimator, + L::Target: Logger, { if self.context.channel_state.is_quiescent() { - return Err(ChannelError::WarnAndDisconnect("Got revoke_and_ack message while quiescent".to_owned())); + return Err(ChannelError::WarnAndDisconnect( + "Got revoke_and_ack message while quiescent".to_owned(), + )); } if !matches!(self.context.channel_state, ChannelState::ChannelReady(_)) { - return Err(ChannelError::close("Got revoke/ACK message when channel was not in an operational state".to_owned())); + return Err(ChannelError::close( + "Got revoke/ACK message when channel was not in an operational state".to_owned(), + )); } if self.context.channel_state.is_peer_disconnected() { - return Err(ChannelError::close("Peer sent revoke_and_ack when we needed a channel_reestablish".to_owned())); + return Err(ChannelError::close( + "Peer sent revoke_and_ack when we needed a channel_reestablish".to_owned(), + )); } - if self.context.channel_state.is_both_sides_shutdown() && self.context.last_sent_closing_fee.is_some() { - return Err(ChannelError::close("Peer sent revoke_and_ack after we'd started exchanging closing_signeds".to_owned())); + if self.context.channel_state.is_both_sides_shutdown() + && self.context.last_sent_closing_fee.is_some() + { + return Err(ChannelError::close( + "Peer sent revoke_and_ack after we'd started exchanging closing_signeds".to_owned(), + )); } - let secret = secp_check!(SecretKey::from_slice(&msg.per_commitment_secret), "Peer provided an invalid per_commitment_secret".to_owned()); + let secret = secp_check!( + SecretKey::from_slice(&msg.per_commitment_secret), + "Peer provided an invalid per_commitment_secret".to_owned() + ); - if let Some(counterparty_prev_commitment_point) = self.context.counterparty_prev_commitment_point { - if PublicKey::from_secret_key(&self.context.secp_ctx, &secret) != counterparty_prev_commitment_point { + if let Some(counterparty_prev_commitment_point) = + self.context.counterparty_prev_commitment_point + { + if PublicKey::from_secret_key(&self.context.secp_ctx, &secret) + != counterparty_prev_commitment_point + { return Err(ChannelError::close("Got a revoke commitment secret which didn't correspond to their current pubkey".to_owned())); } } @@ -7046,7 +7065,9 @@ where #[cfg(any(test, fuzzing))] { - for funding in core::iter::once(&mut self.funding).chain(self.pending_funding.iter_mut()) { + for funding in + core::iter::once(&mut self.funding).chain(self.pending_funding.iter_mut()) + { *funding.next_local_commitment_tx_fee_info_cached.lock().unwrap() = None; *funding.next_remote_commitment_tx_fee_info_cached.lock().unwrap() = None; } @@ -7054,18 +7075,29 @@ where match &self.context.holder_signer { ChannelSignerType::Ecdsa(ecdsa) => { - ecdsa.validate_counterparty_revocation( - self.context.cur_counterparty_commitment_transaction_number + 1, - &secret - ).map_err(|_| ChannelError::close("Failed to validate revocation from peer".to_owned()))?; + ecdsa + .validate_counterparty_revocation( + self.context.cur_counterparty_commitment_transaction_number + 1, + &secret, + ) + .map_err(|_| { + ChannelError::close("Failed to validate revocation from peer".to_owned()) + })?; }, // TODO (taproot|arik) #[cfg(taproot)] - _ => todo!() + _ => todo!(), }; - self.context.commitment_secrets.provide_secret(self.context.cur_counterparty_commitment_transaction_number + 1, msg.per_commitment_secret) - .map_err(|_| ChannelError::close("Previous secrets did not match new one".to_owned()))?; + self.context + .commitment_secrets + .provide_secret( + self.context.cur_counterparty_commitment_transaction_number + 1, + msg.per_commitment_secret, + ) + .map_err(|_| { + ChannelError::close("Previous secrets did not match new one".to_owned()) + })?; self.context.latest_monitor_update_id += 1; let mut monitor_update = ChannelMonitorUpdate { update_id: self.context.latest_monitor_update_id, @@ -7082,7 +7114,8 @@ where // channel based on that, but stepping stuff here should be safe either way. self.context.channel_state.clear_awaiting_remote_revoke(); self.mark_response_received(); - self.context.counterparty_prev_commitment_point = self.context.counterparty_cur_commitment_point; + self.context.counterparty_prev_commitment_point = + self.context.counterparty_cur_commitment_point; self.context.counterparty_cur_commitment_point = Some(msg.next_per_commitment_point); self.context.cur_counterparty_commitment_transaction_number -= 1; @@ -7090,7 +7123,11 @@ where self.context.announcement_sigs_state = AnnouncementSigsState::PeerReceived; } - log_trace!(logger, "Updating HTLCs on receipt of RAA in channel {}...", &self.context.channel_id()); + log_trace!( + logger, + "Updating HTLCs on receipt of RAA in channel {}...", + &self.context.channel_id() + ); let mut to_forward_infos = Vec::new(); let mut pending_update_adds = Vec::new(); let mut revoked_htlcs = Vec::new(); @@ -7104,7 +7141,8 @@ where // Take references explicitly so that we can hold multiple references to self.context. let pending_inbound_htlcs: &mut Vec<_> = &mut self.context.pending_inbound_htlcs; let pending_outbound_htlcs: &mut Vec<_> = &mut self.context.pending_outbound_htlcs; - let expecting_peer_commitment_signed = &mut self.context.expecting_peer_commitment_signed; + let expecting_peer_commitment_signed = + &mut self.context.expecting_peer_commitment_signed; // We really shouldnt have two passes here, but retain gives a non-mutable ref (Rust bug) pending_inbound_htlcs.retain(|htlc| { @@ -7115,15 +7153,24 @@ where } *expecting_peer_commitment_signed = true; false - } else { true } + } 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(mut reason) = outcome.clone() { // We really want take() here, but, again, non-mut ref :( + log_trace!( + logger, + " ...removing outbound AwaitingRemovedRemoteRevoke {}", + &htlc.payment_hash + ); + 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); + let hold_time = + u32::try_from(now.saturating_sub(timestamp).as_millis()) + .unwrap_or(u32::MAX); reason.set_hold_time(hold_time); } @@ -7134,14 +7181,19 @@ where value_to_self_msat_diff -= htlc.amount_msat as i64; } false - } else { true } + } else { + true + } }); for htlc in pending_inbound_htlcs.iter_mut() { - let swap = if let &InboundHTLCState::AwaitingRemoteRevokeToAnnounce(_) = &htlc.state { + let swap = if let &InboundHTLCState::AwaitingRemoteRevokeToAnnounce(_) = &htlc.state + { true } else if let &InboundHTLCState::AwaitingAnnouncedRemoteRevoke(_) = &htlc.state { true - } else { false }; + } else { + false + }; if swap { let mut state = InboundHTLCState::Committed; mem::swap(&mut state, &mut htlc.state); @@ -7150,20 +7202,31 @@ where log_trace!(logger, " ...promoting inbound AwaitingRemoteRevokeToAnnounce {} to AwaitingAnnouncedRemoteRevoke", &htlc.payment_hash); htlc.state = InboundHTLCState::AwaitingAnnouncedRemoteRevoke(resolution); require_commitment = true; - } else if let InboundHTLCState::AwaitingAnnouncedRemoteRevoke(resolution) = state { + } else if let InboundHTLCState::AwaitingAnnouncedRemoteRevoke(resolution) = + state + { match resolution { - InboundHTLCResolution::Resolved { pending_htlc_status } => + InboundHTLCResolution::Resolved { pending_htlc_status } => { match pending_htlc_status { PendingHTLCStatus::Fail(fail_msg) => { log_trace!(logger, " ...promoting inbound AwaitingAnnouncedRemoteRevoke {} to LocalRemoved due to PendingHTLCStatus indicating failure", &htlc.payment_hash); require_commitment = true; match fail_msg { HTLCFailureMsg::Relay(msg) => { - htlc.state = InboundHTLCState::LocalRemoved(InboundHTLCRemovalReason::FailRelay(msg.clone().into())); + htlc.state = InboundHTLCState::LocalRemoved( + InboundHTLCRemovalReason::FailRelay( + msg.clone().into(), + ), + ); update_fail_htlcs.push(msg) }, HTLCFailureMsg::Malformed(msg) => { - htlc.state = InboundHTLCState::LocalRemoved(InboundHTLCRemovalReason::FailMalformed((msg.sha256_of_onion, msg.failure_code))); + htlc.state = InboundHTLCState::LocalRemoved( + InboundHTLCRemovalReason::FailMalformed(( + msg.sha256_of_onion, + msg.failure_code, + )), + ); update_fail_malformed_htlcs.push(msg) }, } @@ -7172,24 +7235,31 @@ where log_trace!(logger, " ...promoting inbound AwaitingAnnouncedRemoteRevoke {} to Committed, attempting to forward", &htlc.payment_hash); to_forward_infos.push((forward_info, htlc.htlc_id)); htlc.state = InboundHTLCState::Committed; - } + }, } + }, InboundHTLCResolution::Pending { update_add_htlc } => { log_trace!(logger, " ...promoting inbound AwaitingAnnouncedRemoteRevoke {} to Committed", &htlc.payment_hash); pending_update_adds.push(update_add_htlc); htlc.state = InboundHTLCState::Committed; - } + }, } } } } for htlc in pending_outbound_htlcs.iter_mut() { if let OutboundHTLCState::LocalAnnounced(_) = htlc.state { - log_trace!(logger, " ...promoting outbound LocalAnnounced {} to Committed", &htlc.payment_hash); + log_trace!( + logger, + " ...promoting outbound LocalAnnounced {} to Committed", + &htlc.payment_hash + ); htlc.state = OutboundHTLCState::Committed; *expecting_peer_commitment_signed = true; } - if let &mut OutboundHTLCState::AwaitingRemoteRevokeToRemove(ref mut outcome) = &mut htlc.state { + if let &mut OutboundHTLCState::AwaitingRemoteRevokeToRemove(ref mut outcome) = + &mut htlc.state + { log_trace!(logger, " ...promoting outbound AwaitingRemoteRevokeToRemove {} to AwaitingRemovedRemoteRevoke", &htlc.payment_hash); // Swap against a dummy variant to avoid a potentially expensive clone of `OutboundHTLCOutcome::Failure(HTLCFailReason)` let mut reason = OutboundHTLCOutcome::Success(PaymentPreimage([0u8; 32])); @@ -7201,19 +7271,26 @@ where } for funding in core::iter::once(&mut self.funding).chain(self.pending_funding.iter_mut()) { - funding.value_to_self_msat = (funding.value_to_self_msat as i64 + value_to_self_msat_diff) as u64; + funding.value_to_self_msat = + (funding.value_to_self_msat as i64 + value_to_self_msat_diff) as u64; } if let Some((feerate, update_state)) = self.context.pending_update_fee { match update_state { FeeUpdateState::Outbound => { debug_assert!(self.funding.is_outbound()); - log_trace!(logger, " ...promoting outbound fee update {} to Committed", feerate); + log_trace!( + logger, + " ...promoting outbound fee update {} to Committed", + feerate + ); self.context.feerate_per_kw = feerate; self.context.pending_update_fee = None; self.context.expecting_peer_commitment_signed = true; }, - FeeUpdateState::RemoteAnnounced => { debug_assert!(!self.funding.is_outbound()); }, + FeeUpdateState::RemoteAnnounced => { + debug_assert!(!self.funding.is_outbound()); + }, FeeUpdateState::AwaitingRemoteRevokeToAnnounce => { debug_assert!(!self.funding.is_outbound()); log_trace!(logger, " ...promoting inbound AwaitingRemoteRevokeToAnnounce fee update {} to Committed", feerate); @@ -7225,19 +7302,24 @@ where } let release_monitor = self.context.blocked_monitor_updates.is_empty() && !hold_mon_update; - let release_state_str = - if hold_mon_update { "Holding" } else if release_monitor { "Releasing" } else { "Blocked" }; + let release_state_str = if hold_mon_update { + "Holding" + } else if release_monitor { + "Releasing" + } else { + "Blocked" + }; macro_rules! return_with_htlcs_to_fail { ($htlcs_to_fail: expr) => { if !release_monitor { - self.context.blocked_monitor_updates.push(PendingChannelMonitorUpdate { - update: monitor_update, - }); + self.context + .blocked_monitor_updates + .push(PendingChannelMonitorUpdate { update: monitor_update }); return Ok(($htlcs_to_fail, None)); } else { return Ok(($htlcs_to_fail, Some(monitor_update))); } - } + }; } self.context.monitor_pending_update_adds.append(&mut pending_update_adds); @@ -7252,7 +7334,14 @@ where log_debug!(logger, "Received a valid revoke_and_ack for channel {} with holding cell HTLCs freed. {} monitor update.", &self.context.channel_id(), release_state_str); - self.monitor_updating_paused(false, true, false, to_forward_infos, revoked_htlcs, finalized_claimed_htlcs); + self.monitor_updating_paused( + false, + true, + false, + to_forward_infos, + revoked_htlcs, + finalized_claimed_htlcs, + ); return_with_htlcs_to_fail!(htlcs_to_fail); }, (None, htlcs_to_fail) => { @@ -7269,8 +7358,12 @@ where self.context.latest_monitor_update_id = monitor_update.update_id; monitor_update.updates.append(&mut additional_update.updates); - log_debug!(logger, "Received a valid revoke_and_ack for channel {}. {} monitor update.", - &self.context.channel_id(), release_state_str); + log_debug!( + logger, + "Received a valid revoke_and_ack for channel {}. {} monitor update.", + &self.context.channel_id(), + release_state_str + ); if self.context.channel_state.can_generate_new_commitment() { log_debug!(logger, "Responding with a commitment update with {} HTLCs failed for channel {}", update_fail_htlcs.len() + update_fail_malformed_htlcs.len(), @@ -7284,20 +7377,38 @@ where } else { "can continue progress" }; - log_debug!(logger, "Holding back commitment update until channel {} {}", - &self.context.channel_id, reason); + log_debug!( + logger, + "Holding back commitment update until channel {} {}", + &self.context.channel_id, + reason + ); } - self.monitor_updating_paused(false, true, false, to_forward_infos, revoked_htlcs, finalized_claimed_htlcs); + self.monitor_updating_paused( + false, + true, + false, + to_forward_infos, + revoked_htlcs, + finalized_claimed_htlcs, + ); return_with_htlcs_to_fail!(htlcs_to_fail); } else { log_debug!(logger, "Received a valid revoke_and_ack for channel {} with no reply necessary. {} monitor update.", &self.context.channel_id(), release_state_str); - self.monitor_updating_paused(false, false, false, to_forward_infos, revoked_htlcs, finalized_claimed_htlcs); + self.monitor_updating_paused( + false, + false, + false, + to_forward_infos, + revoked_htlcs, + finalized_claimed_htlcs, + ); return_with_htlcs_to_fail!(htlcs_to_fail); } - } + }, } } diff --git a/lightning/src/ln/onion_route_tests.rs b/lightning/src/ln/onion_route_tests.rs index 3af6a82c541..7344a1a62e1 100644 --- a/lightning/src/ln/onion_route_tests.rs +++ b/lightning/src/ln/onion_route_tests.rs @@ -1,5 +1,3 @@ -#![cfg_attr(rustfmt, rustfmt_skip)] - // This file is Copyright its original authors, visible in version control // history. // @@ -14,51 +12,79 @@ //! returned errors decode to the correct thing. use crate::chain::channelmonitor::{CLTV_CLAIM_BUFFER, LATENCY_GRACE_PERIOD_BLOCKS}; -use crate::sign::{EntropySource, NodeSigner, Recipient}; use crate::events::{Event, HTLCHandlingFailureType, PathFailure, PaymentFailureReason}; -use crate::types::payment::{PaymentHash, PaymentSecret}; use crate::ln::channel::EXPIRE_PREV_CONFIG_TICKS; -use crate::ln::channelmanager::{HTLCForwardInfo, FailureCode, CLTV_FAR_FAR_AWAY, DISABLE_GOSSIP_TICKS, MIN_CLTV_EXPIRY_DELTA, PendingAddHTLCInfo, PendingHTLCInfo, PendingHTLCRouting, PaymentId, RecipientOnionFields}; -use crate::ln::onion_utils::{self, LocalHTLCFailureReason}; -use crate::routing::gossip::{NetworkUpdate, RoutingFees}; -use crate::routing::router::{get_route, PaymentParameters, Route, RouteParameters, RouteHint, RouteHintHop, Path, TrampolineHop, BlindedTail, RouteHop}; -use crate::types::features::{InitFeatures, Bolt11InvoiceFeatures}; +use crate::ln::channelmanager::{ + FailureCode, HTLCForwardInfo, PaymentId, PendingAddHTLCInfo, PendingHTLCInfo, + PendingHTLCRouting, RecipientOnionFields, CLTV_FAR_FAR_AWAY, DISABLE_GOSSIP_TICKS, + MIN_CLTV_EXPIRY_DELTA, +}; use crate::ln::functional_test_utils::test_default_channel_config; use crate::ln::msgs; use crate::ln::msgs::{ - BaseMessageHandler, ChannelMessageHandler, ChannelUpdate, FinalOnionHopData, - OutboundOnionPayload, OutboundTrampolinePayload, MessageSendEvent, + BaseMessageHandler, ChannelMessageHandler, ChannelUpdate, FinalOnionHopData, MessageSendEvent, + OutboundOnionPayload, OutboundTrampolinePayload, }; +use crate::ln::onion_utils::{self, LocalHTLCFailureReason}; use crate::ln::wire::Encode; +use crate::routing::gossip::{NetworkUpdate, RoutingFees}; +use crate::routing::router::{ + get_route, BlindedTail, Path, PaymentParameters, Route, RouteHint, RouteHintHop, RouteHop, + RouteParameters, TrampolineHop, +}; +use crate::sign::{EntropySource, NodeSigner, Recipient}; +use crate::types::features::{Bolt11InvoiceFeatures, InitFeatures}; +use crate::types::payment::{PaymentHash, PaymentSecret}; +use crate::util::config::{ChannelConfig, MaxDustHTLCExposure, UserConfig}; +use crate::util::errors::APIError; use crate::util::ser::{BigSize, Writeable, Writer}; use crate::util::test_utils; -use crate::util::config::{UserConfig, ChannelConfig, MaxDustHTLCExposure}; -use crate::util::errors::APIError; use bitcoin::constants::ChainHash; -use bitcoin::hashes::{Hash, HashEngine}; use bitcoin::hashes::hmac::{Hmac, HmacEngine}; use bitcoin::hashes::sha256::Hash as Sha256; +use bitcoin::hashes::{Hash, HashEngine}; use bitcoin::secp256k1; use bitcoin::secp256k1::{PublicKey, Secp256k1, SecretKey}; +use crate::blinded_path::BlindedHop; use crate::io; +use crate::ln::functional_test_utils::*; +use crate::ln::onion_utils::{construct_trampoline_onion_keys, construct_trampoline_onion_packet}; use crate::prelude::*; use bitcoin::hex::{DisplayHex, FromHex}; use types::features::{ChannelFeatures, Features, NodeFeatures}; -use crate::blinded_path::BlindedHop; -use crate::ln::functional_test_utils::*; -use crate::ln::onion_utils::{construct_trampoline_onion_keys, construct_trampoline_onion_packet}; use super::msgs::OnionErrorPacket; use super::onion_utils::AttributionData; -fn run_onion_failure_test(_name: &str, test_case: u8, nodes: &Vec, route: &Route, payment_hash: &PaymentHash, payment_secret: &PaymentSecret, callback_msg: F1, callback_node: F2, expected_retryable: bool, expected_error_code: Option, expected_channel_update: Option, expected_short_channel_id: Option, expected_failure_type: Option) - where F1: for <'a> FnMut(&'a mut msgs::UpdateAddHTLC), - F2: FnMut(), +fn run_onion_failure_test( + _name: &str, test_case: u8, nodes: &Vec, route: &Route, payment_hash: &PaymentHash, + payment_secret: &PaymentSecret, callback_msg: F1, callback_node: F2, expected_retryable: bool, + expected_error_code: Option, + expected_channel_update: Option, expected_short_channel_id: Option, + expected_failure_type: Option, +) where + F1: for<'a> FnMut(&'a mut msgs::UpdateAddHTLC), + F2: FnMut(), { - run_onion_failure_test_with_fail_intercept(_name, test_case, nodes, route, payment_hash, payment_secret, callback_msg, |_|{}, callback_node, expected_retryable, expected_error_code, expected_channel_update, expected_short_channel_id, expected_failure_type); + run_onion_failure_test_with_fail_intercept( + _name, + test_case, + nodes, + route, + payment_hash, + payment_secret, + callback_msg, + |_| {}, + callback_node, + expected_retryable, + expected_error_code, + expected_channel_update, + expected_short_channel_id, + expected_failure_type, + ); } // test_case @@ -68,16 +94,17 @@ fn run_onion_failure_test(_name: &str, test_case: u8, nodes: &Vec, // 3: final node fails backward (but tamper onion payloads from node0) // 100: trigger error in the intermediate node and tamper returning fail_htlc // 200: trigger error in the final node and tamper returning fail_htlc -fn run_onion_failure_test_with_fail_intercept( +fn run_onion_failure_test_with_fail_intercept( _name: &str, test_case: u8, nodes: &Vec, route: &Route, payment_hash: &PaymentHash, payment_secret: &PaymentSecret, mut callback_msg: F1, mut callback_fail: F2, - mut callback_node: F3, expected_retryable: bool, expected_error_reason: Option, + mut callback_node: F3, expected_retryable: bool, + expected_error_reason: Option, expected_channel_update: Option, expected_short_channel_id: Option, expected_failure_type: Option, -) - where F1: for <'a> FnMut(&'a mut msgs::UpdateAddHTLC), - F2: for <'a> FnMut(&'a mut msgs::UpdateFailHTLC), - F3: FnMut(), +) where + F1: for<'a> FnMut(&'a mut msgs::UpdateAddHTLC), + F2: for<'a> FnMut(&'a mut msgs::UpdateFailHTLC), + F3: FnMut(), { macro_rules! expect_event { ($node: expr, $event_type: path) => {{ @@ -87,20 +114,27 @@ fn run_onion_failure_test_with_fail_intercept( $event_type { .. } => {}, _ => panic!("Unexpected event"), } - }} + }}; } macro_rules! expect_htlc_forward { ($node: expr) => {{ expect_event!($node, Event::PendingHTLCsForwardable); $node.node.process_pending_htlc_forwards(); - }} + }}; } // 0 ~~> 2 send payment let payment_id = PaymentId(nodes[0].keys_manager.backing.get_secure_random_bytes()); - nodes[0].node.send_payment_with_route(route.clone(), *payment_hash, - RecipientOnionFields::secret_only(*payment_secret), payment_id).unwrap(); + nodes[0] + .node + .send_payment_with_route( + route.clone(), + *payment_hash, + RecipientOnionFields::secret_only(*payment_secret), + payment_id, + ) + .unwrap(); check_added_monitors!(nodes[0], 1); let update_0 = get_htlc_update_msgs!(nodes[0], nodes[1].node.get_our_node_id()); // temper update_add (0 => 1) @@ -114,15 +148,24 @@ fn run_onion_failure_test_with_fail_intercept( commitment_signed_dance!(nodes[1], nodes[0], &update_0.commitment_signed, false, true); let update_1_0 = match test_case { - 0|100 => { // intermediate node failure; fail backward to 0 + 0 | 100 => { + // intermediate node failure; fail backward to 0 expect_pending_htlcs_forwardable!(nodes[1]); - expect_htlc_handling_failed_destinations!(nodes[1].node.get_and_clear_pending_events(), &[expected_failure_type.clone().unwrap()]); + expect_htlc_handling_failed_destinations!( + nodes[1].node.get_and_clear_pending_events(), + &[expected_failure_type.clone().unwrap()] + ); check_added_monitors(&nodes[1], 1); let update_1_0 = get_htlc_update_msgs!(nodes[1], nodes[0].node.get_our_node_id()); - assert!(update_1_0.update_fail_htlcs.len()+update_1_0.update_fail_malformed_htlcs.len()==1 && (update_1_0.update_fail_htlcs.len()==1 || update_1_0.update_fail_malformed_htlcs.len()==1)); + assert!( + update_1_0.update_fail_htlcs.len() + update_1_0.update_fail_malformed_htlcs.len() + == 1 && (update_1_0.update_fail_htlcs.len() == 1 + || update_1_0.update_fail_malformed_htlcs.len() == 1) + ); update_1_0 }, - 1|2|3|200 => { // final node failure; forwarding to 2 + 1 | 2 | 3 | 200 => { + // final node failure; forwarding to 2 assert!(nodes[1].node.get_and_clear_pending_msg_events().is_empty()); // forwarding on 1 if test_case != 200 { @@ -147,10 +190,16 @@ fn run_onion_failure_test_with_fail_intercept( expect_htlc_forward!(&nodes[2]); expect_event!(&nodes[2], Event::PaymentClaimable); callback_node(); - expect_pending_htlcs_forwardable_and_htlc_handling_failed!(nodes[2], vec![HTLCHandlingFailureType::Receive { payment_hash: payment_hash.clone() }]); + expect_pending_htlcs_forwardable_and_htlc_handling_failed!( + nodes[2], + vec![HTLCHandlingFailureType::Receive { payment_hash: payment_hash.clone() }] + ); } else if test_case == 1 || test_case == 3 { expect_htlc_forward!(&nodes[2]); - expect_htlc_handling_failed_destinations!(nodes[2].node.get_and_clear_pending_events(), vec![expected_failure_type.clone().unwrap()]); + expect_htlc_handling_failed_destinations!( + nodes[2].node.get_and_clear_pending_events(), + vec![expected_failure_type.clone().unwrap()] + ); } check_added_monitors!(&nodes[2], 1); @@ -182,14 +231,24 @@ fn run_onion_failure_test_with_fail_intercept( } nodes[0].node.handle_update_fail_htlc(nodes[1].node.get_our_node_id(), &fail_msg); } else { - nodes[0].node.handle_update_fail_malformed_htlc(nodes[1].node.get_our_node_id(), &update_1_0.update_fail_malformed_htlcs[0]); + nodes[0].node.handle_update_fail_malformed_htlc( + nodes[1].node.get_our_node_id(), + &update_1_0.update_fail_malformed_htlcs[0], + ); }; commitment_signed_dance!(nodes[0], nodes[1], update_1_0.commitment_signed, false, true); let events = nodes[0].node.get_and_clear_pending_events(); assert_eq!(events.len(), 2); - if let &Event::PaymentPathFailed { ref payment_failed_permanently, ref short_channel_id, ref error_code, failure: PathFailure::OnPath { ref network_update }, .. } = &events[0] { + if let &Event::PaymentPathFailed { + ref payment_failed_permanently, + ref short_channel_id, + ref error_code, + failure: PathFailure::OnPath { ref network_update }, + .. + } = &events[0] + { assert_eq!(*payment_failed_permanently, !expected_retryable); assert_eq!(error_code.is_none(), expected_error_reason.is_none()); if let Some(expected_reason) = expected_error_reason { @@ -199,7 +258,11 @@ fn run_onion_failure_test_with_fail_intercept( match network_update { Some(update) => match update { &NetworkUpdate::ChannelFailure { ref short_channel_id, ref is_permanent } => { - if let NetworkUpdate::ChannelFailure { short_channel_id: ref expected_short_channel_id, is_permanent: ref expected_is_permanent } = expected_channel_update.unwrap() { + if let NetworkUpdate::ChannelFailure { + short_channel_id: ref expected_short_channel_id, + is_permanent: ref expected_is_permanent, + } = expected_channel_update.unwrap() + { assert!(*short_channel_id == *expected_short_channel_id); assert!(*is_permanent == *expected_is_permanent); } else { @@ -207,14 +270,18 @@ fn run_onion_failure_test_with_fail_intercept( } }, &NetworkUpdate::NodeFailure { ref node_id, ref is_permanent } => { - if let NetworkUpdate::NodeFailure { node_id: ref expected_node_id, is_permanent: ref expected_is_permanent } = expected_channel_update.unwrap() { + if let NetworkUpdate::NodeFailure { + node_id: ref expected_node_id, + is_permanent: ref expected_is_permanent, + } = expected_channel_update.unwrap() + { assert!(*node_id == *expected_node_id); assert!(*is_permanent == *expected_is_permanent); } else { panic!("Unexpected message event"); } }, - } + }, None => panic!("Expected channel update"), } } else { @@ -232,15 +299,22 @@ fn run_onion_failure_test_with_fail_intercept( panic!("Unexpected event"); } match events[1] { - Event::PaymentFailed { payment_hash: ev_payment_hash, payment_id: ev_payment_id, reason: ref ev_reason } => { + Event::PaymentFailed { + payment_hash: ev_payment_hash, + payment_id: ev_payment_id, + reason: ref ev_reason, + } => { assert_eq!(Some(*payment_hash), ev_payment_hash); assert_eq!(payment_id, ev_payment_id); - assert_eq!(if expected_retryable { - PaymentFailureReason::RetriesExhausted - } else { - PaymentFailureReason::RecipientRejected - }, ev_reason.unwrap()); - } + assert_eq!( + if expected_retryable { + PaymentFailureReason::RetriesExhausted + } else { + PaymentFailureReason::RecipientRejected + }, + ev_reason.unwrap() + ); + }, _ => panic!("Unexpected second event"), } } @@ -248,8 +322,8 @@ fn run_onion_failure_test_with_fail_intercept( impl msgs::ChannelUpdate { fn dummy(short_channel_id: u64) -> msgs::ChannelUpdate { use bitcoin::hash_types::BlockHash; - use bitcoin::secp256k1::ffi::Signature as FFISignature; use bitcoin::secp256k1::ecdsa::Signature; + use bitcoin::secp256k1::ffi::Signature as FFISignature; msgs::ChannelUpdate { signature: Signature::from(unsafe { FFISignature::new() }), contents: msgs::UnsignedChannelUpdate { @@ -264,13 +338,13 @@ impl msgs::ChannelUpdate { fee_base_msat: 0, fee_proportional_millionths: 0, excess_data: vec![], - } + }, } } } struct BogusOnionHopData { - data: Vec + data: Vec, } impl BogusOnionHopData { fn new(orig: msgs::OutboundOnionPayload) -> Self { @@ -296,16 +370,37 @@ fn test_fee_failures() { let chanmon_cfgs = create_chanmon_cfgs(3); let node_cfgs = create_node_cfgs(3, &chanmon_cfgs); - let node_chanmgrs = create_node_chanmgrs(3, &node_cfgs, &[Some(config.clone()), Some(config.clone()), Some(config)]); + let node_chanmgrs = create_node_chanmgrs( + 3, + &node_cfgs, + &[Some(config.clone()), Some(config.clone()), Some(config)], + ); let mut nodes = create_network(3, &node_cfgs, &node_chanmgrs); - let channels = [create_announced_chan_between_nodes(&nodes, 0, 1), create_announced_chan_between_nodes(&nodes, 1, 2)]; + let channels = [ + create_announced_chan_between_nodes(&nodes, 0, 1), + create_announced_chan_between_nodes(&nodes, 1, 2), + ]; // positive case - let (route, payment_hash_success, payment_preimage_success, payment_secret_success) = get_route_and_payment_hash!(nodes[0], nodes[2], 40_000); - nodes[0].node.send_payment_with_route(route.clone(), payment_hash_success, - RecipientOnionFields::secret_only(payment_secret_success), PaymentId(payment_hash_success.0)).unwrap(); + let (route, payment_hash_success, payment_preimage_success, payment_secret_success) = + get_route_and_payment_hash!(nodes[0], nodes[2], 40_000); + nodes[0] + .node + .send_payment_with_route( + route.clone(), + payment_hash_success, + RecipientOnionFields::secret_only(payment_secret_success), + PaymentId(payment_hash_success.0), + ) + .unwrap(); check_added_monitors!(nodes[0], 1); - pass_along_route(&nodes[0], &[&[&nodes[1], &nodes[2]]], 40_000, payment_hash_success, payment_secret_success); + pass_along_route( + &nodes[0], + &[&[&nodes[1], &nodes[2]]], + 40_000, + payment_hash_success, + payment_secret_success, + ); claim_payment(&nodes[0], &[&nodes[1], &nodes[2]], payment_preimage_success); // If the hop gives fee_insufficient but enough fees were provided, then the previous hop @@ -313,10 +408,26 @@ fn test_fee_failures() { // because we ignore channel update contents, we will still blame the 2nd channel. let (_, payment_hash, payment_secret) = get_payment_preimage_hash!(nodes[2]); let short_channel_id = channels[1].0.contents.short_channel_id; - run_onion_failure_test("fee_insufficient", 100, &nodes, &route, &payment_hash, &payment_secret, |msg| { - msg.amount_msat -= 1; - }, || {}, true, Some(LocalHTLCFailureReason::FeeInsufficient), Some(NetworkUpdate::ChannelFailure { short_channel_id, is_permanent: false}), Some(short_channel_id), - Some(HTLCHandlingFailureType::Forward { node_id: Some(nodes[2].node.get_our_node_id()), channel_id: channels[1].2 })); + run_onion_failure_test( + "fee_insufficient", + 100, + &nodes, + &route, + &payment_hash, + &payment_secret, + |msg| { + msg.amount_msat -= 1; + }, + || {}, + true, + Some(LocalHTLCFailureReason::FeeInsufficient), + Some(NetworkUpdate::ChannelFailure { short_channel_id, is_permanent: false }), + Some(short_channel_id), + Some(HTLCHandlingFailureType::Forward { + node_id: Some(nodes[2].node.get_our_node_id()), + channel_id: channels[1].2, + }), + ); // In an earlier version, we spuriously failed to forward payments if the expected feerate // changed between the channel open and the payment. @@ -325,11 +436,25 @@ fn test_fee_failures() { *feerate_lock *= 2; } - let (payment_preimage_success, payment_hash_success, payment_secret_success) = get_payment_preimage_hash!(nodes[2]); - nodes[0].node.send_payment_with_route(route, payment_hash_success, - RecipientOnionFields::secret_only(payment_secret_success), PaymentId(payment_hash_success.0)).unwrap(); + let (payment_preimage_success, payment_hash_success, payment_secret_success) = + get_payment_preimage_hash!(nodes[2]); + nodes[0] + .node + .send_payment_with_route( + route, + payment_hash_success, + RecipientOnionFields::secret_only(payment_secret_success), + PaymentId(payment_hash_success.0), + ) + .unwrap(); check_added_monitors!(nodes[0], 1); - pass_along_route(&nodes[0], &[&[&nodes[1], &nodes[2]]], 40_000, payment_hash_success, payment_secret_success); + pass_along_route( + &nodes[0], + &[&[&nodes[1], &nodes[2]]], + 40_000, + payment_hash_success, + payment_secret_success, + ); claim_payment(&nodes[0], &[&nodes[1], &nodes[2]], payment_preimage_success); } @@ -352,143 +477,425 @@ fn test_onion_failure() { let chanmon_cfgs = create_chanmon_cfgs(3); let node_cfgs = create_node_cfgs(3, &chanmon_cfgs); - let node_chanmgrs = create_node_chanmgrs(3, &node_cfgs, &[Some(config.clone()), Some(config), Some(node_2_cfg)]); + let node_chanmgrs = create_node_chanmgrs( + 3, + &node_cfgs, + &[Some(config.clone()), Some(config), Some(node_2_cfg)], + ); let mut nodes = create_network(3, &node_cfgs, &node_chanmgrs); - let channels = [create_announced_chan_between_nodes(&nodes, 0, 1), create_announced_chan_between_nodes(&nodes, 1, 2)]; + let channels = [ + create_announced_chan_between_nodes(&nodes, 0, 1), + create_announced_chan_between_nodes(&nodes, 1, 2), + ]; for node in nodes.iter() { *node.keys_manager.override_random_bytes.lock().unwrap() = Some([3; 32]); } - let (route, payment_hash, _, payment_secret) = get_route_and_payment_hash!(nodes[0], nodes[2], 40000); + let (route, payment_hash, _, payment_secret) = + get_route_and_payment_hash!(nodes[0], nodes[2], 40000); // positive case - send_payment(&nodes[0], &vec!(&nodes[1], &nodes[2])[..], 40000); + send_payment(&nodes[0], &vec![&nodes[1], &nodes[2]][..], 40000); - let next_hop_failure = HTLCHandlingFailureType::Forward { node_id: Some(nodes[2].node.get_our_node_id()), channel_id: channels[1].2 }; + let next_hop_failure = HTLCHandlingFailureType::Forward { + node_id: Some(nodes[2].node.get_our_node_id()), + channel_id: channels[1].2, + }; // intermediate node failure let short_channel_id = channels[1].0.contents.short_channel_id; - run_onion_failure_test("invalid_realm", 0, &nodes, &route, &payment_hash, &payment_secret, |msg| { - let session_priv = SecretKey::from_slice(&[3; 32]).unwrap(); - let cur_height = nodes[0].best_block_info().1 + 1; - let onion_keys = onion_utils::construct_onion_keys(&Secp256k1::new(), &route.paths[0], &session_priv); - let recipient_onion_fields = RecipientOnionFields::spontaneous_empty(); - let (mut onion_payloads, _htlc_msat, _htlc_cltv) = onion_utils::build_onion_payloads( - &route.paths[0], 40000, &recipient_onion_fields, cur_height, &None, None, None).unwrap(); - let mut new_payloads = Vec::new(); - for payload in onion_payloads.drain(..) { - new_payloads.push(BogusOnionHopData::new(payload)); - } - // break the first (non-final) hop payload by swapping the realm (0) byte for a byte - // describing a length-1 TLV payload, which is obviously bogus. - new_payloads[0].data[0] = 1; - msg.onion_routing_packet = onion_utils::construct_onion_packet_with_writable_hopdata(new_payloads, onion_keys, [0; 32], &payment_hash).unwrap(); - }, ||{}, true, Some(LocalHTLCFailureReason::InvalidOnionPayload), Some(NetworkUpdate::ChannelFailure{short_channel_id, is_permanent: true}), Some(short_channel_id), Some(HTLCHandlingFailureType::InvalidOnion)); + run_onion_failure_test( + "invalid_realm", + 0, + &nodes, + &route, + &payment_hash, + &payment_secret, + |msg| { + let session_priv = SecretKey::from_slice(&[3; 32]).unwrap(); + let cur_height = nodes[0].best_block_info().1 + 1; + let onion_keys = onion_utils::construct_onion_keys( + &Secp256k1::new(), + &route.paths[0], + &session_priv, + ); + let recipient_onion_fields = RecipientOnionFields::spontaneous_empty(); + let (mut onion_payloads, _htlc_msat, _htlc_cltv) = onion_utils::build_onion_payloads( + &route.paths[0], + 40000, + &recipient_onion_fields, + cur_height, + &None, + None, + None, + ) + .unwrap(); + let mut new_payloads = Vec::new(); + for payload in onion_payloads.drain(..) { + new_payloads.push(BogusOnionHopData::new(payload)); + } + // break the first (non-final) hop payload by swapping the realm (0) byte for a byte + // describing a length-1 TLV payload, which is obviously bogus. + new_payloads[0].data[0] = 1; + msg.onion_routing_packet = onion_utils::construct_onion_packet_with_writable_hopdata( + new_payloads, + onion_keys, + [0; 32], + &payment_hash, + ) + .unwrap(); + }, + || {}, + true, + Some(LocalHTLCFailureReason::InvalidOnionPayload), + Some(NetworkUpdate::ChannelFailure { short_channel_id, is_permanent: true }), + Some(short_channel_id), + Some(HTLCHandlingFailureType::InvalidOnion), + ); // final node failure let short_channel_id = channels[1].0.contents.short_channel_id; - run_onion_failure_test("invalid_realm", 3, &nodes, &route, &payment_hash, &payment_secret, |msg| { - let session_priv = SecretKey::from_slice(&[3; 32]).unwrap(); - let cur_height = nodes[0].best_block_info().1 + 1; - let onion_keys = onion_utils::construct_onion_keys(&Secp256k1::new(), &route.paths[0], &session_priv); - let recipient_onion_fields = RecipientOnionFields::spontaneous_empty(); - let (mut onion_payloads, _htlc_msat, _htlc_cltv) = onion_utils::build_onion_payloads( - &route.paths[0], 40000, &recipient_onion_fields, cur_height, &None, None, None).unwrap(); - let mut new_payloads = Vec::new(); - for payload in onion_payloads.drain(..) { - new_payloads.push(BogusOnionHopData::new(payload)); - } - // break the last-hop payload by swapping the realm (0) byte for a byte describing a - // length-1 TLV payload, which is obviously bogus. - new_payloads[1].data[0] = 1; - msg.onion_routing_packet = onion_utils::construct_onion_packet_with_writable_hopdata(new_payloads, onion_keys, [0; 32], &payment_hash).unwrap(); - }, ||{}, false, Some(LocalHTLCFailureReason::InvalidOnionPayload), Some(NetworkUpdate::ChannelFailure{short_channel_id, is_permanent: true}), Some(short_channel_id), Some(HTLCHandlingFailureType::InvalidOnion)); + run_onion_failure_test( + "invalid_realm", + 3, + &nodes, + &route, + &payment_hash, + &payment_secret, + |msg| { + let session_priv = SecretKey::from_slice(&[3; 32]).unwrap(); + let cur_height = nodes[0].best_block_info().1 + 1; + let onion_keys = onion_utils::construct_onion_keys( + &Secp256k1::new(), + &route.paths[0], + &session_priv, + ); + let recipient_onion_fields = RecipientOnionFields::spontaneous_empty(); + let (mut onion_payloads, _htlc_msat, _htlc_cltv) = onion_utils::build_onion_payloads( + &route.paths[0], + 40000, + &recipient_onion_fields, + cur_height, + &None, + None, + None, + ) + .unwrap(); + let mut new_payloads = Vec::new(); + for payload in onion_payloads.drain(..) { + new_payloads.push(BogusOnionHopData::new(payload)); + } + // break the last-hop payload by swapping the realm (0) byte for a byte describing a + // length-1 TLV payload, which is obviously bogus. + new_payloads[1].data[0] = 1; + msg.onion_routing_packet = onion_utils::construct_onion_packet_with_writable_hopdata( + new_payloads, + onion_keys, + [0; 32], + &payment_hash, + ) + .unwrap(); + }, + || {}, + false, + Some(LocalHTLCFailureReason::InvalidOnionPayload), + Some(NetworkUpdate::ChannelFailure { short_channel_id, is_permanent: true }), + Some(short_channel_id), + Some(HTLCHandlingFailureType::InvalidOnion), + ); // the following three with run_onion_failure_test_with_fail_intercept() test only the origin node // receiving simulated fail messages // intermediate node failure - run_onion_failure_test_with_fail_intercept("temporary_node_failure", 100, &nodes, &route, &payment_hash, &payment_secret, |msg| { - // trigger error - msg.amount_msat -= 1; - }, |msg| { - // and tamper returning error message - let session_priv = SecretKey::from_slice(&[3; 32]).unwrap(); - let onion_keys = onion_utils::construct_onion_keys(&Secp256k1::new(), &route.paths[0], &session_priv); - let failure = onion_utils::build_failure_packet(onion_keys[0].shared_secret.as_ref(), LocalHTLCFailureReason::TemporaryNodeFailure, &[0;0], 0); - msg.reason = failure.data; - msg.attribution_data = failure.attribution_data; - }, ||{}, true, Some(LocalHTLCFailureReason::TemporaryNodeFailure), 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())); + run_onion_failure_test_with_fail_intercept( + "temporary_node_failure", + 100, + &nodes, + &route, + &payment_hash, + &payment_secret, + |msg| { + // trigger error + msg.amount_msat -= 1; + }, + |msg| { + // and tamper returning error message + let session_priv = SecretKey::from_slice(&[3; 32]).unwrap(); + let onion_keys = onion_utils::construct_onion_keys( + &Secp256k1::new(), + &route.paths[0], + &session_priv, + ); + let failure = onion_utils::build_failure_packet( + onion_keys[0].shared_secret.as_ref(), + LocalHTLCFailureReason::TemporaryNodeFailure, + &[0; 0], + 0, + ); + msg.reason = failure.data; + msg.attribution_data = failure.attribution_data; + }, + || {}, + true, + Some(LocalHTLCFailureReason::TemporaryNodeFailure), + Some(NetworkUpdate::NodeFailure { + node_id: route.paths[0].hops[0].pubkey, + is_permanent: false, + }), + Some(route.paths[0].hops[0].short_channel_id), + Some(next_hop_failure.clone()), + ); // final node failure - run_onion_failure_test_with_fail_intercept("temporary_node_failure", 200, &nodes, &route, &payment_hash, &payment_secret, |_msg| {}, |msg| { - // and tamper returning error message - let session_priv = SecretKey::from_slice(&[3; 32]).unwrap(); - let onion_keys = onion_utils::construct_onion_keys(&Secp256k1::new(), &route.paths[0], &session_priv); - let failure = onion_utils::build_failure_packet(onion_keys[1].shared_secret.as_ref(), LocalHTLCFailureReason::TemporaryNodeFailure, &[0;0], 0); - msg.reason = failure.data; - msg.attribution_data = failure.attribution_data; - }, ||{ - nodes[2].node.fail_htlc_backwards(&payment_hash); - }, true, Some(LocalHTLCFailureReason::TemporaryNodeFailure), Some(NetworkUpdate::NodeFailure{node_id: route.paths[0].hops[1].pubkey, is_permanent: false}), Some(route.paths[0].hops[1].short_channel_id), None); + run_onion_failure_test_with_fail_intercept( + "temporary_node_failure", + 200, + &nodes, + &route, + &payment_hash, + &payment_secret, + |_msg| {}, + |msg| { + // and tamper returning error message + let session_priv = SecretKey::from_slice(&[3; 32]).unwrap(); + let onion_keys = onion_utils::construct_onion_keys( + &Secp256k1::new(), + &route.paths[0], + &session_priv, + ); + let failure = onion_utils::build_failure_packet( + onion_keys[1].shared_secret.as_ref(), + LocalHTLCFailureReason::TemporaryNodeFailure, + &[0; 0], + 0, + ); + msg.reason = failure.data; + msg.attribution_data = failure.attribution_data; + }, + || { + nodes[2].node.fail_htlc_backwards(&payment_hash); + }, + true, + Some(LocalHTLCFailureReason::TemporaryNodeFailure), + Some(NetworkUpdate::NodeFailure { + node_id: route.paths[0].hops[1].pubkey, + is_permanent: false, + }), + Some(route.paths[0].hops[1].short_channel_id), + None, + ); let (_, payment_hash, payment_secret) = get_payment_preimage_hash!(nodes[2]); // intermediate node failure - run_onion_failure_test_with_fail_intercept("permanent_node_failure", 100, &nodes, &route, &payment_hash, &payment_secret, |msg| { - msg.amount_msat -= 1; - }, |msg| { - let session_priv = SecretKey::from_slice(&[3; 32]).unwrap(); - let onion_keys = onion_utils::construct_onion_keys(&Secp256k1::new(), &route.paths[0], &session_priv); - let failure = onion_utils::build_failure_packet(onion_keys[0].shared_secret.as_ref(), LocalHTLCFailureReason::PermanentNodeFailure, &[0;0], 0); - msg.reason = failure.data; - msg.attribution_data = failure.attribution_data; - }, ||{}, true, Some(LocalHTLCFailureReason::PermanentNodeFailure), 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())); + run_onion_failure_test_with_fail_intercept( + "permanent_node_failure", + 100, + &nodes, + &route, + &payment_hash, + &payment_secret, + |msg| { + msg.amount_msat -= 1; + }, + |msg| { + let session_priv = SecretKey::from_slice(&[3; 32]).unwrap(); + let onion_keys = onion_utils::construct_onion_keys( + &Secp256k1::new(), + &route.paths[0], + &session_priv, + ); + let failure = onion_utils::build_failure_packet( + onion_keys[0].shared_secret.as_ref(), + LocalHTLCFailureReason::PermanentNodeFailure, + &[0; 0], + 0, + ); + msg.reason = failure.data; + msg.attribution_data = failure.attribution_data; + }, + || {}, + true, + Some(LocalHTLCFailureReason::PermanentNodeFailure), + Some(NetworkUpdate::NodeFailure { + node_id: route.paths[0].hops[0].pubkey, + is_permanent: true, + }), + Some(route.paths[0].hops[0].short_channel_id), + Some(next_hop_failure.clone()), + ); // final node failure - run_onion_failure_test_with_fail_intercept("permanent_node_failure", 200, &nodes, &route, &payment_hash, &payment_secret, |_msg| {}, |msg| { - let session_priv = SecretKey::from_slice(&[3; 32]).unwrap(); - let onion_keys = onion_utils::construct_onion_keys(&Secp256k1::new(), &route.paths[0], &session_priv); - let failure = onion_utils::build_failure_packet(onion_keys[1].shared_secret.as_ref(), LocalHTLCFailureReason::PermanentNodeFailure, &[0;0], 0); - msg.reason = failure.data; - msg.attribution_data = failure.attribution_data; - }, ||{ - nodes[2].node.fail_htlc_backwards(&payment_hash); - }, false, Some(LocalHTLCFailureReason::PermanentNodeFailure), Some(NetworkUpdate::NodeFailure{node_id: route.paths[0].hops[1].pubkey, is_permanent: true}), Some(route.paths[0].hops[1].short_channel_id), None); + run_onion_failure_test_with_fail_intercept( + "permanent_node_failure", + 200, + &nodes, + &route, + &payment_hash, + &payment_secret, + |_msg| {}, + |msg| { + let session_priv = SecretKey::from_slice(&[3; 32]).unwrap(); + let onion_keys = onion_utils::construct_onion_keys( + &Secp256k1::new(), + &route.paths[0], + &session_priv, + ); + let failure = onion_utils::build_failure_packet( + onion_keys[1].shared_secret.as_ref(), + LocalHTLCFailureReason::PermanentNodeFailure, + &[0; 0], + 0, + ); + msg.reason = failure.data; + msg.attribution_data = failure.attribution_data; + }, + || { + nodes[2].node.fail_htlc_backwards(&payment_hash); + }, + false, + Some(LocalHTLCFailureReason::PermanentNodeFailure), + Some(NetworkUpdate::NodeFailure { + node_id: route.paths[0].hops[1].pubkey, + is_permanent: true, + }), + Some(route.paths[0].hops[1].short_channel_id), + None, + ); let (_, payment_hash, payment_secret) = get_payment_preimage_hash!(nodes[2]); // intermediate node failure - run_onion_failure_test_with_fail_intercept("required_node_feature_missing", 100, &nodes, &route, &payment_hash, &payment_secret, |msg| { - msg.amount_msat -= 1; - }, |msg| { - let session_priv = SecretKey::from_slice(&[3; 32]).unwrap(); - let onion_keys = onion_utils::construct_onion_keys(&Secp256k1::new(), &route.paths[0], &session_priv); - let failure = onion_utils::build_failure_packet(onion_keys[0].shared_secret.as_ref(), LocalHTLCFailureReason::RequiredNodeFeature, &[0;0], 0); - msg.reason = failure.data; - msg.attribution_data = failure.attribution_data; - }, ||{ - nodes[2].node.fail_htlc_backwards(&payment_hash); - }, true, Some(LocalHTLCFailureReason::RequiredNodeFeature), 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())); + run_onion_failure_test_with_fail_intercept( + "required_node_feature_missing", + 100, + &nodes, + &route, + &payment_hash, + &payment_secret, + |msg| { + msg.amount_msat -= 1; + }, + |msg| { + let session_priv = SecretKey::from_slice(&[3; 32]).unwrap(); + let onion_keys = onion_utils::construct_onion_keys( + &Secp256k1::new(), + &route.paths[0], + &session_priv, + ); + let failure = onion_utils::build_failure_packet( + onion_keys[0].shared_secret.as_ref(), + LocalHTLCFailureReason::RequiredNodeFeature, + &[0; 0], + 0, + ); + msg.reason = failure.data; + msg.attribution_data = failure.attribution_data; + }, + || { + nodes[2].node.fail_htlc_backwards(&payment_hash); + }, + true, + Some(LocalHTLCFailureReason::RequiredNodeFeature), + Some(NetworkUpdate::NodeFailure { + node_id: route.paths[0].hops[0].pubkey, + is_permanent: true, + }), + Some(route.paths[0].hops[0].short_channel_id), + Some(next_hop_failure.clone()), + ); // final node failure - run_onion_failure_test_with_fail_intercept("required_node_feature_missing", 200, &nodes, &route, &payment_hash, &payment_secret, |_msg| {}, |msg| { - let session_priv = SecretKey::from_slice(&[3; 32]).unwrap(); - let onion_keys = onion_utils::construct_onion_keys(&Secp256k1::new(), &route.paths[0], &session_priv); - let failure = onion_utils::build_failure_packet(onion_keys[1].shared_secret.as_ref(), LocalHTLCFailureReason::RequiredNodeFeature, &[0;0], 0); - msg.reason = failure.data; - msg.attribution_data = failure.attribution_data; - }, ||{ - nodes[2].node.fail_htlc_backwards(&payment_hash); - }, false, Some(LocalHTLCFailureReason::RequiredNodeFeature), Some(NetworkUpdate::NodeFailure{node_id: route.paths[0].hops[1].pubkey, is_permanent: true}), Some(route.paths[0].hops[1].short_channel_id), None); + run_onion_failure_test_with_fail_intercept( + "required_node_feature_missing", + 200, + &nodes, + &route, + &payment_hash, + &payment_secret, + |_msg| {}, + |msg| { + let session_priv = SecretKey::from_slice(&[3; 32]).unwrap(); + let onion_keys = onion_utils::construct_onion_keys( + &Secp256k1::new(), + &route.paths[0], + &session_priv, + ); + let failure = onion_utils::build_failure_packet( + onion_keys[1].shared_secret.as_ref(), + LocalHTLCFailureReason::RequiredNodeFeature, + &[0; 0], + 0, + ); + msg.reason = failure.data; + msg.attribution_data = failure.attribution_data; + }, + || { + nodes[2].node.fail_htlc_backwards(&payment_hash); + }, + false, + Some(LocalHTLCFailureReason::RequiredNodeFeature), + Some(NetworkUpdate::NodeFailure { + node_id: route.paths[0].hops[1].pubkey, + is_permanent: true, + }), + Some(route.paths[0].hops[1].short_channel_id), + None, + ); let (_, payment_hash, payment_secret) = get_payment_preimage_hash!(nodes[2]); // Our immediate peer sent UpdateFailMalformedHTLC because it couldn't understand the onion in // the UpdateAddHTLC that we sent. let short_channel_id = channels[0].0.contents.short_channel_id; - run_onion_failure_test("invalid_onion_version", 0, &nodes, &route, &payment_hash, &payment_secret, |msg| { msg.onion_routing_packet.version = 1; }, ||{}, true, - Some(LocalHTLCFailureReason::InvalidOnionVersion), None, Some(short_channel_id), Some(HTLCHandlingFailureType::InvalidOnion)); + run_onion_failure_test( + "invalid_onion_version", + 0, + &nodes, + &route, + &payment_hash, + &payment_secret, + |msg| { + msg.onion_routing_packet.version = 1; + }, + || {}, + true, + Some(LocalHTLCFailureReason::InvalidOnionVersion), + None, + Some(short_channel_id), + Some(HTLCHandlingFailureType::InvalidOnion), + ); - run_onion_failure_test("invalid_onion_hmac", 0, &nodes, &route, &payment_hash, &payment_secret, |msg| { msg.onion_routing_packet.hmac = [3; 32]; }, ||{}, true, - Some(LocalHTLCFailureReason::InvalidOnionHMAC), None, Some(short_channel_id), Some(HTLCHandlingFailureType::InvalidOnion)); + run_onion_failure_test( + "invalid_onion_hmac", + 0, + &nodes, + &route, + &payment_hash, + &payment_secret, + |msg| { + msg.onion_routing_packet.hmac = [3; 32]; + }, + || {}, + true, + Some(LocalHTLCFailureReason::InvalidOnionHMAC), + None, + Some(short_channel_id), + Some(HTLCHandlingFailureType::InvalidOnion), + ); - run_onion_failure_test("invalid_onion_key", 0, &nodes, &route, &payment_hash, &payment_secret, |msg| { msg.onion_routing_packet.public_key = Err(secp256k1::Error::InvalidPublicKey);}, ||{}, true, - Some(LocalHTLCFailureReason::InvalidOnionKey), None, Some(short_channel_id), Some(HTLCHandlingFailureType::InvalidOnion)); + run_onion_failure_test( + "invalid_onion_key", + 0, + &nodes, + &route, + &payment_hash, + &payment_secret, + |msg| { + msg.onion_routing_packet.public_key = Err(secp256k1::Error::InvalidPublicKey); + }, + || {}, + true, + Some(LocalHTLCFailureReason::InvalidOnionKey), + None, + Some(short_channel_id), + Some(HTLCHandlingFailureType::InvalidOnion), + ); let short_channel_id = channels[1].0.contents.short_channel_id; let chan_update = ChannelUpdate::dummy(short_channel_id); @@ -497,72 +904,201 @@ fn test_onion_failure() { err_data.extend_from_slice(&(chan_update.serialized_length() as u16 + 2).to_be_bytes()); err_data.extend_from_slice(&ChannelUpdate::TYPE.to_be_bytes()); err_data.extend_from_slice(&chan_update.encode()); - run_onion_failure_test_with_fail_intercept("temporary_channel_failure", 100, &nodes, &route, &payment_hash, &payment_secret, |msg| { - msg.amount_msat -= 1; - }, |msg| { - let session_priv = SecretKey::from_slice(&[3; 32]).unwrap(); - let onion_keys = onion_utils::construct_onion_keys(&Secp256k1::new(), &route.paths[0], &session_priv); - let failure = onion_utils::build_failure_packet(onion_keys[0].shared_secret.as_ref(), LocalHTLCFailureReason::TemporaryChannelFailure, &err_data, 0); - msg.reason = failure.data; - msg.attribution_data = failure.attribution_data; - }, ||{}, true, Some(LocalHTLCFailureReason::TemporaryChannelFailure), - Some(NetworkUpdate::ChannelFailure { short_channel_id, is_permanent: false }), - Some(short_channel_id), Some(next_hop_failure.clone())); + run_onion_failure_test_with_fail_intercept( + "temporary_channel_failure", + 100, + &nodes, + &route, + &payment_hash, + &payment_secret, + |msg| { + msg.amount_msat -= 1; + }, + |msg| { + let session_priv = SecretKey::from_slice(&[3; 32]).unwrap(); + let onion_keys = onion_utils::construct_onion_keys( + &Secp256k1::new(), + &route.paths[0], + &session_priv, + ); + let failure = onion_utils::build_failure_packet( + onion_keys[0].shared_secret.as_ref(), + LocalHTLCFailureReason::TemporaryChannelFailure, + &err_data, + 0, + ); + msg.reason = failure.data; + msg.attribution_data = failure.attribution_data; + }, + || {}, + true, + Some(LocalHTLCFailureReason::TemporaryChannelFailure), + Some(NetworkUpdate::ChannelFailure { short_channel_id, is_permanent: false }), + Some(short_channel_id), + Some(next_hop_failure.clone()), + ); // Check we can still handle onion failures that include channel updates without a type prefix let err_data_without_type = chan_update.encode_with_len(); - run_onion_failure_test_with_fail_intercept("temporary_channel_failure", 100, &nodes, &route, &payment_hash, &payment_secret, |msg| { - msg.amount_msat -= 1; - }, |msg| { - let session_priv = SecretKey::from_slice(&[3; 32]).unwrap(); - let onion_keys = onion_utils::construct_onion_keys(&Secp256k1::new(), &route.paths[0], &session_priv); - let failure = onion_utils::build_failure_packet(onion_keys[0].shared_secret.as_ref(), LocalHTLCFailureReason::TemporaryChannelFailure, &err_data_without_type, 0); - msg.reason = failure.data; - msg.attribution_data = failure.attribution_data; - }, ||{}, true, Some(LocalHTLCFailureReason::TemporaryChannelFailure), - Some(NetworkUpdate::ChannelFailure { short_channel_id, is_permanent: false }), - Some(short_channel_id), Some(next_hop_failure.clone())); + run_onion_failure_test_with_fail_intercept( + "temporary_channel_failure", + 100, + &nodes, + &route, + &payment_hash, + &payment_secret, + |msg| { + msg.amount_msat -= 1; + }, + |msg| { + let session_priv = SecretKey::from_slice(&[3; 32]).unwrap(); + let onion_keys = onion_utils::construct_onion_keys( + &Secp256k1::new(), + &route.paths[0], + &session_priv, + ); + let failure = onion_utils::build_failure_packet( + onion_keys[0].shared_secret.as_ref(), + LocalHTLCFailureReason::TemporaryChannelFailure, + &err_data_without_type, + 0, + ); + msg.reason = failure.data; + msg.attribution_data = failure.attribution_data; + }, + || {}, + true, + Some(LocalHTLCFailureReason::TemporaryChannelFailure), + Some(NetworkUpdate::ChannelFailure { short_channel_id, is_permanent: false }), + Some(short_channel_id), + Some(next_hop_failure.clone()), + ); let short_channel_id = channels[1].0.contents.short_channel_id; - run_onion_failure_test_with_fail_intercept("permanent_channel_failure", 100, &nodes, &route, &payment_hash, &payment_secret, |msg| { - msg.amount_msat -= 1; - }, |msg| { - let session_priv = SecretKey::from_slice(&[3; 32]).unwrap(); - let onion_keys = onion_utils::construct_onion_keys(&Secp256k1::new(), &route.paths[0], &session_priv); - let failure = onion_utils::build_failure_packet(onion_keys[0].shared_secret.as_ref(), LocalHTLCFailureReason::PermanentChannelFailure, &[0;0], 0); - msg.reason = failure.data; - msg.attribution_data = failure.attribution_data; - // short_channel_id from the processing node - }, ||{}, true, Some(LocalHTLCFailureReason::PermanentChannelFailure), Some(NetworkUpdate::ChannelFailure{short_channel_id, is_permanent: true}), Some(short_channel_id), Some(next_hop_failure.clone())); + run_onion_failure_test_with_fail_intercept( + "permanent_channel_failure", + 100, + &nodes, + &route, + &payment_hash, + &payment_secret, + |msg| { + msg.amount_msat -= 1; + }, + |msg| { + let session_priv = SecretKey::from_slice(&[3; 32]).unwrap(); + let onion_keys = onion_utils::construct_onion_keys( + &Secp256k1::new(), + &route.paths[0], + &session_priv, + ); + let failure = onion_utils::build_failure_packet( + onion_keys[0].shared_secret.as_ref(), + LocalHTLCFailureReason::PermanentChannelFailure, + &[0; 0], + 0, + ); + msg.reason = failure.data; + msg.attribution_data = failure.attribution_data; + // short_channel_id from the processing node + }, + || {}, + true, + Some(LocalHTLCFailureReason::PermanentChannelFailure), + Some(NetworkUpdate::ChannelFailure { short_channel_id, is_permanent: true }), + Some(short_channel_id), + Some(next_hop_failure.clone()), + ); let short_channel_id = channels[1].0.contents.short_channel_id; - run_onion_failure_test_with_fail_intercept("required_channel_feature_missing", 100, &nodes, &route, &payment_hash, &payment_secret, |msg| { - msg.amount_msat -= 1; - }, |msg| { - let session_priv = SecretKey::from_slice(&[3; 32]).unwrap(); - let onion_keys = onion_utils::construct_onion_keys(&Secp256k1::new(), &route.paths[0], &session_priv); - let failure = onion_utils::build_failure_packet(onion_keys[0].shared_secret.as_ref(), LocalHTLCFailureReason::RequiredChannelFeature, &[0;0], 0); - msg.reason = failure.data; - msg.attribution_data = failure.attribution_data; - // short_channel_id from the processing node - }, ||{}, true, Some(LocalHTLCFailureReason::RequiredChannelFeature), Some(NetworkUpdate::ChannelFailure{short_channel_id, is_permanent: true}), Some(short_channel_id), Some(next_hop_failure.clone())); + run_onion_failure_test_with_fail_intercept( + "required_channel_feature_missing", + 100, + &nodes, + &route, + &payment_hash, + &payment_secret, + |msg| { + msg.amount_msat -= 1; + }, + |msg| { + let session_priv = SecretKey::from_slice(&[3; 32]).unwrap(); + let onion_keys = onion_utils::construct_onion_keys( + &Secp256k1::new(), + &route.paths[0], + &session_priv, + ); + let failure = onion_utils::build_failure_packet( + onion_keys[0].shared_secret.as_ref(), + LocalHTLCFailureReason::RequiredChannelFeature, + &[0; 0], + 0, + ); + msg.reason = failure.data; + msg.attribution_data = failure.attribution_data; + // short_channel_id from the processing node + }, + || {}, + true, + Some(LocalHTLCFailureReason::RequiredChannelFeature), + Some(NetworkUpdate::ChannelFailure { short_channel_id, is_permanent: true }), + Some(short_channel_id), + Some(next_hop_failure.clone()), + ); let mut bogus_route = route.clone(); bogus_route.paths[0].hops[1].short_channel_id -= 1; let short_channel_id = bogus_route.paths[0].hops[1].short_channel_id; - run_onion_failure_test("unknown_next_peer", 100, &nodes, &bogus_route, &payment_hash, &payment_secret, |_| {}, ||{}, true, Some(LocalHTLCFailureReason::UnknownNextPeer), - Some(NetworkUpdate::ChannelFailure{short_channel_id, is_permanent:true}), Some(short_channel_id), Some(HTLCHandlingFailureType::InvalidForward { requested_forward_scid: short_channel_id })); + run_onion_failure_test( + "unknown_next_peer", + 100, + &nodes, + &bogus_route, + &payment_hash, + &payment_secret, + |_| {}, + || {}, + true, + Some(LocalHTLCFailureReason::UnknownNextPeer), + Some(NetworkUpdate::ChannelFailure { short_channel_id, is_permanent: true }), + Some(short_channel_id), + Some(HTLCHandlingFailureType::InvalidForward { requested_forward_scid: short_channel_id }), + ); let short_channel_id = channels[1].0.contents.short_channel_id; - let amt_to_forward = nodes[1].node.per_peer_state.read().unwrap().get(&nodes[2].node.get_our_node_id()) - .unwrap().lock().unwrap().channel_by_id.get(&channels[1].2).unwrap() - .context().get_counterparty_htlc_minimum_msat() - 1; + let amt_to_forward = nodes[1] + .node + .per_peer_state + .read() + .unwrap() + .get(&nodes[2].node.get_our_node_id()) + .unwrap() + .lock() + .unwrap() + .channel_by_id + .get(&channels[1].2) + .unwrap() + .context() + .get_counterparty_htlc_minimum_msat() + - 1; let mut bogus_route = route.clone(); let route_len = bogus_route.paths[0].hops.len(); - bogus_route.paths[0].hops[route_len-1].fee_msat = amt_to_forward; - run_onion_failure_test("amount_below_minimum", 100, &nodes, &bogus_route, &payment_hash, &payment_secret, |_| {}, ||{}, true, Some(LocalHTLCFailureReason::AmountBelowMinimum), + bogus_route.paths[0].hops[route_len - 1].fee_msat = amt_to_forward; + run_onion_failure_test( + "amount_below_minimum", + 100, + &nodes, + &bogus_route, + &payment_hash, + &payment_secret, + |_| {}, + || {}, + true, + Some(LocalHTLCFailureReason::AmountBelowMinimum), Some(NetworkUpdate::ChannelFailure { short_channel_id, is_permanent: false }), - Some(short_channel_id), Some(next_hop_failure.clone())); + Some(short_channel_id), + Some(next_hop_failure.clone()), + ); // Clear pending payments so that the following positive test has the correct payment hash. for node in nodes.iter() { @@ -570,122 +1106,318 @@ fn test_onion_failure() { } // Test a positive test-case with one extra msat, meeting the minimum. - bogus_route.paths[0].hops[route_len-1].fee_msat = amt_to_forward + 1; - let preimage = send_along_route(&nodes[0], bogus_route, &[&nodes[1], &nodes[2]], amt_to_forward+1).0; + bogus_route.paths[0].hops[route_len - 1].fee_msat = amt_to_forward + 1; + let preimage = + send_along_route(&nodes[0], bogus_route, &[&nodes[1], &nodes[2]], amt_to_forward + 1).0; claim_payment(&nodes[0], &[&nodes[1], &nodes[2]], preimage); // We ignore channel update contents in onion errors, so will blame the 2nd channel even though // the first node is the one that messed up. let short_channel_id = channels[1].0.contents.short_channel_id; - run_onion_failure_test("fee_insufficient", 100, &nodes, &route, &payment_hash, &payment_secret, |msg| { - msg.amount_msat -= 1; - }, || {}, true, Some(LocalHTLCFailureReason::FeeInsufficient), Some(NetworkUpdate::ChannelFailure { short_channel_id, is_permanent: false}), Some(short_channel_id), Some(next_hop_failure.clone())); + run_onion_failure_test( + "fee_insufficient", + 100, + &nodes, + &route, + &payment_hash, + &payment_secret, + |msg| { + msg.amount_msat -= 1; + }, + || {}, + true, + Some(LocalHTLCFailureReason::FeeInsufficient), + Some(NetworkUpdate::ChannelFailure { short_channel_id, is_permanent: false }), + Some(short_channel_id), + Some(next_hop_failure.clone()), + ); let short_channel_id = channels[1].0.contents.short_channel_id; - run_onion_failure_test("incorrect_cltv_expiry", 100, &nodes, &route, &payment_hash, &payment_secret, |msg| { - // need to violate: cltv_expiry - cltv_expiry_delta >= outgoing_cltv_value - msg.cltv_expiry -= 1; - }, || {}, true, Some(LocalHTLCFailureReason::IncorrectCLTVExpiry), Some(NetworkUpdate::ChannelFailure { short_channel_id, is_permanent: false}), Some(short_channel_id), Some(next_hop_failure.clone())); + run_onion_failure_test( + "incorrect_cltv_expiry", + 100, + &nodes, + &route, + &payment_hash, + &payment_secret, + |msg| { + // need to violate: cltv_expiry - cltv_expiry_delta >= outgoing_cltv_value + msg.cltv_expiry -= 1; + }, + || {}, + true, + Some(LocalHTLCFailureReason::IncorrectCLTVExpiry), + Some(NetworkUpdate::ChannelFailure { short_channel_id, is_permanent: false }), + Some(short_channel_id), + Some(next_hop_failure.clone()), + ); let short_channel_id = channels[1].0.contents.short_channel_id; - run_onion_failure_test("expiry_too_soon", 100, &nodes, &route, &payment_hash, &payment_secret, |msg| { - let height = msg.cltv_expiry - CLTV_CLAIM_BUFFER - LATENCY_GRACE_PERIOD_BLOCKS + 1; - connect_blocks(&nodes[0], height - nodes[0].best_block_info().1); - connect_blocks(&nodes[1], height - nodes[1].best_block_info().1); - connect_blocks(&nodes[2], height - nodes[2].best_block_info().1); - }, ||{}, true, Some(LocalHTLCFailureReason::CLTVExpiryTooSoon), - Some(NetworkUpdate::ChannelFailure { short_channel_id, is_permanent: false }), - Some(short_channel_id), Some(next_hop_failure.clone())); - - run_onion_failure_test("unknown_payment_hash", 2, &nodes, &route, &payment_hash, &payment_secret, |_| {}, || { - nodes[2].node.fail_htlc_backwards(&payment_hash); - }, false, Some(LocalHTLCFailureReason::IncorrectPaymentDetails), None, None, None); + run_onion_failure_test( + "expiry_too_soon", + 100, + &nodes, + &route, + &payment_hash, + &payment_secret, + |msg| { + let height = msg.cltv_expiry - CLTV_CLAIM_BUFFER - LATENCY_GRACE_PERIOD_BLOCKS + 1; + connect_blocks(&nodes[0], height - nodes[0].best_block_info().1); + connect_blocks(&nodes[1], height - nodes[1].best_block_info().1); + connect_blocks(&nodes[2], height - nodes[2].best_block_info().1); + }, + || {}, + true, + Some(LocalHTLCFailureReason::CLTVExpiryTooSoon), + Some(NetworkUpdate::ChannelFailure { short_channel_id, is_permanent: false }), + Some(short_channel_id), + Some(next_hop_failure.clone()), + ); + + run_onion_failure_test( + "unknown_payment_hash", + 2, + &nodes, + &route, + &payment_hash, + &payment_secret, + |_| {}, + || { + nodes[2].node.fail_htlc_backwards(&payment_hash); + }, + false, + Some(LocalHTLCFailureReason::IncorrectPaymentDetails), + None, + None, + None, + ); let (_, payment_hash, payment_secret) = get_payment_preimage_hash!(nodes[2]); - run_onion_failure_test("final_expiry_too_soon", 1, &nodes, &route, &payment_hash, &payment_secret, |msg| { - let height = msg.cltv_expiry - CLTV_CLAIM_BUFFER - LATENCY_GRACE_PERIOD_BLOCKS + 1; - connect_blocks(&nodes[0], height - nodes[0].best_block_info().1); - connect_blocks(&nodes[1], height - nodes[1].best_block_info().1); - connect_blocks(&nodes[2], height - nodes[2].best_block_info().1); - }, || {}, false, Some(LocalHTLCFailureReason::IncorrectPaymentDetails), None, None, Some(HTLCHandlingFailureType::Receive { payment_hash })); - - run_onion_failure_test("final_incorrect_cltv_expiry", 1, &nodes, &route, &payment_hash, &payment_secret, |_| {}, || { - nodes[1].node.process_pending_update_add_htlcs(); - for (_, pending_forwards) in nodes[1].node.forward_htlcs.lock().unwrap().iter_mut() { - for f in pending_forwards.iter_mut() { - match f { - &mut HTLCForwardInfo::AddHTLC(PendingAddHTLCInfo { ref mut forward_info, .. }) => - forward_info.outgoing_cltv_value -= 1, - _ => {}, + run_onion_failure_test( + "final_expiry_too_soon", + 1, + &nodes, + &route, + &payment_hash, + &payment_secret, + |msg| { + let height = msg.cltv_expiry - CLTV_CLAIM_BUFFER - LATENCY_GRACE_PERIOD_BLOCKS + 1; + connect_blocks(&nodes[0], height - nodes[0].best_block_info().1); + connect_blocks(&nodes[1], height - nodes[1].best_block_info().1); + connect_blocks(&nodes[2], height - nodes[2].best_block_info().1); + }, + || {}, + false, + Some(LocalHTLCFailureReason::IncorrectPaymentDetails), + None, + None, + Some(HTLCHandlingFailureType::Receive { payment_hash }), + ); + + run_onion_failure_test( + "final_incorrect_cltv_expiry", + 1, + &nodes, + &route, + &payment_hash, + &payment_secret, + |_| {}, + || { + nodes[1].node.process_pending_update_add_htlcs(); + for (_, pending_forwards) in nodes[1].node.forward_htlcs.lock().unwrap().iter_mut() { + for f in pending_forwards.iter_mut() { + match f { + &mut HTLCForwardInfo::AddHTLC(PendingAddHTLCInfo { + ref mut forward_info, + .. + }) => forward_info.outgoing_cltv_value -= 1, + _ => {}, + } } } - } - }, true, Some(LocalHTLCFailureReason::FinalIncorrectCLTVExpiry), None, Some(channels[1].0.contents.short_channel_id), Some(HTLCHandlingFailureType::Receive { payment_hash })); - - run_onion_failure_test("final_incorrect_htlc_amount", 1, &nodes, &route, &payment_hash, &payment_secret, |_| {}, || { - nodes[1].node.process_pending_update_add_htlcs(); - // violate amt_to_forward > msg.amount_msat - for (_, pending_forwards) in nodes[1].node.forward_htlcs.lock().unwrap().iter_mut() { - for f in pending_forwards.iter_mut() { - match f { - &mut HTLCForwardInfo::AddHTLC(PendingAddHTLCInfo { ref mut forward_info, .. }) => - forward_info.outgoing_amt_msat -= 1, - _ => {}, + }, + true, + Some(LocalHTLCFailureReason::FinalIncorrectCLTVExpiry), + None, + Some(channels[1].0.contents.short_channel_id), + Some(HTLCHandlingFailureType::Receive { payment_hash }), + ); + + run_onion_failure_test( + "final_incorrect_htlc_amount", + 1, + &nodes, + &route, + &payment_hash, + &payment_secret, + |_| {}, + || { + nodes[1].node.process_pending_update_add_htlcs(); + // violate amt_to_forward > msg.amount_msat + for (_, pending_forwards) in nodes[1].node.forward_htlcs.lock().unwrap().iter_mut() { + for f in pending_forwards.iter_mut() { + match f { + &mut HTLCForwardInfo::AddHTLC(PendingAddHTLCInfo { + ref mut forward_info, + .. + }) => forward_info.outgoing_amt_msat -= 1, + _ => {}, + } } } - } - }, true, Some(LocalHTLCFailureReason::FinalIncorrectHTLCAmount), None, Some(channels[1].0.contents.short_channel_id), Some(HTLCHandlingFailureType::Receive { payment_hash })); + }, + true, + Some(LocalHTLCFailureReason::FinalIncorrectHTLCAmount), + None, + Some(channels[1].0.contents.short_channel_id), + Some(HTLCHandlingFailureType::Receive { payment_hash }), + ); let short_channel_id = channels[1].0.contents.short_channel_id; - run_onion_failure_test("channel_disabled", 100, &nodes, &route, &payment_hash, &payment_secret, |_| {}, || { - // disconnect event to the channel between nodes[1] ~ nodes[2] - nodes[1].node.peer_disconnected(nodes[2].node.get_our_node_id()); - nodes[2].node.peer_disconnected(nodes[1].node.get_our_node_id()); - }, true, Some(LocalHTLCFailureReason::TemporaryChannelFailure), - Some(NetworkUpdate::ChannelFailure { short_channel_id, is_permanent: false }), - Some(short_channel_id), Some(next_hop_failure.clone())); - run_onion_failure_test("channel_disabled", 100, &nodes, &route, &payment_hash, &payment_secret, |_| {}, || { - // disconnect event to the channel between nodes[1] ~ nodes[2] - for _ in 0..DISABLE_GOSSIP_TICKS + 1 { - nodes[1].node.timer_tick_occurred(); - nodes[2].node.timer_tick_occurred(); - } - nodes[1].node.get_and_clear_pending_msg_events(); - nodes[2].node.get_and_clear_pending_msg_events(); - }, true, Some(LocalHTLCFailureReason::ChannelDisabled), - Some(NetworkUpdate::ChannelFailure { short_channel_id, is_permanent: false }), - Some(short_channel_id), Some(next_hop_failure.clone())); + run_onion_failure_test( + "channel_disabled", + 100, + &nodes, + &route, + &payment_hash, + &payment_secret, + |_| {}, + || { + // disconnect event to the channel between nodes[1] ~ nodes[2] + nodes[1].node.peer_disconnected(nodes[2].node.get_our_node_id()); + nodes[2].node.peer_disconnected(nodes[1].node.get_our_node_id()); + }, + true, + Some(LocalHTLCFailureReason::TemporaryChannelFailure), + Some(NetworkUpdate::ChannelFailure { short_channel_id, is_permanent: false }), + Some(short_channel_id), + Some(next_hop_failure.clone()), + ); + run_onion_failure_test( + "channel_disabled", + 100, + &nodes, + &route, + &payment_hash, + &payment_secret, + |_| {}, + || { + // disconnect event to the channel between nodes[1] ~ nodes[2] + for _ in 0..DISABLE_GOSSIP_TICKS + 1 { + nodes[1].node.timer_tick_occurred(); + nodes[2].node.timer_tick_occurred(); + } + nodes[1].node.get_and_clear_pending_msg_events(); + nodes[2].node.get_and_clear_pending_msg_events(); + }, + true, + Some(LocalHTLCFailureReason::ChannelDisabled), + Some(NetworkUpdate::ChannelFailure { short_channel_id, is_permanent: false }), + Some(short_channel_id), + Some(next_hop_failure.clone()), + ); reconnect_nodes(ReconnectArgs::new(&nodes[1], &nodes[2])); - run_onion_failure_test("expiry_too_far", 100, &nodes, &route, &payment_hash, &payment_secret, |msg| { - let session_priv = SecretKey::from_slice(&[3; 32]).unwrap(); - let mut route = route.clone(); - let height = nodes[2].best_block_info().1; - route.paths[0].hops[1].cltv_expiry_delta += CLTV_FAR_FAR_AWAY + route.paths[0].hops[0].cltv_expiry_delta + 1; - let onion_keys = onion_utils::construct_onion_keys(&Secp256k1::new(), &route.paths[0], &session_priv); - let recipient_onion_fields = RecipientOnionFields::spontaneous_empty(); - let (onion_payloads, _, htlc_cltv) = onion_utils::build_onion_payloads( - &route.paths[0], 40000, &recipient_onion_fields, height, &None, None, None).unwrap(); - let onion_packet = onion_utils::construct_onion_packet(onion_payloads, onion_keys, [0; 32], &payment_hash).unwrap(); - msg.cltv_expiry = htlc_cltv; - msg.onion_routing_packet = onion_packet; - }, ||{}, true, Some(LocalHTLCFailureReason::CLTVExpiryTooFar), 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())); - - run_onion_failure_test_with_fail_intercept("mpp_timeout", 200, &nodes, &route, &payment_hash, &payment_secret, |_msg| {}, |msg| { - // Tamper returning error message - let session_priv = SecretKey::from_slice(&[3; 32]).unwrap(); - let onion_keys = onion_utils::construct_onion_keys(&Secp256k1::new(), &route.paths[0], &session_priv); - let failure = onion_utils::build_failure_packet(onion_keys[1].shared_secret.as_ref(), LocalHTLCFailureReason::MPPTimeout, &[0;0], 0); - msg.reason = failure.data; - msg.attribution_data = failure.attribution_data; - }, ||{ - nodes[2].node.fail_htlc_backwards(&payment_hash); - }, true, Some(LocalHTLCFailureReason::MPPTimeout), None, None, None); - - run_onion_failure_test_with_fail_intercept("bogus err packet with valid hmac", 200, &nodes, - &route, &payment_hash, &payment_secret, |_msg| {}, |msg| { + run_onion_failure_test( + "expiry_too_far", + 100, + &nodes, + &route, + &payment_hash, + &payment_secret, + |msg| { let session_priv = SecretKey::from_slice(&[3; 32]).unwrap(); - let onion_keys = onion_utils::construct_onion_keys(&Secp256k1::new(), &route.paths[0], &session_priv); + let mut route = route.clone(); + let height = nodes[2].best_block_info().1; + route.paths[0].hops[1].cltv_expiry_delta += + CLTV_FAR_FAR_AWAY + route.paths[0].hops[0].cltv_expiry_delta + 1; + let onion_keys = onion_utils::construct_onion_keys( + &Secp256k1::new(), + &route.paths[0], + &session_priv, + ); + let recipient_onion_fields = RecipientOnionFields::spontaneous_empty(); + let (onion_payloads, _, htlc_cltv) = onion_utils::build_onion_payloads( + &route.paths[0], + 40000, + &recipient_onion_fields, + height, + &None, + None, + None, + ) + .unwrap(); + let onion_packet = onion_utils::construct_onion_packet( + onion_payloads, + onion_keys, + [0; 32], + &payment_hash, + ) + .unwrap(); + msg.cltv_expiry = htlc_cltv; + msg.onion_routing_packet = onion_packet; + }, + || {}, + true, + Some(LocalHTLCFailureReason::CLTVExpiryTooFar), + 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()), + ); + + run_onion_failure_test_with_fail_intercept( + "mpp_timeout", + 200, + &nodes, + &route, + &payment_hash, + &payment_secret, + |_msg| {}, + |msg| { + // Tamper returning error message + let session_priv = SecretKey::from_slice(&[3; 32]).unwrap(); + let onion_keys = onion_utils::construct_onion_keys( + &Secp256k1::new(), + &route.paths[0], + &session_priv, + ); + let failure = onion_utils::build_failure_packet( + onion_keys[1].shared_secret.as_ref(), + LocalHTLCFailureReason::MPPTimeout, + &[0; 0], + 0, + ); + msg.reason = failure.data; + msg.attribution_data = failure.attribution_data; + }, + || { + nodes[2].node.fail_htlc_backwards(&payment_hash); + }, + true, + Some(LocalHTLCFailureReason::MPPTimeout), + None, + None, + None, + ); + + run_onion_failure_test_with_fail_intercept( + "bogus err packet with valid hmac", + 200, + &nodes, + &route, + &payment_hash, + &payment_secret, + |_msg| {}, + |msg| { + let session_priv = SecretKey::from_slice(&[3; 32]).unwrap(); + let onion_keys = onion_utils::construct_onion_keys( + &Secp256k1::new(), + &route.paths[0], + &session_priv, + ); let mut decoded_err_packet = msgs::DecodedOnionErrorPacket { failuremsg: vec![0], pad: vec![0; 255], @@ -699,31 +1431,69 @@ fn test_onion_failure() { data: decoded_err_packet.encode(), attribution_data: Some(AttributionData::new()), }; - onion_error.attribution_data.as_mut().unwrap().add_hmacs(&onion_keys[1].shared_secret.as_ref(), &onion_error.data); + onion_error + .attribution_data + .as_mut() + .unwrap() + .add_hmacs(&onion_keys[1].shared_secret.as_ref(), &onion_error.data); onion_utils::test_crypt_failure_packet( - &onion_keys[1].shared_secret.as_ref(), &mut onion_error); + &onion_keys[1].shared_secret.as_ref(), + &mut onion_error, + ); msg.reason = onion_error.data; msg.attribution_data = onion_error.attribution_data; - }, || nodes[2].node.fail_htlc_backwards(&payment_hash), false, None, - Some(NetworkUpdate::NodeFailure { node_id: route.paths[0].hops[1].pubkey, is_permanent: true }), - Some(channels[1].0.contents.short_channel_id), None); + }, + || nodes[2].node.fail_htlc_backwards(&payment_hash), + false, + None, + Some(NetworkUpdate::NodeFailure { + node_id: route.paths[0].hops[1].pubkey, + is_permanent: true, + }), + Some(channels[1].0.contents.short_channel_id), + None, + ); - run_onion_failure_test_with_fail_intercept("bogus err packet that is too short for an hmac", 200, &nodes, - &route, &payment_hash, &payment_secret, |_msg| {}, |msg| { + run_onion_failure_test_with_fail_intercept( + "bogus err packet that is too short for an hmac", + 200, + &nodes, + &route, + &payment_hash, + &payment_secret, + |_msg| {}, + |msg| { msg.reason = vec![1, 2, 3]; - }, || nodes[2].node.fail_htlc_backwards(&payment_hash), false, None, - None, None, None); + }, + || nodes[2].node.fail_htlc_backwards(&payment_hash), + false, + None, + None, + None, + None, + ); - run_onion_failure_test_with_fail_intercept("0-length channel update in intermediate node UPDATE onion failure", - 100, &nodes, &route, &payment_hash, &payment_secret, |msg| { + run_onion_failure_test_with_fail_intercept( + "0-length channel update in intermediate node UPDATE onion failure", + 100, + &nodes, + &route, + &payment_hash, + &payment_secret, + |msg| { msg.amount_msat -= 1; - }, |msg| { + }, + |msg| { let session_priv = SecretKey::from_slice(&[3; 32]).unwrap(); - let onion_keys = onion_utils::construct_onion_keys(&Secp256k1::new(), &route.paths[0], &session_priv); + let onion_keys = onion_utils::construct_onion_keys( + &Secp256k1::new(), + &route.paths[0], + &session_priv, + ); let mut decoded_err_packet = msgs::DecodedOnionErrorPacket { failuremsg: vec![ 0x10, 0x7, // UPDATE|7 - 0x0, 0x0 // 0-len channel update + 0x0, 0x0, // 0-len channel update ], pad: vec![0; 255 - 4 /* 4-byte error message */], hmac: [0; 32], @@ -732,29 +1502,51 @@ fn test_onion_failure() { let mut hmac = HmacEngine::::new(&um); hmac.input(&decoded_err_packet.encode()[32..]); decoded_err_packet.hmac = Hmac::from_engine(hmac).to_byte_array(); - let mut onion_error = OnionErrorPacket{ + let mut onion_error = OnionErrorPacket { data: decoded_err_packet.encode(), attribution_data: Some(AttributionData::new()), }; - onion_error.attribution_data.as_mut().unwrap().add_hmacs(&onion_keys[0].shared_secret.as_ref(), &onion_error.data); + onion_error + .attribution_data + .as_mut() + .unwrap() + .add_hmacs(&onion_keys[0].shared_secret.as_ref(), &onion_error.data); onion_utils::test_crypt_failure_packet( - &onion_keys[0].shared_secret.as_ref(), &mut onion_error); + &onion_keys[0].shared_secret.as_ref(), + &mut onion_error, + ); msg.reason = onion_error.data; msg.attribution_data = onion_error.attribution_data; - }, || {}, true, Some(LocalHTLCFailureReason::TemporaryChannelFailure), + }, + || {}, + true, + Some(LocalHTLCFailureReason::TemporaryChannelFailure), Some(NetworkUpdate::ChannelFailure { short_channel_id: channels[1].0.contents.short_channel_id, is_permanent: false, }), - Some(channels[1].0.contents.short_channel_id), Some(next_hop_failure.clone())); - run_onion_failure_test_with_fail_intercept("0-length channel update in final node UPDATE onion failure", - 200, &nodes, &route, &payment_hash, &payment_secret, |_msg| {}, |msg| { + Some(channels[1].0.contents.short_channel_id), + Some(next_hop_failure.clone()), + ); + run_onion_failure_test_with_fail_intercept( + "0-length channel update in final node UPDATE onion failure", + 200, + &nodes, + &route, + &payment_hash, + &payment_secret, + |_msg| {}, + |msg| { let session_priv = SecretKey::from_slice(&[3; 32]).unwrap(); - let onion_keys = onion_utils::construct_onion_keys(&Secp256k1::new(), &route.paths[0], &session_priv); + let onion_keys = onion_utils::construct_onion_keys( + &Secp256k1::new(), + &route.paths[0], + &session_priv, + ); let mut decoded_err_packet = msgs::DecodedOnionErrorPacket { failuremsg: vec![ 0x10, 0x7, // UPDATE|7 - 0x0, 0x0 // 0-len channel update + 0x0, 0x0, // 0-len channel update ], pad: vec![0; 255 - 4 /* 4-byte error message */], hmac: [0; 32], @@ -763,21 +1555,32 @@ fn test_onion_failure() { let mut hmac = HmacEngine::::new(&um); hmac.input(&decoded_err_packet.encode()[32..]); decoded_err_packet.hmac = Hmac::from_engine(hmac).to_byte_array(); - let mut onion_error = OnionErrorPacket{ + let mut onion_error = OnionErrorPacket { data: decoded_err_packet.encode(), attribution_data: Some(AttributionData::new()), }; - onion_error.attribution_data.as_mut().unwrap().add_hmacs(&onion_keys[1].shared_secret.as_ref(), &onion_error.data); + onion_error + .attribution_data + .as_mut() + .unwrap() + .add_hmacs(&onion_keys[1].shared_secret.as_ref(), &onion_error.data); onion_utils::test_crypt_failure_packet( - &onion_keys[1].shared_secret.as_ref(), &mut onion_error); + &onion_keys[1].shared_secret.as_ref(), + &mut onion_error, + ); msg.reason = onion_error.data; msg.attribution_data = onion_error.attribution_data; - }, || nodes[2].node.fail_htlc_backwards(&payment_hash), true, Some(LocalHTLCFailureReason::TemporaryChannelFailure), + }, + || nodes[2].node.fail_htlc_backwards(&payment_hash), + true, + Some(LocalHTLCFailureReason::TemporaryChannelFailure), Some(NetworkUpdate::ChannelFailure { short_channel_id: channels[1].0.contents.short_channel_id, is_permanent: false, }), - Some(channels[1].0.contents.short_channel_id), None); + Some(channels[1].0.contents.short_channel_id), + None, + ); } #[test] @@ -788,10 +1591,19 @@ fn test_overshoot_final_cltv() { let mut nodes = create_network(3, &node_cfgs, &node_chanmgrs); create_announced_chan_between_nodes(&nodes, 0, 1); create_announced_chan_between_nodes(&nodes, 1, 2); - let (route, payment_hash, payment_preimage, payment_secret) = get_route_and_payment_hash!(nodes[0], nodes[2], 40000); + let (route, payment_hash, payment_preimage, payment_secret) = + get_route_and_payment_hash!(nodes[0], nodes[2], 40000); let payment_id = PaymentId(nodes[0].keys_manager.backing.get_secure_random_bytes()); - nodes[0].node.send_payment_with_route(route, payment_hash, RecipientOnionFields::secret_only(payment_secret), payment_id).unwrap(); + nodes[0] + .node + .send_payment_with_route( + route, + payment_hash, + RecipientOnionFields::secret_only(payment_secret), + payment_id, + ) + .unwrap(); check_added_monitors!(nodes[0], 1); let update_0 = get_htlc_update_msgs!(nodes[0], nodes[1].node.get_our_node_id()); @@ -803,8 +1615,9 @@ fn test_overshoot_final_cltv() { for (_, pending_forwards) in nodes[1].node.forward_htlcs.lock().unwrap().iter_mut() { for f in pending_forwards.iter_mut() { match f { - &mut HTLCForwardInfo::AddHTLC(PendingAddHTLCInfo { ref mut forward_info, .. }) => - forward_info.outgoing_cltv_value += 1, + &mut HTLCForwardInfo::AddHTLC(PendingAddHTLCInfo { + ref mut forward_info, .. + }) => forward_info.outgoing_cltv_value += 1, _ => {}, } } @@ -829,7 +1642,8 @@ fn do_test_onion_failure_stale_channel_update(announce_for_forwarding: bool) { config.channel_handshake_config.announce_for_forwarding = announce_for_forwarding; config.channel_handshake_limits.force_announced_channel_preference = false; config.accept_forwards_to_priv_channels = !announce_for_forwarding; - config.channel_config.max_dust_htlc_exposure = MaxDustHTLCExposure::FeeRateMultiplier(5_000_000 / 253); + config.channel_config.max_dust_htlc_exposure = + MaxDustHTLCExposure::FeeRateMultiplier(5_000_000 / 253); let chanmon_cfgs = create_chanmon_cfgs(3); let node_cfgs = create_node_cfgs(3, &chanmon_cfgs); let persister; @@ -838,18 +1652,12 @@ fn do_test_onion_failure_stale_channel_update(announce_for_forwarding: bool) { let channel_manager_1_deserialized; let mut nodes = create_network(3, &node_cfgs, &node_chanmgrs); - let other_channel = create_chan_between_nodes( - &nodes[0], &nodes[1], - ); + let other_channel = create_chan_between_nodes(&nodes[0], &nodes[1]); let channel_to_update = if announce_for_forwarding { - let channel = create_announced_chan_between_nodes( - &nodes, 1, 2, - ); + let channel = create_announced_chan_between_nodes(&nodes, 1, 2); (channel.2, channel.0.contents.short_channel_id) } else { - let channel = create_unannounced_chan_between_nodes_with_value( - &nodes, 1, 2, 100000, 10001, - ); + let channel = create_unannounced_chan_between_nodes_with_value(&nodes, 1, 2, 100000, 10001); (channel.0.channel_id, channel.0.short_channel_id_alias.unwrap()) }; let channel_to_update_counterparty = &nodes[2].node.get_our_node_id(); @@ -872,13 +1680,22 @@ fn do_test_onion_failure_stale_channel_update(announce_for_forwarding: bool) { htlc_maximum_msat: None, htlc_minimum_msat: None, }])]; - let payment_params = PaymentParameters::from_node_id(*channel_to_update_counterparty, TEST_FINAL_CLTV) - .with_bolt11_features(nodes[2].node.bolt11_invoice_features()).unwrap() - .with_route_hints(hop_hints).unwrap(); + let payment_params = + PaymentParameters::from_node_id(*channel_to_update_counterparty, TEST_FINAL_CLTV) + .with_bolt11_features(nodes[2].node.bolt11_invoice_features()) + .unwrap() + .with_route_hints(hop_hints) + .unwrap(); get_route_and_payment_hash!(nodes[0], nodes[2], payment_params, PAYMENT_AMT) }; - send_along_route_with_secret(&nodes[0], route.clone(), &[&[&nodes[1], &nodes[2]]], PAYMENT_AMT, - payment_hash, payment_secret); + send_along_route_with_secret( + &nodes[0], + route.clone(), + &[&[&nodes[1], &nodes[2]]], + PAYMENT_AMT, + payment_hash, + payment_secret, + ); claim_payment(&nodes[0], &[&nodes[1], &nodes[2]], payment_preimage); // Closure to force expiry of a channel's previous config. @@ -889,11 +1706,15 @@ fn do_test_onion_failure_stale_channel_update(announce_for_forwarding: bool) { }; // Closure to update and retrieve the latest ChannelUpdate. - let update_and_get_channel_update = |config: &ChannelConfig, expect_new_update: bool, - prev_update: Option<&msgs::ChannelUpdate>, should_expire_prev_config: bool| -> Option { - nodes[1].node.update_channel_config( - channel_to_update_counterparty, &[channel_to_update.0], config, - ).unwrap(); + let update_and_get_channel_update = |config: &ChannelConfig, + expect_new_update: bool, + prev_update: Option<&msgs::ChannelUpdate>, + should_expire_prev_config: bool| + -> Option { + nodes[1] + .node + .update_channel_config(channel_to_update_counterparty, &[channel_to_update.0], config) + .unwrap(); let events = nodes[1].node.get_and_clear_pending_msg_events(); assert_eq!(events.len(), expect_new_update as usize); if !expect_new_update { @@ -925,11 +1746,25 @@ fn do_test_onion_failure_stale_channel_update(announce_for_forwarding: bool) { // second hop. let expect_onion_failure = |name: &str, error_reason: LocalHTLCFailureReason| { let short_channel_id = channel_to_update.1; - let network_update = NetworkUpdate::ChannelFailure { short_channel_id, is_permanent: false }; + let network_update = + NetworkUpdate::ChannelFailure { short_channel_id, is_permanent: false }; run_onion_failure_test( - name, 100, &nodes, &route, &payment_hash, &payment_secret, |_| {}, || {}, true, - Some(error_reason), Some(network_update), Some(short_channel_id), - Some(HTLCHandlingFailureType::Forward { node_id: Some(nodes[2].node.get_our_node_id()), channel_id: channel_to_update.0 }), + name, + 100, + &nodes, + &route, + &payment_hash, + &payment_secret, + |_| {}, + || {}, + true, + Some(error_reason), + Some(network_update), + Some(short_channel_id), + Some(HTLCHandlingFailureType::Forward { + node_id: Some(nodes[2].node.get_our_node_id()), + channel_id: channel_to_update.0, + }), ); }; @@ -937,22 +1772,35 @@ fn do_test_onion_failure_stale_channel_update(announce_for_forwarding: bool) { let mut invalid_config = default_config.clone(); invalid_config.cltv_expiry_delta = 0; match nodes[1].node.update_channel_config( - channel_to_update_counterparty, &[channel_to_update.0], &invalid_config, + channel_to_update_counterparty, + &[channel_to_update.0], + &invalid_config, ) { - Err(APIError::APIMisuseError{ .. }) => {}, + Err(APIError::APIMisuseError { .. }) => {}, _ => panic!("unexpected result applying invalid cltv_expiry_delta"), } // Increase the base fee which should trigger a new ChannelUpdate. - let mut config = nodes[1].node.list_usable_channels().iter() - .find(|channel| channel.channel_id == channel_to_update.0).unwrap() - .config.unwrap(); + let mut config = nodes[1] + .node + .list_usable_channels() + .iter() + .find(|channel| channel.channel_id == channel_to_update.0) + .unwrap() + .config + .unwrap(); config.forwarding_fee_base_msat = u32::max_value(); let msg = update_and_get_channel_update(&config.clone(), true, None, false).unwrap(); // The old policy should still be in effect until a new block is connected. - send_along_route_with_secret(&nodes[0], route.clone(), &[&[&nodes[1], &nodes[2]]], PAYMENT_AMT, - payment_hash, payment_secret); + send_along_route_with_secret( + &nodes[0], + route.clone(), + &[&[&nodes[1], &nodes[2]]], + PAYMENT_AMT, + payment_hash, + payment_secret, + ); claim_payment(&nodes[0], &[&nodes[1], &nodes[2]], payment_preimage); // Connect a block, which should expire the previous config, leading to a failure when @@ -985,11 +1833,23 @@ fn do_test_onion_failure_stale_channel_update(announce_for_forwarding: bool) { let config_after_restart = { let chan_1_monitor_serialized = get_monitor!(nodes[1], other_channel.3).encode(); let chan_2_monitor_serialized = get_monitor!(nodes[1], channel_to_update.0).encode(); - reload_node!(nodes[1], nodes[1].node.get_current_default_configuration().clone(), &nodes[1].node.encode(), - &[&chan_1_monitor_serialized, &chan_2_monitor_serialized], persister, chain_monitor, channel_manager_1_deserialized); - nodes[1].node.list_channels().iter() - .find(|channel| channel.channel_id == channel_to_update.0).unwrap() - .config.unwrap() + reload_node!( + nodes[1], + nodes[1].node.get_current_default_configuration().clone(), + &nodes[1].node.encode(), + &[&chan_1_monitor_serialized, &chan_2_monitor_serialized], + persister, + chain_monitor, + channel_manager_1_deserialized + ); + nodes[1] + .node + .list_channels() + .iter() + .find(|channel| channel.channel_id == channel_to_update.0) + .unwrap() + .config + .unwrap() }; assert_eq!(config, config_after_restart); } @@ -1020,9 +1880,12 @@ fn test_always_create_tlv_format_onion_payloads() { create_announced_chan_between_nodes(&nodes, 0, 1); create_announced_chan_between_nodes(&nodes, 1, 2); - let payment_params = PaymentParameters::from_node_id(nodes[2].node.get_our_node_id(), TEST_FINAL_CLTV) - .with_bolt11_features(Bolt11InvoiceFeatures::empty()).unwrap(); - let (route, _payment_hash, _payment_preimage, _payment_secret) = get_route_and_payment_hash!(nodes[0], nodes[2], payment_params, 40000); + let payment_params = + PaymentParameters::from_node_id(nodes[2].node.get_our_node_id(), TEST_FINAL_CLTV) + .with_bolt11_features(Bolt11InvoiceFeatures::empty()) + .unwrap(); + let (route, _payment_hash, _payment_preimage, _payment_secret) = + get_route_and_payment_hash!(nodes[0], nodes[2], payment_params, 40000); let hops = &route.paths[0].hops; // Asserts that the first hop to `node[1]` signals no support for variable length onions. @@ -1033,21 +1896,33 @@ fn test_always_create_tlv_format_onion_payloads() { let cur_height = nodes[0].best_block_info().1 + 1; let recipient_onion_fields = RecipientOnionFields::spontaneous_empty(); let (onion_payloads, _htlc_msat, _htlc_cltv) = onion_utils::build_onion_payloads( - &route.paths[0], 40000, &recipient_onion_fields, cur_height, &None, None, None).unwrap(); + &route.paths[0], + 40000, + &recipient_onion_fields, + cur_height, + &None, + None, + None, + ) + .unwrap(); match onion_payloads[0] { - msgs::OutboundOnionPayload::Forward {..} => {}, - _ => { panic!( + msgs::OutboundOnionPayload::Forward { .. } => {}, + _ => { + panic!( "Should have generated a `msgs::OnionHopDataFormat::NonFinalNode` payload for `hops[0]`, despite that the features signals no support for variable length onions" - )} + ) + }, } match onion_payloads[1] { - msgs::OutboundOnionPayload::Receive {..} => {}, - _ => {panic!( + msgs::OutboundOnionPayload::Receive { .. } => {}, + _ => { + panic!( "Should have generated a `msgs::OnionHopDataFormat::FinalNode` payload for `hops[1]`, despite that the features signals no support for variable length onions" - )} + ) + }, } } @@ -1057,14 +1932,17 @@ fn test_trampoline_onion_payload_serialization() { let trampoline_payload = OutboundTrampolinePayload::Forward { amt_to_forward: 100000000, outgoing_cltv_value: 800000, - outgoing_node_id: PublicKey::from_slice(&>::from_hex("02edabbd16b41c8371b92ef2f04c1185b4f03b6dcd52ba9b78d9d7c89c8f221145").unwrap()).unwrap(), + outgoing_node_id: PublicKey::from_slice( + &>::from_hex( + "02edabbd16b41c8371b92ef2f04c1185b4f03b6dcd52ba9b78d9d7c89c8f221145", + ) + .unwrap(), + ) + .unwrap(), }; - let slice_to_hex = |slice: &[u8]| { - slice.iter() - .map(|b| format!("{:02x}", b).to_string()) - .collect::() - }; + let slice_to_hex = + |slice: &[u8]| slice.iter().map(|b| format!("{:02x}", b).to_string()).collect::(); let carol_payload_hex = slice_to_hex(&trampoline_payload.encode()); assert_eq!(carol_payload_hex, "2e020405f5e10004030c35000e2102edabbd16b41c8371b92ef2f04c1185b4f03b6dcd52ba9b78d9d7c89c8f221145"); @@ -1083,7 +1961,13 @@ fn test_trampoline_onion_payload_assembly_values() { hops: vec![ // Bob RouteHop { - pubkey: PublicKey::from_slice(&>::from_hex("0324653eac434488002cc06bbfb7f10fe18991e35f9fe4302dbea6d2353dc0ab1c").unwrap()).unwrap(), + pubkey: PublicKey::from_slice( + &>::from_hex( + "0324653eac434488002cc06bbfb7f10fe18991e35f9fe4302dbea6d2353dc0ab1c", + ) + .unwrap(), + ) + .unwrap(), node_features: NodeFeatures::empty(), short_channel_id: 0, channel_features: ChannelFeatures::empty(), @@ -1091,10 +1975,15 @@ fn test_trampoline_onion_payload_assembly_values() { cltv_expiry_delta: 24, maybe_announced_channel: false, }, - // Carol RouteHop { - pubkey: PublicKey::from_slice(&>::from_hex("027f31ebc5462c1fdce1b737ecff52d37d75dea43ce11c74d25aa297165faa2007").unwrap()).unwrap(), + pubkey: PublicKey::from_slice( + &>::from_hex( + "027f31ebc5462c1fdce1b737ecff52d37d75dea43ce11c74d25aa297165faa2007", + ) + .unwrap(), + ) + .unwrap(), node_features: NodeFeatures::empty(), short_channel_id: (572330 << 40) + (42 << 16) + 2821, channel_features: ChannelFeatures::empty(), @@ -1107,43 +1996,90 @@ fn test_trampoline_onion_payload_assembly_values() { trampoline_hops: vec![ // Carol's pubkey TrampolineHop { - pubkey: PublicKey::from_slice(&>::from_hex("027f31ebc5462c1fdce1b737ecff52d37d75dea43ce11c74d25aa297165faa2007").unwrap()).unwrap(), + pubkey: PublicKey::from_slice( + &>::from_hex( + "027f31ebc5462c1fdce1b737ecff52d37d75dea43ce11c74d25aa297165faa2007", + ) + .unwrap(), + ) + .unwrap(), node_features: Features::empty(), fee_msat: 2_500, cltv_expiry_delta: 24, }, // Dave's pubkey (the intro node needs to be duplicated) TrampolineHop { - pubkey: PublicKey::from_slice(&>::from_hex("032c0b7cf95324a07d05398b240174dc0c2be444d96b159aa6c7f7b1e668680991").unwrap()).unwrap(), + pubkey: PublicKey::from_slice( + &>::from_hex( + "032c0b7cf95324a07d05398b240174dc0c2be444d96b159aa6c7f7b1e668680991", + ) + .unwrap(), + ) + .unwrap(), node_features: Features::empty(), fee_msat: 150_500, cltv_expiry_delta: 36, - } + }, ], hops: vec![ // Dave's blinded node id BlindedHop { - blinded_node_id: PublicKey::from_slice(&>::from_hex("0295d40514096a8be54859e7dfe947b376eaafea8afe5cb4eb2c13ff857ed0b4be").unwrap()).unwrap(), + blinded_node_id: PublicKey::from_slice( + &>::from_hex( + "0295d40514096a8be54859e7dfe947b376eaafea8afe5cb4eb2c13ff857ed0b4be", + ) + .unwrap(), + ) + .unwrap(), encrypted_payload: vec![], }, // Eve's blinded node id BlindedHop { - blinded_node_id: PublicKey::from_slice(&>::from_hex("020e2dbadcc2005e859819ddebbe88a834ae8a6d2b049233c07335f15cd1dc5f22").unwrap()).unwrap(), + blinded_node_id: PublicKey::from_slice( + &>::from_hex( + "020e2dbadcc2005e859819ddebbe88a834ae8a6d2b049233c07335f15cd1dc5f22", + ) + .unwrap(), + ) + .unwrap(), encrypted_payload: vec![], - } + }, ], - blinding_point: PublicKey::from_slice(&>::from_hex("02988face71e92c345a068f740191fd8e53be14f0bb957ef730d3c5f76087b960e").unwrap()).unwrap(), + blinding_point: PublicKey::from_slice( + &>::from_hex( + "02988face71e92c345a068f740191fd8e53be14f0bb957ef730d3c5f76087b960e", + ) + .unwrap(), + ) + .unwrap(), excess_final_cltv_expiry_delta: 0, - final_value_msat: amt_msat + final_value_msat: amt_msat, }), }; assert_eq!(path.fee_msat(), 156_000); assert_eq!(path.final_value_msat(), amt_msat); assert_eq!(path.final_cltv_expiry_delta(), None); - let payment_secret = PaymentSecret(SecretKey::from_slice(&>::from_hex("7494b65bc092b48a75465e43e29be807eb2cc535ce8aaba31012b8ff1ceac5da").unwrap()).unwrap().secret_bytes()); + let payment_secret = PaymentSecret( + SecretKey::from_slice( + &>::from_hex( + "7494b65bc092b48a75465e43e29be807eb2cc535ce8aaba31012b8ff1ceac5da", + ) + .unwrap(), + ) + .unwrap() + .secret_bytes(), + ); let recipient_onion_fields = RecipientOnionFields::secret_only(payment_secret); - let (trampoline_payloads, outer_total_msat, outer_starting_htlc_offset) = onion_utils::build_trampoline_onion_payloads(&path.blinded_tail.as_ref().unwrap(), amt_msat, &recipient_onion_fields, cur_height, &None).unwrap(); + let (trampoline_payloads, outer_total_msat, outer_starting_htlc_offset) = + onion_utils::build_trampoline_onion_payloads( + &path.blinded_tail.as_ref().unwrap(), + amt_msat, + &recipient_onion_fields, + cur_height, + &None, + ) + .unwrap(); assert_eq!(trampoline_payloads.len(), 3); assert_eq!(outer_total_msat, 150_153_000); assert_eq!(outer_starting_htlc_offset, 800_060); @@ -1151,7 +2087,13 @@ fn test_trampoline_onion_payload_assembly_values() { let trampoline_carol_payload = &trampoline_payloads[0]; let trampoline_dave_payload = &trampoline_payloads[1]; let trampoline_eve_payload = &trampoline_payloads[2]; - if let OutboundTrampolinePayload::BlindedReceive { sender_intended_htlc_amt_msat, total_msat, cltv_expiry_height, .. } = trampoline_eve_payload { + if let OutboundTrampolinePayload::BlindedReceive { + sender_intended_htlc_amt_msat, + total_msat, + cltv_expiry_height, + .. + } = trampoline_eve_payload + { assert_eq!(sender_intended_htlc_amt_msat, &150_000_000); assert_eq!(total_msat, &150_000_000); assert_eq!(cltv_expiry_height, &800_000); @@ -1159,11 +2101,14 @@ fn test_trampoline_onion_payload_assembly_values() { panic!("Eve Trampoline payload must be BlindedReceive"); } - if let OutboundTrampolinePayload::BlindedForward { .. } = trampoline_dave_payload {} else { + if let OutboundTrampolinePayload::BlindedForward { .. } = trampoline_dave_payload { + } else { panic!("Dave Trampoline payload must be BlindedForward"); } - if let OutboundTrampolinePayload::Forward { amt_to_forward, outgoing_cltv_value, .. } = trampoline_carol_payload { + if let OutboundTrampolinePayload::Forward { amt_to_forward, outgoing_cltv_value, .. } = + trampoline_carol_payload + { assert_eq!(amt_to_forward, &150_150_500); assert_eq!(outgoing_cltv_value, &800_036); } else { @@ -1172,33 +2117,56 @@ fn test_trampoline_onion_payload_assembly_values() { // all dummy values let secp_ctx = Secp256k1::new(); - let session_priv = SecretKey::from_slice(&>::from_hex("a64feb81abd58e473df290e9e1c07dc3e56114495cadf33191f44ba5448ebe99").unwrap()).unwrap(); + let session_priv = SecretKey::from_slice( + &>::from_hex("a64feb81abd58e473df290e9e1c07dc3e56114495cadf33191f44ba5448ebe99") + .unwrap(), + ) + .unwrap(); let prng_seed = onion_utils::gen_pad_from_shared_secret(&session_priv.secret_bytes()); let payment_hash = PaymentHash(session_priv.secret_bytes()); - let onion_keys = construct_trampoline_onion_keys(&secp_ctx, &path.blinded_tail.as_ref().unwrap(), &session_priv); + let onion_keys = construct_trampoline_onion_keys( + &secp_ctx, + &path.blinded_tail.as_ref().unwrap(), + &session_priv, + ); let trampoline_packet = construct_trampoline_onion_packet( trampoline_payloads, onion_keys, prng_seed, &payment_hash, None, - ).unwrap(); + ) + .unwrap(); - let (outer_payloads, total_msat, total_htlc_offset) = onion_utils::build_onion_payloads(&path, outer_total_msat, &recipient_onion_fields, outer_starting_htlc_offset, &None, None, Some(trampoline_packet)).unwrap(); + let (outer_payloads, total_msat, total_htlc_offset) = onion_utils::build_onion_payloads( + &path, + outer_total_msat, + &recipient_onion_fields, + outer_starting_htlc_offset, + &None, + None, + Some(trampoline_packet), + ) + .unwrap(); assert_eq!(outer_payloads.len(), 2); assert_eq!(total_msat, 150_156_000); assert_eq!(total_htlc_offset, 800_084); let outer_bob_payload = &outer_payloads[0]; let outer_carol_payload = &outer_payloads[1]; - if let OutboundOnionPayload::TrampolineEntrypoint { amt_to_forward, outgoing_cltv_value, .. } = outer_carol_payload { + if let OutboundOnionPayload::TrampolineEntrypoint { + amt_to_forward, outgoing_cltv_value, .. + } = outer_carol_payload + { assert_eq!(amt_to_forward, &150_153_000); assert_eq!(outgoing_cltv_value, &800_060); } else { panic!("Carol payload must be TrampolineEntrypoint"); } - if let OutboundOnionPayload::Forward { amt_to_forward, outgoing_cltv_value, .. } = outer_bob_payload { + if let OutboundOnionPayload::Forward { amt_to_forward, outgoing_cltv_value, .. } = + outer_bob_payload + { assert_eq!(amt_to_forward, &150_153_000); assert_eq!(outgoing_cltv_value, &800_084); } else { @@ -1216,7 +2184,8 @@ fn test_trampoline_onion_payload_assembly_values() { &None, None, prng_seed, - ).unwrap(); + ) + .unwrap(); assert_eq!(total_msat_combined, total_msat); assert_eq!(total_htlc_offset_combined, total_htlc_offset); } @@ -1228,7 +2197,13 @@ fn test_trampoline_onion_payload_construction_vectors() { let trampoline_payload_carol = OutboundTrampolinePayload::Forward { amt_to_forward: 150_150_500, outgoing_cltv_value: 800_036, - outgoing_node_id: PublicKey::from_slice(&>::from_hex("032c0b7cf95324a07d05398b240174dc0c2be444d96b159aa6c7f7b1e668680991").unwrap()).unwrap(), + outgoing_node_id: PublicKey::from_slice( + &>::from_hex( + "032c0b7cf95324a07d05398b240174dc0c2be444d96b159aa6c7f7b1e668680991", + ) + .unwrap(), + ) + .unwrap(), }; let carol_payload = trampoline_payload_carol.encode().to_lower_hex_string(); assert_eq!(carol_payload, "2e020408f31d6404030c35240e21032c0b7cf95324a07d05398b240174dc0c2be444d96b159aa6c7f7b1e668680991"); @@ -1252,10 +2227,19 @@ fn test_trampoline_onion_payload_construction_vectors() { let eve_payload = trampoline_payload_eve.encode().to_lower_hex_string(); assert_eq!(eve_payload, "e4020408f0d18004030c35000ad1bcd747394fbd4d99588da075a623316e15a576df5bc785cccc7cd6ec7b398acce6faf520175f9ec920f2ef261cdb83dc28cc3a0eeb970107b3306489bf771ef5b1213bca811d345285405861d08a655b6c237fa247a8b4491beee20c878a60e9816492026d8feb9dafa84585b253978db6a0aa2945df5ef445c61e801fb82f43d5f00716baf9fc9b3de50bc22950a36bda8fc27bfb1242e5860c7e687438d4133e058770361a19b6c271a2a07788d34dccc27e39b9829b061a4d960eac4a2c2b0f4de506c24f9af3868c0aff6dda27281c120408f0d180"); - let trampoline_payloads = vec![trampoline_payload_carol, trampoline_payload_dave, trampoline_payload_eve]; - - let trampoline_session_key = SecretKey::from_slice(&>::from_hex("a64feb81abd58e473df290e9e1c07dc3e56114495cadf33191f44ba5448ebe99").unwrap()).unwrap(); - let associated_data_slice = SecretKey::from_slice(&>::from_hex("e89bc505e84aaca09613833fc58c9069078fb43bfbea0488f34eec9db99b5f82").unwrap()).unwrap(); + let trampoline_payloads = + vec![trampoline_payload_carol, trampoline_payload_dave, trampoline_payload_eve]; + + let trampoline_session_key = SecretKey::from_slice( + &>::from_hex("a64feb81abd58e473df290e9e1c07dc3e56114495cadf33191f44ba5448ebe99") + .unwrap(), + ) + .unwrap(); + let associated_data_slice = SecretKey::from_slice( + &>::from_hex("e89bc505e84aaca09613833fc58c9069078fb43bfbea0488f34eec9db99b5f82") + .unwrap(), + ) + .unwrap(); let associated_data = PaymentHash(associated_data_slice.secret_bytes()); let trampoline_hops = Path { @@ -1264,39 +2248,80 @@ fn test_trampoline_onion_payload_construction_vectors() { trampoline_hops: vec![ // Carol's pubkey TrampolineHop { - pubkey: PublicKey::from_slice(&>::from_hex("027f31ebc5462c1fdce1b737ecff52d37d75dea43ce11c74d25aa297165faa2007").unwrap()).unwrap(), + pubkey: PublicKey::from_slice( + &>::from_hex( + "027f31ebc5462c1fdce1b737ecff52d37d75dea43ce11c74d25aa297165faa2007", + ) + .unwrap(), + ) + .unwrap(), node_features: Features::empty(), fee_msat: 0, cltv_expiry_delta: 0, }, // Dave's pubkey (the intro node needs to be duplicated) TrampolineHop { - pubkey: PublicKey::from_slice(&>::from_hex("032c0b7cf95324a07d05398b240174dc0c2be444d96b159aa6c7f7b1e668680991").unwrap()).unwrap(), + pubkey: PublicKey::from_slice( + &>::from_hex( + "032c0b7cf95324a07d05398b240174dc0c2be444d96b159aa6c7f7b1e668680991", + ) + .unwrap(), + ) + .unwrap(), node_features: Features::empty(), fee_msat: 0, cltv_expiry_delta: 0, - } + }, ], hops: vec![ // Dave's blinded node id BlindedHop { - blinded_node_id: PublicKey::from_slice(&>::from_hex("0295d40514096a8be54859e7dfe947b376eaafea8afe5cb4eb2c13ff857ed0b4be").unwrap()).unwrap(), + blinded_node_id: PublicKey::from_slice( + &>::from_hex( + "0295d40514096a8be54859e7dfe947b376eaafea8afe5cb4eb2c13ff857ed0b4be", + ) + .unwrap(), + ) + .unwrap(), encrypted_payload: vec![], }, // Eve's blinded node id BlindedHop { - blinded_node_id: PublicKey::from_slice(&>::from_hex("020e2dbadcc2005e859819ddebbe88a834ae8a6d2b049233c07335f15cd1dc5f22").unwrap()).unwrap(), + blinded_node_id: PublicKey::from_slice( + &>::from_hex( + "020e2dbadcc2005e859819ddebbe88a834ae8a6d2b049233c07335f15cd1dc5f22", + ) + .unwrap(), + ) + .unwrap(), encrypted_payload: vec![], - } + }, ], - blinding_point: PublicKey::from_slice(&>::from_hex("02988face71e92c345a068f740191fd8e53be14f0bb957ef730d3c5f76087b960e").unwrap()).unwrap(), + blinding_point: PublicKey::from_slice( + &>::from_hex( + "02988face71e92c345a068f740191fd8e53be14f0bb957ef730d3c5f76087b960e", + ) + .unwrap(), + ) + .unwrap(), excess_final_cltv_expiry_delta: 0, - final_value_msat: 0 + final_value_msat: 0, }), }; - let trampoline_onion_keys = construct_trampoline_onion_keys(&Secp256k1::new(), &trampoline_hops.blinded_tail.unwrap(), &trampoline_session_key); - let trampoline_onion_packet = construct_trampoline_onion_packet(trampoline_payloads, trampoline_onion_keys, [0u8; 32], &associated_data, None).unwrap(); + let trampoline_onion_keys = construct_trampoline_onion_keys( + &Secp256k1::new(), + &trampoline_hops.blinded_tail.unwrap(), + &trampoline_session_key, + ); + let trampoline_onion_packet = construct_trampoline_onion_packet( + trampoline_payloads, + trampoline_onion_keys, + [0u8; 32], + &associated_data, + None, + ) + .unwrap(); let trampoline_onion_packet_hex = trampoline_onion_packet.encode().to_lower_hex_string(); assert_eq!(trampoline_onion_packet_hex, "0002bc59a9abc893d75a8d4f56a6572f9a3507323a8de22abe0496ea8d37da166a8b4bba0e560f1a9deb602bfd98fe9167141d0b61d669df90c0149096d505b85d3d02806e6c12caeb308b878b6bc7f1b15839c038a6443cd3bec3a94c2293165375555f6d7720862b525930f41fddcc02260d197abd93fb58e60835fd97d9dc14e7979c12f59df08517b02e3e4d50e1817de4271df66d522c4e9675df71c635c4176a8381bc22b342ff4e9031cede87f74cc039fca74aa0a3786bc1db2e158a9a520ecb99667ef9a6bbfaf5f0e06f81c27ca48134ba2103229145937c5dc7b8ecc5201d6aeb592e78faa3c05d3a035df77628f0be9b1af3ef7d386dd5cc87b20778f47ebd40dbfcf12b9071c5d7112ab84c3e0c5c14867e684d09a18bc93ac47d73b7343e3403ef6e3b70366835988920e7d772c3719d3596e53c29c4017cb6938421a557ce81b4bb26701c25bf622d4c69f1359dc85857a375c5c74987a4d3152f66987001c68a50c4bf9e0b1dab4ad1a64b0535319bbf6c4fbe4f9c50cb65f5ef887bfb91b0a57c0f86ba3d91cbeea1607fb0c12c6c75d03bbb0d3a3019c40597027f5eebca23083e50ec79d41b1152131853525bf3fc13fb0be62c2e3ce733f59671eee5c4064863fb92ae74be9ca68b9c716f9519fd268478ee27d91d466b0de51404de3226b74217d28250ead9d2c95411e0230570f547d4cc7c1d589791623131aa73965dccc5aa17ec12b442215ce5d346df664d799190df5dd04a13"); @@ -1307,24 +2332,38 @@ fn test_trampoline_onion_payload_construction_vectors() { amt_to_forward: 150153000, outgoing_cltv_value: 800060, }, - // Carol OutboundOnionPayload::TrampolineEntrypoint { amt_to_forward: 150153000, outgoing_cltv_value: 800060, trampoline_packet: trampoline_onion_packet, - multipath_trampoline_data: Some(FinalOnionHopData{ - payment_secret: PaymentSecret(SecretKey::from_slice(&>::from_hex("7494b65bc092b48a75465e43e29be807eb2cc535ce8aaba31012b8ff1ceac5da").unwrap()).unwrap().secret_bytes()), - total_msat: 150153000 + multipath_trampoline_data: Some(FinalOnionHopData { + payment_secret: PaymentSecret( + SecretKey::from_slice( + &>::from_hex( + "7494b65bc092b48a75465e43e29be807eb2cc535ce8aaba31012b8ff1ceac5da", + ) + .unwrap(), + ) + .unwrap() + .secret_bytes(), + ), + total_msat: 150153000, }), - } + }, ]; let outer_hops = Path { hops: vec![ // Bob RouteHop { - pubkey: PublicKey::from_slice(&>::from_hex("0324653eac434488002cc06bbfb7f10fe18991e35f9fe4302dbea6d2353dc0ab1c").unwrap()).unwrap(), + pubkey: PublicKey::from_slice( + &>::from_hex( + "0324653eac434488002cc06bbfb7f10fe18991e35f9fe4302dbea6d2353dc0ab1c", + ) + .unwrap(), + ) + .unwrap(), node_features: NodeFeatures::empty(), short_channel_id: 0, channel_features: ChannelFeatures::empty(), @@ -1332,10 +2371,15 @@ fn test_trampoline_onion_payload_construction_vectors() { cltv_expiry_delta: 0, maybe_announced_channel: false, }, - // Carol RouteHop { - pubkey: PublicKey::from_slice(&>::from_hex("027f31ebc5462c1fdce1b737ecff52d37d75dea43ce11c74d25aa297165faa2007").unwrap()).unwrap(), + pubkey: PublicKey::from_slice( + &>::from_hex( + "027f31ebc5462c1fdce1b737ecff52d37d75dea43ce11c74d25aa297165faa2007", + ) + .unwrap(), + ) + .unwrap(), node_features: NodeFeatures::empty(), short_channel_id: 0, channel_features: ChannelFeatures::empty(), @@ -1353,16 +2397,27 @@ fn test_trampoline_onion_payload_construction_vectors() { let carol_payload = outer_payloads[1].encode().to_lower_hex_string(); assert_eq!(carol_payload, "fd0255020408f3272804030c353c08247494b65bc092b48a75465e43e29be807eb2cc535ce8aaba31012b8ff1ceac5da08f3272814fd02200002bc59a9abc893d75a8d4f56a6572f9a3507323a8de22abe0496ea8d37da166a8b4bba0e560f1a9deb602bfd98fe9167141d0b61d669df90c0149096d505b85d3d02806e6c12caeb308b878b6bc7f1b15839c038a6443cd3bec3a94c2293165375555f6d7720862b525930f41fddcc02260d197abd93fb58e60835fd97d9dc14e7979c12f59df08517b02e3e4d50e1817de4271df66d522c4e9675df71c635c4176a8381bc22b342ff4e9031cede87f74cc039fca74aa0a3786bc1db2e158a9a520ecb99667ef9a6bbfaf5f0e06f81c27ca48134ba2103229145937c5dc7b8ecc5201d6aeb592e78faa3c05d3a035df77628f0be9b1af3ef7d386dd5cc87b20778f47ebd40dbfcf12b9071c5d7112ab84c3e0c5c14867e684d09a18bc93ac47d73b7343e3403ef6e3b70366835988920e7d772c3719d3596e53c29c4017cb6938421a557ce81b4bb26701c25bf622d4c69f1359dc85857a375c5c74987a4d3152f66987001c68a50c4bf9e0b1dab4ad1a64b0535319bbf6c4fbe4f9c50cb65f5ef887bfb91b0a57c0f86ba3d91cbeea1607fb0c12c6c75d03bbb0d3a3019c40597027f5eebca23083e50ec79d41b1152131853525bf3fc13fb0be62c2e3ce733f59671eee5c4064863fb92ae74be9ca68b9c716f9519fd268478ee27d91d466b0de51404de3226b74217d28250ead9d2c95411e0230570f547d4cc7c1d589791623131aa73965dccc5aa17ec12b442215ce5d346df664d799190df5dd04a13"); - let outer_session_key = SecretKey::from_slice(&>::from_hex("4f777e8dac16e6dfe333066d9efb014f7a51d11762ff76eca4d3a95ada99ba3e").unwrap()).unwrap(); - let outer_onion_keys = onion_utils::construct_onion_keys(&Secp256k1::new(), &outer_hops, &outer_session_key); - let outer_onion_prng_seed = onion_utils::gen_pad_from_shared_secret(&outer_session_key.secret_bytes()); - let outer_onion_packet = onion_utils::construct_onion_packet(outer_payloads, outer_onion_keys, outer_onion_prng_seed, &associated_data).unwrap(); + let outer_session_key = SecretKey::from_slice( + &>::from_hex("4f777e8dac16e6dfe333066d9efb014f7a51d11762ff76eca4d3a95ada99ba3e") + .unwrap(), + ) + .unwrap(); + let outer_onion_keys = + onion_utils::construct_onion_keys(&Secp256k1::new(), &outer_hops, &outer_session_key); + let outer_onion_prng_seed = + onion_utils::gen_pad_from_shared_secret(&outer_session_key.secret_bytes()); + let outer_onion_packet = onion_utils::construct_onion_packet( + outer_payloads, + outer_onion_keys, + outer_onion_prng_seed, + &associated_data, + ) + .unwrap(); let outer_onion_packet_hex = outer_onion_packet.encode().to_lower_hex_string(); assert_eq!(outer_onion_packet_hex, "00025fd60556c134ae97e4baedba220a644037754ee67c54fd05e93bf40c17cbb73362fb9dee96001ff229945595b6edb59437a6bc143406d3f90f749892a84d8d430c6890437d26d5bfc599d565316ef51347521075bbab87c59c57bcf20af7e63d7192b46cf171e4f73cb11f9f603915389105d91ad630224bea95d735e3988add1e24b5bf28f1d7128db64284d90a839ba340d088c74b1fb1bd21136b1809428ec5399c8649e9bdf92d2dcfc694deae5046fa5b2bdf646847aaad73f5e95275763091c90e71031cae1f9a770fdea559642c9c02f424a2a28163dd0957e3874bd28a97bec67d18c0321b0e68bc804aa8345b17cb626e2348ca06c8312a167c989521056b0f25c55559d446507d6c491d50605cb79fa87929ce64b0a9860926eeaec2c431d926a1cadb9a1186e4061cb01671a122fc1f57602cbef06d6c194ec4b715c2e3dd4120baca3172cd81900b49fef857fb6d6afd24c983b608108b0a5ac0c1c6c52011f23b8778059ffadd1bb7cd06e2525417365f485a7fd1d4a9ba3818ede7cdc9e71afee8532252d08e2531ca52538655b7e8d912f7ec6d37bbcce8d7ec690709dbf9321e92c565b78e7fe2c22edf23e0902153d1ca15a112ad32fb19695ec65ce11ddf670da7915f05ad4b86c154fb908cb567315d1124f303f75fa075ebde8ef7bb12e27737ad9e4924439097338ea6d7a6fc3721b88c9b830a34e8d55f4c582b74a3895cc848fe57f4fe29f115dabeb6b3175be15d94408ed6771109cfaf57067ae658201082eae7605d26b1449af4425ae8e8f58cdda5c6265f1fd7a386fc6cea3074e4f25b909b96175883676f7610a00fdf34df9eb6c7b9a4ae89b839c69fd1f285e38cdceb634d782cc6d81179759bc9fd47d7fd060470d0b048287764c6837963274e708314f017ac7dc26d0554d59bfcfd3136225798f65f0b0fea337c6b256ebbb63a90b994c0ab93fd8b1d6bd4c74aebe535d6110014cd3d525394027dfe8faa98b4e9b2bee7949eb1961f1b026791092f84deea63afab66603dbe9b6365a102a1fef2f6b9744bc1bb091a8da9130d34d4d39f25dbad191649cfb67e10246364b7ce0c6ec072f9690cabb459d9fda0c849e17535de4357e9907270c75953fca3c845bb613926ecf73205219c7057a4b6bb244c184362bb4e2f24279dc4e60b94a5b1ec11c34081a628428ba5646c995b9558821053ba9c84a05afbf00dabd60223723096516d2f5668f3ec7e11612b01eb7a3a0506189a2272b88e89807943adb34291a17f6cb5516ffd6f945a1c42a524b21f096d66f350b1dad4db455741ae3d0e023309fbda5ef55fb0dc74f3297041448b2be76c525141963934c6afc53d263fb7836626df502d7c2ee9e79cbbd87afd84bbb8dfbf45248af3cd61ad5fac827e7683ca4f91dfad507a8eb9c17b2c9ac5ec051fe645a4a6cb37136f6f19b611e0ea8da7960af2d779507e55f57305bc74b7568928c5dd5132990fe54c22117df91c257d8c7b61935a018a28c1c3b17bab8e4294fa699161ec21123c9fc4e71079df31f300c2822e1246561e04765d3aab333eafd026c7431ac7616debb0e022746f4538e1c6348b600c988eeb2d051fc60c468dca260a84c79ab3ab8342dc345a764672848ea234e17332bc124799daf7c5fcb2e2358514a7461357e1c19c802c5ee32deccf1776885dd825bedd5f781d459984370a6b7ae885d4483a76ddb19b30f47ed47cd56aa5a079a89793dbcad461c59f2e002067ac98dd5a534e525c9c46c2af730741bf1f8629357ec0bfc0bc9ecb31af96777e507648ff4260dc3673716e098d9111dfd245f1d7c55a6de340deb8bd7a053e5d62d760f184dc70ca8fa255b9023b9b9aedfb6e419a5b5951ba0f83b603793830ee68d442d7b88ee1bbf6bbd1bcd6f68cc1af"); } fn do_test_fail_htlc_backwards_with_reason(failure_code: FailureCode) { - let chanmon_cfgs = create_chanmon_cfgs(2); let node_cfgs = create_node_cfgs(2, &chanmon_cfgs); let node_chanmgrs = create_node_chanmgrs(2, &node_cfgs, &[None, None]); @@ -1371,9 +2426,17 @@ fn do_test_fail_htlc_backwards_with_reason(failure_code: FailureCode) { create_announced_chan_between_nodes(&nodes, 0, 1); let payment_amount = 100_000; - let (route, payment_hash, _, payment_secret) = get_route_and_payment_hash!(nodes[0], nodes[1], payment_amount); - nodes[0].node.send_payment_with_route(route, payment_hash, - RecipientOnionFields::secret_only(payment_secret), PaymentId(payment_hash.0)).unwrap(); + let (route, payment_hash, _, payment_secret) = + get_route_and_payment_hash!(nodes[0], nodes[1], payment_amount); + nodes[0] + .node + .send_payment_with_route( + route, + payment_hash, + RecipientOnionFields::secret_only(payment_secret), + PaymentId(payment_hash.0), + ) + .unwrap(); check_added_monitors!(nodes[0], 1); let mut events = nodes[0].node.get_and_clear_pending_msg_events(); @@ -1385,13 +2448,28 @@ fn do_test_fail_htlc_backwards_with_reason(failure_code: FailureCode) { expect_payment_claimable!(nodes[1], payment_hash, payment_secret, payment_amount); nodes[1].node.fail_htlc_backwards_with_reason(&payment_hash, failure_code); - expect_pending_htlcs_forwardable_and_htlc_handling_failed!(nodes[1], vec![HTLCHandlingFailureType::Receive { payment_hash: payment_hash }]); + expect_pending_htlcs_forwardable_and_htlc_handling_failed!( + nodes[1], + vec![HTLCHandlingFailureType::Receive { payment_hash }] + ); check_added_monitors!(nodes[1], 1); let events = nodes[1].node.get_and_clear_pending_msg_events(); assert_eq!(events.len(), 1); let (update_fail_htlc, commitment_signed) = match events[0] { - MessageSendEvent::UpdateHTLCs { node_id: _, channel_id: _, updates: msgs::CommitmentUpdate { ref update_add_htlcs, ref update_fulfill_htlcs, ref update_fail_htlcs, ref update_fail_malformed_htlcs, ref update_fee, ref commitment_signed } } => { + MessageSendEvent::UpdateHTLCs { + node_id: _, + channel_id: _, + updates: + msgs::CommitmentUpdate { + ref update_add_htlcs, + ref update_fulfill_htlcs, + ref update_fail_htlcs, + ref update_fail_malformed_htlcs, + ref update_fee, + ref commitment_signed, + }, + } => { assert!(update_add_htlcs.is_empty()); assert!(update_fulfill_htlcs.is_empty()); assert_eq!(update_fail_htlcs.len(), 1); @@ -1413,17 +2491,20 @@ fn do_test_fail_htlc_backwards_with_reason(failure_code: FailureCode) { htlc_msat_height_data.extend_from_slice(&CHAN_CONFIRM_DEPTH.to_be_bytes()); htlc_msat_height_data }, - FailureCode::InvalidOnionPayload(data) => { - match data { - Some((typ, offset)) => [BigSize(typ).encode(), offset.encode()].concat(), - None => Vec::new(), - } - } + FailureCode::InvalidOnionPayload(data) => match data { + Some((typ, offset)) => [BigSize(typ).encode(), offset.encode()].concat(), + None => Vec::new(), + }, }; let failure_code = failure_code.into(); - expect_payment_failed!(nodes[0], payment_hash, failure_code.is_permanent(), failure_code, failure_data); - + expect_payment_failed!( + nodes[0], + payment_hash, + failure_code.is_permanent(), + failure_code, + failure_data + ); } #[test] @@ -1440,41 +2521,49 @@ macro_rules! get_phantom_route { let phantom_pubkey = $nodes[1].keys_manager.get_node_id(Recipient::PhantomNode).unwrap(); let phantom_route_hint = $nodes[1].node.get_phantom_route_hints(); let payment_params = PaymentParameters::from_node_id(phantom_pubkey, TEST_FINAL_CLTV) - .with_bolt11_features($nodes[1].node.bolt11_invoice_features()).unwrap() + .with_bolt11_features($nodes[1].node.bolt11_invoice_features()) + .unwrap() .with_route_hints(vec![RouteHint(vec![ - RouteHintHop { - src_node_id: $nodes[0].node.get_our_node_id(), - short_channel_id: $channel.0.contents.short_channel_id, - fees: RoutingFees { - base_msat: $channel.0.contents.fee_base_msat, - proportional_millionths: $channel.0.contents.fee_proportional_millionths, - }, - cltv_expiry_delta: $channel.0.contents.cltv_expiry_delta, - htlc_minimum_msat: None, - htlc_maximum_msat: None, + RouteHintHop { + src_node_id: $nodes[0].node.get_our_node_id(), + short_channel_id: $channel.0.contents.short_channel_id, + fees: RoutingFees { + base_msat: $channel.0.contents.fee_base_msat, + proportional_millionths: $channel.0.contents.fee_proportional_millionths, }, - RouteHintHop { - src_node_id: phantom_route_hint.real_node_pubkey, - short_channel_id: phantom_route_hint.phantom_scid, - fees: RoutingFees { - base_msat: 0, - proportional_millionths: 0, - }, - cltv_expiry_delta: MIN_CLTV_EXPIRY_DELTA, - htlc_minimum_msat: None, - htlc_maximum_msat: None, - } - ])]).unwrap(); + cltv_expiry_delta: $channel.0.contents.cltv_expiry_delta, + htlc_minimum_msat: None, + htlc_maximum_msat: None, + }, + RouteHintHop { + src_node_id: phantom_route_hint.real_node_pubkey, + short_channel_id: phantom_route_hint.phantom_scid, + fees: RoutingFees { base_msat: 0, proportional_millionths: 0 }, + cltv_expiry_delta: MIN_CLTV_EXPIRY_DELTA, + htlc_minimum_msat: None, + htlc_maximum_msat: None, + }, + ])]) + .unwrap(); let scorer = test_utils::TestScorer::new(); let network_graph = $nodes[0].network_graph.read_only(); let route_params = RouteParameters::from_payment_params_and_value(payment_params, $amt); - (get_route( - &$nodes[0].node.get_our_node_id(), &route_params, &network_graph, - Some(&$nodes[0].node.list_usable_channels().iter().collect::>()), - $nodes[0].logger, &scorer, &Default::default(), &[0u8; 32] - ).unwrap(), phantom_route_hint.phantom_scid) - } -}} + ( + get_route( + &$nodes[0].node.get_our_node_id(), + &route_params, + &network_graph, + Some(&$nodes[0].node.list_usable_channels().iter().collect::>()), + $nodes[0].logger, + &scorer, + &Default::default(), + &[0u8; 32], + ) + .unwrap(), + phantom_route_hint.phantom_scid, + ) + }}; +} #[test] fn test_phantom_onion_hmac_failure() { @@ -1487,12 +2576,20 @@ fn test_phantom_onion_hmac_failure() { // Get the route. let recv_value_msat = 10_000; - let (_, payment_hash, payment_secret) = get_payment_preimage_hash!(nodes[1], Some(recv_value_msat)); + let (_, payment_hash, payment_secret) = + get_payment_preimage_hash!(nodes[1], Some(recv_value_msat)); let (route, phantom_scid) = get_phantom_route!(nodes, recv_value_msat, channel); // Route the HTLC through to the destination. - nodes[0].node.send_payment_with_route(route, payment_hash, - RecipientOnionFields::secret_only(payment_secret), PaymentId(payment_hash.0)).unwrap(); + nodes[0] + .node + .send_payment_with_route( + route, + payment_hash, + RecipientOnionFields::secret_only(payment_secret), + PaymentId(payment_hash.0), + ) + .unwrap(); check_added_monitors!(nodes[0], 1); let update_0 = get_htlc_update_msgs!(nodes[0], nodes[1].node.get_our_node_id()); let mut update_add = update_0.update_add_htlcs[0].clone(); @@ -1508,10 +2605,12 @@ fn test_phantom_onion_hmac_failure() { let mut pending_forward = forward_htlcs.get_mut(&phantom_scid).unwrap(); match pending_forward[0] { HTLCForwardInfo::AddHTLC(PendingAddHTLCInfo { - forward_info: PendingHTLCInfo { - routing: PendingHTLCRouting::Forward { ref mut onion_packet, .. }, - .. - }, .. + forward_info: + PendingHTLCInfo { + routing: PendingHTLCRouting::Forward { ref mut onion_packet, .. }, + .. + }, + .. }) => { onion_packet.hmac[onion_packet.hmac.len() - 1] ^= 1; Sha256::hash(&onion_packet.hop_data).to_byte_array().to_vec() @@ -1520,7 +2619,10 @@ fn test_phantom_onion_hmac_failure() { } }; nodes[1].node.process_pending_htlc_forwards(); - expect_pending_htlcs_forwardable_and_htlc_handling_failed_ignore!(nodes[1], vec![HTLCHandlingFailureType::Receive { payment_hash }]); + expect_pending_htlcs_forwardable_and_htlc_handling_failed_ignore!( + nodes[1], + vec![HTLCHandlingFailureType::Receive { payment_hash }] + ); nodes[1].node.process_pending_htlc_forwards(); let update_1 = get_htlc_update_msgs!(nodes[1], nodes[0].node.get_our_node_id()); check_added_monitors!(&nodes[1], 1); @@ -1548,14 +2650,22 @@ fn test_phantom_invalid_onion_payload() { // Get the route. let recv_value_msat = 10_000; - let (_, payment_hash, payment_secret) = get_payment_preimage_hash!(nodes[1], Some(recv_value_msat)); + let (_, payment_hash, payment_secret) = + get_payment_preimage_hash!(nodes[1], Some(recv_value_msat)); let (route, phantom_scid) = get_phantom_route!(nodes, recv_value_msat, channel); // We'll use the session priv later when constructing an invalid onion packet. let session_priv = [3; 32]; *nodes[0].keys_manager.override_random_bytes.lock().unwrap() = Some(session_priv); - nodes[0].node.send_payment_with_route(route.clone(), payment_hash, - RecipientOnionFields::secret_only(payment_secret), PaymentId(payment_hash.0)).unwrap(); + nodes[0] + .node + .send_payment_with_route( + route.clone(), + payment_hash, + RecipientOnionFields::secret_only(payment_secret), + PaymentId(payment_hash.0), + ) + .unwrap(); check_added_monitors!(nodes[0], 1); let update_0 = get_htlc_update_msgs!(nodes[0], nodes[1].node.get_our_node_id()); let mut update_add = update_0.update_add_htlcs[0].clone(); @@ -1570,25 +2680,44 @@ fn test_phantom_invalid_onion_payload() { for f in pending_forwards.iter_mut() { match f { &mut HTLCForwardInfo::AddHTLC(PendingAddHTLCInfo { - forward_info: PendingHTLCInfo { - routing: PendingHTLCRouting::Forward { ref mut onion_packet, .. }, - .. - }, .. + forward_info: + PendingHTLCInfo { + routing: PendingHTLCRouting::Forward { ref mut onion_packet, .. }, + .. + }, + .. }) => { // Construct the onion payloads for the entire route and an invalid amount. let height = nodes[0].best_block_info().1; let session_priv = SecretKey::from_slice(&session_priv).unwrap(); - let mut onion_keys = onion_utils::construct_onion_keys(&Secp256k1::new(), &route.paths[0], &session_priv); + let mut onion_keys = onion_utils::construct_onion_keys( + &Secp256k1::new(), + &route.paths[0], + &session_priv, + ); let recipient_onion_fields = RecipientOnionFields::secret_only(payment_secret); let (mut onion_payloads, _, _) = onion_utils::build_onion_payloads( - &route.paths[0], msgs::MAX_VALUE_MSAT + 1, - &recipient_onion_fields, height + 1, &None, None, None).unwrap(); + &route.paths[0], + msgs::MAX_VALUE_MSAT + 1, + &recipient_onion_fields, + height + 1, + &None, + None, + None, + ) + .unwrap(); // We only want to construct the onion packet for the last hop, not the entire route, so // remove the first hop's payload and its keys. onion_keys.remove(0); onion_payloads.remove(0); - let new_onion_packet = onion_utils::construct_onion_packet(onion_payloads, onion_keys, [0; 32], &payment_hash).unwrap(); + let new_onion_packet = onion_utils::construct_onion_packet( + onion_payloads, + onion_keys, + [0; 32], + &payment_hash, + ) + .unwrap(); onion_packet.hop_data = new_onion_packet.hop_data; onion_packet.hmac = new_onion_packet.hmac; }, @@ -1597,7 +2726,10 @@ fn test_phantom_invalid_onion_payload() { } } nodes[1].node.process_pending_htlc_forwards(); - expect_pending_htlcs_forwardable_and_htlc_handling_failed_ignore!(nodes[1], vec![HTLCHandlingFailureType::Receive { payment_hash }]); + expect_pending_htlcs_forwardable_and_htlc_handling_failed_ignore!( + nodes[1], + vec![HTLCHandlingFailureType::Receive { payment_hash }] + ); nodes[1].node.process_pending_htlc_forwards(); let update_1 = get_htlc_update_msgs!(nodes[1], nodes[0].node.get_our_node_id()); check_added_monitors!(&nodes[1], 1); @@ -1626,12 +2758,20 @@ fn test_phantom_final_incorrect_cltv_expiry() { // Get the route. let recv_value_msat = 10_000; - let (_, payment_hash, payment_secret) = get_payment_preimage_hash!(nodes[1], Some(recv_value_msat)); + let (_, payment_hash, payment_secret) = + get_payment_preimage_hash!(nodes[1], Some(recv_value_msat)); let (route, phantom_scid) = get_phantom_route!(nodes, recv_value_msat, channel); // Route the HTLC through to the destination. - nodes[0].node.send_payment_with_route(route, payment_hash, - RecipientOnionFields::secret_only(payment_secret), PaymentId(payment_hash.0)).unwrap(); + nodes[0] + .node + .send_payment_with_route( + route, + payment_hash, + RecipientOnionFields::secret_only(payment_secret), + PaymentId(payment_hash.0), + ) + .unwrap(); check_added_monitors!(nodes[0], 1); let update_0 = get_htlc_update_msgs!(nodes[0], nodes[1].node.get_our_node_id()); let mut update_add = update_0.update_add_htlcs[0].clone(); @@ -1646,7 +2786,8 @@ fn test_phantom_final_incorrect_cltv_expiry() { for f in pending_forwards.iter_mut() { match f { &mut HTLCForwardInfo::AddHTLC(PendingAddHTLCInfo { - forward_info: PendingHTLCInfo { ref mut outgoing_cltv_value, .. }, .. + forward_info: PendingHTLCInfo { ref mut outgoing_cltv_value, .. }, + .. }) => { *outgoing_cltv_value -= 1; }, @@ -1655,7 +2796,10 @@ fn test_phantom_final_incorrect_cltv_expiry() { } } nodes[1].node.process_pending_htlc_forwards(); - expect_pending_htlcs_forwardable_and_htlc_handling_failed_ignore!(nodes[1], vec![HTLCHandlingFailureType::Receive { payment_hash }]); + expect_pending_htlcs_forwardable_and_htlc_handling_failed_ignore!( + nodes[1], + vec![HTLCHandlingFailureType::Receive { payment_hash }] + ); nodes[1].node.process_pending_htlc_forwards(); let update_1 = get_htlc_update_msgs!(nodes[1], nodes[0].node.get_our_node_id()); check_added_monitors!(&nodes[1], 1); @@ -1684,15 +2828,23 @@ fn test_phantom_failure_too_low_cltv() { // Get the route. let recv_value_msat = 10_000; - let (_, payment_hash, payment_secret) = get_payment_preimage_hash!(nodes[1], Some(recv_value_msat)); + let (_, payment_hash, payment_secret) = + get_payment_preimage_hash!(nodes[1], Some(recv_value_msat)); let (mut route, phantom_scid) = get_phantom_route!(nodes, recv_value_msat, channel); // Modify the route to have a too-low cltv. route.paths[0].hops[1].cltv_expiry_delta = 5; // Route the HTLC through to the destination. - nodes[0].node.send_payment_with_route(route, payment_hash, - RecipientOnionFields::secret_only(payment_secret), PaymentId(payment_hash.0)).unwrap(); + nodes[0] + .node + .send_payment_with_route( + route, + payment_hash, + RecipientOnionFields::secret_only(payment_secret), + PaymentId(payment_hash.0), + ) + .unwrap(); check_added_monitors!(nodes[0], 1); let update_0 = get_htlc_update_msgs!(nodes[0], nodes[1].node.get_our_node_id()); let mut update_add = update_0.update_add_htlcs[0].clone(); @@ -1702,7 +2854,10 @@ fn test_phantom_failure_too_low_cltv() { expect_pending_htlcs_forwardable_ignore!(nodes[1]); nodes[1].node.process_pending_htlc_forwards(); - expect_pending_htlcs_forwardable_and_htlc_handling_failed_ignore!(nodes[1], vec![HTLCHandlingFailureType::Receive { payment_hash }]); + expect_pending_htlcs_forwardable_and_htlc_handling_failed_ignore!( + nodes[1], + vec![HTLCHandlingFailureType::Receive { payment_hash }] + ); nodes[1].node.process_pending_htlc_forwards(); let update_1 = get_htlc_update_msgs!(nodes[1], nodes[0].node.get_our_node_id()); check_added_monitors!(&nodes[1], 1); @@ -1713,9 +2868,7 @@ fn test_phantom_failure_too_low_cltv() { // Ensure the payment fails with the expected error. let mut error_data = recv_value_msat.to_be_bytes().to_vec(); - error_data.extend_from_slice( - &nodes[0].node.best_block.read().unwrap().height.to_be_bytes(), - ); + error_data.extend_from_slice(&nodes[0].node.best_block.read().unwrap().height.to_be_bytes()); let mut fail_conditions = PaymentFailedConditions::new() .blamed_scid(phantom_scid) .expected_htlc_error_data(LocalHTLCFailureReason::IncorrectPaymentDetails, &error_data); @@ -1735,12 +2888,20 @@ fn test_phantom_failure_modified_cltv() { // Get the route. let recv_value_msat = 10_000; - let (_, payment_hash, payment_secret) = get_payment_preimage_hash!(nodes[1], Some(recv_value_msat)); + let (_, payment_hash, payment_secret) = + get_payment_preimage_hash!(nodes[1], Some(recv_value_msat)); let (mut route, phantom_scid) = get_phantom_route!(nodes, recv_value_msat, channel); // Route the HTLC through to the destination. - nodes[0].node.send_payment_with_route(route, payment_hash, - RecipientOnionFields::secret_only(payment_secret), PaymentId(payment_hash.0)).unwrap(); + nodes[0] + .node + .send_payment_with_route( + route, + payment_hash, + RecipientOnionFields::secret_only(payment_secret), + PaymentId(payment_hash.0), + ) + .unwrap(); check_added_monitors!(nodes[0], 1); let update_0 = get_htlc_update_msgs!(nodes[0], nodes[1].node.get_our_node_id()); let mut update_add = update_0.update_add_htlcs[0].clone(); @@ -1786,12 +2947,20 @@ fn test_phantom_failure_expires_too_soon() { // Get the route. let recv_value_msat = 10_000; - let (_, payment_hash, payment_secret) = get_payment_preimage_hash!(nodes[1], Some(recv_value_msat)); + let (_, payment_hash, payment_secret) = + get_payment_preimage_hash!(nodes[1], Some(recv_value_msat)); let (mut route, phantom_scid) = get_phantom_route!(nodes, recv_value_msat, channel); // Route the HTLC through to the destination. - nodes[0].node.send_payment_with_route(route, payment_hash, - RecipientOnionFields::secret_only(payment_secret), PaymentId(payment_hash.0)).unwrap(); + nodes[0] + .node + .send_payment_with_route( + route, + payment_hash, + RecipientOnionFields::secret_only(payment_secret), + PaymentId(payment_hash.0), + ) + .unwrap(); check_added_monitors!(nodes[0], 1); let update_0 = get_htlc_update_msgs!(nodes[0], nodes[1].node.get_our_node_id()); let mut update_add = update_0.update_add_htlcs[0].clone(); @@ -1832,12 +3001,20 @@ fn test_phantom_failure_too_low_recv_amt() { // Get the route with a too-low amount. let recv_amt_msat = 10_000; let bad_recv_amt_msat = recv_amt_msat - 10; - let (_, payment_hash, payment_secret) = get_payment_preimage_hash!(nodes[1], Some(recv_amt_msat)); + let (_, payment_hash, payment_secret) = + get_payment_preimage_hash!(nodes[1], Some(recv_amt_msat)); let (mut route, phantom_scid) = get_phantom_route!(nodes, bad_recv_amt_msat, channel); // Route the HTLC through to the destination. - nodes[0].node.send_payment_with_route(route, payment_hash, - RecipientOnionFields::secret_only(payment_secret), PaymentId(payment_hash.0)).unwrap(); + nodes[0] + .node + .send_payment_with_route( + route, + payment_hash, + RecipientOnionFields::secret_only(payment_secret), + PaymentId(payment_hash.0), + ) + .unwrap(); check_added_monitors!(nodes[0], 1); let update_0 = get_htlc_update_msgs!(nodes[0], nodes[1].node.get_our_node_id()); let mut update_add = update_0.update_add_htlcs[0].clone(); @@ -1849,7 +3026,10 @@ fn test_phantom_failure_too_low_recv_amt() { nodes[1].node.process_pending_htlc_forwards(); expect_pending_htlcs_forwardable_ignore!(nodes[1]); nodes[1].node.process_pending_htlc_forwards(); - expect_pending_htlcs_forwardable_and_htlc_handling_failed_ignore!(nodes[1], vec![HTLCHandlingFailureType::Receive { payment_hash: payment_hash.clone() }]); + expect_pending_htlcs_forwardable_and_htlc_handling_failed_ignore!( + nodes[1], + vec![HTLCHandlingFailureType::Receive { payment_hash: payment_hash.clone() }] + ); nodes[1].node.process_pending_htlc_forwards(); let update_1 = get_htlc_update_msgs!(nodes[1], nodes[0].node.get_our_node_id()); check_added_monitors!(&nodes[1], 1); @@ -1879,9 +3059,11 @@ fn do_test_phantom_dust_exposure_failure(multiplier_dust_limit: bool) { let mut receiver_config = UserConfig::default(); // Default test fee estimator rate is 253, so to set the max dust exposure to the dust limit, // we need to set the multiplier to 2. - receiver_config.channel_config.max_dust_htlc_exposure = - if multiplier_dust_limit { MaxDustHTLCExposure::FeeRateMultiplier(2) } - else { MaxDustHTLCExposure::FixedLimitMsat(max_dust_exposure) }; + receiver_config.channel_config.max_dust_htlc_exposure = if multiplier_dust_limit { + MaxDustHTLCExposure::FeeRateMultiplier(2) + } else { + MaxDustHTLCExposure::FixedLimitMsat(max_dust_exposure) + }; receiver_config.channel_handshake_config.announce_for_forwarding = true; let chanmon_cfgs = create_chanmon_cfgs(2); @@ -1892,12 +3074,20 @@ fn do_test_phantom_dust_exposure_failure(multiplier_dust_limit: bool) { let channel = create_announced_chan_between_nodes(&nodes, 0, 1); // Get the route with an amount exceeding the dust exposure threshold of nodes[1]. - let (_, payment_hash, payment_secret) = get_payment_preimage_hash!(nodes[1], Some(max_dust_exposure + 1)); + let (_, payment_hash, payment_secret) = + get_payment_preimage_hash!(nodes[1], Some(max_dust_exposure + 1)); let (mut route, phantom_scid) = get_phantom_route!(nodes, max_dust_exposure + 1, channel); // Route the HTLC through to the destination. - nodes[0].node.send_payment_with_route(route.clone(), payment_hash, - RecipientOnionFields::secret_only(payment_secret), PaymentId(payment_hash.0)).unwrap(); + nodes[0] + .node + .send_payment_with_route( + route.clone(), + payment_hash, + RecipientOnionFields::secret_only(payment_secret), + PaymentId(payment_hash.0), + ) + .unwrap(); check_added_monitors!(nodes[0], 1); let update_0 = get_htlc_update_msgs!(nodes[0], nodes[1].node.get_our_node_id()); let mut update_add = update_0.update_add_htlcs[0].clone(); @@ -1937,12 +3127,20 @@ fn test_phantom_failure_reject_payment() { // Get the route with a too-low amount. let recv_amt_msat = 10_000; - let (_, payment_hash, payment_secret) = get_payment_preimage_hash!(nodes[1], Some(recv_amt_msat)); + let (_, payment_hash, payment_secret) = + get_payment_preimage_hash!(nodes[1], Some(recv_amt_msat)); let (mut route, phantom_scid) = get_phantom_route!(nodes, recv_amt_msat, channel); // Route the HTLC through to the destination. - nodes[0].node.send_payment_with_route(route.clone(), payment_hash, - RecipientOnionFields::secret_only(payment_secret), PaymentId(payment_hash.0)).unwrap(); + nodes[0] + .node + .send_payment_with_route( + route.clone(), + payment_hash, + RecipientOnionFields::secret_only(payment_secret), + PaymentId(payment_hash.0), + ) + .unwrap(); check_added_monitors!(nodes[0], 1); let update_0 = get_htlc_update_msgs!(nodes[0], nodes[1].node.get_our_node_id()); let mut update_add = update_0.update_add_htlcs[0].clone(); @@ -1954,9 +3152,19 @@ fn test_phantom_failure_reject_payment() { nodes[1].node.process_pending_htlc_forwards(); expect_pending_htlcs_forwardable_ignore!(nodes[1]); nodes[1].node.process_pending_htlc_forwards(); - expect_payment_claimable!(nodes[1], payment_hash, payment_secret, recv_amt_msat, None, route.paths[0].hops.last().unwrap().pubkey); + expect_payment_claimable!( + nodes[1], + payment_hash, + payment_secret, + recv_amt_msat, + None, + route.paths[0].hops.last().unwrap().pubkey + ); nodes[1].node.fail_htlc_backwards(&payment_hash); - expect_pending_htlcs_forwardable_and_htlc_handling_failed_ignore!(nodes[1], vec![HTLCHandlingFailureType::Receive { payment_hash }]); + expect_pending_htlcs_forwardable_and_htlc_handling_failed_ignore!( + nodes[1], + vec![HTLCHandlingFailureType::Receive { payment_hash }] + ); nodes[1].node.process_pending_htlc_forwards(); let update_1 = get_htlc_update_msgs!(nodes[1], nodes[0].node.get_our_node_id()); diff --git a/lightning/src/ln/outbound_payment.rs b/lightning/src/ln/outbound_payment.rs index e6dd364b4b0..6d6296ebf24 100644 --- a/lightning/src/ln/outbound_payment.rs +++ b/lightning/src/ln/outbound_payment.rs @@ -1615,17 +1615,23 @@ impl OutboundPayments { } } - #[rustfmt::skip] - fn push_path_failed_evs_and_scids>, L: Deref>( + fn push_path_failed_evs_and_scids< + I: ExactSizeIterator + Iterator>, + L: Deref, + >( payment_id: PaymentId, payment_hash: PaymentHash, route_params: &mut RouteParameters, paths: Vec, path_results: I, logger: &L, pending_events: &Mutex)>>, - ) where L::Target: Logger { + ) where + L::Target: Logger, + { let mut events = pending_events.lock().unwrap(); debug_assert_eq!(paths.len(), path_results.len()); for (path, path_res) in paths.into_iter().zip(path_results) { if let Err(e) = path_res { - if let APIError::MonitorUpdateInProgress = e { continue } + if let APIError::MonitorUpdateInProgress = e { + continue; + } log_error!(logger, "Failed to send along path due to error: {:?}", e); let mut failed_scid = None; if let APIError::ChannelUnavailable { .. } = e { @@ -1633,18 +1639,21 @@ impl OutboundPayments { failed_scid = Some(scid); route_params.payment_params.previously_failed_channels.push(scid); } - events.push_back((events::Event::PaymentPathFailed { - payment_id: Some(payment_id), - payment_hash, - payment_failed_permanently: false, - failure: events::PathFailure::InitialSend { err: e }, - path, - short_channel_id: failed_scid, - #[cfg(any(test, feature = "_test_utils"))] - error_code: None, - #[cfg(any(test, feature = "_test_utils"))] - error_data: None, - }, None)); + events.push_back(( + events::Event::PaymentPathFailed { + payment_id: Some(payment_id), + payment_hash, + payment_failed_permanently: false, + failure: events::PathFailure::InitialSend { err: e }, + path, + short_channel_id: failed_scid, + #[cfg(any(test, feature = "_test_utils"))] + error_code: None, + #[cfg(any(test, feature = "_test_utils"))] + error_data: None, + }, + None, + )); } } } @@ -2253,21 +2262,33 @@ impl OutboundPayments { } // Returns a bool indicating whether a PendingHTLCsForwardable event should be generated. - #[rustfmt::skip] pub(super) fn fail_htlc( &self, source: &HTLCSource, payment_hash: &PaymentHash, onion_error: &HTLCFailReason, path: &Path, session_priv: &SecretKey, payment_id: &PaymentId, probing_cookie_secret: [u8; 32], secp_ctx: &Secp256k1, - pending_events: &Mutex)>>, logger: &L, - ) -> bool where L::Target: Logger { + pending_events: &Mutex)>>, + logger: &L, + ) -> bool + where + L::Target: Logger, + { #[cfg(any(test, feature = "_test_utils"))] let DecodedOnionFailure { - network_update, short_channel_id, payment_failed_permanently, onion_error_code, - onion_error_data, failed_within_blinded_path, .. + network_update, + short_channel_id, + payment_failed_permanently, + onion_error_code, + onion_error_data, + failed_within_blinded_path, + .. } = onion_error.decode_onion_failure(secp_ctx, logger, &source); #[cfg(not(any(test, feature = "_test_utils")))] let DecodedOnionFailure { - network_update, short_channel_id, payment_failed_permanently, failed_within_blinded_path, .. + network_update, + short_channel_id, + payment_failed_permanently, + failed_within_blinded_path, + .. } = onion_error.decode_onion_failure(secp_ctx, logger, &source); let payment_is_probe = payment_is_probe(payment_hash, &payment_id, probing_cookie_secret); @@ -2280,7 +2301,8 @@ impl OutboundPayments { let already_awaiting_retry = outbounds.iter().any(|(_, pmt)| { let mut awaiting_retry = false; if pmt.is_auto_retryable_now() { - if let PendingOutboundPayment::Retryable { pending_amt_msat, total_msat, .. } = pmt { + if let PendingOutboundPayment::Retryable { pending_amt_msat, total_msat, .. } = pmt + { if pending_amt_msat < total_msat { awaiting_retry = true; } @@ -2291,55 +2313,72 @@ impl OutboundPayments { let mut full_failure_ev = None; let mut pending_retry_ev = false; - let attempts_remaining = if let hash_map::Entry::Occupied(mut payment) = outbounds.entry(*payment_id) { - if !payment.get_mut().remove(&session_priv_bytes, Some(&path)) { - log_trace!(logger, "Received duplicative fail for HTLC with payment_hash {}", &payment_hash); - return false - } - if payment.get().is_fulfilled() { - log_trace!(logger, "Received failure of HTLC with payment_hash {} after payment completion", &payment_hash); - return false - } - let mut is_retryable_now = payment.get().is_auto_retryable_now(); - if let Some(scid) = short_channel_id { - // TODO: If we decided to blame ourselves (or one of our channels) in - // process_onion_failure we should close that channel as it implies our - // next-hop is needlessly blaming us! - payment.get_mut().insert_previously_failed_scid(scid); - } - if failed_within_blinded_path { - debug_assert!(short_channel_id.is_none()); - if let Some(bt) = &path.blinded_tail { - payment.get_mut().insert_previously_failed_blinded_path(&bt); - } else { debug_assert!(false); } - } + let attempts_remaining = + if let hash_map::Entry::Occupied(mut payment) = outbounds.entry(*payment_id) { + if !payment.get_mut().remove(&session_priv_bytes, Some(&path)) { + log_trace!( + logger, + "Received duplicative fail for HTLC with payment_hash {}", + &payment_hash + ); + return false; + } + if payment.get().is_fulfilled() { + log_trace!( + logger, + "Received failure of HTLC with payment_hash {} after payment completion", + &payment_hash + ); + return false; + } + let mut is_retryable_now = payment.get().is_auto_retryable_now(); + if let Some(scid) = short_channel_id { + // TODO: If we decided to blame ourselves (or one of our channels) in + // process_onion_failure we should close that channel as it implies our + // next-hop is needlessly blaming us! + payment.get_mut().insert_previously_failed_scid(scid); + } + if failed_within_blinded_path { + debug_assert!(short_channel_id.is_none()); + if let Some(bt) = &path.blinded_tail { + payment.get_mut().insert_previously_failed_blinded_path(&bt); + } else { + debug_assert!(false); + } + } - if payment_is_probe || !is_retryable_now || payment_failed_permanently { - let reason = if payment_failed_permanently { - PaymentFailureReason::RecipientRejected - } else { - PaymentFailureReason::RetriesExhausted - }; - payment.get_mut().mark_abandoned(reason); - is_retryable_now = false; - } - if payment.get().remaining_parts() == 0 { - if let PendingOutboundPayment::Abandoned { payment_hash, reason, .. } = payment.get() { - if !payment_is_probe { - full_failure_ev = Some(events::Event::PaymentFailed { - payment_id: *payment_id, - payment_hash: Some(*payment_hash), - reason: *reason, - }); + if payment_is_probe || !is_retryable_now || payment_failed_permanently { + let reason = if payment_failed_permanently { + PaymentFailureReason::RecipientRejected + } else { + PaymentFailureReason::RetriesExhausted + }; + payment.get_mut().mark_abandoned(reason); + is_retryable_now = false; + } + if payment.get().remaining_parts() == 0 { + if let PendingOutboundPayment::Abandoned { payment_hash, reason, .. } = + payment.get() + { + if !payment_is_probe { + full_failure_ev = Some(events::Event::PaymentFailed { + payment_id: *payment_id, + payment_hash: Some(*payment_hash), + reason: *reason, + }); + } + payment.remove(); } - payment.remove(); } - } - is_retryable_now - } else { - log_trace!(logger, "Received duplicative fail for HTLC with payment_hash {}", &payment_hash); - return false - }; + is_retryable_now + } else { + log_trace!( + logger, + "Received duplicative fail for HTLC with payment_hash {}", + &payment_hash + ); + return false; + }; core::mem::drop(outbounds); log_trace!(logger, "Failing outbound payment HTLC with payment_hash {}", &payment_hash); @@ -2376,13 +2415,15 @@ impl OutboundPayments { #[cfg(any(test, feature = "_test_utils"))] error_code: onion_error_code.map(|f| f.failure_code()), #[cfg(any(test, feature = "_test_utils"))] - error_data: onion_error_data + error_data: onion_error_data, } } }; let mut pending_events = pending_events.lock().unwrap(); pending_events.push_back((path_failure, None)); - if let Some(ev) = full_failure_ev { pending_events.push_back((ev, None)); } + if let Some(ev) = full_failure_ev { + pending_events.push_back((ev, None)); + } pending_retry_ev } From 10279293f46374e8a6f7d9f1f88310f87cf6fc42 Mon Sep 17 00:00:00 2001 From: Joost Jager Date: Fri, 20 Jun 2025 08:11:47 +0200 Subject: [PATCH 2/7] Rustfmt cleanup --- lightning/src/ln/channel.rs | 2 +- lightning/src/ln/onion_route_tests.rs | 515 ++++++++------------------ lightning/src/ln/outbound_payment.rs | 28 +- 3 files changed, 159 insertions(+), 386 deletions(-) diff --git a/lightning/src/ln/channel.rs b/lightning/src/ln/channel.rs index da6a436b5e9..15b6c9aa4e0 100644 --- a/lightning/src/ln/channel.rs +++ b/lightning/src/ln/channel.rs @@ -7165,8 +7165,8 @@ where " ...removing outbound AwaitingRemovedRemoteRevoke {}", &htlc.payment_hash ); + // 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()) diff --git a/lightning/src/ln/onion_route_tests.rs b/lightning/src/ln/onion_route_tests.rs index 7344a1a62e1..faf4b5a6ea7 100644 --- a/lightning/src/ln/onion_route_tests.rs +++ b/lightning/src/ln/onion_route_tests.rs @@ -25,7 +25,9 @@ use crate::ln::msgs::{ BaseMessageHandler, ChannelMessageHandler, ChannelUpdate, FinalOnionHopData, MessageSendEvent, OutboundOnionPayload, OutboundTrampolinePayload, }; -use crate::ln::onion_utils::{self, LocalHTLCFailureReason}; +use crate::ln::onion_utils::{ + self, build_onion_payloads, construct_onion_keys, LocalHTLCFailureReason, +}; use crate::ln::wire::Encode; use crate::routing::gossip::{NetworkUpdate, RoutingFees}; use crate::routing::router::{ @@ -126,14 +128,10 @@ fn run_onion_failure_test_with_fail_intercept( // 0 ~~> 2 send payment let payment_id = PaymentId(nodes[0].keys_manager.backing.get_secure_random_bytes()); + let recipient_onion = RecipientOnionFields::secret_only(*payment_secret); nodes[0] .node - .send_payment_with_route( - route.clone(), - *payment_hash, - RecipientOnionFields::secret_only(*payment_secret), - payment_id, - ) + .send_payment_with_route(route.clone(), *payment_hash, recipient_onion, payment_id) .unwrap(); check_added_monitors!(nodes[0], 1); let update_0 = get_htlc_update_msgs!(nodes[0], nodes[1].node.get_our_node_id()); @@ -157,11 +155,9 @@ fn run_onion_failure_test_with_fail_intercept( ); check_added_monitors(&nodes[1], 1); let update_1_0 = get_htlc_update_msgs!(nodes[1], nodes[0].node.get_our_node_id()); - assert!( - update_1_0.update_fail_htlcs.len() + update_1_0.update_fail_malformed_htlcs.len() - == 1 && (update_1_0.update_fail_htlcs.len() == 1 - || update_1_0.update_fail_malformed_htlcs.len() == 1) - ); + let fail_len = update_1_0.update_fail_htlcs.len(); + let malformed_len = update_1_0.update_fail_malformed_htlcs.len(); + assert!(fail_len + malformed_len == 1 && (fail_len == 1 || malformed_len == 1)); update_1_0 }, 1 | 2 | 3 | 200 => { @@ -306,14 +302,13 @@ fn run_onion_failure_test_with_fail_intercept( } => { assert_eq!(Some(*payment_hash), ev_payment_hash); assert_eq!(payment_id, ev_payment_id); - assert_eq!( - if expected_retryable { - PaymentFailureReason::RetriesExhausted - } else { - PaymentFailureReason::RecipientRejected - }, - ev_reason.unwrap() - ); + + let expected_reason = if expected_retryable { + PaymentFailureReason::RetriesExhausted + } else { + PaymentFailureReason::RecipientRejected + }; + assert_eq!(expected_reason, ev_reason.unwrap()); }, _ => panic!("Unexpected second event"), } @@ -384,14 +379,11 @@ fn test_fee_failures() { // positive case let (route, payment_hash_success, payment_preimage_success, payment_secret_success) = get_route_and_payment_hash!(nodes[0], nodes[2], 40_000); + let recipient_onion = RecipientOnionFields::secret_only(payment_secret_success); + let payment_id = PaymentId(payment_hash_success.0); nodes[0] .node - .send_payment_with_route( - route.clone(), - payment_hash_success, - RecipientOnionFields::secret_only(payment_secret_success), - PaymentId(payment_hash_success.0), - ) + .send_payment_with_route(route.clone(), payment_hash_success, recipient_onion, payment_id) .unwrap(); check_added_monitors!(nodes[0], 1); pass_along_route( @@ -438,14 +430,11 @@ fn test_fee_failures() { let (payment_preimage_success, payment_hash_success, payment_secret_success) = get_payment_preimage_hash!(nodes[2]); + let recipient_onion = RecipientOnionFields::secret_only(payment_secret_success); + let payment_id = PaymentId(payment_hash_success.0); nodes[0] .node - .send_payment_with_route( - route, - payment_hash_success, - RecipientOnionFields::secret_only(payment_secret_success), - PaymentId(payment_hash_success.0), - ) + .send_payment_with_route(route, payment_hash_success, recipient_onion, payment_id) .unwrap(); check_added_monitors!(nodes[0], 1); pass_along_route( @@ -512,22 +501,13 @@ fn test_onion_failure() { |msg| { let session_priv = SecretKey::from_slice(&[3; 32]).unwrap(); let cur_height = nodes[0].best_block_info().1 + 1; - let onion_keys = onion_utils::construct_onion_keys( - &Secp256k1::new(), - &route.paths[0], - &session_priv, - ); - let recipient_onion_fields = RecipientOnionFields::spontaneous_empty(); - let (mut onion_payloads, _htlc_msat, _htlc_cltv) = onion_utils::build_onion_payloads( - &route.paths[0], - 40000, - &recipient_onion_fields, - cur_height, - &None, - None, - None, - ) - .unwrap(); + let onion_keys = + construct_onion_keys(&Secp256k1::new(), &route.paths[0], &session_priv); + let recipient_fields = RecipientOnionFields::spontaneous_empty(); + let path = &route.paths[0]; + let (mut onion_payloads, _htlc_msat, _htlc_cltv) = + build_onion_payloads(path, 40000, &recipient_fields, cur_height, &None, None, None) + .unwrap(); let mut new_payloads = Vec::new(); for payload in onion_payloads.drain(..) { new_payloads.push(BogusOnionHopData::new(payload)); @@ -563,22 +543,13 @@ fn test_onion_failure() { |msg| { let session_priv = SecretKey::from_slice(&[3; 32]).unwrap(); let cur_height = nodes[0].best_block_info().1 + 1; - let onion_keys = onion_utils::construct_onion_keys( - &Secp256k1::new(), - &route.paths[0], - &session_priv, - ); - let recipient_onion_fields = RecipientOnionFields::spontaneous_empty(); - let (mut onion_payloads, _htlc_msat, _htlc_cltv) = onion_utils::build_onion_payloads( - &route.paths[0], - 40000, - &recipient_onion_fields, - cur_height, - &None, - None, - None, - ) - .unwrap(); + let onion_keys = + construct_onion_keys(&Secp256k1::new(), &route.paths[0], &session_priv); + let recipient_fields = RecipientOnionFields::spontaneous_empty(); + let path = &route.paths[0]; + let (mut onion_payloads, _htlc_msat, _htlc_cltv) = + build_onion_payloads(path, 40000, &recipient_fields, cur_height, &None, None, None) + .unwrap(); let mut new_payloads = Vec::new(); for payload in onion_payloads.drain(..) { new_payloads.push(BogusOnionHopData::new(payload)); @@ -619,11 +590,8 @@ fn test_onion_failure() { |msg| { // and tamper returning error message let session_priv = SecretKey::from_slice(&[3; 32]).unwrap(); - let onion_keys = onion_utils::construct_onion_keys( - &Secp256k1::new(), - &route.paths[0], - &session_priv, - ); + let onion_keys = + construct_onion_keys(&Secp256k1::new(), &route.paths[0], &session_priv); let failure = onion_utils::build_failure_packet( onion_keys[0].shared_secret.as_ref(), LocalHTLCFailureReason::TemporaryNodeFailure, @@ -656,11 +624,8 @@ fn test_onion_failure() { |msg| { // and tamper returning error message let session_priv = SecretKey::from_slice(&[3; 32]).unwrap(); - let onion_keys = onion_utils::construct_onion_keys( - &Secp256k1::new(), - &route.paths[0], - &session_priv, - ); + let onion_keys = + construct_onion_keys(&Secp256k1::new(), &route.paths[0], &session_priv); let failure = onion_utils::build_failure_packet( onion_keys[1].shared_secret.as_ref(), LocalHTLCFailureReason::TemporaryNodeFailure, @@ -697,11 +662,8 @@ fn test_onion_failure() { }, |msg| { let session_priv = SecretKey::from_slice(&[3; 32]).unwrap(); - let onion_keys = onion_utils::construct_onion_keys( - &Secp256k1::new(), - &route.paths[0], - &session_priv, - ); + let onion_keys = + construct_onion_keys(&Secp256k1::new(), &route.paths[0], &session_priv); let failure = onion_utils::build_failure_packet( onion_keys[0].shared_secret.as_ref(), LocalHTLCFailureReason::PermanentNodeFailure, @@ -733,11 +695,8 @@ fn test_onion_failure() { |_msg| {}, |msg| { let session_priv = SecretKey::from_slice(&[3; 32]).unwrap(); - let onion_keys = onion_utils::construct_onion_keys( - &Secp256k1::new(), - &route.paths[0], - &session_priv, - ); + let onion_keys = + construct_onion_keys(&Secp256k1::new(), &route.paths[0], &session_priv); let failure = onion_utils::build_failure_packet( onion_keys[1].shared_secret.as_ref(), LocalHTLCFailureReason::PermanentNodeFailure, @@ -774,11 +733,8 @@ fn test_onion_failure() { }, |msg| { let session_priv = SecretKey::from_slice(&[3; 32]).unwrap(); - let onion_keys = onion_utils::construct_onion_keys( - &Secp256k1::new(), - &route.paths[0], - &session_priv, - ); + let onion_keys = + construct_onion_keys(&Secp256k1::new(), &route.paths[0], &session_priv); let failure = onion_utils::build_failure_packet( onion_keys[0].shared_secret.as_ref(), LocalHTLCFailureReason::RequiredNodeFeature, @@ -812,11 +768,8 @@ fn test_onion_failure() { |_msg| {}, |msg| { let session_priv = SecretKey::from_slice(&[3; 32]).unwrap(); - let onion_keys = onion_utils::construct_onion_keys( - &Secp256k1::new(), - &route.paths[0], - &session_priv, - ); + let onion_keys = + construct_onion_keys(&Secp256k1::new(), &route.paths[0], &session_priv); let failure = onion_utils::build_failure_packet( onion_keys[1].shared_secret.as_ref(), LocalHTLCFailureReason::RequiredNodeFeature, @@ -916,11 +869,8 @@ fn test_onion_failure() { }, |msg| { let session_priv = SecretKey::from_slice(&[3; 32]).unwrap(); - let onion_keys = onion_utils::construct_onion_keys( - &Secp256k1::new(), - &route.paths[0], - &session_priv, - ); + let onion_keys = + construct_onion_keys(&Secp256k1::new(), &route.paths[0], &session_priv); let failure = onion_utils::build_failure_packet( onion_keys[0].shared_secret.as_ref(), LocalHTLCFailureReason::TemporaryChannelFailure, @@ -952,11 +902,8 @@ fn test_onion_failure() { }, |msg| { let session_priv = SecretKey::from_slice(&[3; 32]).unwrap(); - let onion_keys = onion_utils::construct_onion_keys( - &Secp256k1::new(), - &route.paths[0], - &session_priv, - ); + let onion_keys = + construct_onion_keys(&Secp256k1::new(), &route.paths[0], &session_priv); let failure = onion_utils::build_failure_packet( onion_keys[0].shared_secret.as_ref(), LocalHTLCFailureReason::TemporaryChannelFailure, @@ -987,11 +934,8 @@ fn test_onion_failure() { }, |msg| { let session_priv = SecretKey::from_slice(&[3; 32]).unwrap(); - let onion_keys = onion_utils::construct_onion_keys( - &Secp256k1::new(), - &route.paths[0], - &session_priv, - ); + let onion_keys = + construct_onion_keys(&Secp256k1::new(), &route.paths[0], &session_priv); let failure = onion_utils::build_failure_packet( onion_keys[0].shared_secret.as_ref(), LocalHTLCFailureReason::PermanentChannelFailure, @@ -1023,11 +967,8 @@ fn test_onion_failure() { }, |msg| { let session_priv = SecretKey::from_slice(&[3; 32]).unwrap(); - let onion_keys = onion_utils::construct_onion_keys( - &Secp256k1::new(), - &route.paths[0], - &session_priv, - ); + let onion_keys = + construct_onion_keys(&Secp256k1::new(), &route.paths[0], &session_priv); let failure = onion_utils::build_failure_packet( onion_keys[0].shared_secret.as_ref(), LocalHTLCFailureReason::RequiredChannelFeature, @@ -1066,21 +1007,11 @@ fn test_onion_failure() { ); let short_channel_id = channels[1].0.contents.short_channel_id; - let amt_to_forward = nodes[1] - .node - .per_peer_state - .read() - .unwrap() - .get(&nodes[2].node.get_our_node_id()) - .unwrap() - .lock() - .unwrap() - .channel_by_id - .get(&channels[1].2) - .unwrap() - .context() - .get_counterparty_htlc_minimum_msat() - - 1; + let amt_to_forward = { + let (per_peer_state, mut peer_state); + let chan = get_channel_ref!(nodes[1], nodes[2], per_peer_state, peer_state, channels[1].2); + chan.context().get_counterparty_htlc_minimum_msat() - 1 + }; let mut bogus_route = route.clone(); let route_len = bogus_route.paths[0].hops.len(); bogus_route.paths[0].hops[route_len - 1].fee_msat = amt_to_forward; @@ -1331,22 +1262,13 @@ fn test_onion_failure() { let height = nodes[2].best_block_info().1; route.paths[0].hops[1].cltv_expiry_delta += CLTV_FAR_FAR_AWAY + route.paths[0].hops[0].cltv_expiry_delta + 1; - let onion_keys = onion_utils::construct_onion_keys( - &Secp256k1::new(), - &route.paths[0], - &session_priv, - ); - let recipient_onion_fields = RecipientOnionFields::spontaneous_empty(); - let (onion_payloads, _, htlc_cltv) = onion_utils::build_onion_payloads( - &route.paths[0], - 40000, - &recipient_onion_fields, - height, - &None, - None, - None, - ) - .unwrap(); + let onion_keys = + construct_onion_keys(&Secp256k1::new(), &route.paths[0], &session_priv); + let recipient_fields = RecipientOnionFields::spontaneous_empty(); + let path = &route.paths[0]; + let (onion_payloads, _, htlc_cltv) = + build_onion_payloads(path, 40000, &recipient_fields, height, &None, None, None) + .unwrap(); let onion_packet = onion_utils::construct_onion_packet( onion_payloads, onion_keys, @@ -1379,11 +1301,8 @@ fn test_onion_failure() { |msg| { // Tamper returning error message let session_priv = SecretKey::from_slice(&[3; 32]).unwrap(); - let onion_keys = onion_utils::construct_onion_keys( - &Secp256k1::new(), - &route.paths[0], - &session_priv, - ); + let onion_keys = + construct_onion_keys(&Secp256k1::new(), &route.paths[0], &session_priv); let failure = onion_utils::build_failure_packet( onion_keys[1].shared_secret.as_ref(), LocalHTLCFailureReason::MPPTimeout, @@ -1413,11 +1332,8 @@ fn test_onion_failure() { |_msg| {}, |msg| { let session_priv = SecretKey::from_slice(&[3; 32]).unwrap(); - let onion_keys = onion_utils::construct_onion_keys( - &Secp256k1::new(), - &route.paths[0], - &session_priv, - ); + let onion_keys = + construct_onion_keys(&Secp256k1::new(), &route.paths[0], &session_priv); let mut decoded_err_packet = msgs::DecodedOnionErrorPacket { failuremsg: vec![0], pad: vec![0; 255], @@ -1485,11 +1401,8 @@ fn test_onion_failure() { }, |msg| { let session_priv = SecretKey::from_slice(&[3; 32]).unwrap(); - let onion_keys = onion_utils::construct_onion_keys( - &Secp256k1::new(), - &route.paths[0], - &session_priv, - ); + let onion_keys = + construct_onion_keys(&Secp256k1::new(), &route.paths[0], &session_priv); let mut decoded_err_packet = msgs::DecodedOnionErrorPacket { failuremsg: vec![ 0x10, 0x7, // UPDATE|7 @@ -1538,11 +1451,8 @@ fn test_onion_failure() { |_msg| {}, |msg| { let session_priv = SecretKey::from_slice(&[3; 32]).unwrap(); - let onion_keys = onion_utils::construct_onion_keys( - &Secp256k1::new(), - &route.paths[0], - &session_priv, - ); + let onion_keys = + construct_onion_keys(&Secp256k1::new(), &route.paths[0], &session_priv); let mut decoded_err_packet = msgs::DecodedOnionErrorPacket { failuremsg: vec![ 0x10, 0x7, // UPDATE|7 @@ -1595,14 +1505,10 @@ fn test_overshoot_final_cltv() { get_route_and_payment_hash!(nodes[0], nodes[2], 40000); let payment_id = PaymentId(nodes[0].keys_manager.backing.get_secure_random_bytes()); + let recipient_onion = RecipientOnionFields::secret_only(payment_secret); nodes[0] .node - .send_payment_with_route( - route, - payment_hash, - RecipientOnionFields::secret_only(payment_secret), - payment_id, - ) + .send_payment_with_route(route, payment_hash, recipient_onion, payment_id) .unwrap(); check_added_monitors!(nodes[0], 1); @@ -1894,17 +1800,11 @@ fn test_always_create_tlv_format_onion_payloads() { assert!(!hops[1].node_features.supports_variable_length_onion()); let cur_height = nodes[0].best_block_info().1 + 1; - let recipient_onion_fields = RecipientOnionFields::spontaneous_empty(); - let (onion_payloads, _htlc_msat, _htlc_cltv) = onion_utils::build_onion_payloads( - &route.paths[0], - 40000, - &recipient_onion_fields, - cur_height, - &None, - None, - None, - ) - .unwrap(); + let recipient_fields = RecipientOnionFields::spontaneous_empty(); + let path = &route.paths[0]; + let (onion_payloads, _htlc_msat, _htlc_cltv) = + build_onion_payloads(path, 40000, &recipient_fields, cur_height, &None, None, None) + .unwrap(); match onion_payloads[0] { msgs::OutboundOnionPayload::Forward { .. } => {}, @@ -1926,19 +1826,24 @@ fn test_always_create_tlv_format_onion_payloads() { } } +const BOB_HEX: &str = "0324653eac434488002cc06bbfb7f10fe18991e35f9fe4302dbea6d2353dc0ab1c"; +const CAROL_HEX: &str = "027f31ebc5462c1fdce1b737ecff52d37d75dea43ce11c74d25aa297165faa2007"; +const DAVE_HEX: &str = "032c0b7cf95324a07d05398b240174dc0c2be444d96b159aa6c7f7b1e668680991"; +const DAVE_BLINDED_HEX: &str = "0295d40514096a8be54859e7dfe947b376eaafea8afe5cb4eb2c13ff857ed0b4be"; +const EVE_BLINDED_HEX: &str = "020e2dbadcc2005e859819ddebbe88a834ae8a6d2b049233c07335f15cd1dc5f22"; +const BLINDING_POINT_HEX: &str = + "02988face71e92c345a068f740191fd8e53be14f0bb957ef730d3c5f76087b960e"; +const SECRET_HEX: &str = "7494b65bc092b48a75465e43e29be807eb2cc535ce8aaba31012b8ff1ceac5da"; +const SESSION_HEX: &str = "a64feb81abd58e473df290e9e1c07dc3e56114495cadf33191f44ba5448ebe99"; + #[test] fn test_trampoline_onion_payload_serialization() { // As per https://github.com/lightning/bolts/blob/c01d2e6267d4a8d1095f0f1188970055a9a22d29/bolt04/trampoline-payment-onion-test.json#L3 + let hex = "02edabbd16b41c8371b92ef2f04c1185b4f03b6dcd52ba9b78d9d7c89c8f221145"; let trampoline_payload = OutboundTrampolinePayload::Forward { amt_to_forward: 100000000, outgoing_cltv_value: 800000, - outgoing_node_id: PublicKey::from_slice( - &>::from_hex( - "02edabbd16b41c8371b92ef2f04c1185b4f03b6dcd52ba9b78d9d7c89c8f221145", - ) - .unwrap(), - ) - .unwrap(), + outgoing_node_id: PublicKey::from_slice(&>::from_hex(hex).unwrap()).unwrap(), }; let slice_to_hex = @@ -1961,13 +1866,7 @@ fn test_trampoline_onion_payload_assembly_values() { hops: vec![ // Bob RouteHop { - pubkey: PublicKey::from_slice( - &>::from_hex( - "0324653eac434488002cc06bbfb7f10fe18991e35f9fe4302dbea6d2353dc0ab1c", - ) - .unwrap(), - ) - .unwrap(), + pubkey: PublicKey::from_slice(&>::from_hex(BOB_HEX).unwrap()).unwrap(), node_features: NodeFeatures::empty(), short_channel_id: 0, channel_features: ChannelFeatures::empty(), @@ -1977,13 +1876,7 @@ fn test_trampoline_onion_payload_assembly_values() { }, // Carol RouteHop { - pubkey: PublicKey::from_slice( - &>::from_hex( - "027f31ebc5462c1fdce1b737ecff52d37d75dea43ce11c74d25aa297165faa2007", - ) - .unwrap(), - ) - .unwrap(), + pubkey: PublicKey::from_slice(&>::from_hex(CAROL_HEX).unwrap()).unwrap(), node_features: NodeFeatures::empty(), short_channel_id: (572330 << 40) + (42 << 16) + 2821, channel_features: ChannelFeatures::empty(), @@ -1996,26 +1889,15 @@ fn test_trampoline_onion_payload_assembly_values() { trampoline_hops: vec![ // Carol's pubkey TrampolineHop { - pubkey: PublicKey::from_slice( - &>::from_hex( - "027f31ebc5462c1fdce1b737ecff52d37d75dea43ce11c74d25aa297165faa2007", - ) + pubkey: PublicKey::from_slice(&>::from_hex(CAROL_HEX).unwrap()) .unwrap(), - ) - .unwrap(), node_features: Features::empty(), fee_msat: 2_500, cltv_expiry_delta: 24, }, // Dave's pubkey (the intro node needs to be duplicated) TrampolineHop { - pubkey: PublicKey::from_slice( - &>::from_hex( - "032c0b7cf95324a07d05398b240174dc0c2be444d96b159aa6c7f7b1e668680991", - ) - .unwrap(), - ) - .unwrap(), + pubkey: PublicKey::from_slice(&>::from_hex(DAVE_HEX).unwrap()).unwrap(), node_features: Features::empty(), fee_msat: 150_500, cltv_expiry_delta: 36, @@ -2025,10 +1907,7 @@ fn test_trampoline_onion_payload_assembly_values() { // Dave's blinded node id BlindedHop { blinded_node_id: PublicKey::from_slice( - &>::from_hex( - "0295d40514096a8be54859e7dfe947b376eaafea8afe5cb4eb2c13ff857ed0b4be", - ) - .unwrap(), + &>::from_hex(DAVE_BLINDED_HEX).unwrap(), ) .unwrap(), encrypted_payload: vec![], @@ -2036,20 +1915,14 @@ fn test_trampoline_onion_payload_assembly_values() { // Eve's blinded node id BlindedHop { blinded_node_id: PublicKey::from_slice( - &>::from_hex( - "020e2dbadcc2005e859819ddebbe88a834ae8a6d2b049233c07335f15cd1dc5f22", - ) - .unwrap(), + &>::from_hex(EVE_BLINDED_HEX).unwrap(), ) .unwrap(), encrypted_payload: vec![], }, ], blinding_point: PublicKey::from_slice( - &>::from_hex( - "02988face71e92c345a068f740191fd8e53be14f0bb957ef730d3c5f76087b960e", - ) - .unwrap(), + &>::from_hex(BLINDING_POINT_HEX).unwrap(), ) .unwrap(), excess_final_cltv_expiry_delta: 0, @@ -2061,14 +1934,7 @@ fn test_trampoline_onion_payload_assembly_values() { assert_eq!(path.final_cltv_expiry_delta(), None); let payment_secret = PaymentSecret( - SecretKey::from_slice( - &>::from_hex( - "7494b65bc092b48a75465e43e29be807eb2cc535ce8aaba31012b8ff1ceac5da", - ) - .unwrap(), - ) - .unwrap() - .secret_bytes(), + SecretKey::from_slice(&>::from_hex(SECRET_HEX).unwrap()).unwrap().secret_bytes(), ); let recipient_onion_fields = RecipientOnionFields::secret_only(payment_secret); let (trampoline_payloads, outer_total_msat, outer_starting_htlc_offset) = @@ -2117,11 +1983,7 @@ fn test_trampoline_onion_payload_assembly_values() { // all dummy values let secp_ctx = Secp256k1::new(); - let session_priv = SecretKey::from_slice( - &>::from_hex("a64feb81abd58e473df290e9e1c07dc3e56114495cadf33191f44ba5448ebe99") - .unwrap(), - ) - .unwrap(); + let session_priv = SecretKey::from_slice(&>::from_hex(SESSION_HEX).unwrap()).unwrap(); let prng_seed = onion_utils::gen_pad_from_shared_secret(&session_priv.secret_bytes()); let payment_hash = PaymentHash(session_priv.secret_bytes()); @@ -2139,7 +2001,7 @@ fn test_trampoline_onion_payload_assembly_values() { ) .unwrap(); - let (outer_payloads, total_msat, total_htlc_offset) = onion_utils::build_onion_payloads( + let (outer_payloads, total_msat, total_htlc_offset) = build_onion_payloads( &path, outer_total_msat, &recipient_onion_fields, @@ -2197,20 +2059,14 @@ fn test_trampoline_onion_payload_construction_vectors() { let trampoline_payload_carol = OutboundTrampolinePayload::Forward { amt_to_forward: 150_150_500, outgoing_cltv_value: 800_036, - outgoing_node_id: PublicKey::from_slice( - &>::from_hex( - "032c0b7cf95324a07d05398b240174dc0c2be444d96b159aa6c7f7b1e668680991", - ) - .unwrap(), - ) - .unwrap(), + outgoing_node_id: PublicKey::from_slice(&>::from_hex(DAVE_HEX).unwrap()).unwrap(), }; let carol_payload = trampoline_payload_carol.encode().to_lower_hex_string(); assert_eq!(carol_payload, "2e020408f31d6404030c35240e21032c0b7cf95324a07d05398b240174dc0c2be444d96b159aa6c7f7b1e668680991"); let trampoline_payload_dave = OutboundTrampolinePayload::BlindedForward { encrypted_tlvs: &>::from_hex("0ccf3c8a58deaa603f657ee2a5ed9d604eb5c8ca1e5f801989afa8f3ea6d789bbdde2c7e7a1ef9ca8c38d2c54760febad8446d3f273ddb537569ef56613846ccd3aba78a").unwrap(), - intro_node_blinding_point: Some(PublicKey::from_slice(&>::from_hex("02988face71e92c345a068f740191fd8e53be14f0bb957ef730d3c5f76087b960e").unwrap()).unwrap()), + intro_node_blinding_point: Some(PublicKey::from_slice(&>::from_hex(BLINDING_POINT_HEX).unwrap()).unwrap()), }; let dave_payload = trampoline_payload_dave.encode().to_lower_hex_string(); assert_eq!(dave_payload, "690a440ccf3c8a58deaa603f657ee2a5ed9d604eb5c8ca1e5f801989afa8f3ea6d789bbdde2c7e7a1ef9ca8c38d2c54760febad8446d3f273ddb537569ef56613846ccd3aba78a0c2102988face71e92c345a068f740191fd8e53be14f0bb957ef730d3c5f76087b960e"); @@ -2230,11 +2086,8 @@ fn test_trampoline_onion_payload_construction_vectors() { let trampoline_payloads = vec![trampoline_payload_carol, trampoline_payload_dave, trampoline_payload_eve]; - let trampoline_session_key = SecretKey::from_slice( - &>::from_hex("a64feb81abd58e473df290e9e1c07dc3e56114495cadf33191f44ba5448ebe99") - .unwrap(), - ) - .unwrap(); + let trampoline_session_key = + SecretKey::from_slice(&>::from_hex(SESSION_HEX).unwrap()).unwrap(); let associated_data_slice = SecretKey::from_slice( &>::from_hex("e89bc505e84aaca09613833fc58c9069078fb43bfbea0488f34eec9db99b5f82") .unwrap(), @@ -2248,26 +2101,15 @@ fn test_trampoline_onion_payload_construction_vectors() { trampoline_hops: vec![ // Carol's pubkey TrampolineHop { - pubkey: PublicKey::from_slice( - &>::from_hex( - "027f31ebc5462c1fdce1b737ecff52d37d75dea43ce11c74d25aa297165faa2007", - ) + pubkey: PublicKey::from_slice(&>::from_hex(CAROL_HEX).unwrap()) .unwrap(), - ) - .unwrap(), node_features: Features::empty(), fee_msat: 0, cltv_expiry_delta: 0, }, // Dave's pubkey (the intro node needs to be duplicated) TrampolineHop { - pubkey: PublicKey::from_slice( - &>::from_hex( - "032c0b7cf95324a07d05398b240174dc0c2be444d96b159aa6c7f7b1e668680991", - ) - .unwrap(), - ) - .unwrap(), + pubkey: PublicKey::from_slice(&>::from_hex(DAVE_HEX).unwrap()).unwrap(), node_features: Features::empty(), fee_msat: 0, cltv_expiry_delta: 0, @@ -2277,10 +2119,7 @@ fn test_trampoline_onion_payload_construction_vectors() { // Dave's blinded node id BlindedHop { blinded_node_id: PublicKey::from_slice( - &>::from_hex( - "0295d40514096a8be54859e7dfe947b376eaafea8afe5cb4eb2c13ff857ed0b4be", - ) - .unwrap(), + &>::from_hex(DAVE_BLINDED_HEX).unwrap(), ) .unwrap(), encrypted_payload: vec![], @@ -2288,20 +2127,14 @@ fn test_trampoline_onion_payload_construction_vectors() { // Eve's blinded node id BlindedHop { blinded_node_id: PublicKey::from_slice( - &>::from_hex( - "020e2dbadcc2005e859819ddebbe88a834ae8a6d2b049233c07335f15cd1dc5f22", - ) - .unwrap(), + &>::from_hex(EVE_BLINDED_HEX).unwrap(), ) .unwrap(), encrypted_payload: vec![], }, ], blinding_point: PublicKey::from_slice( - &>::from_hex( - "02988face71e92c345a068f740191fd8e53be14f0bb957ef730d3c5f76087b960e", - ) - .unwrap(), + &>::from_hex(BLINDING_POINT_HEX).unwrap(), ) .unwrap(), excess_final_cltv_expiry_delta: 0, @@ -2339,14 +2172,9 @@ fn test_trampoline_onion_payload_construction_vectors() { trampoline_packet: trampoline_onion_packet, multipath_trampoline_data: Some(FinalOnionHopData { payment_secret: PaymentSecret( - SecretKey::from_slice( - &>::from_hex( - "7494b65bc092b48a75465e43e29be807eb2cc535ce8aaba31012b8ff1ceac5da", - ) - .unwrap(), - ) - .unwrap() - .secret_bytes(), + SecretKey::from_slice(&>::from_hex(SECRET_HEX).unwrap()) + .unwrap() + .secret_bytes(), ), total_msat: 150153000, }), @@ -2357,13 +2185,7 @@ fn test_trampoline_onion_payload_construction_vectors() { hops: vec![ // Bob RouteHop { - pubkey: PublicKey::from_slice( - &>::from_hex( - "0324653eac434488002cc06bbfb7f10fe18991e35f9fe4302dbea6d2353dc0ab1c", - ) - .unwrap(), - ) - .unwrap(), + pubkey: PublicKey::from_slice(&>::from_hex(BOB_HEX).unwrap()).unwrap(), node_features: NodeFeatures::empty(), short_channel_id: 0, channel_features: ChannelFeatures::empty(), @@ -2373,13 +2195,7 @@ fn test_trampoline_onion_payload_construction_vectors() { }, // Carol RouteHop { - pubkey: PublicKey::from_slice( - &>::from_hex( - "027f31ebc5462c1fdce1b737ecff52d37d75dea43ce11c74d25aa297165faa2007", - ) - .unwrap(), - ) - .unwrap(), + pubkey: PublicKey::from_slice(&>::from_hex(CAROL_HEX).unwrap()).unwrap(), node_features: NodeFeatures::empty(), short_channel_id: 0, channel_features: ChannelFeatures::empty(), @@ -2402,8 +2218,7 @@ fn test_trampoline_onion_payload_construction_vectors() { .unwrap(), ) .unwrap(); - let outer_onion_keys = - onion_utils::construct_onion_keys(&Secp256k1::new(), &outer_hops, &outer_session_key); + let outer_onion_keys = construct_onion_keys(&Secp256k1::new(), &outer_hops, &outer_session_key); let outer_onion_prng_seed = onion_utils::gen_pad_from_shared_secret(&outer_session_key.secret_bytes()); let outer_onion_packet = onion_utils::construct_onion_packet( @@ -2428,14 +2243,10 @@ fn do_test_fail_htlc_backwards_with_reason(failure_code: FailureCode) { let payment_amount = 100_000; let (route, payment_hash, _, payment_secret) = get_route_and_payment_hash!(nodes[0], nodes[1], payment_amount); + let recipient_onion = RecipientOnionFields::secret_only(payment_secret); nodes[0] .node - .send_payment_with_route( - route, - payment_hash, - RecipientOnionFields::secret_only(payment_secret), - PaymentId(payment_hash.0), - ) + .send_payment_with_route(route, payment_hash, recipient_onion, PaymentId(payment_hash.0)) .unwrap(); check_added_monitors!(nodes[0], 1); @@ -2581,14 +2392,10 @@ fn test_phantom_onion_hmac_failure() { let (route, phantom_scid) = get_phantom_route!(nodes, recv_value_msat, channel); // Route the HTLC through to the destination. + let recipient_onion = RecipientOnionFields::secret_only(payment_secret); nodes[0] .node - .send_payment_with_route( - route, - payment_hash, - RecipientOnionFields::secret_only(payment_secret), - PaymentId(payment_hash.0), - ) + .send_payment_with_route(route, payment_hash, recipient_onion, PaymentId(payment_hash.0)) .unwrap(); check_added_monitors!(nodes[0], 1); let update_0 = get_htlc_update_msgs!(nodes[0], nodes[1].node.get_our_node_id()); @@ -2657,14 +2464,11 @@ fn test_phantom_invalid_onion_payload() { // We'll use the session priv later when constructing an invalid onion packet. let session_priv = [3; 32]; *nodes[0].keys_manager.override_random_bytes.lock().unwrap() = Some(session_priv); + let recipient_onion = RecipientOnionFields::secret_only(payment_secret); + let payment_id = PaymentId(payment_hash.0); nodes[0] .node - .send_payment_with_route( - route.clone(), - payment_hash, - RecipientOnionFields::secret_only(payment_secret), - PaymentId(payment_hash.0), - ) + .send_payment_with_route(route.clone(), payment_hash, recipient_onion, payment_id) .unwrap(); check_added_monitors!(nodes[0], 1); let update_0 = get_htlc_update_msgs!(nodes[0], nodes[1].node.get_our_node_id()); @@ -2690,13 +2494,10 @@ fn test_phantom_invalid_onion_payload() { // Construct the onion payloads for the entire route and an invalid amount. let height = nodes[0].best_block_info().1; let session_priv = SecretKey::from_slice(&session_priv).unwrap(); - let mut onion_keys = onion_utils::construct_onion_keys( - &Secp256k1::new(), - &route.paths[0], - &session_priv, - ); + let mut onion_keys = + construct_onion_keys(&Secp256k1::new(), &route.paths[0], &session_priv); let recipient_onion_fields = RecipientOnionFields::secret_only(payment_secret); - let (mut onion_payloads, _, _) = onion_utils::build_onion_payloads( + let (mut onion_payloads, _, _) = build_onion_payloads( &route.paths[0], msgs::MAX_VALUE_MSAT + 1, &recipient_onion_fields, @@ -2763,14 +2564,10 @@ fn test_phantom_final_incorrect_cltv_expiry() { let (route, phantom_scid) = get_phantom_route!(nodes, recv_value_msat, channel); // Route the HTLC through to the destination. + let recipient_onion = RecipientOnionFields::secret_only(payment_secret); nodes[0] .node - .send_payment_with_route( - route, - payment_hash, - RecipientOnionFields::secret_only(payment_secret), - PaymentId(payment_hash.0), - ) + .send_payment_with_route(route, payment_hash, recipient_onion, PaymentId(payment_hash.0)) .unwrap(); check_added_monitors!(nodes[0], 1); let update_0 = get_htlc_update_msgs!(nodes[0], nodes[1].node.get_our_node_id()); @@ -2836,14 +2633,10 @@ fn test_phantom_failure_too_low_cltv() { route.paths[0].hops[1].cltv_expiry_delta = 5; // Route the HTLC through to the destination. + let recipient_onion = RecipientOnionFields::secret_only(payment_secret); nodes[0] .node - .send_payment_with_route( - route, - payment_hash, - RecipientOnionFields::secret_only(payment_secret), - PaymentId(payment_hash.0), - ) + .send_payment_with_route(route, payment_hash, recipient_onion, PaymentId(payment_hash.0)) .unwrap(); check_added_monitors!(nodes[0], 1); let update_0 = get_htlc_update_msgs!(nodes[0], nodes[1].node.get_our_node_id()); @@ -2893,14 +2686,10 @@ fn test_phantom_failure_modified_cltv() { let (mut route, phantom_scid) = get_phantom_route!(nodes, recv_value_msat, channel); // Route the HTLC through to the destination. + let recipient_onion = RecipientOnionFields::secret_only(payment_secret); nodes[0] .node - .send_payment_with_route( - route, - payment_hash, - RecipientOnionFields::secret_only(payment_secret), - PaymentId(payment_hash.0), - ) + .send_payment_with_route(route, payment_hash, recipient_onion, PaymentId(payment_hash.0)) .unwrap(); check_added_monitors!(nodes[0], 1); let update_0 = get_htlc_update_msgs!(nodes[0], nodes[1].node.get_our_node_id()); @@ -2952,14 +2741,10 @@ fn test_phantom_failure_expires_too_soon() { let (mut route, phantom_scid) = get_phantom_route!(nodes, recv_value_msat, channel); // Route the HTLC through to the destination. + let recipient_onion = RecipientOnionFields::secret_only(payment_secret); nodes[0] .node - .send_payment_with_route( - route, - payment_hash, - RecipientOnionFields::secret_only(payment_secret), - PaymentId(payment_hash.0), - ) + .send_payment_with_route(route, payment_hash, recipient_onion, PaymentId(payment_hash.0)) .unwrap(); check_added_monitors!(nodes[0], 1); let update_0 = get_htlc_update_msgs!(nodes[0], nodes[1].node.get_our_node_id()); @@ -3006,14 +2791,10 @@ fn test_phantom_failure_too_low_recv_amt() { let (mut route, phantom_scid) = get_phantom_route!(nodes, bad_recv_amt_msat, channel); // Route the HTLC through to the destination. + let recipient_onion = RecipientOnionFields::secret_only(payment_secret); nodes[0] .node - .send_payment_with_route( - route, - payment_hash, - RecipientOnionFields::secret_only(payment_secret), - PaymentId(payment_hash.0), - ) + .send_payment_with_route(route, payment_hash, recipient_onion, PaymentId(payment_hash.0)) .unwrap(); check_added_monitors!(nodes[0], 1); let update_0 = get_htlc_update_msgs!(nodes[0], nodes[1].node.get_our_node_id()); @@ -3079,14 +2860,11 @@ fn do_test_phantom_dust_exposure_failure(multiplier_dust_limit: bool) { let (mut route, phantom_scid) = get_phantom_route!(nodes, max_dust_exposure + 1, channel); // Route the HTLC through to the destination. + let recipient_onion = RecipientOnionFields::secret_only(payment_secret); + let payment_id = PaymentId(payment_hash.0); nodes[0] .node - .send_payment_with_route( - route.clone(), - payment_hash, - RecipientOnionFields::secret_only(payment_secret), - PaymentId(payment_hash.0), - ) + .send_payment_with_route(route.clone(), payment_hash, recipient_onion, payment_id) .unwrap(); check_added_monitors!(nodes[0], 1); let update_0 = get_htlc_update_msgs!(nodes[0], nodes[1].node.get_our_node_id()); @@ -3132,14 +2910,11 @@ fn test_phantom_failure_reject_payment() { let (mut route, phantom_scid) = get_phantom_route!(nodes, recv_amt_msat, channel); // Route the HTLC through to the destination. + let recipient_onion = RecipientOnionFields::secret_only(payment_secret); + let payment_id = PaymentId(payment_hash.0); nodes[0] .node - .send_payment_with_route( - route.clone(), - payment_hash, - RecipientOnionFields::secret_only(payment_secret), - PaymentId(payment_hash.0), - ) + .send_payment_with_route(route.clone(), payment_hash, recipient_onion, payment_id) .unwrap(); check_added_monitors!(nodes[0], 1); let update_0 = get_htlc_update_msgs!(nodes[0], nodes[1].node.get_our_node_id()); diff --git a/lightning/src/ln/outbound_payment.rs b/lightning/src/ln/outbound_payment.rs index 6d6296ebf24..00c9e60c669 100644 --- a/lightning/src/ln/outbound_payment.rs +++ b/lightning/src/ln/outbound_payment.rs @@ -1639,21 +1639,19 @@ impl OutboundPayments { failed_scid = Some(scid); route_params.payment_params.previously_failed_channels.push(scid); } - events.push_back(( - events::Event::PaymentPathFailed { - payment_id: Some(payment_id), - payment_hash, - payment_failed_permanently: false, - failure: events::PathFailure::InitialSend { err: e }, - path, - short_channel_id: failed_scid, - #[cfg(any(test, feature = "_test_utils"))] - error_code: None, - #[cfg(any(test, feature = "_test_utils"))] - error_data: None, - }, - None, - )); + let event = events::Event::PaymentPathFailed { + payment_id: Some(payment_id), + payment_hash, + payment_failed_permanently: false, + failure: events::PathFailure::InitialSend { err: e }, + path, + short_channel_id: failed_scid, + #[cfg(any(test, feature = "_test_utils"))] + error_code: None, + #[cfg(any(test, feature = "_test_utils"))] + error_data: None, + }; + events.push_back((event, None)); } } } From cc97418cdf871b6d2dbf792b7a056d40e419006e Mon Sep 17 00:00:00 2001 From: Joost Jager Date: Tue, 17 Jun 2025 16:48:33 +0200 Subject: [PATCH 3/7] Reduce hold time resolution In the spec process, it was agreed to report hold time in multiples of 100 ms. --- lightning/src/ln/channel.rs | 10 ++++++---- lightning/src/ln/onion_utils.rs | 5 ++++- 2 files changed, 10 insertions(+), 5 deletions(-) diff --git a/lightning/src/ln/channel.rs b/lightning/src/ln/channel.rs index 15b6c9aa4e0..1d3a42c2d95 100644 --- a/lightning/src/ln/channel.rs +++ b/lightning/src/ln/channel.rs @@ -62,7 +62,9 @@ use crate::ln::interactivetxs::{ }; use crate::ln::msgs; use crate::ln::msgs::{ClosingSigned, ClosingSignedFeeRange, DecodeError, OnionErrorPacket}; -use crate::ln::onion_utils::{AttributionData, HTLCFailReason, LocalHTLCFailureReason}; +use crate::ln::onion_utils::{ + AttributionData, HTLCFailReason, LocalHTLCFailureReason, HOLD_TIME_UNIT_MILLIS, +}; use crate::ln::script::{self, ShutdownScript}; use crate::ln::types::ChannelId; use crate::routing::gossip::NodeId; @@ -7168,9 +7170,9 @@ where // We really want take() here, but, again, non-mut ref :( if let OutboundHTLCOutcome::Failure(mut reason) = outcome.clone() { 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); + let elapsed_millis = now.saturating_sub(timestamp).as_millis(); + let elapsed_units = elapsed_millis / HOLD_TIME_UNIT_MILLIS; + let hold_time = u32::try_from(elapsed_units).unwrap_or(u32::MAX); reason.set_hold_time(hold_time); } diff --git a/lightning/src/ln/onion_utils.rs b/lightning/src/ln/onion_utils.rs index f6b56ef78ce..4dd6131515b 100644 --- a/lightning/src/ln/onion_utils.rs +++ b/lightning/src/ln/onion_utils.rs @@ -44,6 +44,9 @@ use crate::prelude::*; const DEFAULT_MIN_FAILURE_PACKET_LEN: usize = 256; +/// The unit size of the hold time. This is used to reduce the hold time resolution to improve privacy. +pub(crate) const HOLD_TIME_UNIT_MILLIS: u128 = 100; + pub(crate) struct OnionKeys { #[cfg(test)] pub(crate) shared_secret: SharedSecret, @@ -1223,7 +1226,7 @@ where logger, "Htlc hold time at pos {}: {} ms", route_hop_idx, - hold_time + hold_time * HOLD_TIME_UNIT_MILLIS as u32 ); // Shift attribution data to prepare for processing the next hop. From d975986434cec816226ed350cab278cb52c9cc0c Mon Sep 17 00:00:00 2001 From: Joost Jager Date: Wed, 18 Jun 2025 14:34:26 +0200 Subject: [PATCH 4/7] Expose hold times in the PaymentPathFailed event during test --- lightning-background-processor/src/lib.rs | 2 ++ lightning/src/events/mod.rs | 10 ++++++++++ lightning/src/ln/outbound_payment.rs | 5 +++++ lightning/src/util/ser.rs | 1 + 4 files changed, 18 insertions(+) diff --git a/lightning-background-processor/src/lib.rs b/lightning-background-processor/src/lib.rs index 96c8ecc798a..235bb39c7d4 100644 --- a/lightning-background-processor/src/lib.rs +++ b/lightning-background-processor/src/lib.rs @@ -2699,6 +2699,7 @@ mod tests { short_channel_id: Some(scored_scid), error_code: None, error_data: None, + hold_times: Vec::new(), }); let event = $receive.expect("PaymentPathFailed not handled within deadline"); match event { @@ -2718,6 +2719,7 @@ mod tests { short_channel_id: None, error_code: None, error_data: None, + hold_times: Vec::new(), }); let event = $receive.expect("PaymentPathFailed not handled within deadline"); match event { diff --git a/lightning/src/events/mod.rs b/lightning/src/events/mod.rs index 632d3a39af4..d01af737c32 100644 --- a/lightning/src/events/mod.rs +++ b/lightning/src/events/mod.rs @@ -1131,6 +1131,8 @@ pub enum Event { error_code: Option, #[cfg(any(test, feature = "_test_utils"))] error_data: Option>, + #[cfg(any(test, feature = "_test_utils"))] + hold_times: Vec, }, /// Indicates that a probe payment we sent returned successful, i.e., only failed at the destination. /// @@ -1696,12 +1698,16 @@ impl Writeable for Event { ref error_code, #[cfg(any(test, feature = "_test_utils"))] ref error_data, + #[cfg(any(test, feature = "_test_utils"))] + ref hold_times, } => { 3u8.write(writer)?; #[cfg(any(test, feature = "_test_utils"))] error_code.write(writer)?; #[cfg(any(test, feature = "_test_utils"))] error_data.write(writer)?; + #[cfg(any(test, feature = "_test_utils"))] + hold_times.write(writer)?; write_tlv_fields!(writer, { (0, payment_hash, required), (1, None::, option), // network_update in LDK versions prior to 0.0.114 @@ -2121,6 +2127,8 @@ impl MaybeReadable for Event { let error_code = Readable::read(reader)?; #[cfg(any(test, feature = "_test_utils"))] let error_data = Readable::read(reader)?; + #[cfg(any(test, feature = "_test_utils"))] + let hold_times = Readable::read(reader)?; let mut payment_hash = PaymentHash([0; 32]); let mut payment_failed_permanently = false; let mut network_update = None; @@ -2154,6 +2162,8 @@ impl MaybeReadable for Event { error_code, #[cfg(any(test, feature = "_test_utils"))] error_data, + #[cfg(any(test, feature = "_test_utils"))] + hold_times, })) }; f() diff --git a/lightning/src/ln/outbound_payment.rs b/lightning/src/ln/outbound_payment.rs index 00c9e60c669..40b2b0ff22c 100644 --- a/lightning/src/ln/outbound_payment.rs +++ b/lightning/src/ln/outbound_payment.rs @@ -1650,6 +1650,8 @@ impl OutboundPayments { error_code: None, #[cfg(any(test, feature = "_test_utils"))] error_data: None, + #[cfg(any(test, feature = "_test_utils"))] + hold_times: Vec::new(), }; events.push_back((event, None)); } @@ -2278,6 +2280,7 @@ impl OutboundPayments { onion_error_code, onion_error_data, failed_within_blinded_path, + hold_times, .. } = onion_error.decode_onion_failure(secp_ctx, logger, &source); #[cfg(not(any(test, feature = "_test_utils")))] @@ -2414,6 +2417,8 @@ impl OutboundPayments { error_code: onion_error_code.map(|f| f.failure_code()), #[cfg(any(test, feature = "_test_utils"))] error_data: onion_error_data, + #[cfg(any(test, feature = "_test_utils"))] + hold_times, } } }; diff --git a/lightning/src/util/ser.rs b/lightning/src/util/ser.rs index 737a558946e..c2d6484ccd6 100644 --- a/lightning/src/util/ser.rs +++ b/lightning/src/util/ser.rs @@ -1089,6 +1089,7 @@ impl_readable_for_vec!(crate::routing::router::BlindedTail); impl_for_vec!(crate::routing::router::TrampolineHop); impl_for_vec_with_element_length_prefix!(crate::ln::msgs::UpdateAddHTLC); impl_writeable_for_vec_with_element_length_prefix!(&crate::ln::msgs::UpdateAddHTLC); +impl_for_vec!(u32); impl Writeable for Vec { #[inline] From b97cc8bb8b10aa50932a07055f800c94250e0191 Mon Sep 17 00:00:00 2001 From: Joost Jager Date: Wed, 18 Jun 2025 14:38:56 +0200 Subject: [PATCH 5/7] Test hold time setting and decoding --- lightning/src/ln/onion_route_tests.rs | 74 ++++++++++++++++++++------- 1 file changed, 56 insertions(+), 18 deletions(-) diff --git a/lightning/src/ln/onion_route_tests.rs b/lightning/src/ln/onion_route_tests.rs index faf4b5a6ea7..ffd587a3a16 100644 --- a/lightning/src/ln/onion_route_tests.rs +++ b/lightning/src/ln/onion_route_tests.rs @@ -96,6 +96,7 @@ fn run_onion_failure_test( // 3: final node fails backward (but tamper onion payloads from node0) // 100: trigger error in the intermediate node and tamper returning fail_htlc // 200: trigger error in the final node and tamper returning fail_htlc +// 201: trigger error in the final node and delay fn run_onion_failure_test_with_fail_intercept( _name: &str, test_case: u8, nodes: &Vec, route: &Route, payment_hash: &PaymentHash, payment_secret: &PaymentSecret, mut callback_msg: F1, mut callback_fail: F2, @@ -160,11 +161,11 @@ fn run_onion_failure_test_with_fail_intercept( assert!(fail_len + malformed_len == 1 && (fail_len == 1 || malformed_len == 1)); update_1_0 }, - 1 | 2 | 3 | 200 => { + 1 | 2 | 3 | 200 | 201 => { // final node failure; forwarding to 2 assert!(nodes[1].node.get_and_clear_pending_msg_events().is_empty()); // forwarding on 1 - if test_case != 200 { + if test_case != 200 && test_case != 201 { callback_node(); } expect_htlc_forward!(&nodes[1]); @@ -182,20 +183,26 @@ fn run_onion_failure_test_with_fail_intercept( nodes[2].node.handle_update_add_htlc(nodes[1].node.get_our_node_id(), &update_add_1); commitment_signed_dance!(nodes[2], nodes[1], update_1.commitment_signed, false, true); - if test_case == 2 || test_case == 200 { - expect_htlc_forward!(&nodes[2]); - expect_event!(&nodes[2], Event::PaymentClaimable); - callback_node(); - expect_pending_htlcs_forwardable_and_htlc_handling_failed!( - nodes[2], - vec![HTLCHandlingFailureType::Receive { payment_hash: payment_hash.clone() }] - ); - } else if test_case == 1 || test_case == 3 { - expect_htlc_forward!(&nodes[2]); - expect_htlc_handling_failed_destinations!( - nodes[2].node.get_and_clear_pending_events(), - vec![expected_failure_type.clone().unwrap()] - ); + match test_case { + 2 | 200 | 201 => { + expect_htlc_forward!(&nodes[2]); + expect_event!(&nodes[2], Event::PaymentClaimable); + callback_node(); + expect_pending_htlcs_forwardable_and_htlc_handling_failed!( + nodes[2], + vec![HTLCHandlingFailureType::Receive { + payment_hash: payment_hash.clone() + }] + ); + }, + 1 | 3 => { + expect_htlc_forward!(&nodes[2]); + expect_htlc_handling_failed_destinations!( + nodes[2].node.get_and_clear_pending_events(), + vec![expected_failure_type.clone().unwrap()] + ); + }, + _ => {}, } check_added_monitors!(&nodes[2], 1); @@ -203,8 +210,14 @@ fn run_onion_failure_test_with_fail_intercept( assert!(update_2_1.update_fail_htlcs.len() == 1); let mut fail_msg = update_2_1.update_fail_htlcs[0].clone(); - if test_case == 200 { - callback_fail(&mut fail_msg); + match test_case { + // Trigger error in the final node and tamper returning fail_htlc. + 200 => callback_fail(&mut fail_msg), + // Trigger error in the final node and delay. + 201 => { + std::thread::sleep(std::time::Duration::from_millis(200)); + }, + _ => {}, } // 2 => 1 @@ -242,9 +255,17 @@ fn run_onion_failure_test_with_fail_intercept( ref short_channel_id, ref error_code, failure: PathFailure::OnPath { ref network_update }, + ref hold_times, .. } = &events[0] { + // When resolution is delayed, we expect that to show up in the hold times. Hold times are only reported in std. + if test_case == 201 { + #[cfg(feature = "std")] + assert!(hold_times.iter().any(|ht| *ht > 0)); + #[cfg(not(feature = "std"))] + assert!(hold_times.iter().all(|ht| *ht == 0)); + } assert_eq!(*payment_failed_permanently, !expected_retryable); assert_eq!(error_code.is_none(), expected_error_reason.is_none()); if let Some(expected_reason) = expected_error_reason { @@ -1491,6 +1512,23 @@ fn test_onion_failure() { Some(channels[1].0.contents.short_channel_id), None, ); + run_onion_failure_test( + "delayed_fail", + 201, + &nodes, + &route, + &payment_hash, + &payment_secret, + |_| {}, + || { + nodes[2].node.fail_htlc_backwards(&payment_hash); + }, + false, + Some(LocalHTLCFailureReason::IncorrectPaymentDetails), + None, + None, + None, + ); } #[test] From 0082a707b556c0048cf93a6a75f89b6265e100f8 Mon Sep 17 00:00:00 2001 From: Joost Jager Date: Tue, 17 Jun 2025 16:53:58 +0200 Subject: [PATCH 6/7] Update attribution_data tlv type to 1 Interop passed and the main specification work is done. Eclair, currently the only implementation supporting attribution data, also uses tlv type 1 already. --- lightning/src/ln/msgs.rs | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/lightning/src/ln/msgs.rs b/lightning/src/ln/msgs.rs index 2b18901591e..96b459574b4 100644 --- a/lightning/src/ln/msgs.rs +++ b/lightning/src/ln/msgs.rs @@ -3061,8 +3061,7 @@ impl_writeable_msg!(UpdateFailHTLC, { htlc_id, reason }, { - // Specified TLV key 1 plus 100 during testing phase. - (101, attribution_data, option) + (1, attribution_data, option) }); impl_writeable_msg!(UpdateFailMalformedHTLC, { @@ -5582,7 +5581,7 @@ mod tests { }), }; let encoded_value = update_fail_htlc.encode(); - let target_value = >::from_hex("020202020202020202020202020202020202020202020202020202020202020200083a840000034d0020010101010101010101010101010101010101010101010101010101010101010165fd03980303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303").unwrap(); + let target_value = >::from_hex("020202020202020202020202020202020202020202020202020202020202020200083a840000034d0020010101010101010101010101010101010101010101010101010101010101010101fd03980303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303").unwrap(); assert_eq!(encoded_value, target_value); } From 1fc1a1478db6aa634f57a36683bf91dbe950e461 Mon Sep 17 00:00:00 2001 From: Joost Jager Date: Fri, 20 Jun 2025 17:45:39 +0200 Subject: [PATCH 7/7] Remove redundant clause in onion_route_tests assertion If two unsigned ints sum to one, one of them has to be equal to one. --- lightning/src/ln/onion_route_tests.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lightning/src/ln/onion_route_tests.rs b/lightning/src/ln/onion_route_tests.rs index ffd587a3a16..0612b9abe58 100644 --- a/lightning/src/ln/onion_route_tests.rs +++ b/lightning/src/ln/onion_route_tests.rs @@ -158,7 +158,7 @@ fn run_onion_failure_test_with_fail_intercept( let update_1_0 = get_htlc_update_msgs!(nodes[1], nodes[0].node.get_our_node_id()); let fail_len = update_1_0.update_fail_htlcs.len(); let malformed_len = update_1_0.update_fail_malformed_htlcs.len(); - assert!(fail_len + malformed_len == 1 && (fail_len == 1 || malformed_len == 1)); + assert!(fail_len + malformed_len == 1); update_1_0 }, 1 | 2 | 3 | 200 | 201 => {