Re-order imports
[rust-lightning] / lightning / src / ln / onion_utils.rs
index f2b5c69e9e677fb10e3886e4240b58bacd66395a..fab73cf73ec4320cfbe499a29d947ce168b5ad41 100644 (file)
@@ -7,14 +7,16 @@
 // You may not use this file except in accordance with one or both of these
 // licenses.
 
+use crate::blinded_path::BlindedHop;
 use crate::crypto::chacha20::ChaCha20;
 use crate::crypto::streams::ChaChaReader;
+use crate::ln::channel::TOTAL_BITCOIN_SUPPLY_SATOSHIS;
 use crate::ln::channelmanager::{HTLCSource, RecipientOnionFields};
+use crate::ln::features::{ChannelFeatures, NodeFeatures};
 use crate::ln::msgs;
-use crate::ln::wire::Encode;
-use crate::ln::{PaymentHash, PaymentPreimage};
+use crate::ln::types::{PaymentHash, PaymentPreimage};
 use crate::routing::gossip::NetworkUpdate;
-use crate::routing::router::{BlindedTail, Path, RouteHop};
+use crate::routing::router::{Path, RouteHop, RouteParameters};
 use crate::sign::NodeSigner;
 use crate::util::errors::{self, APIError};
 use crate::util::logger::Logger;
@@ -174,18 +176,60 @@ pub(super) fn construct_onion_keys<T: secp256k1::Signing>(
 }
 
 /// returns the hop data, as well as the first-hop value_msat and CLTV value we should send.
