Merge pull request #2831 from jkczyz/2024-01-fix-cltv-expiry-delta
authorMatt Corallo <649246+TheBlueMatt@users.noreply.github.com>
Wed, 17 Jan 2024 00:47:48 +0000 (00:47 +0000)
committerGitHub <noreply@github.com>
Wed, 17 Jan 2024 00:47:48 +0000 (00:47 +0000)
Use consistent `cltv_expiry_delta` in `ForwardTlvs`

lightning-net-tokio/src/lib.rs
lightning/src/ln/blinded_payment_tests.rs
lightning/src/ln/channelmanager.rs
lightning/src/ln/msgs.rs
lightning/src/ln/onion_payment.rs
lightning/src/ln/onion_utils.rs
lightning/src/ln/peer_handler.rs

index 1aa2cc25a13cc0772ee2e958da66aca602ce0eb4..be41a2401244f0bff743e44b9e7ec26482d95227 100644 (file)
@@ -502,6 +502,9 @@ impl peer_handler::SocketDescriptor for SocketDescriptor {
                                                        written_len += res;
                                                        if written_len == data.len() { return written_len; }
                                                },
+                                               Err(ref e) if e.kind() == std::io::ErrorKind::WouldBlock => {
+                                                       continue;
+                                               }
                                                Err(_) => return written_len,
                                        }
                                },
index 9b580d1fa7599df71a70cf3d8819d4402545d5c8..a3126b3b53744ee2a8fc596e116ddfd354375ea4 100644 (file)
@@ -490,6 +490,29 @@ fn two_hop_blinded_path_success() {
        claim_payment(&nodes[0], &[&nodes[1], &nodes[2]], payment_preimage);
 }
 
