diff --git a/lightning/src/ln/async_payments_tests.rs b/lightning/src/ln/async_payments_tests.rs index b888b9ceb5c..1d9c6fb84c7 100644 --- a/lightning/src/ln/async_payments_tests.rs +++ b/lightning/src/ln/async_payments_tests.rs @@ -20,7 +20,7 @@ use crate::ln::msgs::{ BaseMessageHandler, ChannelMessageHandler, MessageSendEvent, OnionMessageHandler, }; use crate::ln::offers_tests; -use crate::ln::onion_utils::INVALID_ONION_BLINDING; +use crate::ln::onion_utils::LocalHTLCFailureReason; use crate::ln::outbound_payment::PendingOutboundPayment; use crate::ln::outbound_payment::Retry; use crate::offers::invoice_request::InvoiceRequest; @@ -179,7 +179,10 @@ fn invalid_keysend_payment_secret() { assert_eq!(updates_2_1.update_fail_malformed_htlcs.len(), 1); let update_malformed = &updates_2_1.update_fail_malformed_htlcs[0]; assert_eq!(update_malformed.sha256_of_onion, [0; 32]); - assert_eq!(update_malformed.failure_code, INVALID_ONION_BLINDING); + assert_eq!( + update_malformed.failure_code, + LocalHTLCFailureReason::InvalidOnionBlinding.failure_code() + ); nodes[1] .node .handle_update_fail_malformed_htlc(nodes[2].node.get_our_node_id(), update_malformed); @@ -196,7 +199,8 @@ fn invalid_keysend_payment_secret() { &nodes[0], payment_hash, false, - PaymentFailedConditions::new().expected_htlc_error_data(INVALID_ONION_BLINDING, &[0; 32]), + PaymentFailedConditions::new() + .expected_htlc_error_data(LocalHTLCFailureReason::InvalidOnionBlinding, &[0; 32]), ); } diff --git a/lightning/src/ln/blinded_payment_tests.rs b/lightning/src/ln/blinded_payment_tests.rs index 49bb82ba0a9..5d65545046a 100644 --- a/lightning/src/ln/blinded_payment_tests.rs +++ b/lightning/src/ln/blinded_payment_tests.rs @@ -26,8 +26,7 @@ use crate::ln::inbound_payment::ExpandedKey; use crate::ln::msgs; use crate::ln::msgs::{BaseMessageHandler, ChannelMessageHandler, UnsignedGossipMessage, MessageSendEvent}; use crate::ln::onion_payment; -use crate::ln::onion_utils; -use crate::ln::onion_utils::INVALID_ONION_BLINDING; +use crate::ln::onion_utils::{self, LocalHTLCFailureReason}; use crate::ln::outbound_payment::{Retry, IDEMPOTENCY_TIMEOUT_TICKS}; use crate::offers::invoice::UnsignedBolt12Invoice; use crate::offers::nonce::Nonce; @@ -118,7 +117,7 @@ pub fn fail_blinded_htlc_backwards( match i { 0 => { let mut payment_failed_conditions = PaymentFailedConditions::new() - .expected_htlc_error_data(INVALID_ONION_BLINDING, &[0; 32]); + .expected_htlc_error_data(LocalHTLCFailureReason::InvalidOnionBlinding, &[0; 32]); if retry_expected { payment_failed_conditions = payment_failed_conditions.retry_expected(); } @@ -137,7 +136,7 @@ pub fn fail_blinded_htlc_backwards( assert_eq!(blinded_node_updates.update_fail_malformed_htlcs.len(), 1); let update_malformed = &blinded_node_updates.update_fail_malformed_htlcs[0]; assert_eq!(update_malformed.sha256_of_onion, [0; 32]); - assert_eq!(update_malformed.failure_code, INVALID_ONION_BLINDING); + assert_eq!(update_malformed.failure_code, LocalHTLCFailureReason::InvalidOnionBlinding.failure_code()); nodes[i-1].node.handle_update_fail_malformed_htlc(nodes[i].node.get_our_node_id(), update_malformed); do_commitment_signed_dance(&nodes[i-1], &nodes[i], &blinded_node_updates.commitment_signed, true, false); } @@ -437,11 +436,11 @@ fn do_forward_checks_failure(check: ForwardCheckFail, intro_fails: bool) { match check { ForwardCheckFail::ForwardPayloadEncodedAsReceive => { expect_payment_failed_conditions(&nodes[0], payment_hash, false, - PaymentFailedConditions::new().expected_htlc_error_data(0x4000 | 22, &[0; 0])); + PaymentFailedConditions::new().expected_htlc_error_data(LocalHTLCFailureReason::InvalidOnionPayload, &[0; 0])); } _ => { expect_payment_failed_conditions(&nodes[0], payment_hash, false, - PaymentFailedConditions::new().expected_htlc_error_data(INVALID_ONION_BLINDING, &[0; 32])); + PaymentFailedConditions::new().expected_htlc_error_data(LocalHTLCFailureReason::InvalidOnionBlinding, &[0; 32])); } }; return @@ -469,12 +468,12 @@ fn do_forward_checks_failure(check: ForwardCheckFail, intro_fails: bool) { let mut updates = get_htlc_update_msgs!(nodes[2], nodes[1].node.get_our_node_id()); let update_malformed = &mut updates.update_fail_malformed_htlcs[0]; - assert_eq!(update_malformed.failure_code, INVALID_ONION_BLINDING); + assert_eq!(update_malformed.failure_code, LocalHTLCFailureReason::InvalidOnionBlinding.failure_code()); assert_eq!(update_malformed.sha256_of_onion, [0; 32]); // Ensure the intro node will properly blind the error if its downstream node failed to do so. update_malformed.sha256_of_onion = [1; 32]; - update_malformed.failure_code = INVALID_ONION_BLINDING ^ 1; + update_malformed.failure_code = LocalHTLCFailureReason::InvalidOnionBlinding.failure_code() ^ 1; nodes[1].node.handle_update_fail_malformed_htlc(nodes[2].node.get_our_node_id(), update_malformed); do_commitment_signed_dance(&nodes[1], &nodes[2], &updates.commitment_signed, true, false); @@ -482,7 +481,7 @@ fn do_forward_checks_failure(check: ForwardCheckFail, intro_fails: bool) { nodes[0].node.handle_update_fail_htlc(nodes[1].node.get_our_node_id(), &updates.update_fail_htlcs[0]); do_commitment_signed_dance(&nodes[0], &nodes[1], &updates.commitment_signed, false, false); expect_payment_failed_conditions(&nodes[0], payment_hash, false, - PaymentFailedConditions::new().expected_htlc_error_data(INVALID_ONION_BLINDING, &[0; 32])); + PaymentFailedConditions::new().expected_htlc_error_data(LocalHTLCFailureReason::InvalidOnionBlinding, &[0; 32])); } #[test] @@ -534,7 +533,7 @@ fn failed_backwards_to_intro_node() { let mut updates = get_htlc_update_msgs!(nodes[2], nodes[1].node.get_our_node_id()); let mut update_malformed = &mut updates.update_fail_malformed_htlcs[0]; // Check that the final node encodes its failure correctly. - assert_eq!(update_malformed.failure_code, INVALID_ONION_BLINDING); + assert_eq!(update_malformed.failure_code, LocalHTLCFailureReason::InvalidOnionBlinding.failure_code()); assert_eq!(update_malformed.sha256_of_onion, [0; 32]); // Modify such the final hop does not correctly blind their error so we can ensure the intro node @@ -547,7 +546,7 @@ fn failed_backwards_to_intro_node() { nodes[0].node.handle_update_fail_htlc(nodes[1].node.get_our_node_id(), &updates.update_fail_htlcs[0]); do_commitment_signed_dance(&nodes[0], &nodes[1], &updates.commitment_signed, false, false); expect_payment_failed_conditions(&nodes[0], payment_hash, false, - PaymentFailedConditions::new().expected_htlc_error_data(INVALID_ONION_BLINDING, &[0; 32])); + PaymentFailedConditions::new().expected_htlc_error_data(LocalHTLCFailureReason::InvalidOnionBlinding, &[0; 32])); } enum ProcessPendingHTLCsCheck { @@ -655,12 +654,12 @@ fn do_forward_fail_in_process_pending_htlc_fwds(check: ProcessPendingHTLCsCheck, let mut updates = get_htlc_update_msgs!(nodes[2], nodes[1].node.get_our_node_id()); let update_malformed = &mut updates.update_fail_malformed_htlcs[0]; - assert_eq!(update_malformed.failure_code, INVALID_ONION_BLINDING); + assert_eq!(update_malformed.failure_code, LocalHTLCFailureReason::InvalidOnionBlinding.failure_code()); assert_eq!(update_malformed.sha256_of_onion, [0; 32]); // Ensure the intro node will properly blind the error if its downstream node failed to do so. update_malformed.sha256_of_onion = [1; 32]; - update_malformed.failure_code = INVALID_ONION_BLINDING ^ 1; + update_malformed.failure_code = LocalHTLCFailureReason::InvalidOnionBlinding.failure_code() ^ 1; nodes[1].node.handle_update_fail_malformed_htlc(nodes[2].node.get_our_node_id(), update_malformed); do_commitment_signed_dance(&nodes[1], &nodes[2], &updates.commitment_signed, true, false); @@ -668,7 +667,7 @@ fn do_forward_fail_in_process_pending_htlc_fwds(check: ProcessPendingHTLCsCheck, nodes[0].node.handle_update_fail_htlc(nodes[1].node.get_our_node_id(), &updates.update_fail_htlcs[0]); do_commitment_signed_dance(&nodes[0], &nodes[1], &updates.commitment_signed, false, false); expect_payment_failed_conditions(&nodes[0], payment_hash, false, - PaymentFailedConditions::new().expected_htlc_error_data(INVALID_ONION_BLINDING, &[0; 32])); + PaymentFailedConditions::new().expected_htlc_error_data(LocalHTLCFailureReason::InvalidOnionBlinding, &[0; 32])); } #[test] @@ -1042,7 +1041,7 @@ fn do_multi_hop_receiver_fail(check: ReceiveCheckFail) { assert_eq!(updates_2_1.update_fail_malformed_htlcs.len(), 1); let update_malformed = &updates_2_1.update_fail_malformed_htlcs[0]; assert_eq!(update_malformed.sha256_of_onion, [0; 32]); - assert_eq!(update_malformed.failure_code, INVALID_ONION_BLINDING); + assert_eq!(update_malformed.failure_code, LocalHTLCFailureReason::InvalidOnionBlinding.failure_code()); nodes[1].node.handle_update_fail_malformed_htlc(nodes[2].node.get_our_node_id(), update_malformed); do_commitment_signed_dance(&nodes[1], &nodes[2], &updates_2_1.commitment_signed, true, false); @@ -1064,7 +1063,7 @@ fn do_multi_hop_receiver_fail(check: ReceiveCheckFail) { nodes[0].node.handle_update_fail_htlc(nodes[1].node.get_our_node_id(), &updates_1_0.update_fail_htlcs[0]); do_commitment_signed_dance(&nodes[0], &nodes[1], &updates_1_0.commitment_signed, false, false); expect_payment_failed_conditions(&nodes[0], payment_hash, false, - PaymentFailedConditions::new().expected_htlc_error_data(INVALID_ONION_BLINDING, &[0; 32])); + PaymentFailedConditions::new().expected_htlc_error_data(LocalHTLCFailureReason::InvalidOnionBlinding, &[0; 32])); } #[test] @@ -1131,7 +1130,7 @@ fn blinded_path_retries() { assert_eq!(updates.update_fail_malformed_htlcs.len(), 1); let update_malformed = &updates.update_fail_malformed_htlcs[0]; assert_eq!(update_malformed.sha256_of_onion, [0; 32]); - assert_eq!(update_malformed.failure_code, INVALID_ONION_BLINDING); + assert_eq!(update_malformed.failure_code, LocalHTLCFailureReason::InvalidOnionBlinding.failure_code()); $intro_node.node.handle_update_fail_malformed_htlc(nodes[3].node.get_our_node_id(), update_malformed); do_commitment_signed_dance(&$intro_node, &nodes[3], &updates.commitment_signed, true, false); @@ -1251,7 +1250,7 @@ fn min_htlc() { nodes[0].node.handle_update_fail_htlc(nodes[1].node.get_our_node_id(), &updates.update_fail_htlcs[0]); do_commitment_signed_dance(&nodes[0], &nodes[1], &updates.commitment_signed, false, false); expect_payment_failed_conditions(&nodes[0], payment_hash, false, - PaymentFailedConditions::new().expected_htlc_error_data(INVALID_ONION_BLINDING, &[0; 32])); + PaymentFailedConditions::new().expected_htlc_error_data(LocalHTLCFailureReason::InvalidOnionBlinding, &[0; 32])); } #[test] @@ -1446,7 +1445,7 @@ fn fails_receive_tlvs_authentication() { commitment_signed_dance!(nodes[0], nodes[1], update_fail.commitment_signed, false); expect_payment_failed_conditions( &nodes[0], payment_hash, true, - PaymentFailedConditions::new().expected_htlc_error_data(0x4000 | 22, &[]), + PaymentFailedConditions::new().expected_htlc_error_data(LocalHTLCFailureReason::InvalidOnionPayload, &[]), ); } @@ -1728,7 +1727,8 @@ fn route_blinding_spec_test_vector() { match onion_payment::decode_incoming_update_add_htlc_onion( &eve_update_add, &eve_node_signer, &logger, &secp_ctx ) { - Err(HTLCFailureMsg::Malformed(msg)) => assert_eq!(msg.failure_code, INVALID_ONION_BLINDING), + Err((HTLCFailureMsg::Malformed(msg), _)) => assert_eq!(msg.failure_code, + LocalHTLCFailureReason::InvalidOnionBlinding.failure_code()), _ => panic!("Unexpected error") } } @@ -2160,7 +2160,7 @@ fn do_test_trampoline_single_hop_receive(success: bool) { } { let payment_failed_conditions = PaymentFailedConditions::new() - .expected_htlc_error_data(0x4000 | 22, &[0; 0]); + .expected_htlc_error_data(LocalHTLCFailureReason::InvalidOnionPayload, &[0; 0]); expect_payment_failed_conditions(&nodes[0], payment_hash, true, payment_failed_conditions); } } @@ -2453,10 +2453,9 @@ fn test_trampoline_forward_rejection() { do_commitment_signed_dance(&nodes[0], &nodes[1], &unblinded_node_updates.commitment_signed, false, false); } { - // Expect a PERM|10 (unknown_next_peer) error while we are unable to route forwarding - // Trampoline payments. + // Expect UnknownNextPeer error while we are unable to route forwarding Trampoline payments. let payment_failed_conditions = PaymentFailedConditions::new() - .expected_htlc_error_data(0x4000 | 10, &[0; 0]); + .expected_htlc_error_data(LocalHTLCFailureReason::UnknownNextPeer, &[0; 0]); expect_payment_failed_conditions(&nodes[0], payment_hash, false, payment_failed_conditions); } } diff --git a/lightning/src/ln/channel.rs b/lightning/src/ln/channel.rs index 005ec566f0e..0625b94a758 100644 --- a/lightning/src/ln/channel.rs +++ b/lightning/src/ln/channel.rs @@ -50,7 +50,7 @@ use crate::ln::chan_utils::{ #[cfg(splicing)] use crate::ln::chan_utils::FUNDING_TRANSACTION_WITNESS_WEIGHT; use crate::ln::chan_utils; -use crate::ln::onion_utils::{HTLCFailReason, AttributionData}; +use crate::ln::onion_utils::{HTLCFailReason, LocalHTLCFailureReason, AttributionData}; use crate::chain::BestBlock; use crate::chain::chaininterface::{FeeEstimator, ConfirmationTarget, LowerBoundedFeeEstimator, fee_for_weight}; use crate::chain::channelmonitor::{ChannelMonitor, ChannelMonitorUpdate, ChannelMonitorUpdateStep, LATENCY_GRACE_PERIOD_BLOCKS}; @@ -6066,22 +6066,14 @@ impl FundedChannel where ); update_add_count += 1; }, - Err(e) => { - match e { - ChannelError::Ignore(ref msg) => { - log_info!(logger, "Failed to send HTLC with payment_hash {} due to {} in channel {}", &payment_hash, msg, &self.context.channel_id()); - // If we fail to send here, then this HTLC should - // be failed backwards. Failing to send here - // indicates that this HTLC may keep being put back - // into the holding cell without ever being - // successfully forwarded/failed/fulfilled, causing - // our counterparty to eventually close on us. - htlcs_to_fail.push((source.clone(), *payment_hash)); - }, - _ => { - panic!("Got a non-IgnoreError action trying to send holding cell HTLC"); - }, - } + Err((_, msg)) => { + log_info!(logger, "Failed to send HTLC with payment_hash {} due to {} in channel {}", &payment_hash, msg, &self.context.channel_id()); + // If we fail to send here, then this HTLC should be failed + // backwards. Failing to send here indicates that this HTLC may + // keep being put back into the holding cell without ever being + // successfully forwarded/failed/fulfilled, causing our + // counterparty to eventually close on us. + htlcs_to_fail.push((source.clone(), *payment_hash)); } } None @@ -7770,21 +7762,17 @@ impl FundedChannel where fn internal_htlc_satisfies_config( &self, htlc: &msgs::UpdateAddHTLC, amt_to_forward: u64, outgoing_cltv_value: u32, config: &ChannelConfig, - ) -> Result<(), (&'static str, u16)> { + ) -> Result<(), (&'static str, LocalHTLCFailureReason)> { let fee = amt_to_forward.checked_mul(config.forwarding_fee_proportional_millionths as u64) .and_then(|prop_fee| (prop_fee / 1000000).checked_add(config.forwarding_fee_base_msat as u64)); if fee.is_none() || htlc.amount_msat < fee.unwrap() || (htlc.amount_msat - fee.unwrap()) < amt_to_forward { - return Err(( - "Prior hop has deviated from specified fees parameters or origin node has obsolete ones", - 0x1000 | 12, // fee_insufficient - )); + return Err(("Prior hop has deviated from specified fees parameters or origin node has obsolete ones", + LocalHTLCFailureReason::FeeInsufficient)); } if (htlc.cltv_expiry as u64) < outgoing_cltv_value as u64 + config.cltv_expiry_delta as u64 { - return Err(( - "Forwarding node has tampered with the intended HTLC values or origin node has an obsolete cltv_expiry_delta", - 0x1000 | 13, // incorrect_cltv_expiry - )); + return Err(("Forwarding node has tampered with the intended HTLC values or origin node has an obsolete cltv_expiry_delta", + LocalHTLCFailureReason::IncorrectCLTVExpiry)); } Ok(()) } @@ -7794,7 +7782,7 @@ impl FundedChannel where /// unsuccessful, falls back to the previous one if one exists. pub fn htlc_satisfies_config( &self, htlc: &msgs::UpdateAddHTLC, amt_to_forward: u64, outgoing_cltv_value: u32, - ) -> Result<(), (&'static str, u16)> { + ) -> Result<(), (&'static str, LocalHTLCFailureReason)> { self.internal_htlc_satisfies_config(&htlc, amt_to_forward, outgoing_cltv_value, &self.context.config()) .or_else(|err| { if let Some(prev_config) = self.context.prev_config() { @@ -7809,13 +7797,13 @@ impl FundedChannel where /// this function determines whether to fail the HTLC, or forward / claim it. pub fn can_accept_incoming_htlc( &self, msg: &msgs::UpdateAddHTLC, fee_estimator: &LowerBoundedFeeEstimator, logger: L - ) -> Result<(), (&'static str, u16)> + ) -> Result<(), (&'static str, LocalHTLCFailureReason)> where F::Target: FeeEstimator, L::Target: Logger { if self.context.channel_state.is_local_shutdown_sent() { - return Err(("Shutdown was already sent", 0x4000|8)) + return Err(("Shutdown was already sent", LocalHTLCFailureReason::ChannelClosed)) } let dust_exposure_limiting_feerate = self.context.get_dust_exposure_limiting_feerate(&fee_estimator); @@ -7826,7 +7814,8 @@ impl FundedChannel where // Note that the total dust exposure includes both the dust HTLCs and the excess mining fees of the counterparty commitment transaction log_info!(logger, "Cannot accept value that would put our total dust exposure at {} over the limit {} on counterparty commitment tx", on_counterparty_tx_dust_htlc_exposure_msat, max_dust_htlc_exposure_msat); - return Err(("Exceeded our total dust exposure limit on counterparty commitment tx", 0x1000|7)) + return Err(("Exceeded our total dust exposure limit on counterparty commitment tx", + LocalHTLCFailureReason::DustLimitCounterparty)) } let htlc_success_dust_limit = if self.funding.get_channel_type().supports_anchors_zero_fee_htlc_tx() { 0 @@ -7840,7 +7829,8 @@ impl FundedChannel where if on_holder_tx_dust_htlc_exposure_msat > max_dust_htlc_exposure_msat { log_info!(logger, "Cannot accept value that would put our exposure to dust HTLCs at {} over the limit {} on holder commitment tx", on_holder_tx_dust_htlc_exposure_msat, max_dust_htlc_exposure_msat); - return Err(("Exceeded our dust exposure limit on holder commitment tx", 0x1000|7)) + return Err(("Exceeded our dust exposure limit on holder commitment tx", + LocalHTLCFailureReason::DustLimitHolder)) } } @@ -7878,7 +7868,7 @@ impl FundedChannel where } if pending_remote_value_msat.saturating_sub(self.funding.holder_selected_channel_reserve_satoshis * 1000).saturating_sub(anchor_outputs_value_msat) < remote_fee_cost_incl_stuck_buffer_msat { log_info!(logger, "Attempting to fail HTLC due to fee spike buffer violation in channel {}. Rebalancing is required.", &self.context.channel_id()); - return Err(("Fee spike buffer violation", 0x1000|7)); + return Err(("Fee spike buffer violation", LocalHTLCFailureReason::FeeSpikeBuffer)); } } @@ -8748,13 +8738,11 @@ impl FundedChannel where /// Queues up an outbound HTLC to send by placing it in the holding cell. You should call /// [`Self::maybe_free_holding_cell_htlcs`] in order to actually generate and send the /// commitment update. - /// - /// `Err`s will only be [`ChannelError::Ignore`]. pub fn queue_add_htlc( &mut self, amount_msat: u64, payment_hash: PaymentHash, cltv_expiry: u32, source: HTLCSource, onion_routing_packet: msgs::OnionPacket, skimmed_fee_msat: Option, blinding_point: Option, fee_estimator: &LowerBoundedFeeEstimator, logger: &L - ) -> Result<(), ChannelError> + ) -> Result<(), (LocalHTLCFailureReason, String)> where F::Target: FeeEstimator, L::Target: Logger { self @@ -8762,8 +8750,7 @@ impl FundedChannel where skimmed_fee_msat, blinding_point, fee_estimator, logger) .map(|msg_opt| assert!(msg_opt.is_none(), "We forced holding cell?")) .map_err(|err| { - if let ChannelError::Ignore(_) = err { /* fine */ } - else { debug_assert!(false, "Queueing cannot trigger channel failure"); } + debug_assert!(err.0.is_temporary(), "Queuing HTLC should return temporary error"); err }) } @@ -8783,38 +8770,40 @@ impl FundedChannel where /// You MUST call [`Self::send_commitment_no_state_update`] prior to calling any other methods /// on this [`FundedChannel`] if `force_holding_cell` is false. /// - /// `Err`s will only be [`ChannelError::Ignore`]. + /// `Err`'s will always be temporary channel failures. fn send_htlc( &mut self, amount_msat: u64, payment_hash: PaymentHash, cltv_expiry: u32, source: HTLCSource, onion_routing_packet: msgs::OnionPacket, mut force_holding_cell: bool, skimmed_fee_msat: Option, blinding_point: Option, fee_estimator: &LowerBoundedFeeEstimator, logger: &L - ) -> Result, ChannelError> + ) -> Result, (LocalHTLCFailureReason, String)> where F::Target: FeeEstimator, L::Target: Logger { if !matches!(self.context.channel_state, ChannelState::ChannelReady(_)) || self.context.channel_state.is_local_shutdown_sent() || self.context.channel_state.is_remote_shutdown_sent() { - return Err(ChannelError::Ignore("Cannot send HTLC until channel is fully established and we haven't started shutting down".to_owned())); + return Err((LocalHTLCFailureReason::ChannelNotReady, + "Cannot send HTLC until channel is fully established and we haven't started shutting down".to_owned())); } let channel_total_msat = self.funding.get_value_satoshis() * 1000; if amount_msat > channel_total_msat { - return Err(ChannelError::Ignore(format!("Cannot send amount {}, because it is more than the total value of the channel {}", amount_msat, channel_total_msat))); + return Err((LocalHTLCFailureReason::AmountExceedsCapacity, + format!("Cannot send amount {}, because it is more than the total value of the channel {}", amount_msat, channel_total_msat))); } if amount_msat == 0 { - return Err(ChannelError::Ignore("Cannot send 0-msat HTLC".to_owned())); + return Err((LocalHTLCFailureReason::ZeroAmount, "Cannot send 0-msat HTLC".to_owned())); } let available_balances = self.get_available_balances(fee_estimator); if amount_msat < available_balances.next_outbound_htlc_minimum_msat { - return Err(ChannelError::Ignore(format!("Cannot send less than our next-HTLC minimum - {} msat", + return Err((LocalHTLCFailureReason::HTLCMinimum, format!("Cannot send less than our next-HTLC minimum - {} msat", available_balances.next_outbound_htlc_minimum_msat))); } if amount_msat > available_balances.next_outbound_htlc_limit_msat { - return Err(ChannelError::Ignore(format!("Cannot send more than our next-HTLC maximum - {} msat", + return Err((LocalHTLCFailureReason::HTLCMaximum, format!("Cannot send more than our next-HTLC maximum - {} msat", available_balances.next_outbound_htlc_limit_msat))); } @@ -8825,7 +8814,8 @@ impl FundedChannel where // disconnected during the time the previous hop was doing the commitment dance we may // end up getting here after the forwarding delay. In any case, returning an // IgnoreError will get ChannelManager to do the right thing and fail backwards now. - return Err(ChannelError::Ignore("Cannot send an HTLC while disconnected from channel counterparty".to_owned())); + return Err((LocalHTLCFailureReason::PeerOffline, + "Cannot send an HTLC while disconnected from channel counterparty".to_owned())); } let need_holding_cell = !self.context.channel_state.can_generate_new_commitment(); @@ -9114,8 +9104,8 @@ impl FundedChannel where { let send_res = self.send_htlc(amount_msat, payment_hash, cltv_expiry, source, onion_routing_packet, false, skimmed_fee_msat, None, fee_estimator, logger); - if let Err(e) = &send_res { if let ChannelError::Ignore(_) = e {} else { debug_assert!(false, "Sending cannot trigger channel failure"); } } - match send_res? { + // All [`LocalHTLCFailureReason`] errors are temporary, so they are [`ChannelError::Ignore`]. + match send_res.map_err(|(_, msg)| ChannelError::Ignore(msg))? { Some(_) => { let monitor_update = self.build_commitment_no_status_check(logger); self.monitor_updating_paused(false, true, false, Vec::new(), Vec::new(), Vec::new()); @@ -11485,7 +11475,7 @@ mod tests { use bitcoin::network::Network; #[cfg(splicing)] use bitcoin::Weight; - use crate::ln::onion_utils::{AttributionData, INVALID_ONION_BLINDING}; + use crate::ln::onion_utils::{AttributionData, LocalHTLCFailureReason}; use crate::types::payment::{PaymentHash, PaymentPreimage}; use crate::ln::channel_keys::{RevocationKey, RevocationBasepoint}; use crate::ln::channelmanager::{self, HTLCSource, PaymentId}; @@ -12127,7 +12117,8 @@ mod tests { htlc_id, err_packet: msgs::OnionErrorPacket { data: vec![42], attribution_data: Some(AttributionData::new()) } }; let dummy_holding_cell_malformed_htlc = |htlc_id| HTLCUpdateAwaitingACK::FailMalformedHTLC { - htlc_id, failure_code: INVALID_ONION_BLINDING, sha256_of_onion: [0; 32], + htlc_id, failure_code: LocalHTLCFailureReason::InvalidOnionBlinding.failure_code(), + sha256_of_onion: [0; 32], }; let mut holding_cell_htlc_updates = Vec::with_capacity(12); for i in 0..12 { diff --git a/lightning/src/ln/channelmanager.rs b/lightning/src/ln/channelmanager.rs index d0a73e89992..8cd5aa99e5d 100644 --- a/lightning/src/ln/channelmanager.rs +++ b/lightning/src/ln/channelmanager.rs @@ -60,7 +60,7 @@ use crate::routing::router::{BlindedTail, InFlightHtlcs, Path, Payee, PaymentPar use crate::ln::onion_payment::{check_incoming_htlc_cltv, create_recv_pending_htlc_info, create_fwd_pending_htlc_info, decode_incoming_update_add_htlc_onion, HopConnector, InboundHTLCErr, NextPacketDetails}; use crate::ln::msgs; use crate::ln::onion_utils::{self}; -use crate::ln::onion_utils::{HTLCFailReason, INVALID_ONION_BLINDING}; +use crate::ln::onion_utils::{HTLCFailReason, LocalHTLCFailureReason}; use crate::ln::msgs::{BaseMessageHandler, ChannelMessageHandler, CommitmentUpdate, DecodeError, LightningError, MessageSendEvent}; #[cfg(test)] use crate::ln::outbound_payment; @@ -87,6 +87,8 @@ use crate::util::string::UntrustedString; use crate::util::ser::{BigSize, FixedLengthReader, LengthReadable, Readable, ReadableArgs, MaybeReadable, Writeable, Writer, VecWriter}; use crate::util::logger::{Level, Logger, WithContext}; use crate::util::errors::APIError; +use super::onion_payment::invalid_payment_err_data; + #[cfg(async_payments)] use { crate::offers::offer::Amount, crate::offers::static_invoice::{DEFAULT_RELATIVE_EXPIRY as STATIC_INVOICE_DEFAULT_RELATIVE_EXPIRY, StaticInvoice, StaticInvoiceBuilder}, @@ -400,10 +402,11 @@ pub(super) enum HTLCForwardInfo { #[derive(Clone, Copy, Debug, Hash, PartialEq, Eq)] pub enum BlindedFailure { /// This HTLC is being failed backwards by the introduction node, and thus should be failed with - /// [`msgs::UpdateFailHTLC`] and error code `0x8000|0x4000|24`. + /// [`msgs::UpdateFailHTLC`] and error code [`LocalHTLCFailureReason::InvalidOnionBlinding`]. FromIntroductionNode, /// This HTLC is being failed backwards by a blinded node within the path, and thus should be - /// failed with [`msgs::UpdateFailMalformedHTLC`] and error code `0x8000|0x4000|24`. + /// failed with [`msgs::UpdateFailMalformedHTLC`] and error code + /// [`LocalHTLCFailureReason::InvalidOnionBlinding`]. FromBlindedNode, } @@ -770,13 +773,13 @@ pub enum FailureCode { InvalidOnionPayload(Option<(u64, u16)>), } -impl Into for FailureCode { - fn into(self) -> u16 { +impl Into for FailureCode { + fn into(self) -> LocalHTLCFailureReason { match self { - FailureCode::TemporaryNodeFailure => 0x2000 | 2, - FailureCode::RequiredNodeFeatureMissing => 0x4000 | 0x2000 | 3, - FailureCode::IncorrectOrUnknownPaymentDetails => 0x4000 | 15, - FailureCode::InvalidOnionPayload(_) => 0x4000 | 22, + FailureCode::TemporaryNodeFailure => LocalHTLCFailureReason::TemporaryNodeFailure, + FailureCode::RequiredNodeFeatureMissing => LocalHTLCFailureReason::RequiredNodeFeature, + FailureCode::IncorrectOrUnknownPaymentDetails => LocalHTLCFailureReason::IncorrectPaymentDetails, + FailureCode::InvalidOnionPayload(_) => LocalHTLCFailureReason::InvalidOnionPayload, } } } @@ -3920,7 +3923,8 @@ where } for htlc_source in failed_htlcs.drain(..) { - let reason = HTLCFailReason::from_failure_code(0x4000 | 8); + let failure_reason = LocalHTLCFailureReason::ChannelClosed; + let reason = HTLCFailReason::from_failure_code(failure_reason); let receiver = HTLCDestination::NextHopChannel { node_id: Some(*counterparty_node_id), channel_id: *channel_id }; self.fail_htlc_backwards_internal(&htlc_source.0, &htlc_source.1, &reason, receiver); } @@ -4043,7 +4047,8 @@ where shutdown_res.closure_reason, shutdown_res.dropped_outbound_htlcs.len()); for htlc_source in shutdown_res.dropped_outbound_htlcs.drain(..) { let (source, payment_hash, counterparty_node_id, channel_id) = htlc_source; - let reason = HTLCFailReason::from_failure_code(0x4000 | 8); + let failure_reason = LocalHTLCFailureReason::ChannelClosed; + let reason = HTLCFailReason::from_failure_code(failure_reason); let receiver = HTLCDestination::NextHopChannel { node_id: Some(counterparty_node_id), channel_id }; self.fail_htlc_backwards_internal(&source, &payment_hash, &reason, receiver); } @@ -4339,22 +4344,25 @@ where fn can_forward_htlc_to_outgoing_channel( &self, chan: &mut FundedChannel, msg: &msgs::UpdateAddHTLC, next_packet: &NextPacketDetails - ) -> Result<(), (&'static str, u16)> { + ) -> Result<(), (&'static str, LocalHTLCFailureReason)> { if !chan.context.should_announce() && !self.default_configuration.accept_forwards_to_priv_channels { // Note that the behavior here should be identical to the above block - we // should NOT reveal the existence or non-existence of a private channel if // we don't allow forwards outbound over them. - return Err(("Refusing to forward to a private channel based on our config.", 0x4000 | 10)); + return Err(("Refusing to forward to a private channel based on our config.", + LocalHTLCFailureReason::PrivateChannelForward)); } if let HopConnector::ShortChannelId(outgoing_scid) = next_packet.outgoing_connector { if chan.funding.get_channel_type().supports_scid_privacy() && outgoing_scid != chan.context.outbound_scid_alias() { // `option_scid_alias` (referred to in LDK as `scid_privacy`) means // "refuse to forward unless the SCID alias was used", so we pretend // we don't have the channel here. - return Err(("Refusing to forward over real channel SCID as our counterparty requested.", 0x4000 | 10)); + return Err(("Refusing to forward over real channel SCID as our counterparty requested.", + LocalHTLCFailureReason::RealSCIDForward)); } } else { - return Err(("Cannot forward by Node ID without SCID.", 0x4000 | 10)); + return Err(("Cannot forward by Node ID without SCID.", + LocalHTLCFailureReason::InvalidTrampolineForward)); } // Note that we could technically not return an error yet here and just hope @@ -4364,19 +4372,18 @@ where // on a small/per-node/per-channel scale. if !chan.context.is_live() { if !chan.context.is_enabled() { - // channel_disabled - return Err(("Forwarding channel has been disconnected for some time.", 0x1000 | 20)); + return Err(("Forwarding channel has been disconnected for some time.", + LocalHTLCFailureReason::ChannelDisabled)); } else { - // temporary_channel_failure - return Err(("Forwarding channel is not in a ready state.", 0x1000 | 7)); + return Err(("Forwarding channel is not in a ready state.", + LocalHTLCFailureReason::ChannelNotReady)); } } - if next_packet.outgoing_amt_msat < chan.context.get_counterparty_htlc_minimum_msat() { // amount_below_minimum - return Err(("HTLC amount was below the htlc_minimum_msat", 0x1000 | 11)); - } - if let Err((err, code)) = chan.htlc_satisfies_config(msg, next_packet.outgoing_amt_msat, next_packet.outgoing_cltv_value) { - return Err((err, code)); + if next_packet.outgoing_amt_msat < chan.context.get_counterparty_htlc_minimum_msat() { + return Err(("HTLC amount was below the htlc_minimum_msat", + LocalHTLCFailureReason::AmountBelowMinimum)); } + chan.htlc_satisfies_config(msg, next_packet.outgoing_amt_msat, next_packet.outgoing_cltv_value)?; Ok(()) } @@ -4405,11 +4412,12 @@ where fn can_forward_htlc( &self, msg: &msgs::UpdateAddHTLC, next_packet_details: &NextPacketDetails - ) -> Result<(), (&'static str, u16)> { + ) -> Result<(), (&'static str, LocalHTLCFailureReason)> { let outgoing_scid = match next_packet_details.outgoing_connector { HopConnector::ShortChannelId(scid) => scid, HopConnector::Trampoline(_) => { - return Err(("Cannot forward by Node ID without SCID.", 0x4000 | 10)); + return Err(("Cannot forward by Node ID without SCID.", + LocalHTLCFailureReason::InvalidTrampolineForward)); } }; match self.do_funded_channel_callback(outgoing_scid, |chan: &mut FundedChannel| { @@ -4424,36 +4432,34 @@ where fake_scid::is_valid_intercept(&self.fake_scid_rand_bytes, outgoing_scid, &self.chain_hash)) || fake_scid::is_valid_phantom(&self.fake_scid_rand_bytes, outgoing_scid, &self.chain_hash) {} else { - return Err(("Don't have available channel for forwarding as requested.", 0x4000 | 10)); + return Err(("Don't have available channel for forwarding as requested.", + LocalHTLCFailureReason::UnknownNextPeer)); } } } let cur_height = self.best_block.read().unwrap().height + 1; - if let Err((err_msg, err_code)) = check_incoming_htlc_cltv( - cur_height, next_packet_details.outgoing_cltv_value, msg.cltv_expiry - ) { - return Err((err_msg, err_code)); - } + check_incoming_htlc_cltv(cur_height, next_packet_details.outgoing_cltv_value, msg.cltv_expiry)?; Ok(()) } fn htlc_failure_from_update_add_err( &self, msg: &msgs::UpdateAddHTLC, counterparty_node_id: &PublicKey, err_msg: &'static str, - err_code: u16, is_intro_node_blinded_forward: bool, + reason: LocalHTLCFailureReason, is_intro_node_blinded_forward: bool, shared_secret: &[u8; 32] ) -> HTLCFailureMsg { // at capacity, we write fields `htlc_msat` and `len` let mut res = VecWriter(Vec::with_capacity(8 + 2)); - if err_code & 0x1000 == 0x1000 { - if err_code == 0x1000 | 11 || err_code == 0x1000 | 12 { + if reason.is_temporary() { + if reason == LocalHTLCFailureReason::AmountBelowMinimum || + reason == LocalHTLCFailureReason::FeeInsufficient { msg.amount_msat.write(&mut res).expect("Writes cannot fail"); } - else if err_code == 0x1000 | 13 { + else if reason == LocalHTLCFailureReason::IncorrectCLTVExpiry { msg.cltv_expiry.write(&mut res).expect("Writes cannot fail"); } - else if err_code == 0x1000 | 20 { + else if reason == LocalHTLCFailureReason::ChannelDisabled { // TODO: underspecified, follow https://github.com/lightning/bolts/issues/791 0u16.write(&mut res).expect("Writes cannot fail"); } @@ -4471,16 +4477,16 @@ where channel_id: msg.channel_id, htlc_id: msg.htlc_id, sha256_of_onion: [0; 32], - failure_code: INVALID_ONION_BLINDING, + failure_code: LocalHTLCFailureReason::InvalidOnionBlinding.failure_code(), }); } - let (err_code, err_data) = if is_intro_node_blinded_forward { - (INVALID_ONION_BLINDING, &[0; 32][..]) + let (reason, err_data) = if is_intro_node_blinded_forward { + (LocalHTLCFailureReason::InvalidOnionBlinding, &[0; 32][..]) } else { - (err_code, &res.0[..]) + (reason, &res.0[..]) }; - let failure = HTLCFailReason::reason(err_code, err_data.to_vec()) + let failure = HTLCFailReason::reason(reason, err_data.to_vec()) .get_encrypted_failure_packet(shared_secret, &None); HTLCFailureMsg::Relay(msgs::UpdateFailHTLC { channel_id: msg.channel_id, @@ -4490,68 +4496,58 @@ where }) } - fn construct_pending_htlc_status<'a>( - &self, msg: &msgs::UpdateAddHTLC, counterparty_node_id: &PublicKey, shared_secret: [u8; 32], - decoded_hop: onion_utils::Hop, allow_underpay: bool, - next_packet_pubkey_opt: Option>, - ) -> PendingHTLCStatus { - macro_rules! return_err { - ($msg: expr, $err_code: expr, $data: expr) => { - { - let logger = WithContext::from(&self.logger, Some(*counterparty_node_id), Some(msg.channel_id), Some(msg.payment_hash)); - log_info!(logger, "Failed to accept/forward incoming HTLC: {}", $msg); - if msg.blinding_point.is_some() { - return PendingHTLCStatus::Fail(HTLCFailureMsg::Malformed( - msgs::UpdateFailMalformedHTLC { - channel_id: msg.channel_id, - htlc_id: msg.htlc_id, - sha256_of_onion: [0; 32], - failure_code: INVALID_ONION_BLINDING, - } - )) - } - let failure = HTLCFailReason::reason($err_code, $data.to_vec()) - .get_encrypted_failure_packet(&shared_secret, &None); - return PendingHTLCStatus::Fail(HTLCFailureMsg::Relay(msgs::UpdateFailHTLC { - channel_id: msg.channel_id, - htlc_id: msg.htlc_id, - reason: failure.data, - attribution_data: failure.attribution_data, - })); + fn construct_pending_htlc_fail_msg<'a>( + &self, msg: &msgs::UpdateAddHTLC, counterparty_node_id: &PublicKey, + shared_secret: [u8; 32], inbound_err: InboundHTLCErr + ) -> HTLCFailureMsg { + let logger = WithContext::from(&self.logger, Some(*counterparty_node_id), Some(msg.channel_id), Some(msg.payment_hash)); + log_info!(logger, "Failed to accept/forward incoming HTLC: {}", inbound_err.msg); + + if msg.blinding_point.is_some() { + return HTLCFailureMsg::Malformed( + msgs::UpdateFailMalformedHTLC { + channel_id: msg.channel_id, + htlc_id: msg.htlc_id, + sha256_of_onion: [0; 32], + failure_code: LocalHTLCFailureReason::InvalidOnionBlinding.failure_code(), } - } + ) } + + let failure = HTLCFailReason::reason(inbound_err.reason, inbound_err.err_data.to_vec()) + .get_encrypted_failure_packet(&shared_secret, &None); + return HTLCFailureMsg::Relay(msgs::UpdateFailHTLC { + channel_id: msg.channel_id, + htlc_id: msg.htlc_id, + reason: failure.data, + attribution_data: failure.attribution_data, + }); + } + + fn get_pending_htlc_info<'a>( + &self, msg: &msgs::UpdateAddHTLC, shared_secret: [u8; 32], + decoded_hop: onion_utils::Hop, allow_underpay: bool, + next_packet_pubkey_opt: Option>, + ) -> Result { match decoded_hop { onion_utils::Hop::Receive { .. } | onion_utils::Hop::BlindedReceive { .. } | onion_utils::Hop::TrampolineReceive { .. } | onion_utils::Hop::TrampolineBlindedReceive { .. } => { // OUR PAYMENT! + // Note that we could obviously respond immediately with an update_fulfill_htlc + // message, however that would leak that we are the recipient of this payment, so + // instead we stay symmetric with the forwarding case, only responding (after a + // delay) once they've send us a commitment_signed! let current_height: u32 = self.best_block.read().unwrap().height; - match create_recv_pending_htlc_info(decoded_hop, shared_secret, msg.payment_hash, + create_recv_pending_htlc_info(decoded_hop, shared_secret, msg.payment_hash, msg.amount_msat, msg.cltv_expiry, None, allow_underpay, msg.skimmed_fee_msat, current_height) - { - Ok(info) => { - // Note that we could obviously respond immediately with an update_fulfill_htlc - // message, however that would leak that we are the recipient of this payment, so - // instead we stay symmetric with the forwarding case, only responding (after a - // delay) once they've sent us a commitment_signed! - PendingHTLCStatus::Forward(info) - }, - Err(InboundHTLCErr { err_code, err_data, msg }) => return_err!(msg, err_code, &err_data) - } }, onion_utils::Hop::Forward { .. } | onion_utils::Hop::BlindedForward { .. } => { - match create_fwd_pending_htlc_info(msg, decoded_hop, shared_secret, next_packet_pubkey_opt) { - Ok(info) => PendingHTLCStatus::Forward(info), - Err(InboundHTLCErr { err_code, err_data, msg }) => return_err!(msg, err_code, &err_data) - } + create_fwd_pending_htlc_info(msg, decoded_hop, shared_secret, next_packet_pubkey_opt) }, onion_utils::Hop::TrampolineForward { .. } | onion_utils::Hop::TrampolineBlindedForward { .. } => { - match create_fwd_pending_htlc_info(msg, decoded_hop, shared_secret, next_packet_pubkey_opt) { - Ok(info) => PendingHTLCStatus::Forward(info), - Err(InboundHTLCErr { err_code, err_data, msg }) => return_err!(msg, err_code, &err_data) - } - } + create_fwd_pending_htlc_info(msg, decoded_hop, shared_secret, next_packet_pubkey_opt) + }, } } @@ -5737,9 +5733,9 @@ where cltv_expiry: incoming_cltv_expiry, }); - let failure_reason = HTLCFailReason::from_failure_code(0x4000 | 10); + let reason = HTLCFailReason::from_failure_code(LocalHTLCFailureReason::UnknownNextPeer); let destination = HTLCDestination::UnknownNextHop { requested_forward_scid: short_channel_id }; - self.fail_htlc_backwards_internal(&htlc_source, &payment.forward_info.payment_hash, &failure_reason, destination); + self.fail_htlc_backwards_internal(&htlc_source, &payment.forward_info.payment_hash, &reason, destination); } else { unreachable!() } // Only `PendingHTLCRouting::Forward`s are intercepted Ok(()) @@ -5792,7 +5788,7 @@ where &update_add_htlc, &*self.node_signer, &*self.logger, &self.secp_ctx ) { Ok(decoded_onion) => decoded_onion, - Err(htlc_fail) => { + Err((htlc_fail, _)) => { htlc_fails.push((htlc_fail, HTLCDestination::InvalidOnion)); continue; }, @@ -5815,9 +5811,9 @@ where ) }) { Some(Ok(_)) => {}, - Some(Err((err, code))) => { + Some(Err((err, reason))) => { let htlc_fail = self.htlc_failure_from_update_add_err( - &update_add_htlc, &incoming_counterparty_node_id, err, code, + &update_add_htlc, &incoming_counterparty_node_id, err, reason, is_intro_node_blinded_forward, &shared_secret, ); let htlc_destination = get_failed_htlc_destination(outgoing_scid_opt, update_add_htlc.payment_hash); @@ -5830,11 +5826,11 @@ where // Now process the HTLC on the outgoing channel if it's a forward. if let Some(next_packet_details) = next_packet_details_opt.as_ref() { - if let Err((err, code)) = self.can_forward_htlc( + if let Err((err, reason)) = self.can_forward_htlc( &update_add_htlc, next_packet_details ) { let htlc_fail = self.htlc_failure_from_update_add_err( - &update_add_htlc, &incoming_counterparty_node_id, err, code, + &update_add_htlc, &incoming_counterparty_node_id, err, reason, is_intro_node_blinded_forward, &shared_secret, ); let htlc_destination = get_failed_htlc_destination(outgoing_scid_opt, update_add_htlc.payment_hash); @@ -5843,15 +5839,14 @@ where } } - match self.construct_pending_htlc_status( - &update_add_htlc, &incoming_counterparty_node_id, shared_secret, next_hop, - incoming_accept_underpaying_htlcs, next_packet_details_opt.map(|d| d.next_packet_pubkey), + match self.get_pending_htlc_info( + &update_add_htlc, shared_secret, next_hop, incoming_accept_underpaying_htlcs, + next_packet_details_opt.map(|d| d.next_packet_pubkey), ) { - PendingHTLCStatus::Forward(htlc_forward) => { - htlc_forwards.push((htlc_forward, update_add_htlc.htlc_id)); - }, - PendingHTLCStatus::Fail(htlc_fail) => { + Ok(info) => htlc_forwards.push((info, update_add_htlc.htlc_id)), + Err(inbound_err) => { let htlc_destination = get_failed_htlc_destination(outgoing_scid_opt, update_add_htlc.payment_hash); + let htlc_fail = self.construct_pending_htlc_fail_msg(&update_add_htlc, &incoming_counterparty_node_id, shared_secret, inbound_err); htlc_fails.push((htlc_fail, htlc_destination)); }, } @@ -5917,7 +5912,7 @@ where }) => { let cltv_expiry = routing.incoming_cltv_expiry(); macro_rules! failure_handler { - ($msg: expr, $err_code: expr, $err_data: expr, $phantom_ss: expr, $next_hop_unknown: expr) => { + ($msg: expr, $reason: expr, $err_data: expr, $phantom_ss: expr, $next_hop_unknown: expr) => { let logger = WithContext::from(&self.logger, forwarding_counterparty, Some(prev_channel_id), Some(payment_hash)); log_info!(logger, "Failed to accept/forward incoming HTLC: {}", $msg); @@ -5941,23 +5936,23 @@ where }; failed_forwards.push((htlc_source, payment_hash, - HTLCFailReason::reason($err_code, $err_data), + HTLCFailReason::reason($reason, $err_data), reason )); continue; } } macro_rules! fail_forward { - ($msg: expr, $err_code: expr, $err_data: expr, $phantom_ss: expr) => { + ($msg: expr, $reason: expr, $err_data: expr, $phantom_ss: expr) => { { - failure_handler!($msg, $err_code, $err_data, $phantom_ss, true); + failure_handler!($msg, $reason, $err_data, $phantom_ss, true); } } } macro_rules! failed_payment { - ($msg: expr, $err_code: expr, $err_data: expr, $phantom_ss: expr) => { + ($msg: expr, $reason: expr, $err_data: expr, $phantom_ss: expr) => { { - failure_handler!($msg, $err_code, $err_data, $phantom_ss, false); + failure_handler!($msg, $reason, $err_data, $phantom_ss, false); } } } @@ -5969,17 +5964,17 @@ where onion_packet.hmac, payment_hash, None, &*self.node_signer ) { Ok(res) => res, - Err(onion_utils::OnionDecodeErr::Malformed { err_msg, err_code }) => { + Err(onion_utils::OnionDecodeErr::Malformed { err_msg, reason }) => { let sha256_of_onion = Sha256::hash(&onion_packet.hop_data).to_byte_array(); // In this scenario, the phantom would have sent us an // `update_fail_malformed_htlc`, meaning here we encrypt the error as // if it came from us (the second-to-last hop) but contains the sha256 // of the onion. - failed_payment!(err_msg, err_code, sha256_of_onion.to_vec(), None); + failed_payment!(err_msg, reason, sha256_of_onion.to_vec(), None); }, - Err(onion_utils::OnionDecodeErr::Relay { err_msg, err_code, shared_secret, .. }) => { + Err(onion_utils::OnionDecodeErr::Relay { err_msg, reason, shared_secret, .. }) => { let phantom_shared_secret = shared_secret.secret_bytes(); - failed_payment!(err_msg, err_code, Vec::new(), Some(phantom_shared_secret)); + failed_payment!(err_msg, reason, Vec::new(), Some(phantom_shared_secret)); }, }; let phantom_shared_secret = next_hop.shared_secret().secret_bytes(); @@ -5993,13 +5988,15 @@ where prev_short_channel_id, prev_counterparty_node_id, prev_funding_outpoint, prev_channel_id, prev_user_channel_id, vec![(info, prev_htlc_id)] )), - Err(InboundHTLCErr { err_code, err_data, msg }) => failed_payment!(msg, err_code, err_data, Some(phantom_shared_secret)) + Err(InboundHTLCErr { reason, err_data, msg }) => failed_payment!(msg, reason, err_data, Some(phantom_shared_secret)) } } else { - fail_forward!(format!("Unknown short channel id {} for forward HTLC", short_chan_id), 0x4000 | 10, Vec::new(), None); + fail_forward!(format!("Unknown short channel id {} for forward HTLC", short_chan_id), + LocalHTLCFailureReason::UnknownNextPeer, Vec::new(), None); } } else { - fail_forward!(format!("Unknown short channel id {} for forward HTLC", short_chan_id), 0x4000 | 10, Vec::new(), None); + fail_forward!(format!("Unknown short channel id {} for forward HTLC", short_chan_id), + LocalHTLCFailureReason::UnknownNextPeer, Vec::new(), None); } }, HTLCForwardInfo::FailHTLC { .. } | HTLCForwardInfo::FailMalformedHTLC { .. } => { @@ -6106,25 +6103,20 @@ where }; log_trace!(logger, "Forwarding HTLC from SCID {} with payment_hash {} and next hop SCID {} over {} channel {} with corresponding peer {}", prev_short_channel_id, &payment_hash, short_chan_id, channel_description, optimal_channel.context.channel_id(), &counterparty_node_id); - if let Err(e) = optimal_channel.queue_add_htlc(outgoing_amt_msat, + if let Err((reason, msg)) = optimal_channel.queue_add_htlc(outgoing_amt_msat, payment_hash, outgoing_cltv_value, htlc_source.clone(), onion_packet.clone(), skimmed_fee_msat, next_blinding_point, &self.fee_estimator, &&logger) { - if let ChannelError::Ignore(msg) = e { - log_trace!(logger, "Failed to forward HTLC with payment_hash {} to peer {}: {}", &payment_hash, &counterparty_node_id, msg); - } else { - panic!("Stated return value requirements in send_htlc() were not met"); - } + log_trace!(logger, "Failed to forward HTLC with payment_hash {} to peer {}: {}", &payment_hash, &counterparty_node_id, msg); if let Some(chan) = peer_state.channel_by_id .get_mut(&forward_chan_id) .and_then(Channel::as_funded_mut) { - let failure_code = 0x1000|7; - let data = self.get_htlc_inbound_temp_fail_data(failure_code); + let data = self.get_htlc_inbound_temp_fail_data(reason); failed_forwards.push((htlc_source, payment_hash, - HTLCFailReason::reason(failure_code, data), + HTLCFailReason::reason(reason, data), HTLCDestination::NextHopChannel { node_id: Some(chan.context.get_counterparty_node_id()), channel_id: forward_chan_id } )); } else { @@ -6262,10 +6254,7 @@ where macro_rules! fail_htlc { ($htlc: expr, $payment_hash: expr) => { debug_assert!(!committed_to_claimable); - let mut htlc_msat_height_data = $htlc.value.to_be_bytes().to_vec(); - htlc_msat_height_data.extend_from_slice( - &self.best_block.read().unwrap().height.to_be_bytes(), - ); + let err_data = invalid_payment_err_data($htlc.value, self.best_block.read().unwrap().height); failed_forwards.push((HTLCSource::PreviousHopData(HTLCPreviousHopData { short_channel_id: $htlc.prev_hop.short_channel_id, user_channel_id: $htlc.prev_hop.user_channel_id, @@ -6278,7 +6267,7 @@ where blinded_failure, cltv_expiry: Some(cltv_expiry), }), payment_hash, - HTLCFailReason::reason(0x4000 | 15, htlc_msat_height_data), + HTLCFailReason::reason(LocalHTLCFailureReason::IncorrectPaymentDetails, err_data), HTLCDestination::FailedPayment { payment_hash: $payment_hash }, )); continue 'next_forwardable_htlc; @@ -6835,7 +6824,8 @@ where for htlc_source in timed_out_mpp_htlcs.drain(..) { let source = HTLCSource::PreviousHopData(htlc_source.0.clone()); - let reason = HTLCFailReason::from_failure_code(23); + let failure_reason = LocalHTLCFailureReason::MPPTimeout; + let reason = HTLCFailReason::from_failure_code(failure_reason); let receiver = HTLCDestination::FailedPayment { payment_hash: htlc_source.1 }; self.fail_htlc_backwards_internal(&source, &htlc_source.1, &reason, receiver); } @@ -6932,14 +6922,14 @@ where /// /// This is for failures on the channel on which the HTLC was *received*, not failures /// forwarding - fn get_htlc_inbound_temp_fail_data(&self, err_code: u16) -> Vec { - debug_assert_eq!(err_code & 0x1000, 0x1000); - debug_assert_ne!(err_code, 0x1000|11); - debug_assert_ne!(err_code, 0x1000|12); - debug_assert_ne!(err_code, 0x1000|13); + fn get_htlc_inbound_temp_fail_data(&self, reason: LocalHTLCFailureReason) -> Vec { + debug_assert!(reason.is_temporary()); + debug_assert!(reason != LocalHTLCFailureReason::AmountBelowMinimum); + debug_assert!(reason != LocalHTLCFailureReason::FeeInsufficient); + debug_assert!(reason != LocalHTLCFailureReason::IncorrectCLTVExpiry); // at capacity, we write fields `disabled_flags` and `len` let mut enc = VecWriter(Vec::with_capacity(4)); - if err_code == 0x1000 | 20 { + if reason == LocalHTLCFailureReason::ChannelDisabled { // No flags for `disabled_flags` are currently defined so they're always two zero bytes. // See https://github.com/lightning/bolts/blob/341ec84/04-onion-routing.md?plain=1#L1008 0u16.write(&mut enc).expect("Writes cannot fail"); @@ -6956,7 +6946,7 @@ where &self, mut htlcs_to_fail: Vec<(HTLCSource, PaymentHash)>, channel_id: ChannelId, counterparty_node_id: &PublicKey ) { - let (failure_code, onion_failure_data) = { + let (failure_reason, onion_failure_data) = { let per_peer_state = self.per_peer_state.read().unwrap(); if let Some(peer_state_mutex) = per_peer_state.get(counterparty_node_id) { let mut peer_state_lock = peer_state_mutex.lock().unwrap(); @@ -6964,22 +6954,22 @@ where match peer_state.channel_by_id.entry(channel_id) { hash_map::Entry::Occupied(chan_entry) => { if let Some(_chan) = chan_entry.get().as_funded() { - let failure_code = 0x1000|7; - let data = self.get_htlc_inbound_temp_fail_data(failure_code); - (failure_code, data) + let reason = LocalHTLCFailureReason::TemporaryChannelFailure; + let data = self.get_htlc_inbound_temp_fail_data(reason); + (reason, data) } else { // We shouldn't be trying to fail holding cell HTLCs on an unfunded channel. debug_assert!(false); - (0x4000|10, Vec::new()) + (LocalHTLCFailureReason::UnknownNextPeer, Vec::new()) } }, - hash_map::Entry::Vacant(_) => (0x4000|10, Vec::new()) + hash_map::Entry::Vacant(_) => (LocalHTLCFailureReason::UnknownNextPeer, Vec::new()) } - } else { (0x4000|10, Vec::new()) } + } else { (LocalHTLCFailureReason::UnknownNextPeer, Vec::new()) } }; for (htlc_src, payment_hash) in htlcs_to_fail.drain(..) { - let reason = HTLCFailReason::reason(failure_code, onion_failure_data.clone()); + let reason = HTLCFailReason::reason(failure_reason, onion_failure_data.clone()); let receiver = HTLCDestination::NextHopChannel { node_id: Some(counterparty_node_id.clone()), channel_id }; self.fail_htlc_backwards_internal(&htlc_src, &payment_hash, &reason, receiver); } @@ -7028,7 +7018,7 @@ where ); let failure = match blinded_failure { Some(BlindedFailure::FromIntroductionNode) => { - let blinded_onion_error = HTLCFailReason::reason(INVALID_ONION_BLINDING, vec![0; 32]); + let blinded_onion_error = HTLCFailReason::reason(LocalHTLCFailureReason::InvalidOnionBlinding, vec![0; 32]); let err_packet = blinded_onion_error.get_encrypted_failure_packet( incoming_packet_shared_secret, phantom_shared_secret ); @@ -7037,7 +7027,7 @@ where Some(BlindedFailure::FromBlindedNode) => { HTLCForwardInfo::FailMalformedHTLC { htlc_id: *htlc_id, - failure_code: INVALID_ONION_BLINDING, + failure_code: LocalHTLCFailureReason::InvalidOnionBlinding.failure_code(), sha256_of_onion: [0; 32] } }, @@ -7231,10 +7221,9 @@ where } } else { for htlc in sources { - let mut htlc_msat_height_data = htlc.value.to_be_bytes().to_vec(); - htlc_msat_height_data.extend_from_slice(&self.best_block.read().unwrap().height.to_be_bytes()); + let err_data = invalid_payment_err_data(htlc.value, self.best_block.read().unwrap().height); let source = HTLCSource::PreviousHopData(htlc.prev_hop); - let reason = HTLCFailReason::reason(0x4000 | 15, htlc_msat_height_data); + let reason = HTLCFailReason::reason(LocalHTLCFailureReason::IncorrectPaymentDetails, err_data); let receiver = HTLCDestination::FailedPayment { payment_hash }; self.fail_htlc_backwards_internal(&source, &payment_hash, &reason, receiver); } @@ -8768,7 +8757,7 @@ This indicates a bug inside LDK. Please report this error at https://github.com/ } for htlc_source in dropped_htlcs.drain(..) { let receiver = HTLCDestination::NextHopChannel { node_id: Some(counterparty_node_id.clone()), channel_id: msg.channel_id }; - let reason = HTLCFailReason::from_failure_code(0x4000 | 8); + let reason = HTLCFailReason::from_failure_code(LocalHTLCFailureReason::ChannelClosed); self.fail_htlc_backwards_internal(&htlc_source.0, &htlc_source.1, &reason, receiver); } if let Some(shutdown_res) = finish_shutdown { @@ -8969,7 +8958,7 @@ This indicates a bug inside LDK. Please report this error at https://github.com/ try_channel_entry!(self, peer_state, Err(chan_err), chan_entry); } if let Some(chan) = chan_entry.get_mut().as_funded_mut() { - try_channel_entry!(self, peer_state, chan.update_fail_malformed_htlc(&msg, HTLCFailReason::reason(msg.failure_code, msg.sha256_of_onion.to_vec())), chan_entry); + try_channel_entry!(self, peer_state, chan.update_fail_malformed_htlc(&msg, HTLCFailReason::reason(msg.failure_code.into(), msg.sha256_of_onion.to_vec())), chan_entry); } else { return try_channel_entry!(self, peer_state, Err(ChannelError::close( "Got an update_fail_malformed_htlc message for an unfunded channel!".into())), chan_entry); @@ -9140,7 +9129,7 @@ This indicates a bug inside LDK. Please report this error at https://github.com/ }); failed_intercept_forwards.push((htlc_source, forward_info.payment_hash, - HTLCFailReason::from_failure_code(0x4000 | 10), + HTLCFailReason::from_failure_code(LocalHTLCFailureReason::UnknownNextPeer), HTLCDestination::InvalidForward { requested_forward_scid: scid }, )); } @@ -9624,8 +9613,9 @@ This indicates a bug inside LDK. Please report this error at https://github.com/ ); } else { log_trace!(logger, "Failing HTLC with hash {} from our monitor", &htlc_update.payment_hash); + let failure_reason = LocalHTLCFailureReason::OnChainTimeout; let receiver = HTLCDestination::NextHopChannel { node_id: Some(counterparty_node_id), channel_id }; - let reason = HTLCFailReason::from_failure_code(0x4000 | 8); + let reason = HTLCFailReason::from_failure_code(failure_reason); self.fail_htlc_backwards_internal(&htlc_update.source, &htlc_update.payment_hash, &reason, receiver); } }, @@ -11700,9 +11690,9 @@ where let res = f(funded_channel); if let Ok((channel_ready_opt, mut timed_out_pending_htlcs, announcement_sigs)) = res { for (source, payment_hash) in timed_out_pending_htlcs.drain(..) { - let failure_code = 0x1000|14; /* expiry_too_soon */ - let data = self.get_htlc_inbound_temp_fail_data(failure_code); - timed_out_htlcs.push((source, payment_hash, HTLCFailReason::reason(failure_code, data), + let reason = LocalHTLCFailureReason::CLTVExpiryTooSoon; + let data = self.get_htlc_inbound_temp_fail_data(reason); + timed_out_htlcs.push((source, payment_hash, HTLCFailReason::reason(reason, data), HTLCDestination::NextHopChannel { node_id: Some(funded_channel.context.get_counterparty_node_id()), channel_id: funded_channel.context.channel_id() })); } let logger = WithChannelContext::from(&self.logger, &funded_channel.context, None); @@ -11822,11 +11812,9 @@ where // number of blocks we generally consider it to take to do a commitment update, // just give up on it and fail the HTLC. if height >= htlc.cltv_expiry - HTLC_FAIL_BACK_BUFFER { - let mut htlc_msat_height_data = htlc.value.to_be_bytes().to_vec(); - htlc_msat_height_data.extend_from_slice(&height.to_be_bytes()); - + let reason = LocalHTLCFailureReason::PaymentClaimBuffer; timed_out_htlcs.push((HTLCSource::PreviousHopData(htlc.prev_hop.clone()), payment_hash.clone(), - HTLCFailReason::reason(0x4000 | 15, htlc_msat_height_data), + HTLCFailReason::reason(reason, invalid_payment_err_data(htlc.value, height)), HTLCDestination::FailedPayment { payment_hash: payment_hash.clone() })); false } else { true } @@ -11855,7 +11843,7 @@ where _ => unreachable!(), }; timed_out_htlcs.push((prev_hop_data, htlc.forward_info.payment_hash, - HTLCFailReason::from_failure_code(0x2000 | 2), + HTLCFailReason::from_failure_code(LocalHTLCFailureReason::ForwardExpiryBuffer), HTLCDestination::InvalidForward { requested_forward_scid })); let logger = WithContext::from( &self.logger, None, Some(htlc.prev_channel_id), Some(htlc.forward_info.payment_hash) @@ -14944,8 +14932,9 @@ where for htlc_source in failed_htlcs.drain(..) { let (source, payment_hash, counterparty_node_id, channel_id) = htlc_source; + let failure_reason = LocalHTLCFailureReason::ChannelClosed; let receiver = HTLCDestination::NextHopChannel { node_id: Some(counterparty_node_id), channel_id }; - let reason = HTLCFailReason::from_failure_code(0x4000 | 8); + let reason = HTLCFailReason::from_failure_code(failure_reason); channel_manager.fail_htlc_backwards_internal(&source, &payment_hash, &reason, receiver); } @@ -14978,7 +14967,7 @@ mod tests { use crate::ln::channelmanager::{create_recv_pending_htlc_info, inbound_payment, ChannelConfigOverrides, HTLCForwardInfo, InterceptId, PaymentId, RecipientOnionFields}; use crate::ln::functional_test_utils::*; use crate::ln::msgs::{self, BaseMessageHandler, ChannelMessageHandler, AcceptChannel, ErrorAction, MessageSendEvent}; - use crate::ln::onion_utils; + use crate::ln::onion_utils::{self, LocalHTLCFailureReason}; use crate::ln::outbound_payment::Retry; use crate::prelude::*; use crate::routing::router::{PaymentParameters, RouteParameters, find_route}; @@ -15967,12 +15956,12 @@ mod tests { // Check that if the amount we received + the penultimate hop extra fee is less than the sender // intended amount, we fail the payment. let current_height: u32 = node[0].node.best_block.read().unwrap().height; - if let Err(crate::ln::channelmanager::InboundHTLCErr { err_code, .. }) = + if let Err(crate::ln::channelmanager::InboundHTLCErr { reason, .. }) = create_recv_pending_htlc_info(hop_data, [0; 32], PaymentHash([0; 32]), sender_intended_amt_msat - extra_fee_msat - 1, 42, None, true, Some(extra_fee_msat), current_height) { - assert_eq!(err_code, 19); + assert_eq!(reason, LocalHTLCFailureReason::FinalIncorrectHTLCAmount); } else { panic!(); } // If amt_received + extra_fee is equal to the sender intended amount, we're fine. diff --git a/lightning/src/ln/functional_test_utils.rs b/lightning/src/ln/functional_test_utils.rs index 042736a9c1a..5546378fc87 100644 --- a/lightning/src/ln/functional_test_utils.rs +++ b/lightning/src/ln/functional_test_utils.rs @@ -24,6 +24,7 @@ use crate::ln::msgs::{BaseMessageHandler, ChannelMessageHandler, MessageSendEven use crate::ln::outbound_payment::Retry; use crate::ln::peer_handler::IgnoringMessageHandler; use crate::onion_message::messenger::OnionMessenger; +use crate::ln::onion_utils::LocalHTLCFailureReason; use crate::routing::gossip::{P2PGossipSync, NetworkGraph, NetworkUpdate}; use crate::routing::router::{self, PaymentParameters, Route, RouteParameters}; use crate::sign::{EntropySource, RandomBytes}; @@ -2511,7 +2512,7 @@ pub fn expect_probe_successful_events(node: &Node, mut probe_results: Vec<(Payme } pub struct PaymentFailedConditions<'a> { - pub(crate) expected_htlc_error_data: Option<(u16, &'a [u8])>, + pub(crate) expected_htlc_error_data: Option<(LocalHTLCFailureReason, &'a [u8])>, pub(crate) expected_blamed_scid: Option, pub(crate) expected_blamed_chan_closed: Option, pub(crate) expected_mpp_parts_remain: bool, @@ -2540,8 +2541,8 @@ impl<'a> PaymentFailedConditions<'a> { self.expected_blamed_chan_closed = Some(closed); self } - pub fn expected_htlc_error_data(mut self, code: u16, data: &'a [u8]) -> Self { - self.expected_htlc_error_data = Some((code, data)); + pub fn expected_htlc_error_data(mut self, reason: LocalHTLCFailureReason, data: &'a [u8]) -> Self { + self.expected_htlc_error_data = Some((reason, data)); self } pub fn retry_expected(mut self) -> Self { @@ -2562,11 +2563,11 @@ macro_rules! expect_payment_failed_with_update { #[cfg(any(test, feature = "_externalize_tests"))] macro_rules! expect_payment_failed { - ($node: expr, $expected_payment_hash: expr, $payment_failed_permanently: expr $(, $expected_error_code: expr, $expected_error_data: expr)*) => { + ($node: expr, $expected_payment_hash: expr, $payment_failed_permanently: expr $(, $expected_error_reason: expr, $expected_error_data: expr)*) => { #[allow(unused_mut)] let mut conditions = $crate::ln::functional_test_utils::PaymentFailedConditions::new(); $( - conditions = conditions.expected_htlc_error_data($expected_error_code, &$expected_error_data); + conditions = conditions.expected_htlc_error_data($expected_error_reason, &$expected_error_data); )* $crate::ln::functional_test_utils::expect_payment_failed_conditions(&$node, $expected_payment_hash, $payment_failed_permanently, conditions); }; @@ -2587,8 +2588,9 @@ pub fn expect_payment_failed_conditions_event<'a, 'b, 'c, 'd, 'e>( { assert!(error_code.is_some(), "expected error_code.is_some() = true"); assert!(error_data.is_some(), "expected error_data.is_some() = true"); + let reason: LocalHTLCFailureReason = error_code.unwrap().into(); if let Some((code, data)) = conditions.expected_htlc_error_data { - assert_eq!(error_code.unwrap(), code, "unexpected error code"); + assert_eq!(reason, code, "unexpected error code"); assert_eq!(&error_data.as_ref().unwrap()[..], data, "unexpected error data"); } } diff --git a/lightning/src/ln/functional_tests.rs b/lightning/src/ln/functional_tests.rs index 4aaa1a3f31e..2558e59b46c 100644 --- a/lightning/src/ln/functional_tests.rs +++ b/lightning/src/ln/functional_tests.rs @@ -17,6 +17,7 @@ use crate::chain::chaininterface::LowerBoundedFeeEstimator; use crate::chain::channelmonitor; use crate::chain::channelmonitor::{Balance, ChannelMonitorUpdateStep, CLTV_CLAIM_BUFFER, LATENCY_GRACE_PERIOD_BLOCKS, ANTI_REORG_DELAY, COUNTERPARTY_CLAIMABLE_WITHIN_BLOCKS_PINNABLE}; use crate::chain::transaction::OutPoint; +use crate::ln::onion_utils::LocalHTLCFailureReason; use crate::sign::{ecdsa::EcdsaChannelSigner, EntropySource, OutputSpender, SignerProvider}; use crate::events::bump_transaction::WalletSource; use crate::events::{Event, FundingInfo, PathFailure, PaymentPurpose, ClosureReason, HTLCDestination, PaymentFailureReason}; @@ -4816,7 +4817,7 @@ fn do_test_htlc_timeout(send_partial_mpp: bool) { // 100_000 msat as u64, followed by the height at which we failed back above let mut expected_failure_data = (100_000 as u64).to_be_bytes().to_vec(); expected_failure_data.extend_from_slice(&(block_count - 1).to_be_bytes()); - expect_payment_failed!(nodes[0], our_payment_hash, true, 0x4000 | 15, &expected_failure_data[..]); + expect_payment_failed!(nodes[0], our_payment_hash, true, LocalHTLCFailureReason::IncorrectPaymentDetails, &expected_failure_data[..]); } #[xtest(feature = "_externalize_tests")] @@ -7783,7 +7784,7 @@ pub fn test_check_htlc_underpaying() { // 10_000 msat as u64, followed by a height of CHAN_CONFIRM_DEPTH as u32 let mut expected_failure_data = (10_000 as u64).to_be_bytes().to_vec(); expected_failure_data.extend_from_slice(&CHAN_CONFIRM_DEPTH.to_be_bytes()); - expect_payment_failed!(nodes[0], our_payment_hash, true, 0x4000|15, &expected_failure_data[..]); + expect_payment_failed!(nodes[0], our_payment_hash, true, LocalHTLCFailureReason::IncorrectPaymentDetails, &expected_failure_data[..]); } #[xtest(feature = "_externalize_tests")] @@ -8976,7 +8977,7 @@ pub fn test_bad_secret_hash() { } } - let expected_error_code = 0x4000|15; // incorrect_or_unknown_payment_details + let expected_error_code = LocalHTLCFailureReason::IncorrectPaymentDetails; // Error data is the HTLC value (100,000) and current block height let expected_error_data = [0, 0, 0, 0, 0, 1, 0x86, 0xa0, 0, 0, 0, CHAN_CONFIRM_DEPTH as u8]; diff --git a/lightning/src/ln/mod.rs b/lightning/src/ln/mod.rs index 53940b9296d..aa2d8c668ba 100644 --- a/lightning/src/ln/mod.rs +++ b/lightning/src/ln/mod.rs @@ -46,7 +46,7 @@ pub mod wire; #[allow(dead_code)] // TODO(dual_funding): Remove once contribution to V2 channels is enabled. pub(crate) mod interactivetxs; -pub use onion_utils::create_payment_onion; +pub use onion_utils::{create_payment_onion, LocalHTLCFailureReason}; // Older rustc (which we support) refuses to let us call the get_payment_preimage_hash!() macro // without the node parameter being mut. This is incorrect, and thus newer rustcs will complain // about an unnecessary mut. Thus, we silence the unused_mut warning in two test modules below. diff --git a/lightning/src/ln/onion_payment.rs b/lightning/src/ln/onion_payment.rs index e785e1867d4..baf2098c36b 100644 --- a/lightning/src/ln/onion_payment.rs +++ b/lightning/src/ln/onion_payment.rs @@ -16,7 +16,7 @@ use crate::ln::channelmanager::{BlindedFailure, BlindedForward, CLTV_FAR_FAR_AWA use crate::types::features::BlindedHopFeatures; use crate::ln::msgs; use crate::ln::onion_utils; -use crate::ln::onion_utils::{HTLCFailReason, INVALID_ONION_BLINDING, ONION_DATA_LEN}; +use crate::ln::onion_utils::{HTLCFailReason, ONION_DATA_LEN, LocalHTLCFailureReason}; use crate::sign::{NodeSigner, Recipient}; use crate::util::logger::Logger; @@ -29,13 +29,21 @@ use core::ops::Deref; #[derive(Clone, Debug, Hash, PartialEq, Eq)] pub struct InboundHTLCErr { /// BOLT 4 error code. - pub err_code: u16, + pub reason: LocalHTLCFailureReason, /// Data attached to this error. pub err_data: Vec, /// Error message text. pub msg: &'static str, } +/// Writes payment data for invalid or unknown payment error code. +pub (super) fn invalid_payment_err_data(amt_msat: u64, current_height: u32) -> Vec{ + let mut err_data = Vec::with_capacity(12); + err_data.extend_from_slice(&amt_msat.to_be_bytes()); + err_data.extend_from_slice(¤t_height.to_be_bytes()); + err_data +} + fn check_blinded_payment_constraints( amt_msat: u64, cltv_expiry: u32, constraints: &PaymentConstraints ) -> Result<(), ()> { @@ -102,7 +110,7 @@ pub(super) fn create_fwd_pending_htlc_info( // unreachable right now since we checked it in `decode_update_add_htlc_onion`. InboundHTLCErr { msg: "Underflow calculating outbound amount or cltv value for blinded forward", - err_code: INVALID_ONION_BLINDING, + reason: LocalHTLCFailureReason::InvalidOnionBlinding, err_data: vec![0; 32], } })?; @@ -112,13 +120,13 @@ pub(super) fn create_fwd_pending_htlc_info( onion_utils::Hop::Receive { .. } | onion_utils::Hop::BlindedReceive { .. } => return Err(InboundHTLCErr { msg: "Final Node OnionHopData provided for us as an intermediary node", - err_code: 0x4000 | 22, + reason: LocalHTLCFailureReason::InvalidOnionPayload, err_data: Vec::new(), }), onion_utils::Hop::TrampolineReceive { .. } | onion_utils::Hop::TrampolineBlindedReceive { .. } => return Err(InboundHTLCErr { msg: "Final Node OnionHopData provided for us as an intermediary node", - err_code: 0x4000 | 22, + reason: LocalHTLCFailureReason::InvalidOnionPayload, err_data: Vec::new(), }), onion_utils::Hop::TrampolineForward { next_trampoline_hop_data, next_trampoline_hop_hmac, new_trampoline_packet_bytes, trampoline_shared_secret, .. } => { @@ -144,7 +152,7 @@ pub(super) fn create_fwd_pending_htlc_info( // unreachable right now since we checked it in `decode_update_add_htlc_onion`. InboundHTLCErr { msg: "Underflow calculating outbound amount or cltv value for blinded forward", - err_code: INVALID_ONION_BLINDING, + reason: LocalHTLCFailureReason::InvalidOnionBlinding, err_data: vec![0; 32], } })?; @@ -191,7 +199,7 @@ pub(super) fn create_fwd_pending_htlc_info( Some(Ok(pubkey)) => pubkey, _ => return Err(InboundHTLCErr { msg: "Missing next Trampoline hop pubkey from intermediate Trampoline forwarding data", - err_code: 0x4000 | 22, + reason: LocalHTLCFailureReason::InvalidTrampolinePayload, err_data: Vec::new(), }), }; @@ -255,7 +263,7 @@ pub(super) fn create_recv_pending_htlc_info( ) .map_err(|()| { InboundHTLCErr { - err_code: INVALID_ONION_BLINDING, + reason: LocalHTLCFailureReason::InvalidOnionBlinding, err_data: vec![0; 32], msg: "Amount or cltv_expiry violated blinded payment constraints", } @@ -285,7 +293,7 @@ pub(super) fn create_recv_pending_htlc_info( ) .map_err(|()| { InboundHTLCErr { - err_code: INVALID_ONION_BLINDING, + reason: LocalHTLCFailureReason::InvalidOnionBlinding, err_data: vec![0; 32], msg: "Amount or cltv_expiry violated blinded payment constraints within Trampoline onion", } @@ -297,21 +305,21 @@ pub(super) fn create_recv_pending_htlc_info( }, onion_utils::Hop::Forward { .. } => { return Err(InboundHTLCErr { - err_code: 0x4000|22, + reason: LocalHTLCFailureReason::InvalidOnionPayload, err_data: Vec::new(), msg: "Got non final data with an HMAC of 0", }) }, onion_utils::Hop::BlindedForward { .. } => { return Err(InboundHTLCErr { - err_code: INVALID_ONION_BLINDING, + reason: LocalHTLCFailureReason::InvalidOnionBlinding, err_data: vec![0; 32], msg: "Got blinded non final data with an HMAC of 0", }) }, onion_utils::Hop::TrampolineForward { .. } | onion_utils::Hop::TrampolineBlindedForward { .. } => { return Err(InboundHTLCErr { - err_code: 0x4000|22, + reason: LocalHTLCFailureReason::InvalidOnionPayload, err_data: Vec::new(), msg: "Got Trampoline non final data with an HMAC of 0", }) @@ -321,7 +329,7 @@ pub(super) fn create_recv_pending_htlc_info( if onion_cltv_expiry > cltv_expiry { return Err(InboundHTLCErr { msg: "Upstream node set CLTV to less than the CLTV set by the sender", - err_code: 18, + reason: LocalHTLCFailureReason::FinalIncorrectCLTVExpiry, err_data: cltv_expiry.to_be_bytes().to_vec() }) } @@ -333,11 +341,9 @@ pub(super) fn create_recv_pending_htlc_info( // payment logic has enough time to fail the HTLC backward before our onchain logic triggers a // channel closure (see HTLC_FAIL_BACK_BUFFER rationale). if cltv_expiry <= current_height + HTLC_FAIL_BACK_BUFFER + 1 { - let mut err_data = Vec::with_capacity(12); - err_data.extend_from_slice(&amt_msat.to_be_bytes()); - err_data.extend_from_slice(¤t_height.to_be_bytes()); return Err(InboundHTLCErr { - err_code: 0x4000 | 15, err_data, + reason: LocalHTLCFailureReason::PaymentClaimBuffer, + err_data: invalid_payment_err_data(amt_msat, current_height), msg: "The final CLTV expiry is too soon to handle", }); } @@ -346,7 +352,7 @@ pub(super) fn create_recv_pending_htlc_info( amt_msat.saturating_add(counterparty_skimmed_fee_msat.unwrap_or(0))) { return Err(InboundHTLCErr { - err_code: 19, + reason: LocalHTLCFailureReason::FinalIncorrectHTLCAmount, err_data: amt_msat.to_be_bytes().to_vec(), msg: "Upstream node sent less than we were supposed to receive in payment", }); @@ -361,8 +367,8 @@ pub(super) fn create_recv_pending_htlc_info( let hashed_preimage = PaymentHash(Sha256::hash(&payment_preimage.0).to_byte_array()); if hashed_preimage != payment_hash { return Err(InboundHTLCErr { - err_code: 0x4000|22, - err_data: Vec::new(), + reason: LocalHTLCFailureReason::InvalidKeysendPreimage, + err_data: invalid_payment_err_data(amt_msat, current_height), msg: "Payment preimage didn't match payment hash", }); } @@ -389,7 +395,7 @@ pub(super) fn create_recv_pending_htlc_info( } } else { return Err(InboundHTLCErr { - err_code: 0x4000|0x2000|3, + reason: LocalHTLCFailureReason::PaymentSecretRequired, err_data: Vec::new(), msg: "We require payment_secrets", }); @@ -424,13 +430,13 @@ where { let (hop, next_packet_details_opt) = decode_incoming_update_add_htlc_onion(msg, node_signer, logger, secp_ctx - ).map_err(|e| { - let (err_code, err_data) = match e { - HTLCFailureMsg::Malformed(m) => (m.failure_code, Vec::new()), - HTLCFailureMsg::Relay(r) => (0x4000 | 22, r.reason), + ).map_err(|(msg, failure_reason)| { + let (reason, err_data) = match msg { + HTLCFailureMsg::Malformed(_) => (failure_reason, Vec::new()), + HTLCFailureMsg::Relay(r) => (LocalHTLCFailureReason::InvalidOnionPayload, r.reason), }; let msg = "Failed to decode update add htlc onion"; - InboundHTLCErr { msg, err_code, err_data } + InboundHTLCErr { msg, reason, err_data } })?; Ok(match hop { onion_utils::Hop::Forward { shared_secret, .. } | @@ -442,17 +448,17 @@ where // Forward should always include the next hop details None => return Err(InboundHTLCErr { msg: "Failed to decode update add htlc onion", - err_code: 0x4000 | 22, + reason: LocalHTLCFailureReason::InvalidOnionPayload, err_data: Vec::new(), }), }; - if let Err((err_msg, code)) = check_incoming_htlc_cltv( + if let Err((err_msg, reason)) = check_incoming_htlc_cltv( cur_height, outgoing_cltv_value, msg.cltv_expiry, ) { return Err(InboundHTLCErr { msg: err_msg, - err_code: code, + reason, err_data: Vec::new(), }); } @@ -488,28 +494,28 @@ pub(super) struct NextPacketDetails { pub(super) fn decode_incoming_update_add_htlc_onion( msg: &msgs::UpdateAddHTLC, node_signer: NS, logger: L, secp_ctx: &Secp256k1, -) -> Result<(onion_utils::Hop, Option), HTLCFailureMsg> +) -> Result<(onion_utils::Hop, Option), (HTLCFailureMsg, LocalHTLCFailureReason)> where NS::Target: NodeSigner, L::Target: Logger, { - let encode_malformed_error = |message: &str, err_code: u16| { + let encode_malformed_error = |message: &str, failure_reason: LocalHTLCFailureReason| { log_info!(logger, "Failed to accept/forward incoming HTLC: {}", message); - let (sha256_of_onion, failure_code) = if msg.blinding_point.is_some() || err_code == INVALID_ONION_BLINDING { - ([0; 32], INVALID_ONION_BLINDING) + let (sha256_of_onion, failure_reason) = if msg.blinding_point.is_some() || failure_reason == LocalHTLCFailureReason::InvalidOnionBlinding { + ([0; 32], LocalHTLCFailureReason::InvalidOnionBlinding) } else { - (Sha256::hash(&msg.onion_routing_packet.hop_data).to_byte_array(), err_code) + (Sha256::hash(&msg.onion_routing_packet.hop_data).to_byte_array(), failure_reason) }; - return Err(HTLCFailureMsg::Malformed(msgs::UpdateFailMalformedHTLC { + return Err((HTLCFailureMsg::Malformed(msgs::UpdateFailMalformedHTLC { channel_id: msg.channel_id, htlc_id: msg.htlc_id, sha256_of_onion, - failure_code, - })); + failure_code: failure_reason.failure_code(), + }), failure_reason)); }; if let Err(_) = msg.onion_routing_packet.public_key { - return encode_malformed_error("invalid ephemeral pubkey", 0x8000 | 0x4000 | 6); + return encode_malformed_error("invalid ephemeral pubkey", LocalHTLCFailureReason::InvalidOnionKey); } if msg.onion_routing_packet.version != 0 { @@ -519,23 +525,23 @@ where //receiving node would have to brute force to figure out which version was put in the //packet by the node that send us the message, in the case of hashing the hop_data, the //node knows the HMAC matched, so they already know what is there... - return encode_malformed_error("Unknown onion packet version", 0x8000 | 0x4000 | 4); + return encode_malformed_error("Unknown onion packet version", LocalHTLCFailureReason::InvalidOnionVersion) } - let encode_relay_error = |message: &str, err_code: u16, shared_secret: [u8; 32], trampoline_shared_secret: Option<[u8; 32]>, data: &[u8]| { + let encode_relay_error = |message: &str, reason: LocalHTLCFailureReason, shared_secret: [u8; 32], trampoline_shared_secret: Option<[u8; 32]>, data: &[u8]| { if msg.blinding_point.is_some() { - return encode_malformed_error(message, INVALID_ONION_BLINDING) + return encode_malformed_error(message, LocalHTLCFailureReason::InvalidOnionBlinding) } log_info!(logger, "Failed to accept/forward incoming HTLC: {}", message); - let failure = HTLCFailReason::reason(err_code, data.to_vec()) + let failure = HTLCFailReason::reason(reason, data.to_vec()) .get_encrypted_failure_packet(&shared_secret, &trampoline_shared_secret); - return Err(HTLCFailureMsg::Relay(msgs::UpdateFailHTLC { + return Err((HTLCFailureMsg::Relay(msgs::UpdateFailHTLC { channel_id: msg.channel_id, htlc_id: msg.htlc_id, reason: failure.data, attribution_data: failure.attribution_data, - })); + }), reason)); }; let next_hop = match onion_utils::decode_next_payment_hop( @@ -543,11 +549,11 @@ where msg.payment_hash, msg.blinding_point, node_signer ) { Ok(res) => res, - Err(onion_utils::OnionDecodeErr::Malformed { err_msg, err_code }) => { - return encode_malformed_error(err_msg, err_code); + Err(onion_utils::OnionDecodeErr::Malformed { err_msg, reason }) => { + return encode_malformed_error(err_msg, reason); }, - Err(onion_utils::OnionDecodeErr::Relay { err_msg, err_code, shared_secret, trampoline_shared_secret }) => { - return encode_relay_error(err_msg, err_code, shared_secret.secret_bytes(), trampoline_shared_secret.map(|tss| tss.secret_bytes()), &[0; 0]); + Err(onion_utils::OnionDecodeErr::Relay { err_msg, reason, shared_secret, trampoline_shared_secret }) => { + return encode_relay_error(err_msg, reason, shared_secret.secret_bytes(), trampoline_shared_secret.map(|tss| tss.secret_bytes()), &[0; 0]); }, }; @@ -567,7 +573,7 @@ where Ok((amt, cltv)) => (amt, cltv), Err(()) => { return encode_relay_error("Underflow calculating outbound amount or cltv value for blinded forward", - INVALID_ONION_BLINDING, shared_secret.secret_bytes(), None, &[0; 32]); + LocalHTLCFailureReason::InvalidOnionBlinding, shared_secret.secret_bytes(), None, &[0; 32]); } }; let next_packet_pubkey = onion_utils::next_hop_pubkey(&secp_ctx, @@ -595,21 +601,19 @@ where pub(super) fn check_incoming_htlc_cltv( cur_height: u32, outgoing_cltv_value: u32, cltv_expiry: u32 -) -> Result<(), (&'static str, u16)> { +) -> Result<(), (&'static str, LocalHTLCFailureReason)> { if (cltv_expiry as u64) < (outgoing_cltv_value) as u64 + MIN_CLTV_EXPIRY_DELTA as u64 { - return Err(( - "Forwarding node has tampered with the intended HTLC values or origin node has an obsolete cltv_expiry_delta", - 0x1000 | 13, // incorrect_cltv_expiry - )); + return Err(("Forwarding node has tampered with the intended HTLC values or origin node has an obsolete cltv_expiry_delta", + LocalHTLCFailureReason::IncorrectCLTVExpiry)); } // Theoretically, channel counterparty shouldn't send us a HTLC expiring now, // but we want to be robust wrt to counterparty packet sanitization (see // HTLC_FAIL_BACK_BUFFER rationale). - if cltv_expiry <= cur_height + HTLC_FAIL_BACK_BUFFER as u32 { // expiry_too_soon - return Err(("CLTV expiry is too close", 0x1000 | 14)); + if cltv_expiry <= cur_height + HTLC_FAIL_BACK_BUFFER as u32 { + return Err(("CLTV expiry is too close", LocalHTLCFailureReason::CLTVExpiryTooSoon)); } - if cltv_expiry > cur_height + CLTV_FAR_FAR_AWAY as u32 { // expiry_too_far - return Err(("CLTV expiry is too far in the future", 21)); + if cltv_expiry > cur_height + CLTV_FAR_FAR_AWAY as u32 { + return Err(("CLTV expiry is too far in the future", LocalHTLCFailureReason::CLTVExpiryTooFar)); } // If the HTLC expires ~now, don't bother trying to forward it to our // counterparty. They should fail it anyway, but we don't want to bother with @@ -620,7 +624,7 @@ pub(super) fn check_incoming_htlc_cltv( // but there is no need to do that, and since we're a bit conservative with our // risk threshold it just results in failing to forward payments. if (outgoing_cltv_value) as u64 <= (cur_height + LATENCY_GRACE_PERIOD_BLOCKS) as u64 { - return Err(("Outgoing CLTV value is too soon", 0x1000 | 14)); + return Err(("Outgoing CLTV value is too soon", LocalHTLCFailureReason::OutgoingCLTVTooSoon)); } Ok(()) diff --git a/lightning/src/ln/onion_route_tests.rs b/lightning/src/ln/onion_route_tests.rs index 1558f4e6c79..ca4fc7863e1 100644 --- a/lightning/src/ln/onion_route_tests.rs +++ b/lightning/src/ln/onion_route_tests.rs @@ -17,7 +17,7 @@ use crate::events::{Event, HTLCDestination, 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; +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}; @@ -52,7 +52,7 @@ use crate::ln::onion_utils::{construct_trampoline_onion_keys, construct_trampoli 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_htlc_destination: Option) +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_htlc_destination: Option) where F1: for <'a> FnMut(&'a mut msgs::UpdateAddHTLC), F2: FnMut(), { @@ -69,7 +69,7 @@ fn run_onion_failure_test(_name: &str, test_case: u8, nodes: &Vec, 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_code: Option, + mut callback_node: F3, expected_retryable: bool, expected_error_reason: Option, expected_channel_update: Option, expected_short_channel_id: Option, expected_htlc_destination: Option, ) @@ -189,7 +189,10 @@ fn run_onion_failure_test_with_fail_intercept( 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] { assert_eq!(*payment_failed_permanently, !expected_retryable); - assert_eq!(*error_code, expected_error_code); + assert_eq!(error_code.is_none(), expected_error_reason.is_none()); + if let Some(expected_reason) = expected_error_reason { + assert_eq!(expected_reason, error_code.unwrap().into()) + } if expected_channel_update.is_some() { match network_update { Some(update) => match update { @@ -278,11 +281,6 @@ impl Writeable for BogusOnionHopData { } } -const BADONION: u16 = 0x8000; -const PERM: u16 = 0x4000; -const NODE: u16 = 0x2000; -const UPDATE: u16 = 0x1000; - #[test] fn test_fee_failures() { // Tests that the fee required when forwarding remains consistent over time. This was @@ -315,7 +313,7 @@ fn test_fee_failures() { 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(UPDATE|12), Some(NetworkUpdate::ChannelFailure { short_channel_id, is_permanent: false}), Some(short_channel_id), + }, || {}, true, Some(LocalHTLCFailureReason::FeeInsufficient), Some(NetworkUpdate::ChannelFailure { short_channel_id, is_permanent: false}), Some(short_channel_id), Some(HTLCDestination::NextHopChannel { 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 @@ -381,7 +379,7 @@ fn test_onion_failure() { // 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(PERM|22), Some(NetworkUpdate::ChannelFailure{short_channel_id, is_permanent: true}), Some(short_channel_id), Some(HTLCDestination::InvalidOnion)); + }, ||{}, true, Some(LocalHTLCFailureReason::InvalidOnionPayload), Some(NetworkUpdate::ChannelFailure{short_channel_id, is_permanent: true}), Some(short_channel_id), Some(HTLCDestination::InvalidOnion)); // final node failure let short_channel_id = channels[1].0.contents.short_channel_id; @@ -400,7 +398,7 @@ fn test_onion_failure() { // 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(PERM|22), Some(NetworkUpdate::ChannelFailure{short_channel_id, is_permanent: true}), Some(short_channel_id), Some(HTLCDestination::InvalidOnion)); + }, ||{}, false, Some(LocalHTLCFailureReason::InvalidOnionPayload), Some(NetworkUpdate::ChannelFailure{short_channel_id, is_permanent: true}), Some(short_channel_id), Some(HTLCDestination::InvalidOnion)); // the following three with run_onion_failure_test_with_fail_intercept() test only the origin node // receiving simulated fail messages @@ -412,22 +410,22 @@ fn test_onion_failure() { // 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).unwrap(); - let failure = onion_utils::build_failure_packet(onion_keys[0].shared_secret.as_ref(), NODE|2, &[0;0], 0); + 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(NODE|2), Some(NetworkUpdate::NodeFailure{node_id: route.paths[0].hops[0].pubkey, is_permanent: false}), Some(route.paths[0].hops[0].short_channel_id), Some(next_hop_failure.clone())); + }, ||{}, 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).unwrap(); - let failure = onion_utils::build_failure_packet(onion_keys[1].shared_secret.as_ref(), NODE|2, &[0;0], 0); + 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(NODE|2), Some(NetworkUpdate::NodeFailure{node_id: route.paths[0].hops[1].pubkey, is_permanent: false}), Some(route.paths[0].hops[1].short_channel_id), None); + }, 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 @@ -436,21 +434,21 @@ 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).unwrap(); - let failure = onion_utils::build_failure_packet(onion_keys[0].shared_secret.as_ref(), PERM|NODE|2, &[0;0], 0); + 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(PERM|NODE|2), Some(NetworkUpdate::NodeFailure{node_id: route.paths[0].hops[0].pubkey, is_permanent: true}), Some(route.paths[0].hops[0].short_channel_id), Some(next_hop_failure.clone())); + }, ||{}, 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).unwrap(); - let failure = onion_utils::build_failure_packet(onion_keys[1].shared_secret.as_ref(), PERM|NODE|2, &[0;0], 0); + 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(PERM|NODE|2), Some(NetworkUpdate::NodeFailure{node_id: route.paths[0].hops[1].pubkey, is_permanent: true}), Some(route.paths[0].hops[1].short_channel_id), None); + }, 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 @@ -459,36 +457,36 @@ 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).unwrap(); - let failure = onion_utils::build_failure_packet(onion_keys[0].shared_secret.as_ref(), PERM|NODE|3, &[0;0], 0); + 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(PERM|NODE|3), Some(NetworkUpdate::NodeFailure{node_id: route.paths[0].hops[0].pubkey, is_permanent: true}), Some(route.paths[0].hops[0].short_channel_id), Some(next_hop_failure.clone())); + }, 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).unwrap(); - let failure = onion_utils::build_failure_packet(onion_keys[1].shared_secret.as_ref(), PERM|NODE|3, &[0;0], 0); + 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(PERM|NODE|3), Some(NetworkUpdate::NodeFailure{node_id: route.paths[0].hops[1].pubkey, is_permanent: true}), Some(route.paths[0].hops[1].short_channel_id), None); + }, 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(BADONION|PERM|4), None, Some(short_channel_id), Some(HTLCDestination::InvalidOnion)); + Some(LocalHTLCFailureReason::InvalidOnionVersion), None, Some(short_channel_id), Some(HTLCDestination::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(BADONION|PERM|5), None, Some(short_channel_id), Some(HTLCDestination::InvalidOnion)); + Some(LocalHTLCFailureReason::InvalidOnionHMAC), None, Some(short_channel_id), Some(HTLCDestination::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(BADONION|PERM|6), None, Some(short_channel_id), Some(HTLCDestination::InvalidOnion)); + Some(LocalHTLCFailureReason::InvalidOnionKey), None, Some(short_channel_id), Some(HTLCDestination::InvalidOnion)); let short_channel_id = channels[1].0.contents.short_channel_id; let chan_update = ChannelUpdate::dummy(short_channel_id); @@ -502,10 +500,10 @@ 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).unwrap(); - let failure = onion_utils::build_failure_packet(onion_keys[0].shared_secret.as_ref(), UPDATE|7, &err_data, 0); + 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(UPDATE|7), + }, ||{}, true, Some(LocalHTLCFailureReason::TemporaryChannelFailure), Some(NetworkUpdate::ChannelFailure { short_channel_id, is_permanent: false }), Some(short_channel_id), Some(next_hop_failure.clone())); @@ -516,10 +514,10 @@ 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).unwrap(); - let failure = onion_utils::build_failure_packet(onion_keys[0].shared_secret.as_ref(), UPDATE|7, &err_data_without_type, 0); + 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(UPDATE|7), + }, ||{}, true, Some(LocalHTLCFailureReason::TemporaryChannelFailure), Some(NetworkUpdate::ChannelFailure { short_channel_id, is_permanent: false }), Some(short_channel_id), Some(next_hop_failure.clone())); @@ -529,11 +527,11 @@ 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).unwrap(); - let failure = onion_utils::build_failure_packet(onion_keys[0].shared_secret.as_ref(), PERM|8, &[0;0], 0); + 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(PERM|8), Some(NetworkUpdate::ChannelFailure{short_channel_id, is_permanent: true}), Some(short_channel_id), Some(next_hop_failure.clone())); + }, ||{}, 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| { @@ -541,16 +539,16 @@ 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).unwrap(); - let failure = onion_utils::build_failure_packet(onion_keys[0].shared_secret.as_ref(), PERM|9, &[0;0], 0); + 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(PERM|9), Some(NetworkUpdate::ChannelFailure{short_channel_id, is_permanent: true}), Some(short_channel_id), Some(next_hop_failure.clone())); + }, ||{}, 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(PERM|10), + 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(HTLCDestination::UnknownNextHop { requested_forward_scid: short_channel_id })); let short_channel_id = channels[1].0.contents.short_channel_id; @@ -560,7 +558,7 @@ fn test_onion_failure() { 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(UPDATE|11), + 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())); @@ -579,13 +577,13 @@ fn test_onion_failure() { 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(UPDATE|12), Some(NetworkUpdate::ChannelFailure { short_channel_id, is_permanent: false}), Some(short_channel_id), Some(next_hop_failure.clone())); + }, || {}, 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(UPDATE|13), Some(NetworkUpdate::ChannelFailure { short_channel_id, is_permanent: false}), Some(short_channel_id), Some(next_hop_failure.clone())); + }, || {}, 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| { @@ -593,13 +591,13 @@ fn test_onion_failure() { 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(UPDATE|14), + }, ||{}, 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(PERM|15), None, None, None); + }, 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| { @@ -607,7 +605,7 @@ fn test_onion_failure() { 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(0x4000 | 15), None, None, Some(HTLCDestination::FailedPayment { payment_hash })); + }, || {}, false, Some(LocalHTLCFailureReason::IncorrectPaymentDetails), None, None, Some(HTLCDestination::FailedPayment { 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(); @@ -620,7 +618,7 @@ fn test_onion_failure() { } } } - }, true, Some(18), None, Some(channels[1].0.contents.short_channel_id), Some(HTLCDestination::FailedPayment { payment_hash })); + }, true, Some(LocalHTLCFailureReason::FinalIncorrectCLTVExpiry), None, Some(channels[1].0.contents.short_channel_id), Some(HTLCDestination::FailedPayment { 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(); @@ -634,14 +632,14 @@ fn test_onion_failure() { } } } - }, true, Some(19), None, Some(channels[1].0.contents.short_channel_id), Some(HTLCDestination::FailedPayment { payment_hash })); + }, true, Some(LocalHTLCFailureReason::FinalIncorrectHTLCAmount), None, Some(channels[1].0.contents.short_channel_id), Some(HTLCDestination::FailedPayment { 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(UPDATE|7), + }, 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, |_| {}, || { @@ -652,7 +650,7 @@ fn test_onion_failure() { } nodes[1].node.get_and_clear_pending_msg_events(); nodes[2].node.get_and_clear_pending_msg_events(); - }, true, Some(UPDATE|20), + }, 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])); @@ -669,18 +667,18 @@ fn test_onion_failure() { 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(21), 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())); + }, ||{}, 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).unwrap(); - let failure = onion_utils::build_failure_packet(onion_keys[1].shared_secret.as_ref(), 23, &[0;0], 0); + 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(23), None, None, None); + }, 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| { @@ -741,7 +739,7 @@ fn test_onion_failure() { &onion_keys[0].shared_secret.as_ref(), &mut onion_error); msg.reason = onion_error.data; msg.attribution_data = onion_error.attribution_data; - }, || {}, true, Some(0x1000|7), + }, || {}, true, Some(LocalHTLCFailureReason::TemporaryChannelFailure), Some(NetworkUpdate::ChannelFailure { short_channel_id: channels[1].0.contents.short_channel_id, is_permanent: false, @@ -772,7 +770,7 @@ fn test_onion_failure() { &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(0x1000|7), + }, || 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, @@ -923,12 +921,12 @@ fn do_test_onion_failure_stale_channel_update(announce_for_forwarding: bool) { // We'll be attempting to route payments using the default ChannelUpdate for channels. This will // lead to onion failures at the first hop once we update the ChannelConfig for the // second hop. - let expect_onion_failure = |name: &str, error_code: u16| { + 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 }; run_onion_failure_test( name, 100, &nodes, &route, &payment_hash, &payment_secret, |_| {}, || {}, true, - Some(error_code), Some(network_update), Some(short_channel_id), + Some(error_reason), Some(network_update), Some(short_channel_id), Some(HTLCDestination::NextHopChannel { node_id: Some(nodes[2].node.get_our_node_id()), channel_id: channel_to_update.0 }), ); }; @@ -958,7 +956,7 @@ fn do_test_onion_failure_stale_channel_update(announce_for_forwarding: bool) { // Connect a block, which should expire the previous config, leading to a failure when // forwarding the HTLC. expire_prev_config(); - expect_onion_failure("fee_insufficient", UPDATE|12); + expect_onion_failure("fee_insufficient", LocalHTLCFailureReason::FeeInsufficient); // Redundant updates should not trigger a new ChannelUpdate. assert!(update_and_get_channel_update(&config, false, None, false).is_none()); @@ -972,14 +970,14 @@ fn do_test_onion_failure_stale_channel_update(announce_for_forwarding: bool) { config.forwarding_fee_base_msat = default_config.forwarding_fee_base_msat; config.cltv_expiry_delta = u16::max_value(); assert!(update_and_get_channel_update(&config, true, Some(&msg), true).is_some()); - expect_onion_failure("incorrect_cltv_expiry", UPDATE|13); + expect_onion_failure("incorrect_cltv_expiry", LocalHTLCFailureReason::IncorrectCLTVExpiry); // Reset the proportional fee and increase the CLTV expiry delta which should trigger a new // ChannelUpdate. config.cltv_expiry_delta = default_config.cltv_expiry_delta; config.forwarding_fee_proportional_millionths = u32::max_value(); assert!(update_and_get_channel_update(&config, true, Some(&msg), true).is_some()); - expect_onion_failure("fee_insufficient", UPDATE|12); + expect_onion_failure("fee_insufficient", LocalHTLCFailureReason::FeeInsufficient); // To test persistence of the updated config, we'll re-initialize the ChannelManager. let config_after_restart = { @@ -1422,9 +1420,7 @@ fn do_test_fail_htlc_backwards_with_reason(failure_code: FailureCode) { }; let failure_code = failure_code.into(); - let permanent_flag = 0x4000; - let permanent_fail = (failure_code & permanent_flag) != 0; - expect_payment_failed!(nodes[0], payment_hash, permanent_fail, failure_code, failure_data); + expect_payment_failed!(nodes[0], payment_hash, failure_code.is_permanent(), failure_code, failure_data); } @@ -1535,7 +1531,7 @@ fn test_phantom_onion_hmac_failure() { let mut fail_conditions = PaymentFailedConditions::new() .blamed_scid(phantom_scid) .blamed_chan_closed(true) - .expected_htlc_error_data(0x8000 | 0x4000 | 5, &sha256_of_onion); + .expected_htlc_error_data(LocalHTLCFailureReason::InvalidOnionHMAC, &sha256_of_onion); expect_payment_failed_conditions(&nodes[0], payment_hash, false, fail_conditions); } @@ -1613,7 +1609,7 @@ fn test_phantom_invalid_onion_payload() { let mut fail_conditions = PaymentFailedConditions::new() .blamed_scid(phantom_scid) .blamed_chan_closed(true) - .expected_htlc_error_data(0x4000 | 22, &error_data); + .expected_htlc_error_data(LocalHTLCFailureReason::InvalidOnionPayload, &error_data); expect_payment_failed_conditions(&nodes[0], payment_hash, true, fail_conditions); } @@ -1671,7 +1667,7 @@ fn test_phantom_final_incorrect_cltv_expiry() { let error_data = expected_cltv.to_be_bytes().to_vec(); let mut fail_conditions = PaymentFailedConditions::new() .blamed_scid(phantom_scid) - .expected_htlc_error_data(18, &error_data); + .expected_htlc_error_data(LocalHTLCFailureReason::FinalIncorrectCLTVExpiry, &error_data); expect_payment_failed_conditions(&nodes[0], payment_hash, false, fail_conditions); } @@ -1720,7 +1716,7 @@ fn test_phantom_failure_too_low_cltv() { ); let mut fail_conditions = PaymentFailedConditions::new() .blamed_scid(phantom_scid) - .expected_htlc_error_data(0x4000 | 15, &error_data); + .expected_htlc_error_data(LocalHTLCFailureReason::IncorrectPaymentDetails, &error_data); expect_payment_failed_conditions(&nodes[0], payment_hash, true, fail_conditions); } @@ -1771,7 +1767,7 @@ fn test_phantom_failure_modified_cltv() { err_data.extend_from_slice(&0u16.to_be_bytes()); let mut fail_conditions = PaymentFailedConditions::new() .blamed_scid(phantom_scid) - .expected_htlc_error_data(0x1000 | 13, &err_data); + .expected_htlc_error_data(LocalHTLCFailureReason::IncorrectCLTVExpiry, &err_data); expect_payment_failed_conditions(&nodes[0], payment_hash, false, fail_conditions); } @@ -1818,7 +1814,7 @@ fn test_phantom_failure_expires_too_soon() { let err_data = 0u16.to_be_bytes(); let mut fail_conditions = PaymentFailedConditions::new() .blamed_scid(phantom_scid) - .expected_htlc_error_data(0x1000 | 14, &err_data); + .expected_htlc_error_data(LocalHTLCFailureReason::CLTVExpiryTooSoon, &err_data); expect_payment_failed_conditions(&nodes[0], payment_hash, false, fail_conditions); } @@ -1865,7 +1861,7 @@ fn test_phantom_failure_too_low_recv_amt() { error_data.extend_from_slice(&nodes[1].node.best_block.read().unwrap().height.to_be_bytes()); let mut fail_conditions = PaymentFailedConditions::new() .blamed_scid(phantom_scid) - .expected_htlc_error_data(0x4000 | 15, &error_data); + .expected_htlc_error_data(LocalHTLCFailureReason::IncorrectPaymentDetails, &error_data); expect_payment_failed_conditions(&nodes[0], payment_hash, true, fail_conditions); } @@ -1923,7 +1919,7 @@ fn do_test_phantom_dust_exposure_failure(multiplier_dust_limit: bool) { let err_data = 0u16.to_be_bytes(); let mut fail_conditions = PaymentFailedConditions::new() .blamed_scid(phantom_scid) - .expected_htlc_error_data(0x1000 | 7, &err_data); + .expected_htlc_error_data(LocalHTLCFailureReason::TemporaryChannelFailure, &err_data); expect_payment_failed_conditions(&nodes[0], payment_hash, false, fail_conditions); } @@ -1973,6 +1969,6 @@ fn test_phantom_failure_reject_payment() { error_data.extend_from_slice(&nodes[1].node.best_block.read().unwrap().height.to_be_bytes()); let mut fail_conditions = PaymentFailedConditions::new() .blamed_scid(phantom_scid) - .expected_htlc_error_data(0x4000 | 15, &error_data); + .expected_htlc_error_data(LocalHTLCFailureReason::IncorrectPaymentDetails, &error_data); expect_payment_failed_conditions(&nodes[0], payment_hash, true, fail_conditions); } diff --git a/lightning/src/ln/onion_utils.rs b/lightning/src/ln/onion_utils.rs index f3399911238..2ebe77778d1 100644 --- a/lightning/src/ln/onion_utils.rs +++ b/lightning/src/ln/onion_utils.rs @@ -696,8 +696,6 @@ pub(crate) fn set_max_path_length( /// the hops can be of variable length. pub(crate) const ONION_DATA_LEN: usize = 20 * 65; -pub(super) const INVALID_ONION_BLINDING: u16 = 0x8000 | 0x4000 | 24; - #[inline] fn shift_slice_right(arr: &mut [u8], amt: usize) { for i in (amt..arr.len()).rev() { @@ -902,8 +900,8 @@ pub(super) fn test_crypt_failure_packet(shared_secret: &[u8], packet: &mut Onion } fn build_unencrypted_failure_packet( - shared_secret: &[u8], failure_type: u16, failure_data: &[u8], hold_time: u32, - min_packet_len: usize, + shared_secret: &[u8], failure_reason: LocalHTLCFailureReason, failure_data: &[u8], + hold_time: u32, min_packet_len: usize, ) -> OnionErrorPacket { assert_eq!(shared_secret.len(), 32); assert!(failure_data.len() <= 64531); @@ -924,7 +922,7 @@ fn build_unencrypted_failure_packet( // Write failure len, type and data. (failure_len as u16).write(&mut writer).unwrap(); - failure_type.write(&mut writer).unwrap(); + failure_reason.failure_code().write(&mut writer).unwrap(); writer.0.extend_from_slice(&failure_data[..]); // Write pad len and resize to match padding. @@ -961,11 +959,12 @@ fn update_attribution_data( } pub(super) fn build_failure_packet( - shared_secret: &[u8], failure_type: u16, failure_data: &[u8], hold_time: u32, + shared_secret: &[u8], failure_reason: LocalHTLCFailureReason, failure_data: &[u8], + hold_time: u32, ) -> OnionErrorPacket { let mut onion_error_packet = build_unencrypted_failure_packet( shared_secret, - failure_type, + failure_reason, failure_data, hold_time, DEFAULT_MIN_FAILURE_PACKET_LEN, @@ -1081,11 +1080,6 @@ where let mut is_from_final_non_blinded_node = false; let mut hop_hold_times: Vec = Vec::new(); - const BADONION: u16 = 0x8000; - const PERM: u16 = 0x4000; - const NODE: u16 = 0x2000; - const UPDATE: u16 = 0x1000; - enum ErrorHop<'a> { RouteHop(&'a RouteHop), TrampolineHop(&'a TrampolineHop), @@ -1474,6 +1468,322 @@ where } } +const BADONION: u16 = 0x8000; +const PERM: u16 = 0x4000; +const NODE: u16 = 0x2000; +const UPDATE: u16 = 0x1000; + +/// The reason that a HTLC was failed by the local node. These errors either represent direct, +/// human-readable mappings of BOLT04 error codes or provide additional information that would +/// otherwise be erased by the BOLT04 error code. +/// +/// For example: +/// [`Self::FeeInsufficient`] is a direct representation of its underlying BOLT04 error code. +/// [`Self::PrivateChannelForward`] provides additional information that is not provided by its +/// BOLT04 error code. +#[derive(Copy, Clone, Debug, Hash, PartialEq, Eq)] +pub enum LocalHTLCFailureReason { + /// There has been a temporary processing failure on the node which may resolve on retry. + TemporaryNodeFailure, + /// These has been a permanent processing failure on the node which will not resolve on retry. + PermanentNodeFailure, + /// The HTLC does not implement a feature that is required by our node. + /// + /// The sender may have outdated gossip, or a bug in its implementation. + RequiredNodeFeature, + /// The onion version specified by the HTLC packet is unknown to our node. + InvalidOnionVersion, + /// The integrity of the HTLC packet cannot be verified because it has an invalid HMAC. + InvalidOnionHMAC, + /// The onion packet has an invalid ephemeral key, so the HTLC cannot be processed. + InvalidOnionKey, + /// A temporary forwarding error has occurred which may resolve on retry. + TemporaryChannelFailure, + /// A permanent forwarding error has occurred which will not resolve on retry. + PermanentChannelFailure, + /// The HTLC does not implement a feature that is required by our channel for processing. + RequiredChannelFeature, + /// The HTLC's target outgoing channel that is not known to our node. + UnknownNextPeer, + /// The HTLC amount is below our advertised htlc_minimum_msat. + /// + /// The sender may have outdated gossip, or a bug in its implementation. + AmountBelowMinimum, + /// The HTLC does not pay sufficient fees. + /// + /// The sender may have outdated gossip, or a bug in its implementation. + FeeInsufficient, + /// The HTLC does not meet the cltv_expiry_delta advertised by our node, set by + /// [`ChannelConfig::cltv_expiry_delta`]. + /// + /// The sender may have outdated gossip, or a bug in its implementation. + /// + /// [`ChannelConfig::cltv_expiry_delta`]: crate::util::config::ChannelConfig::cltv_expiry_delta + IncorrectCLTVExpiry, + /// The HTLC expires too close to the current block height to be safely processed. + CLTVExpiryTooSoon, + /// A payment was made to our node that either had incorrect payment information, or was + /// unknown to us. + IncorrectPaymentDetails, + /// The HTLC's expiry is less than the expiry height specified by the sender. + /// + /// The forwarding node has either tampered with this value, or the sending node has an + /// old best block height. + FinalIncorrectCLTVExpiry, + /// The HTLC's amount is less than the amount specified by the sender. + /// + /// The forwarding node has tampered with this value, or has a bug in its implementation. + FinalIncorrectHTLCAmount, + /// The channel has been marked as disabled because the channel peer is offline. + ChannelDisabled, + /// The HTLC expires too far in the future, so it is rejected to avoid the worst-case outcome + /// of funds being held for extended periods of time. + /// + /// Limit set by ['crate::ln::channelmanager::CLTV_FAR_FAR_AWAY`]. + CLTVExpiryTooFar, + /// The HTLC payload contained in the onion packet could not be understood by our node. + InvalidOnionPayload, + /// The total amount for a multi-part payment did not arrive in time, so the HTLCs partially + /// paying the amount were canceled. + MPPTimeout, + /// Our node was selected as part of a blinded path, but the packet we received was not + /// properly constructed, or had incorrect values for the blinded path. + /// + /// This may happen if the forwarding node tamperd with the HTLC or the sender or recipient + /// implementations have a bug. + InvalidOnionBlinding, + /// A HTLC forward was failed back rather than forwarded on the proposed outgoing channel + /// because its expiry is too close to the current block height to leave time to safely claim + /// it on chain if the channel force closes. + ForwardExpiryBuffer, + /// The HTLC was failed because it has invalid trampoline forwarding information. + InvalidTrampolineForward, + /// A HTLC receive was failed back rather than claimed because its expiry is too close to + /// the current block height to leave time to safely claim it on chain if the channel force + /// closes. + PaymentClaimBuffer, + /// The HTLC was failed because accepting it would push our commitment's total amount of dust + /// HTLCs over the limit that we allow to be burned to miner fees if the channel closed while + /// they are unresolved. + DustLimitHolder, + /// The HTLC was failed because accepting it would push our counterparty's total amount of + /// dust (small) HTLCs over the limit that we allow to be burned to miner fees if the channel + /// closes while they are unresolved. + DustLimitCounterparty, + /// The HTLC was failed because it would drop the remote party's channel balance such that it + /// cannot cover the fees it is required to pay at various fee rates. This buffer is maintained + /// so that channels can always maintain reasonable fee rates. + FeeSpikeBuffer, + /// The HTLC that requested to be forwarded over a private channel was rejected to prevent + /// revealing the existence of the channel. + PrivateChannelForward, + /// The HTLC was failed because it made a request to forward over the real channel ID of a + /// channel that implements `option_scid_alias` which is a privacy feature to prevent the + /// real channel ID from being known. + RealSCIDForward, + /// The HTLC was rejected because our channel has not yet reached sufficient depth to be used. + ChannelNotReady, + /// A keysend payment with a preimage that did not match the HTLC has was rejected. + InvalidKeysendPreimage, + /// The HTLC was failed because it had an invalid trampoline payload. + InvalidTrampolinePayload, + /// A payment was rejected because it did not include the correct payment secret from an + /// invoice. + PaymentSecretRequired, + /// The HTLC was failed because its expiry is too close to the current block height, and we + /// expect that it will immediately be failed back by our downstream peer. + OutgoingCLTVTooSoon, + /// The HTLC was failed because it was pending on a channel which is now in the process of + /// being closed. + ChannelClosed, + /// The HTLC was failed back because its expiry height was reached and funds were timed out + /// on chain. + OnChainTimeout, + /// The HTLC was failed because its amount is greater than the capacity of the channel. + AmountExceedsCapacity, + /// The HTLC was failed because zero amount HTLCs are not allowed. + ZeroAmount, + /// The HTLC was failed because its amount is less than the smallest HTLC that the channel + /// can currently accept. + /// + /// This may occur because the HTLC is smaller than the counterparty's advertised minimum + /// accepted HTLC size, or if we have reached our maximum total dust HTLC exposure. + HTLCMinimum, + /// The HTLC was failed because its amount is more than then largest HTLC that the channel + /// can currently accept. + /// + /// This may occur because the outbound channel has insufficient liquidity to forward the HTLC, + /// we have reached the counterparty's in-flight limits, or the HTLC exceeds our advertised + /// maximum accepted HTLC size. + HTLCMaximum, + /// The HTLC was failed because our remote peer is offline. + PeerOffline, + /// UnknownFailureCode represents BOLT04 failure codes that we are not familiar with. We will + /// encounter this if: + /// - A peer sends us a new failure code that LDK has not yet been upgraded to understand. + /// - We read a deprecated failure code from disk that LDK no longer uses. + /// + /// See + /// for latest defined error codes. + UnknownFailureCode { + /// The bolt 04 failure code. + code: u16, + }, +} + +impl LocalHTLCFailureReason { + pub(super) fn failure_code(&self) -> u16 { + match self { + Self::TemporaryNodeFailure | Self::ForwardExpiryBuffer => NODE | 2, + Self::PermanentNodeFailure => PERM | NODE | 2, + Self::RequiredNodeFeature | Self::PaymentSecretRequired => PERM | NODE | 3, + Self::InvalidOnionVersion => BADONION | PERM | 4, + Self::InvalidOnionHMAC => BADONION | PERM | 5, + Self::InvalidOnionKey => BADONION | PERM | 6, + Self::TemporaryChannelFailure + | Self::DustLimitHolder + | Self::DustLimitCounterparty + | Self::FeeSpikeBuffer + | Self::ChannelNotReady + | Self::AmountExceedsCapacity + | Self::ZeroAmount + | Self::HTLCMinimum + | Self::HTLCMaximum + | Self::PeerOffline => UPDATE | 7, + Self::PermanentChannelFailure | Self::ChannelClosed | Self::OnChainTimeout => PERM | 8, + Self::RequiredChannelFeature => PERM | 9, + Self::UnknownNextPeer + | Self::PrivateChannelForward + | Self::RealSCIDForward + | Self::InvalidTrampolineForward => PERM | 10, + Self::AmountBelowMinimum => UPDATE | 11, + Self::FeeInsufficient => UPDATE | 12, + Self::IncorrectCLTVExpiry => UPDATE | 13, + Self::CLTVExpiryTooSoon | Self::OutgoingCLTVTooSoon => UPDATE | 14, + Self::IncorrectPaymentDetails + | Self::PaymentClaimBuffer + | Self::InvalidKeysendPreimage => PERM | 15, + Self::FinalIncorrectCLTVExpiry => 18, + Self::FinalIncorrectHTLCAmount => 19, + Self::ChannelDisabled => UPDATE | 20, + Self::CLTVExpiryTooFar => 21, + Self::InvalidOnionPayload | Self::InvalidTrampolinePayload => PERM | 22, + Self::MPPTimeout => 23, + Self::InvalidOnionBlinding => BADONION | PERM | 24, + Self::UnknownFailureCode { code } => *code, + } + } + + pub(super) fn is_temporary(&self) -> bool { + self.failure_code() & UPDATE == UPDATE + } + + #[cfg(test)] + pub(super) fn is_permanent(&self) -> bool { + self.failure_code() & PERM == PERM + } +} + +impl Into for u16 { + fn into(self) -> LocalHTLCFailureReason { + if self == (NODE | 2) { + LocalHTLCFailureReason::TemporaryNodeFailure + } else if self == (PERM | NODE | 2) { + LocalHTLCFailureReason::PermanentNodeFailure + } else if self == (PERM | NODE | 3) { + LocalHTLCFailureReason::RequiredNodeFeature + } else if self == (BADONION | PERM | 4) { + LocalHTLCFailureReason::InvalidOnionVersion + } else if self == (BADONION | PERM | 5) { + LocalHTLCFailureReason::InvalidOnionHMAC + } else if self == (BADONION | PERM | 6) { + LocalHTLCFailureReason::InvalidOnionKey + } else if self == (UPDATE | 7) { + LocalHTLCFailureReason::TemporaryChannelFailure + } else if self == (PERM | 8) { + LocalHTLCFailureReason::PermanentChannelFailure + } else if self == (PERM | 9) { + LocalHTLCFailureReason::RequiredChannelFeature + } else if self == (PERM | 10) { + LocalHTLCFailureReason::UnknownNextPeer + } else if self == (UPDATE | 11) { + LocalHTLCFailureReason::AmountBelowMinimum + } else if self == (UPDATE | 12) { + LocalHTLCFailureReason::FeeInsufficient + } else if self == (UPDATE | 13) { + LocalHTLCFailureReason::IncorrectCLTVExpiry + } else if self == (UPDATE | 14) { + LocalHTLCFailureReason::CLTVExpiryTooSoon + } else if self == (PERM | 15) { + LocalHTLCFailureReason::IncorrectPaymentDetails + } else if self == 18 { + LocalHTLCFailureReason::FinalIncorrectCLTVExpiry + } else if self == 19 { + LocalHTLCFailureReason::FinalIncorrectHTLCAmount + } else if self == (UPDATE | 20) { + LocalHTLCFailureReason::ChannelDisabled + } else if self == 21 { + LocalHTLCFailureReason::CLTVExpiryTooFar + } else if self == (PERM | 22) { + LocalHTLCFailureReason::InvalidOnionPayload + } else if self == 23 { + LocalHTLCFailureReason::MPPTimeout + } else if self == (BADONION | PERM | 24) { + LocalHTLCFailureReason::InvalidOnionBlinding + } else { + LocalHTLCFailureReason::UnknownFailureCode { code: self } + } + } +} + +impl_writeable_tlv_based_enum!(LocalHTLCFailureReason, + (1, TemporaryNodeFailure) => {}, + (3, PermanentNodeFailure) => {}, + (5, RequiredNodeFeature) => {}, + (7, InvalidOnionVersion) => {}, + (9, InvalidOnionHMAC) => {}, + (11, InvalidOnionKey) => {}, + (13, TemporaryChannelFailure) => {}, + (15, PermanentChannelFailure) => {}, + (17, RequiredChannelFeature) => {}, + (19, UnknownNextPeer) => {}, + (21, AmountBelowMinimum) => {}, + (23, FeeInsufficient) => {}, + (25, IncorrectCLTVExpiry) => {}, + (27, CLTVExpiryTooSoon) => {}, + (29, IncorrectPaymentDetails) => {}, + (31, FinalIncorrectCLTVExpiry) => {}, + (33, FinalIncorrectHTLCAmount) => {}, + (35, ChannelDisabled) => {}, + (37, CLTVExpiryTooFar) => {}, + (39, InvalidOnionPayload) => {}, + (41, MPPTimeout) => {}, + (43, InvalidOnionBlinding) => {}, + (45, InvalidTrampolineForward) => {}, + (47, PaymentClaimBuffer) => {}, + (49, DustLimitHolder) => {}, + (51, DustLimitCounterparty) => {}, + (53, FeeSpikeBuffer) => {}, + (55, OnChainTimeout) => {}, + (57, PrivateChannelForward) => {}, + (59, RealSCIDForward) => {}, + (61, ChannelNotReady) => {}, + (63, InvalidKeysendPreimage) => {}, + (65, InvalidTrampolinePayload) => {}, + (67, PaymentSecretRequired) => {}, + (69, ForwardExpiryBuffer) => {}, + (71, OutgoingCLTVTooSoon) => {}, + (73, ChannelClosed) => {}, + (75, UnknownFailureCode) => { + (0, code, required), + }, + (77, AmountExceedsCapacity) => {}, + (79, ZeroAmount) => {}, + (81, HTLCMinimum) => {}, + (83, HTLCMaximum) => {}, + (85, PeerOffline) => {}, +); + #[derive(Clone)] // See Channel::revoke_and_ack for why, tl;dr: Rust bug #[cfg_attr(test, derive(PartialEq))] pub(super) struct HTLCFailReason(HTLCFailReasonRepr); @@ -1482,7 +1792,7 @@ pub(super) struct HTLCFailReason(HTLCFailReasonRepr); #[cfg_attr(test, derive(PartialEq))] enum HTLCFailReasonRepr { LightningError { err: msgs::OnionErrorPacket, hold_time: Option }, - Reason { failure_code: u16, data: Vec }, + Reason { data: Vec, reason: LocalHTLCFailureReason }, } impl HTLCFailReason { @@ -1499,8 +1809,8 @@ impl HTLCFailReason { impl core::fmt::Debug for HTLCFailReason { fn fmt(&self, f: &mut core::fmt::Formatter) -> Result<(), core::fmt::Error> { match self.0 { - HTLCFailReasonRepr::Reason { ref failure_code, .. } => { - write!(f, "HTLC error code {}", failure_code) + HTLCFailReasonRepr::Reason { ref reason, .. } => { + write!(f, "HTLC error code {}", reason.failure_code()) }, HTLCFailReasonRepr::LightningError { .. } => { write!(f, "pre-built LightningError") @@ -1540,57 +1850,96 @@ impl_writeable_tlv_based_enum!(HTLCFailReasonRepr, (_unused, err, (static_value, msgs::OnionErrorPacket { data: data.ok_or(DecodeError::InvalidValue)?, attribution_data })), }, (1, Reason) => { - (0, failure_code, required), + (0, _failure_code, (legacy, u16, + |r: &HTLCFailReasonRepr| match r { + HTLCFailReasonRepr::LightningError{ .. } => None, + HTLCFailReasonRepr::Reason{ reason, .. } => Some(reason.failure_code()) + })), (2, data, required_vec), + // failure_code was required, and is replaced by reason in 0.2 so any time we do not have a + // reason available failure_code will be Some and can be expressed as a reason. + (4, reason, (default_value, >::into(_failure_code.ok_or(DecodeError::InvalidValue)?))), }, ); impl HTLCFailReason { - #[rustfmt::skip] - pub(super) fn reason(failure_code: u16, data: Vec) -> Self { - const BADONION: u16 = 0x8000; - const PERM: u16 = 0x4000; - const NODE: u16 = 0x2000; - const UPDATE: u16 = 0x1000; - - if failure_code == 2 | NODE { debug_assert!(data.is_empty()) } - else if failure_code == 2 | PERM | NODE { debug_assert!(data.is_empty()) } - else if failure_code == 3 | PERM | NODE { debug_assert!(data.is_empty()) } - else if failure_code == 4 | BADONION | PERM { debug_assert_eq!(data.len(), 32) } - else if failure_code == 5 | BADONION | PERM { debug_assert_eq!(data.len(), 32) } - else if failure_code == 6 | BADONION | PERM { debug_assert_eq!(data.len(), 32) } - else if failure_code == 7 | UPDATE { - debug_assert_eq!(data.len() - 2, u16::from_be_bytes(data[0..2].try_into().unwrap()) as usize) } - else if failure_code == 8 | PERM { debug_assert!(data.is_empty()) } - else if failure_code == 9 | PERM { debug_assert!(data.is_empty()) } - else if failure_code == 10 | PERM { debug_assert!(data.is_empty()) } - else if failure_code == 11 | UPDATE { - debug_assert_eq!(data.len() - 2 - 8, u16::from_be_bytes(data[8..10].try_into().unwrap()) as usize) } - else if failure_code == 12 | UPDATE { - debug_assert_eq!(data.len() - 2 - 8, u16::from_be_bytes(data[8..10].try_into().unwrap()) as usize) } - else if failure_code == 13 | UPDATE { - debug_assert_eq!(data.len() - 2 - 4, u16::from_be_bytes(data[4..6].try_into().unwrap()) as usize) } - else if failure_code == 14 | UPDATE { - debug_assert_eq!(data.len() - 2, u16::from_be_bytes(data[0..2].try_into().unwrap()) as usize) } - else if failure_code == 15 | PERM { debug_assert_eq!(data.len(), 12) } - else if failure_code == 18 { debug_assert_eq!(data.len(), 4) } - else if failure_code == 19 { debug_assert_eq!(data.len(), 8) } - else if failure_code == 20 | UPDATE { - debug_assert_eq!(data.len() - 2 - 2, u16::from_be_bytes(data[2..4].try_into().unwrap()) as usize) } - else if failure_code == 21 { debug_assert!(data.is_empty()) } - else if failure_code == 22 | PERM { debug_assert!(data.len() <= 11) } - else if failure_code == 23 { debug_assert!(data.is_empty()) } - else if failure_code == INVALID_ONION_BLINDING { debug_assert_eq!(data.len(), 32) } - else if failure_code & BADONION != 0 { - // We set some bogus BADONION failure codes in test, so ignore unknown ones. + pub(super) fn reason(failure_reason: LocalHTLCFailureReason, data: Vec) -> Self { + match failure_reason { + LocalHTLCFailureReason::TemporaryNodeFailure + | LocalHTLCFailureReason::ForwardExpiryBuffer => debug_assert!(data.is_empty()), + LocalHTLCFailureReason::PermanentNodeFailure => debug_assert!(data.is_empty()), + LocalHTLCFailureReason::RequiredNodeFeature + | LocalHTLCFailureReason::PaymentSecretRequired => debug_assert!(data.is_empty()), + LocalHTLCFailureReason::InvalidOnionVersion => debug_assert_eq!(data.len(), 32), + LocalHTLCFailureReason::InvalidOnionHMAC => debug_assert_eq!(data.len(), 32), + LocalHTLCFailureReason::InvalidOnionKey => debug_assert_eq!(data.len(), 32), + LocalHTLCFailureReason::TemporaryChannelFailure + | LocalHTLCFailureReason::DustLimitHolder + | LocalHTLCFailureReason::DustLimitCounterparty + | LocalHTLCFailureReason::FeeSpikeBuffer + | LocalHTLCFailureReason::ChannelNotReady + | LocalHTLCFailureReason::AmountExceedsCapacity + | LocalHTLCFailureReason::ZeroAmount + | LocalHTLCFailureReason::HTLCMinimum + | LocalHTLCFailureReason::HTLCMaximum + | LocalHTLCFailureReason::PeerOffline => { + debug_assert_eq!( + data.len() - 2, + u16::from_be_bytes(data[0..2].try_into().unwrap()) as usize + ) + }, + LocalHTLCFailureReason::PermanentChannelFailure + | LocalHTLCFailureReason::OnChainTimeout + | LocalHTLCFailureReason::ChannelClosed => debug_assert!(data.is_empty()), + LocalHTLCFailureReason::RequiredChannelFeature => debug_assert!(data.is_empty()), + LocalHTLCFailureReason::UnknownNextPeer + | LocalHTLCFailureReason::PrivateChannelForward + | LocalHTLCFailureReason::RealSCIDForward + | LocalHTLCFailureReason::InvalidTrampolineForward => debug_assert!(data.is_empty()), + LocalHTLCFailureReason::AmountBelowMinimum => debug_assert_eq!( + data.len() - 2 - 8, + u16::from_be_bytes(data[8..10].try_into().unwrap()) as usize + ), + LocalHTLCFailureReason::FeeInsufficient => debug_assert_eq!( + data.len() - 2 - 8, + u16::from_be_bytes(data[8..10].try_into().unwrap()) as usize + ), + LocalHTLCFailureReason::IncorrectCLTVExpiry => debug_assert_eq!( + data.len() - 2 - 4, + u16::from_be_bytes(data[4..6].try_into().unwrap()) as usize + ), + LocalHTLCFailureReason::CLTVExpiryTooSoon + | LocalHTLCFailureReason::OutgoingCLTVTooSoon => debug_assert_eq!( + data.len() - 2, + u16::from_be_bytes(data[0..2].try_into().unwrap()) as usize + ), + LocalHTLCFailureReason::IncorrectPaymentDetails + | LocalHTLCFailureReason::PaymentClaimBuffer + | LocalHTLCFailureReason::InvalidKeysendPreimage => debug_assert_eq!(data.len(), 12), + LocalHTLCFailureReason::FinalIncorrectCLTVExpiry => debug_assert_eq!(data.len(), 4), + LocalHTLCFailureReason::FinalIncorrectHTLCAmount => debug_assert_eq!(data.len(), 8), + LocalHTLCFailureReason::ChannelDisabled => debug_assert_eq!( + data.len() - 2 - 2, + u16::from_be_bytes(data[2..4].try_into().unwrap()) as usize + ), + LocalHTLCFailureReason::CLTVExpiryTooFar => debug_assert!(data.is_empty()), + LocalHTLCFailureReason::InvalidOnionPayload + | LocalHTLCFailureReason::InvalidTrampolinePayload => debug_assert!(data.len() <= 11), + LocalHTLCFailureReason::MPPTimeout => debug_assert!(data.is_empty()), + LocalHTLCFailureReason::InvalidOnionBlinding => debug_assert_eq!(data.len(), 32), + LocalHTLCFailureReason::UnknownFailureCode { code } => { + // We set some bogus BADONION failure codes in tests, so allow unknown BADONION. + if code & BADONION == 0 { + debug_assert!(false, "Unknown failure code: {}", code) + } + }, } - else { debug_assert!(false, "Unknown failure code: {}", failure_code) } - Self(HTLCFailReasonRepr::Reason { failure_code, data }) + Self(HTLCFailReasonRepr::Reason { data, reason: failure_reason }) } - pub(super) fn from_failure_code(failure_code: u16) -> Self { - Self::reason(failure_code, Vec::new()) + pub(super) fn from_failure_code(failure_reason: LocalHTLCFailureReason) -> Self { + Self::reason(failure_reason, Vec::new()) } pub(super) fn from_msg(msg: &msgs::UpdateFailHTLC) -> Self { @@ -1612,7 +1961,7 @@ impl HTLCFailReason { &self, incoming_packet_shared_secret: &[u8; 32], secondary_shared_secret: &Option<[u8; 32]>, ) -> msgs::OnionErrorPacket { match self.0 { - HTLCFailReasonRepr::Reason { ref failure_code, ref data } => { + HTLCFailReasonRepr::Reason { ref reason, ref data } => { // Final hop always reports zero hold time. let hold_time: u32 = 0; @@ -1620,7 +1969,7 @@ impl HTLCFailReason { // Phantom hop always reports zero hold time too. let mut packet = build_failure_packet( secondary_shared_secret, - *failure_code, + *reason, &data[..], hold_time, ); @@ -1632,7 +1981,7 @@ impl HTLCFailReason { } else { build_failure_packet( incoming_packet_shared_secret, - *failure_code, + *reason, &data[..], hold_time, ) @@ -1661,7 +2010,7 @@ impl HTLCFailReason { process_onion_failure(secp_ctx, logger, &htlc_source, err.clone()) }, #[allow(unused)] - HTLCFailReasonRepr::Reason { ref failure_code, ref data, .. } => { + HTLCFailReasonRepr::Reason { ref data, ref reason } => { // we get a fail_malformed_htlc from the first hop // TODO: We'd like to generate a NetworkUpdate for temporary // failures here, but that would be insufficient as find_route @@ -1675,7 +2024,7 @@ impl HTLCFailReason { failed_within_blinded_path: false, hold_times: Vec::new(), #[cfg(any(test, feature = "_test_utils"))] - onion_error_code: Some(*failure_code), + onion_error_code: Some(reason.failure_code()), #[cfg(any(test, feature = "_test_utils"))] onion_error_data: Some(data.clone()), } @@ -1823,14 +2172,14 @@ impl Hop { #[derive(Debug)] pub(crate) enum OnionDecodeErr { /// The HMAC of the onion packet did not match the hop data. - Malformed { err_msg: &'static str, err_code: u16 }, + Malformed { err_msg: &'static str, reason: LocalHTLCFailureReason }, /// We failed to decode the onion payload. /// /// If the payload we failed to decode belonged to a Trampoline onion, following the successful /// decoding of the outer onion, the trampoline_shared_secret field should be set. Relay { err_msg: &'static str, - err_code: u16, + reason: LocalHTLCFailureReason, shared_secret: SharedSecret, trampoline_shared_secret: Option, }, @@ -1881,12 +2230,12 @@ where return Err(OnionDecodeErr::Malformed { err_msg: "Final Node OnionHopData provided for us as an intermediary node", - err_code: INVALID_ONION_BLINDING, + reason: LocalHTLCFailureReason::InvalidOnionBlinding, }); } Err(OnionDecodeErr::Relay { err_msg: "Final Node OnionHopData provided for us as an intermediary node", - err_code: 0x4000 | 22, + reason: LocalHTLCFailureReason::InvalidOnionPayload, shared_secret, trampoline_shared_secret: None, }) @@ -1981,7 +2330,7 @@ where if hop_data.intro_node_blinding_point.is_some() { return Err(OnionDecodeErr::Relay { err_msg: "Non-final intro node Trampoline onion data provided to us as last hop", - err_code: 0x4000 | 22, + reason: LocalHTLCFailureReason::InvalidOnionPayload, shared_secret, trampoline_shared_secret: Some(SharedSecret::from_bytes( trampoline_shared_secret, @@ -1990,14 +2339,14 @@ where } Err(OnionDecodeErr::Malformed { err_msg: "Non-final Trampoline onion data provided to us as last hop", - err_code: INVALID_ONION_BLINDING, + reason: LocalHTLCFailureReason::InvalidOnionBlinding, }) }, Ok((msgs::InboundTrampolinePayload::BlindedReceive(hop_data), Some(_))) => { if hop_data.intro_node_blinding_point.is_some() { return Err(OnionDecodeErr::Relay { err_msg: "Final Trampoline intro node onion data provided to us as intermediate hop", - err_code: 0x4000 | 22, + reason: LocalHTLCFailureReason::InvalidTrampolinePayload, shared_secret, trampoline_shared_secret: Some(SharedSecret::from_bytes( trampoline_shared_secret, @@ -2007,13 +2356,13 @@ where Err(OnionDecodeErr::Malformed { err_msg: "Final Trampoline onion data provided to us as intermediate hop", - err_code: INVALID_ONION_BLINDING, + reason: LocalHTLCFailureReason::InvalidOnionBlinding, }) }, Ok((msgs::InboundTrampolinePayload::Forward(_), None)) => { Err(OnionDecodeErr::Relay { err_msg: "Non-final Trampoline onion data provided to us as last hop", - err_code: 0x4000 | 22, + reason: LocalHTLCFailureReason::InvalidTrampolinePayload, shared_secret, trampoline_shared_secret: Some(SharedSecret::from_bytes( trampoline_shared_secret, @@ -2024,7 +2373,7 @@ where Err(OnionDecodeErr::Relay { err_msg: "Final Trampoline onion data provided to us as intermediate hop", - err_code: 0x4000 | 22, + reason: LocalHTLCFailureReason::InvalidTrampolinePayload, shared_secret, trampoline_shared_secret: Some(SharedSecret::from_bytes( trampoline_shared_secret, @@ -2038,12 +2387,12 @@ where if blinding_point.is_some() { return Err(OnionDecodeErr::Malformed { err_msg: "Intermediate Node OnionHopData provided for us as a final node", - err_code: INVALID_ONION_BLINDING, + reason: LocalHTLCFailureReason::InvalidOnionBlinding, }); } Err(OnionDecodeErr::Relay { err_msg: "Intermediate Node OnionHopData provided for us as a final node", - err_code: 0x4000 | 22, + reason: LocalHTLCFailureReason::InvalidOnionPayload, shared_secret, trampoline_shared_secret: None, }) @@ -2172,7 +2521,7 @@ fn decode_next_hop, N: NextPacketBytes>( if !fixed_time_eq(&Hmac::from_engine(hmac).to_byte_array(), &hmac_bytes) { return Err(OnionDecodeErr::Malformed { err_msg: "HMAC Check failed", - err_code: 0x8000 | 0x4000 | 5, + reason: LocalHTLCFailureReason::InvalidOnionHMAC, }); } @@ -2180,19 +2529,19 @@ fn decode_next_hop, N: NextPacketBytes>( let mut chacha_stream = ChaChaReader { chacha: &mut chacha, read: Cursor::new(&hop_data[..]) }; match R::read(&mut chacha_stream, read_args) { Err(err) => { - let error_code = match err { + let reason = match err { // Unknown version - msgs::DecodeError::UnknownVersion => 0x8000 | 0x4000 | 4, + msgs::DecodeError::UnknownVersion => LocalHTLCFailureReason::InvalidOnionVersion, // invalid_onion_payload msgs::DecodeError::UnknownRequiredFeature | msgs::DecodeError::InvalidValue - | msgs::DecodeError::ShortRead => 0x4000 | 22, + | msgs::DecodeError::ShortRead => LocalHTLCFailureReason::InvalidOnionPayload, // Should never happen - _ => 0x2000 | 2, + _ => LocalHTLCFailureReason::TemporaryNodeFailure, }; return Err(OnionDecodeErr::Relay { err_msg: "Unable to decode our hop data", - err_code: error_code, + reason, shared_secret: SharedSecret::from_bytes(shared_secret), trampoline_shared_secret: None, }); @@ -2202,7 +2551,7 @@ fn decode_next_hop, N: NextPacketBytes>( if let Err(_) = chacha_stream.read_exact(&mut hmac[..]) { return Err(OnionDecodeErr::Relay { err_msg: "Unable to decode our hop data", - err_code: 0x4000 | 22, + reason: LocalHTLCFailureReason::InvalidOnionPayload, shared_secret: SharedSecret::from_bytes(shared_secret), trampoline_shared_secret: None, }); @@ -2768,7 +3117,9 @@ mod tests { let decrypted_failure = test_attributable_failure_packet_onion_with_mutation(Some(mutation)); - if decrypted_failure.onion_error_code == Some(0x4000 | 15) { + if decrypted_failure.onion_error_code + == Some(LocalHTLCFailureReason::IncorrectPaymentDetails.failure_code()) + { continue; } @@ -2781,7 +3132,10 @@ mod tests { #[test] fn test_attributable_failure_packet_onion_happy() { let decrypted_failure = test_attributable_failure_packet_onion_with_mutation(None); - assert_eq!(decrypted_failure.onion_error_code, Some(0x4000 | 15)); + assert_eq!( + decrypted_failure.onion_error_code, + Some(LocalHTLCFailureReason::IncorrectPaymentDetails.failure_code()) + ); assert_eq!(decrypted_failure.hold_times, [5, 4, 3, 2, 1]); } @@ -2844,7 +3198,7 @@ mod tests { let onion_keys = build_test_onion_keys(); let mut onion_error = super::build_unencrypted_failure_packet( onion_keys[4].shared_secret.as_ref(), - 0x400f, + LocalHTLCFailureReason::IncorrectPaymentDetails, &failure_data, 1, 1024, @@ -3003,7 +3357,10 @@ mod tests { Some(&trampoline_session_priv), error_packet, ); - assert_eq!(decrypted_failure.onion_error_code, Some(0x400f)); + assert_eq!( + decrypted_failure.onion_error_code, + Some(LocalHTLCFailureReason::IncorrectPaymentDetails.failure_code()) + ); } { @@ -3033,7 +3390,7 @@ mod tests { { // Ensure error decryption works without the Trampoline hops having been hit. - let error_code = 0x2002; + let error_code = LocalHTLCFailureReason::TemporaryNodeFailure; let mut first_hop_error_packet = build_unencrypted_failure_packet( outer_onion_keys[0].shared_secret.as_ref(), error_code, @@ -3049,12 +3406,12 @@ mod tests { let decrypted_failure = process_onion_failure(&secp_ctx, &logger, &htlc_source, first_hop_error_packet); - assert_eq!(decrypted_failure.onion_error_code, Some(error_code)); + assert_eq!(decrypted_failure.onion_error_code, Some(error_code.failure_code())); }; { // Ensure error decryption works from the first Trampoline hop, but at the outer onion. - let error_code = 0x2003; + let error_code = 0x2003.into(); let mut trampoline_outer_hop_error_packet = build_unencrypted_failure_packet( outer_onion_keys[1].shared_secret.as_ref(), error_code, @@ -3080,12 +3437,12 @@ mod tests { &htlc_source, trampoline_outer_hop_error_packet, ); - assert_eq!(decrypted_failure.onion_error_code, Some(error_code)); + assert_eq!(decrypted_failure.onion_error_code, Some(error_code.failure_code())); }; { // Ensure error decryption works from the Trampoline inner onion. - let error_code = 0x2004; + let error_code = 0x2004.into(); let mut trampoline_inner_hop_error_packet = build_unencrypted_failure_packet( trampoline_onion_keys[0].shared_secret.as_ref(), error_code, @@ -3116,12 +3473,12 @@ mod tests { &htlc_source, trampoline_inner_hop_error_packet, ); - assert_eq!(decrypted_failure.onion_error_code, Some(error_code)); + assert_eq!(decrypted_failure.onion_error_code, Some(error_code.failure_code())); } { // Ensure error decryption works from a later hop in the Trampoline inner onion. - let error_code = 0x2005; + let error_code = 0x2005.into(); let mut trampoline_second_hop_error_packet = build_unencrypted_failure_packet( trampoline_onion_keys[1].shared_secret.as_ref(), error_code, @@ -3157,7 +3514,7 @@ mod tests { &htlc_source, trampoline_second_hop_error_packet, ); - assert_eq!(decrypted_failure.onion_error_code, Some(error_code)); + assert_eq!(decrypted_failure.onion_error_code, Some(error_code.failure_code())); } } } @@ -3385,7 +3742,7 @@ mod tests { let shared_secret = [0; 32]; let onion_error = super::build_unencrypted_failure_packet( &shared_secret, - 0x2002, + LocalHTLCFailureReason::TemporaryNodeFailure, &failure_data, 0, DEFAULT_MIN_FAILURE_PACKET_LEN, diff --git a/lightning/src/ln/payment_tests.rs b/lightning/src/ln/payment_tests.rs index 13570393288..c62b80d5653 100644 --- a/lightning/src/ln/payment_tests.rs +++ b/lightning/src/ln/payment_tests.rs @@ -23,7 +23,7 @@ use crate::ln::types::ChannelId; use crate::types::payment::{PaymentHash, PaymentSecret, PaymentPreimage}; use crate::ln::chan_utils; use crate::ln::msgs::{BaseMessageHandler, ChannelMessageHandler, MessageSendEvent}; -use crate::ln::onion_utils; +use crate::ln::onion_utils::{self, LocalHTLCFailureReason}; use crate::ln::outbound_payment::{IDEMPOTENCY_TIMEOUT_TICKS, ProbeSendFailure, Retry, RetryableSendFailure}; use crate::routing::gossip::{EffectiveCapacity, RoutingFees}; use crate::routing::router::{get_route, Path, PaymentParameters, Route, Router, RouteHint, RouteHintHop, RouteHop, RouteParameters}; @@ -344,7 +344,7 @@ fn do_mpp_receive_timeout(send_partial_mpp: bool) { check_added_monitors!(nodes[1], 1); commitment_signed_dance!(nodes[0], nodes[1], htlc_fail_updates_1_0.commitment_signed, false); - expect_payment_failed_conditions(&nodes[0], payment_hash, false, PaymentFailedConditions::new().mpp_parts_remain().expected_htlc_error_data(23, &[][..])); + expect_payment_failed_conditions(&nodes[0], payment_hash, false, PaymentFailedConditions::new().mpp_parts_remain().expected_htlc_error_data(LocalHTLCFailureReason::MPPTimeout, &[][..])); } else { // Pass half of the payment along the second path. let node_2_msgs = remove_first_msg_event_to_node(&nodes[2].node.get_our_node_id(), &mut events); @@ -1952,7 +1952,7 @@ fn do_test_intercepted_payment(test: InterceptTest) { let fail_conditions = PaymentFailedConditions::new() .blamed_scid(intercept_scid) .blamed_chan_closed(true) - .expected_htlc_error_data(0x4000 | 10, &[]); + .expected_htlc_error_data(LocalHTLCFailureReason::UnknownNextPeer, &[]); expect_payment_failed_conditions(&nodes[0], payment_hash, false, fail_conditions); } else if test == InterceptTest::Forward { // Check that we'll fail as expected when sending to a channel that isn't in `ChannelReady` yet. @@ -2025,7 +2025,7 @@ fn do_test_intercepted_payment(test: InterceptTest) { nodes[0].node.handle_update_fail_htlc(nodes[1].node.get_our_node_id(), &htlc_timeout_updates.update_fail_htlcs[0]); commitment_signed_dance!(nodes[0], nodes[1], htlc_timeout_updates.commitment_signed, false); - expect_payment_failed!(nodes[0], payment_hash, false, 0x2000 | 2, []); + expect_payment_failed!(nodes[0], payment_hash, false, LocalHTLCFailureReason::TemporaryNodeFailure, []); // Check for unknown intercept id error. let (_, channel_id) = open_zero_conf_channel(&nodes[1], &nodes[2], None); diff --git a/lightning/src/ln/priv_short_conf_tests.rs b/lightning/src/ln/priv_short_conf_tests.rs index ae2f7cf1b32..ed707771f84 100644 --- a/lightning/src/ln/priv_short_conf_tests.rs +++ b/lightning/src/ln/priv_short_conf_tests.rs @@ -14,6 +14,7 @@ use crate::chain::ChannelMonitorUpdateStatus; use crate::events::{ClosureReason, Event, HTLCDestination}; use crate::ln::channelmanager::{MIN_CLTV_EXPIRY_DELTA, PaymentId, RecipientOnionFields}; +use crate::ln::onion_utils::LocalHTLCFailureReason; use crate::routing::gossip::RoutingFees; use crate::routing::router::{PaymentParameters, RouteHint, RouteHintHop}; use crate::types::features::ChannelTypeFeatures; @@ -456,7 +457,7 @@ fn test_inbound_scid_privacy() { expect_payment_failed_conditions(&nodes[0], payment_hash_2, false, PaymentFailedConditions::new().blamed_scid(last_hop[0].short_channel_id.unwrap()) - .blamed_chan_closed(true).expected_htlc_error_data(0x4000|10, &[0; 0])); + .blamed_chan_closed(true).expected_htlc_error_data(LocalHTLCFailureReason::UnknownNextPeer, &[0; 0])); } #[test] @@ -513,7 +514,7 @@ fn test_scid_alias_returned() { let err_data = 0u16.to_be_bytes(); expect_payment_failed_conditions(&nodes[0], payment_hash, false, PaymentFailedConditions::new().blamed_scid(last_hop[0].inbound_scid_alias.unwrap()) - .blamed_chan_closed(false).expected_htlc_error_data(0x1000|7, &err_data)); + .blamed_chan_closed(false).expected_htlc_error_data(LocalHTLCFailureReason::TemporaryChannelFailure, &err_data)); route.paths[0].hops[1].fee_msat = 10_000; // Reset to the correct payment amount route.paths[0].hops[0].fee_msat = 0; // But set fee paid to the middle hop to 0 @@ -542,7 +543,7 @@ fn test_scid_alias_returned() { err_data.extend_from_slice(&0u16.to_be_bytes()); expect_payment_failed_conditions(&nodes[0], payment_hash, false, PaymentFailedConditions::new().blamed_scid(last_hop[0].inbound_scid_alias.unwrap()) - .blamed_chan_closed(false).expected_htlc_error_data(0x1000|12, &err_data)); + .blamed_chan_closed(false).expected_htlc_error_data(LocalHTLCFailureReason::FeeInsufficient, &err_data)); } #[test] diff --git a/lightning/src/ln/shutdown_tests.rs b/lightning/src/ln/shutdown_tests.rs index b9fec7ce97a..ae4f73231e6 100644 --- a/lightning/src/ln/shutdown_tests.rs +++ b/lightning/src/ln/shutdown_tests.rs @@ -20,7 +20,7 @@ use crate::routing::router::{PaymentParameters, get_route, RouteParameters}; use crate::ln::msgs; use crate::ln::types::ChannelId; use crate::ln::msgs::{BaseMessageHandler, ChannelMessageHandler, ErrorAction, MessageSendEvent}; -use crate::ln::onion_utils::INVALID_ONION_BLINDING; +use crate::ln::onion_utils::LocalHTLCFailureReason; use crate::ln::script::ShutdownScript; use crate::util::test_utils; use crate::util::test_utils::OnGetShutdownScriptpubkey; @@ -484,7 +484,7 @@ fn do_htlc_fail_async_shutdown(blinded_recipient: bool) { if blinded_recipient { expect_payment_failed_conditions(&nodes[0], our_payment_hash, false, - PaymentFailedConditions::new().expected_htlc_error_data(INVALID_ONION_BLINDING, &[0; 32])); + PaymentFailedConditions::new().expected_htlc_error_data(LocalHTLCFailureReason::InvalidOnionBlinding, &[0; 32])); } else { expect_payment_failed_with_update!(nodes[0], our_payment_hash, false, chan_2.0.contents.short_channel_id, true); }