-pub(super) fn build_onion_payloads(
-       path: &Path, total_msat: u64, mut recipient_onion: RecipientOnionFields,
+pub(super) fn build_onion_payloads<'a>(
+       path: &'a Path, total_msat: u64, recipient_onion: &'a RecipientOnionFields,
        starting_htlc_offset: u32, keysend_preimage: &Option<PaymentPreimage>,
-) -> Result<(Vec<msgs::OutboundOnionPayload>, u64, u32), APIError> {
-       let mut cur_value_msat = 0u64;
-       let mut cur_cltv = starting_htlc_offset;
-       let mut last_short_channel_id = 0;
+) -> Result<(Vec<msgs::OutboundOnionPayload<'a>>, u64, u32), APIError> {
        let mut res: Vec<msgs::OutboundOnionPayload> = Vec::with_capacity(
                path.hops.len() + path.blinded_tail.as_ref().map_or(0, |t| t.hops.len()),
        );
+       let blinded_tail_with_hop_iter = path.blinded_tail.as_ref().map(|bt| BlindedTailHopIter {
+               hops: bt.hops.iter(),
+               blinding_point: bt.blinding_point,
+               final_value_msat: bt.final_value_msat,
+               excess_final_cltv_expiry_delta: bt.excess_final_cltv_expiry_delta,
+       });
+
+       let (value_msat, cltv) = build_onion_payloads_callback(
+               path.hops.iter(),
+               blinded_tail_with_hop_iter,
+               total_msat,
+               recipient_onion,
+               starting_htlc_offset,
+               keysend_preimage,
+               |action, payload| match action {
+                       PayloadCallbackAction::PushBack => res.push(payload),
+                       PayloadCallbackAction::PushFront => res.insert(0, payload),
+               },
+       )?;
+       Ok((res, value_msat, cltv))
+}
+
+struct BlindedTailHopIter<'a, I: Iterator<Item = &'a BlindedHop>> {
+       hops: I,
+       blinding_point: PublicKey,
+       final_value_msat: u64,
+       excess_final_cltv_expiry_delta: u32,
+}
+enum PayloadCallbackAction {
+       PushBack,
+       PushFront,
+}
+fn build_onion_payloads_callback<'a, H, B, F>(
+       hops: H, mut blinded_tail: Option<BlindedTailHopIter<'a, B>>, total_msat: u64,
+       recipient_onion: &'a RecipientOnionFields, starting_htlc_offset: u32,
+       keysend_preimage: &Option<PaymentPreimage>, mut callback: F,
+) -> Result<(u64, u32), APIError>
+where
+       H: DoubleEndedIterator<Item = &'a RouteHop>,
+       B: ExactSizeIterator<Item = &'a BlindedHop>,
+       F: FnMut(PayloadCallbackAction, msgs::OutboundOnionPayload<'a>),
+{
+       let mut cur_value_msat = 0u64;
+       let mut cur_cltv = starting_htlc_offset;
+       let mut last_short_channel_id = 0;
 
-       for (idx, hop) in path.hops.iter().rev().enumerate() {
+       for (idx, hop) in hops.rev().enumerate() {
                // First hop gets special values so that it can check, on receipt, that everything is
                // exactly as it should be (and the next hop isn't trying to probe to find out if we're
                // the intended recipient).
@@ -196,47 +240,55 @@ pub(super) fn build_onion_payloads(
                        cur_cltv
                };
                if idx == 0 {
-                       if let Some(BlindedTail {
+                       if let Some(BlindedTailHopIter {
                                blinding_point,
                                hops,
                                final_value_msat,
                                excess_final_cltv_expiry_delta,
                                ..
-                       }) = &path.blinded_tail
+                       }) = blinded_tail.take()
                        {
-                               let mut blinding_point = Some(*blinding_point);
-                               for (i, blinded_hop) in hops.iter().enumerate() {
-                                       if i == hops.len() - 1 {
+                               let mut blinding_point = Some(blinding_point);
+                               let hops_len = hops.len();
+                               for (i, blinded_hop) in hops.enumerate() {
+                                       if i == hops_len - 1 {
                                                cur_value_msat += final_value_msat;
-                                               res.push(msgs::OutboundOnionPayload::BlindedReceive {
-                                                       sender_intended_htlc_amt_msat: *final_value_msat,
-                                                       total_msat,
-                                                       cltv_expiry_height: cur_cltv + excess_final_cltv_expiry_delta,
-                                                       encrypted_tlvs: blinded_hop.encrypted_payload.clone(),
-                                                       intro_node_blinding_point: blinding_point.take(),
-                                                       keysend_preimage: *keysend_preimage,
-                                                       custom_tlvs: recipient_onion.custom_tlvs.clone(),
-                                               });
+                                               callback(
+                                                       PayloadCallbackAction::PushBack,
+                                                       msgs::OutboundOnionPayload::BlindedReceive {
+                                                               sender_intended_htlc_amt_msat: final_value_msat,
+                                                               total_msat,
+                                                               cltv_expiry_height: cur_cltv + excess_final_cltv_expiry_delta,
+                                                               encrypted_tlvs: &blinded_hop.encrypted_payload,
+                                                               intro_node_blinding_point: blinding_point.take(),
+                                                               keysend_preimage: *keysend_preimage,
+                                                               custom_tlvs: &recipient_onion.custom_tlvs,
+                                                       },
+                                               );
                                        } else {
-                                               res.push(msgs::OutboundOnionPayload::BlindedForward {
-                                                       encrypted_tlvs: blinded_hop.encrypted_payload.clone(),
-                                                       intro_node_blinding_point: blinding_point.take(),
-                                               });
+                                               callback(
+                                                       PayloadCallbackAction::PushBack,
+                                                       msgs::OutboundOnionPayload::BlindedForward {
+                                                               encrypted_tlvs: &blinded_hop.encrypted_payload,
+                                                               intro_node_blinding_point: blinding_point.take(),
+                                                       },
+                                               );
                                        }
                                }
                        } else {
-                               res.push(msgs::OutboundOnionPayload::Receive {
-                                       payment_data: if let Some(secret) = recipient_onion.payment_secret.take() {
-                                               Some(msgs::FinalOnionHopData { payment_secret: secret, total_msat })
-                                       } else {
-                                               None
+                               callback(
+                                       PayloadCallbackAction::PushBack,
+                                       msgs::OutboundOnionPayload::Receive {
+                                               payment_data: recipient_onion.payment_secret.map(|payment_secret| {
+                                                       msgs::FinalOnionHopData { payment_secret, total_msat }
+                                               }),
+                                               payment_metadata: recipient_onion.payment_metadata.as_ref(),
+                                               keysend_preimage: *keysend_preimage,
+                                               custom_tlvs: &recipient_onion.custom_tlvs,
+                                               sender_intended_htlc_amt_msat: value_msat,
+                                               cltv_expiry_height: cltv,
                                        },
-                                       payment_metadata: recipient_onion.payment_metadata.take(),
-                                       keysend_preimage: *keysend_preimage,
-                                       custom_tlvs: recipient_onion.custom_tlvs.clone(),
-                                       sender_intended_htlc_amt_msat: value_msat,
-                                       cltv_expiry_height: cltv,
-                               });
+                               );
                        }
                } else {
                        let payload = msgs::OutboundOnionPayload::Forward {
@@ -244,7 +296,7 @@ pub(super) fn build_onion_payloads(
                                amt_to_forward: value_msat,
                                outgoing_cltv_value: cltv,
                        };
-                       res.insert(0, payload);
+                       callback(PayloadCallbackAction::PushFront, payload);
                }
                cur_value_msat += hop.fee_msat;
                if cur_value_msat >= 21000000 * 100000000 * 1000 {
@@ -256,7 +308,78 @@ pub(super) fn build_onion_payloads(
                }
                last_short_channel_id = hop.short_channel_id;
        }
-       Ok((res, cur_value_msat, cur_cltv))
+       Ok((cur_value_msat, cur_cltv))
+}
+
+pub(crate) const MIN_FINAL_VALUE_ESTIMATE_WITH_OVERPAY: u64 = 100_000_000;
+
+pub(crate) fn set_max_path_length(
+       route_params: &mut RouteParameters, recipient_onion: &RecipientOnionFields,
+       keysend_preimage: Option<PaymentPreimage>, best_block_height: u32,
+) -> Result<(), ()> {
+       const PAYLOAD_HMAC_LEN: usize = 32;
+       let unblinded_intermed_payload_len = msgs::OutboundOnionPayload::Forward {
+               short_channel_id: 42,
+               amt_to_forward: TOTAL_BITCOIN_SUPPLY_SATOSHIS,
+               outgoing_cltv_value: route_params.payment_params.max_total_cltv_expiry_delta,
+       }
+       .serialized_length()
+       .saturating_add(PAYLOAD_HMAC_LEN);
+
+       const OVERPAY_ESTIMATE_MULTIPLER: u64 = 3;
+       let final_value_msat_with_overpay_buffer = core::cmp::max(
+               route_params.final_value_msat.saturating_mul(OVERPAY_ESTIMATE_MULTIPLER),
+               MIN_FINAL_VALUE_ESTIMATE_WITH_OVERPAY,
+       );
+
+       let blinded_tail_opt = route_params
+               .payment_params
+               .payee
+               .blinded_route_hints()
+               .iter()
+               .map(|(_, path)| path)
+               .max_by_key(|path| path.serialized_length())
+               .map(|largest_path| BlindedTailHopIter {
+                       hops: largest_path.blinded_hops.iter(),
+                       blinding_point: largest_path.blinding_point,
+                       final_value_msat: final_value_msat_with_overpay_buffer,
+                       excess_final_cltv_expiry_delta: 0,
+               });
+
+       let unblinded_route_hop = RouteHop {
+               pubkey: PublicKey::from_slice(&[2; 33]).unwrap(),
+               node_features: NodeFeatures::empty(),
+               short_channel_id: 42,
+               channel_features: ChannelFeatures::empty(),
+               fee_msat: final_value_msat_with_overpay_buffer,
+               cltv_expiry_delta: route_params.payment_params.max_total_cltv_expiry_delta,
+               maybe_announced_channel: false,
+       };
+       let mut num_reserved_bytes: usize = 0;
+       let build_payloads_res = build_onion_payloads_callback(
+               core::iter::once(&unblinded_route_hop),
+               blinded_tail_opt,
+               final_value_msat_with_overpay_buffer,
+               &recipient_onion,
+               best_block_height,
+               &keysend_preimage,
+               |_, payload| {
+                       num_reserved_bytes = num_reserved_bytes
+                               .saturating_add(payload.serialized_length())
+                               .saturating_add(PAYLOAD_HMAC_LEN);
+               },
+       );
+       debug_assert!(build_payloads_res.is_ok());
+
+       let max_path_length = 1300usize
+               .checked_sub(num_reserved_bytes)
+               .map(|p| p / unblinded_intermed_payload_len)
+               .and_then(|l| u8::try_from(l.saturating_add(1)).ok())
+               .ok_or(())?;
+
+       route_params.payment_params.max_path_length =
+               core::cmp::min(max_path_length, route_params.payment_params.max_path_length);
+       Ok(())
 }
 
 /// Length of the onion data packet. Before TLV-based onions this was 20 65-byte hops, though now
@@ -682,106 +805,16 @@ where
                        {
                                let update_len =
                                        u16::from_be_bytes(update_len_slice.try_into().expect("len is 2")) as usize;
-                               if let Some(mut update_slice) = err_packet
+                               if err_packet
                                        .failuremsg
                                        .get(debug_field_size + 4..debug_field_size + 4 + update_len)
+                                       .is_some()
                                {
-                                       // Historically, the BOLTs were unclear if the message type
-                                       // bytes should be included here or not. The BOLTs have now
-                                       // been updated to indicate that they *are* included, but many
-                                       // nodes still send messages without the type bytes, so we
-                                       // support both here.
-                                       // TODO: Switch to hard require the type prefix, as the current
-                                       // permissiveness introduces the (although small) possibility
-                                       // that we fail to decode legitimate channel updates that
-                                       // happen to start with ChannelUpdate::TYPE, i.e., [0x01, 0x02].
-                                       if update_slice.len() > 2
-                                               && update_slice[0..2] == msgs::ChannelUpdate::TYPE.to_be_bytes()
-                                       {
-                                               update_slice = &update_slice[2..];
-                                       } else {
-                                               log_trace!(logger, "Failure provided features a channel update without type prefix. Deprecated, but allowing for now.");
-                                       }
-                                       let update_opt = msgs::ChannelUpdate::read(&mut Cursor::new(&update_slice));
-                                       if update_opt.is_ok() || update_slice.is_empty() {
-                                               // if channel_update should NOT have caused the failure:
-                                               // MAY treat the channel_update as invalid.
-                                               let is_chan_update_invalid = match error_code & 0xff {
-                                                       7 => false,
-                                                       11 => {
-                                                               update_opt.is_ok()
-                                                                       && amt_to_forward
-                                                                               > update_opt.as_ref().unwrap().contents.htlc_minimum_msat
-                                                       },
-                                                       12 => {
-                                                               update_opt.is_ok()
-                                                                       && amt_to_forward
-                                                                               .checked_mul(
-                                                                                       update_opt
-                                                                                               .as_ref()
-                                                                                               .unwrap()
-                                                                                               .contents
-                                                                                               .fee_proportional_millionths as u64,
-                                                                               )
-                                                                               .map(|prop_fee| prop_fee / 1_000_000)
-                                                                               .and_then(|prop_fee| {
-                                                                                       prop_fee.checked_add(
-                                                                                               update_opt.as_ref().unwrap().contents.fee_base_msat
-                                                                                                       as u64,
-                                                                                       )
-                                                                               })
-                                                                               .map(|fee_msats| route_hop.fee_msat >= fee_msats)
-                                                                               .unwrap_or(false)
-                                                       },
-                                                       13 => {
-                                                               update_opt.is_ok()
-                                                                       && route_hop.cltv_expiry_delta as u16
-                                                                               >= update_opt.as_ref().unwrap().contents.cltv_expiry_delta
-                                                       },
-                                                       14 => false, // expiry_too_soon; always valid?
-                                                       20 => update_opt.as_ref().unwrap().contents.flags & 2 == 0,
-                                                       _ => false, // unknown error code; take channel_update as valid
-                                               };
-                                               if is_chan_update_invalid {
-                                                       // This probably indicates the node which forwarded
-                                                       // to the node in question corrupted something.
-                                                       network_update = Some(NetworkUpdate::ChannelFailure {
-                                                               short_channel_id: route_hop.short_channel_id,
-                                                               is_permanent: true,
-                                                       });
-                                               } else {
-                                                       if let Ok(chan_update) = update_opt {
-                                                               // Make sure the ChannelUpdate contains the expected
-                                                               // short channel id.
-                                                               if failing_route_hop.short_channel_id
-                                                                       == chan_update.contents.short_channel_id
-                                                               {
-                                                                       short_channel_id = Some(failing_route_hop.short_channel_id);
-                                                               } else {
-                                                                       log_info!(logger, "Node provided a channel_update for which it was not authoritative, ignoring.");
-                                                               }
-                                                               network_update =
-                                                                       Some(NetworkUpdate::ChannelUpdateMessage { msg: chan_update })
-                                                       } else {
-                                                               // The node in question intentionally encoded a 0-length channel update. This is
-                                                               // likely due to https://github.com/ElementsProject/lightning/issues/6200.
-                                                               short_channel_id = Some(failing_route_hop.short_channel_id);
-                                                               network_update = Some(NetworkUpdate::ChannelFailure {
-                                                                       short_channel_id: failing_route_hop.short_channel_id,
-                                                                       is_permanent: false,
-                                                               });
-                                                       }
-                                               };
-                                       } else {
-                                               // If the channel_update had a non-zero length (i.e. was
-                                               // present) but we couldn't read it, treat it as a total
-                                               // node failure.
-                                               log_info!(
-                                                       logger,
-                                                       "Failed to read a channel_update of len {} in an onion",
-                                                       update_slice.len()
-                                               );
-                                       }
+                                       network_update = Some(NetworkUpdate::ChannelFailure {
+                                               short_channel_id: failing_route_hop.short_channel_id,
+                                               is_permanent: false,
+                                       });
+                                       short_channel_id = Some(failing_route_hop.short_channel_id);
                                }
                        }
                        if network_update.is_none() {
@@ -1124,7 +1157,7 @@ where
 /// `cur_block_height` should be set to the best known block height + 1.
 pub fn create_payment_onion<T: secp256k1::Signing>(
        secp_ctx: &Secp256k1<T>, path: &Path, session_priv: &SecretKey, total_msat: u64,
-       recipient_onion: RecipientOnionFields, cur_block_height: u32, payment_hash: &PaymentHash,
+       recipient_onion: &RecipientOnionFields, cur_block_height: u32, payment_hash: &PaymentHash,
        keysend_preimage: &Option<PaymentPreimage>, prng_seed: [u8; 32],
 ) -> Result<(msgs::OnionPacket, u64, u32), APIError> {
        let onion_keys = construct_onion_keys(&secp_ctx, &path, &session_priv).map_err(|_| {
@@ -1240,7 +1273,7 @@ mod tests {
        use crate::io;
        use crate::ln::features::{ChannelFeatures, NodeFeatures};
        use crate::ln::msgs;
-       use crate::ln::PaymentHash;
+       use crate::ln::types::PaymentHash;
        use crate::routing::router::{Path, Route, RouteHop};
        use crate::util::ser::{VecWriter, Writeable, Writer};