+#[test]
+fn three_hop_blinded_path_success() {
+       let chanmon_cfgs = create_chanmon_cfgs(5);
+       let node_cfgs = create_node_cfgs(5, &chanmon_cfgs);
+       let node_chanmgrs = create_node_chanmgrs(5, &node_cfgs, &[None, None, None, None, None]);
+       let mut nodes = create_network(5, &node_cfgs, &node_chanmgrs);
+       create_announced_chan_between_nodes_with_value(&nodes, 0, 1, 1_000_000, 0);
+       create_announced_chan_between_nodes_with_value(&nodes, 1, 2, 1_000_000, 0);
+       let chan_upd_2_3 = create_announced_chan_between_nodes_with_value(&nodes, 2, 3, 1_000_000, 0).0.contents;
+       let chan_upd_3_4 = create_announced_chan_between_nodes_with_value(&nodes, 3, 4, 1_000_000, 0).0.contents;
+
+       let amt_msat = 5000;
+       let (payment_preimage, payment_hash, payment_secret) = get_payment_preimage_hash(&nodes[4], Some(amt_msat), None);
+       let route_params = get_blinded_route_parameters(amt_msat, payment_secret,
+               nodes.iter().skip(2).map(|n| n.node.get_our_node_id()).collect(),
+               &[&chan_upd_2_3, &chan_upd_3_4], &chanmon_cfgs[4].keys_manager);
+
+       nodes[0].node.send_payment(payment_hash, RecipientOnionFields::spontaneous_empty(), PaymentId(payment_hash.0), route_params, Retry::Attempts(0)).unwrap();
+       check_added_monitors(&nodes[0], 1);
+       pass_along_route(&nodes[0], &[&[&nodes[1], &nodes[2], &nodes[3], &nodes[4]]], amt_msat, payment_hash, payment_secret);
+       claim_payment(&nodes[0], &[&nodes[1], &nodes[2], &nodes[3], &nodes[4]], payment_preimage);
+}
+
 #[derive(PartialEq)]
 enum ReceiveCheckFail {
        // The recipient fails the payment upon `PaymentClaimable`.
@@ -537,11 +560,11 @@ fn do_multi_hop_receiver_fail(check: ReceiveCheckFail) {
        };
 
        let amt_msat = 5000;
-       let final_cltv_delta = if check == ReceiveCheckFail::ProcessPendingHTLCsCheck {
+       let excess_final_cltv_delta_opt = if check == ReceiveCheckFail::ProcessPendingHTLCsCheck {
                // Set the final CLTV expiry too low to trigger the failure in process_pending_htlc_forwards.
                Some(TEST_FINAL_CLTV as u16 - 2)
        } else { None };
-       let (_, payment_hash, payment_secret) = get_payment_preimage_hash(&nodes[2], Some(amt_msat), final_cltv_delta);
+       let (_, payment_hash, payment_secret) = get_payment_preimage_hash(&nodes[2], Some(amt_msat), excess_final_cltv_delta_opt);
        let mut route_params = get_blinded_route_parameters(amt_msat, payment_secret,
                nodes.iter().skip(1).map(|n| n.node.get_our_node_id()).collect(), &[&chan_upd_1_2],
                &chanmon_cfgs[2].keys_manager);
@@ -549,7 +572,8 @@ fn do_multi_hop_receiver_fail(check: ReceiveCheckFail) {
        let route = if check == ReceiveCheckFail::ProcessPendingHTLCsCheck {
                let mut route = get_route(&nodes[0], &route_params).unwrap();
                // Set the final CLTV expiry too low to trigger the failure in process_pending_htlc_forwards.
-               route.paths[0].blinded_tail.as_mut().map(|bt| bt.excess_final_cltv_expiry_delta = TEST_FINAL_CLTV - 2);
+               route.paths[0].hops.last_mut().map(|h| h.cltv_expiry_delta += excess_final_cltv_delta_opt.unwrap() as u32);
+               route.paths[0].blinded_tail.as_mut().map(|bt| bt.excess_final_cltv_expiry_delta = excess_final_cltv_delta_opt.unwrap() as u32);
                route
        } else if check == ReceiveCheckFail::PaymentConstraints {
                // Create a blinded path where the receiver's encrypted payload has an htlc_minimum_msat that is
@@ -657,6 +681,7 @@ fn do_multi_hop_receiver_fail(check: ReceiveCheckFail) {
                        commitment_signed_dance!(nodes[2], nodes[1], (), false, true, false, false);
                },
                ReceiveCheckFail::ProcessPendingHTLCsCheck => {
+                       assert_eq!(payment_event_1_2.msgs[0].cltv_expiry, nodes[0].best_block_info().1 + 1 + excess_final_cltv_delta_opt.unwrap() as u32);
                        nodes[2].node.handle_update_add_htlc(&nodes[1].node.get_our_node_id(), &payment_event_1_2.msgs[0]);
                        check_added_monitors!(nodes[2], 0);
                        do_commitment_signed_dance(&nodes[2], &nodes[1], &payment_event_1_2.commitment_msg, true, true);
index e405c3a672abe6fc2359da3cac35fdc71259ca4f..87197dd45493ff6ba689179cbe721cd905d834ea 100644 (file)
@@ -202,15 +202,16 @@ pub struct BlindedForward {
        /// onion payload if we're the introduction node. Useful for calculating the next hop's
        /// [`msgs::UpdateAddHTLC::blinding_point`].
        pub inbound_blinding_point: PublicKey,
-       // Another field will be added here when we support forwarding as a non-intro node.
+       /// If needed, this determines how this HTLC should be failed backwards, based on whether we are
+       /// the introduction node.
+       pub failure: BlindedFailure,
 }
 
 impl PendingHTLCRouting {
        // Used to override the onion failure code and data if the HTLC is blinded.
        fn blinded_failure(&self) -> Option<BlindedFailure> {
-               // TODO: needs update when we support forwarding blinded HTLCs as non-intro node
                match self {
-                       Self::Forward { blinded: Some(_), .. } => Some(BlindedFailure::FromIntroductionNode),
+                       Self::Forward { blinded: Some(BlindedForward { failure, .. }), .. } => Some(*failure),
                        Self::Receive { requires_blinded_error: true, .. } => Some(BlindedFailure::FromBlindedNode),
                        _ => None,
                }
@@ -305,10 +306,15 @@ pub(super) enum HTLCForwardInfo {
        },
 }
 
-// Used for failing blinded HTLCs backwards correctly.
+/// Whether this blinded HTLC is being failed backwards by the introduction node or a blinded node,
+/// which determines the failure message that should be used.
 #[derive(Clone, Copy, Debug, Hash, PartialEq, Eq)]
-enum BlindedFailure {
+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`.
        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`.
        FromBlindedNode,
 }
 
@@ -3025,8 +3031,9 @@ where
 
                let is_intro_node_forward = match next_hop {
                        onion_utils::Hop::Forward {
-                               // TODO: update this when we support blinded forwarding as non-intro node
-                               next_hop_data: msgs::InboundOnionPayload::BlindedForward { .. }, ..
+                               next_hop_data: msgs::InboundOnionPayload::BlindedForward {
+                                       intro_node_blinding_point: Some(_), ..
+                               }, ..
                        } => true,
                        _ => false,
                };
@@ -4371,7 +4378,7 @@ where
                                                                                incoming_packet_shared_secret: incoming_shared_secret,
                                                                                // Phantom payments are only PendingHTLCRouting::Receive.
                                                                                phantom_shared_secret: None,
-                                                                               blinded_failure: blinded.map(|_| BlindedFailure::FromIntroductionNode),
+                                                                               blinded_failure: blinded.map(|b| b.failure),
                                                                        });
                                                                        let next_blinding_point = blinded.and_then(|b| {
                                                                                let encrypted_tlvs_ss = self.node_signer.ecdh(
@@ -9351,6 +9358,7 @@ pub fn provided_init_features(config: &UserConfig) -> InitFeatures {
        features.set_channel_type_optional();
        features.set_scid_privacy_optional();
        features.set_zero_conf_optional();
+       features.set_route_blinding_optional();
        if config.channel_handshake_config.negotiate_anchors_zero_fee_htlc_tx {
                features.set_anchors_zero_fee_htlc_tx_optional();
        }
@@ -9496,6 +9504,7 @@ impl_writeable_tlv_based!(PhantomRouteHints, {
 
 impl_writeable_tlv_based!(BlindedForward, {
        (0, inbound_blinding_point, required),
+       (1, failure, (default_value, BlindedFailure::FromIntroductionNode)),
 });
 
 impl_writeable_tlv_based_enum!(PendingHTLCRouting,
index 1794aefe7ef80bda8629d4e0c08aae95afac57df..bc914ce49756ee0933dd7cbcf8ace51f137375be 100644 (file)
@@ -1714,7 +1714,7 @@ mod fuzzy_internal_msgs {
                        payment_relay: PaymentRelay,
                        payment_constraints: PaymentConstraints,
                        features: BlindedHopFeatures,
-                       intro_node_blinding_point: PublicKey,
+                       intro_node_blinding_point: Option<PublicKey>,
                },
                BlindedReceive {
                        sender_intended_htlc_amt_msat: u64,
@@ -2394,7 +2394,7 @@ impl<NS: Deref> ReadableArgs<(Option<PublicKey>, &NS)> for InboundOnionPayload w
                                                payment_relay,
                                                payment_constraints,
                                                features,
-                                               intro_node_blinding_point: intro_node_blinding_point.ok_or(DecodeError::InvalidValue)?,
+                                               intro_node_blinding_point,
                                        })
                                },
                                ChaChaPolyReadAdapter { readable: BlindedPaymentTlvs::Receive(ReceiveTlvs {
index c552bf13b8efd0fa80cd396d23e2f96da5c85eba..00843d5e4e93d0ec11feb609e9d5009c98e85203 100644 (file)
@@ -12,7 +12,7 @@ use crate::blinded_path;
 use crate::blinded_path::payment::{PaymentConstraints, PaymentRelay};
 use crate::chain::channelmonitor::{HTLC_FAIL_BACK_BUFFER, LATENCY_GRACE_PERIOD_BLOCKS};
 use crate::ln::PaymentHash;
-use crate::ln::channelmanager::{BlindedForward, CLTV_FAR_FAR_AWAY, HTLCFailureMsg, MIN_CLTV_EXPIRY_DELTA, PendingHTLCInfo, PendingHTLCRouting};
+use crate::ln::channelmanager::{BlindedFailure, BlindedForward, CLTV_FAR_FAR_AWAY, HTLCFailureMsg, MIN_CLTV_EXPIRY_DELTA, PendingHTLCInfo, PendingHTLCRouting};
 use crate::ln::features::BlindedHopFeatures;
 use crate::ln::msgs;
 use crate::ln::onion_utils;
@@ -73,7 +73,7 @@ pub(super) fn create_fwd_pending_htlc_info(
        };
 
        let (
-               short_channel_id, amt_to_forward, outgoing_cltv_value, inbound_blinding_point
+               short_channel_id, amt_to_forward, outgoing_cltv_value, intro_node_blinding_point
        ) = match hop_data {
                msgs::InboundOnionPayload::Forward { short_channel_id, amt_to_forward, outgoing_cltv_value } =>
                        (short_channel_id, amt_to_forward, outgoing_cltv_value, None),
@@ -91,7 +91,7 @@ pub(super) fn create_fwd_pending_htlc_info(
                                        err_data: vec![0; 32],
                                }
                        })?;
-                       (short_channel_id, amt_to_forward, outgoing_cltv_value, Some(intro_node_blinding_point))
+                       (short_channel_id, amt_to_forward, outgoing_cltv_value, intro_node_blinding_point)
                },
                msgs::InboundOnionPayload::Receive { .. } | msgs::InboundOnionPayload::BlindedReceive { .. } =>
                        return Err(InboundHTLCErr {
@@ -105,7 +105,13 @@ pub(super) fn create_fwd_pending_htlc_info(
                routing: PendingHTLCRouting::Forward {
                        onion_packet: outgoing_packet,
                        short_channel_id,
-                       blinded: inbound_blinding_point.map(|bp| BlindedForward { inbound_blinding_point: bp }),
+                       blinded: intro_node_blinding_point.or(msg.blinding_point)
+                               .map(|bp| BlindedForward {
+                                       inbound_blinding_point: bp,
+                                       failure: intro_node_blinding_point
+                                               .map(|_| BlindedFailure::FromIntroductionNode)
+                                               .unwrap_or(BlindedFailure::FromBlindedNode),
+                               }),
                },
                payment_hash: msg.payment_hash,
                incoming_shared_secret: shared_secret,
index ac0bb6189c6d750e92b4fdf5f6d061cca46d2cb2..137699521f482b6b186a4375477284a010bc897b 100644 (file)
@@ -188,11 +188,10 @@ pub(super) fn build_onion_payloads(path: &Path, total_msat: u64, mut recipient_o
                                for (i, blinded_hop) in hops.iter().enumerate() {
                                        if i == hops.len() - 1 {
                                                cur_value_msat += final_value_msat;
-                                               cur_cltv += excess_final_cltv_expiry_delta;
                                                res.push(msgs::OutboundOnionPayload::BlindedReceive {
                                                        sender_intended_htlc_amt_msat: *final_value_msat,
                                                        total_msat,
-                                                       cltv_expiry_height: cltv,
+                                                       cltv_expiry_height: cur_cltv + excess_final_cltv_expiry_delta,
                                                        encrypted_tlvs: blinded_hop.encrypted_payload.clone(),
                                                        intro_node_blinding_point: blinding_point.take(),
                                                });
index 9a9aea9ae971e2ef727431795e47be35ca70b890..6d46558b188b60c2433f51f002121bad268df60c 100644 (file)
@@ -305,6 +305,7 @@ impl ChannelMessageHandler for ErroringMessageHandler {
                features.set_channel_type_optional();
                features.set_scid_privacy_optional();
                features.set_zero_conf_optional();
+               features.set_route_blinding_optional();
                features
        }