Merge pull request #2584 from TheBlueMatt/2023-09-msrv-try-2
[rust-lightning] / lightning / src / routing / router.rs
index 799b6407981341b0bc4fbfae0f387bdbe23186ad..1758cbbf9c3eaae24151050f488c2524f43109da 100644 (file)
@@ -250,10 +250,20 @@ pub struct RouteHop {
        ///
        /// [`BlindedPath`]: crate::blinded_path::BlindedPath
        pub cltv_expiry_delta: u32,
+       /// Indicates whether this hop is possibly announced in the public network graph.
+       ///
+       /// Will be `true` if there is a possibility that the channel is publicly known, i.e., if we
+       /// either know for sure it's announced in the public graph, or if any public channels exist
+       /// for which the given `short_channel_id` could be an alias for. Will be `false` if we believe
+       /// the channel to be unannounced.
+       ///
+       /// Will be `true` for objects serialized with LDK version 0.0.116 and before.
+       pub maybe_announced_channel: bool,
 }
 
 impl_writeable_tlv_based!(RouteHop, {
        (0, pubkey, required),
+       (1, maybe_announced_channel, (default_value, true)),
        (2, node_features, required),
        (4, short_channel_id, required),
        (6, channel_features, required),
@@ -337,23 +347,34 @@ pub struct Route {
        /// [`BlindedTail`]s are present, then the pubkey of the last [`RouteHop`] in each path must be
        /// the same.
        pub paths: Vec<Path>,
-       /// The `payment_params` parameter passed via [`RouteParameters`] to [`find_route`].
+       /// The `route_params` parameter passed to [`find_route`].
        ///
        /// This is used by `ChannelManager` to track information which may be required for retries.
-       pub payment_params: Option<PaymentParameters>,
+       ///
+       /// Will be `None` for objects serialized with LDK versions prior to 0.0.117.
+       pub route_params: Option<RouteParameters>,
 }
 
 impl Route {
        /// Returns the total amount of fees paid on this [`Route`].
        ///
-       /// This doesn't include any extra payment made to the recipient, which can happen in excess of
-       /// the amount passed to [`find_route`]'s `route_params.final_value_msat`.
+       /// For objects serialized with LDK 0.0.117 and after, this includes any extra payment made to
+       /// the recipient, which can happen in excess of the amount passed to [`find_route`] via
+       /// [`RouteParameters::final_value_msat`], if we had to reach the [`htlc_minimum_msat`] limits.
+       ///
+       /// [`htlc_minimum_msat`]: https://github.com/lightning/bolts/blob/master/07-routing-gossip.md#the-channel_update-message
        pub fn get_total_fees(&self) -> u64 {
-               self.paths.iter().map(|path| path.fee_msat()).sum()
+               let overpaid_value_msat = self.route_params.as_ref()
+                       .map_or(0, |p| self.get_total_amount().saturating_sub(p.final_value_msat));
+               overpaid_value_msat + self.paths.iter().map(|path| path.fee_msat()).sum::<u64>()
        }
 
-       /// Returns the total amount paid on this [`Route`], excluding the fees. Might be more than
-       /// requested if we had to reach htlc_minimum_msat.
+       /// Returns the total amount paid on this [`Route`], excluding the fees.
+       ///
+       /// Might be more than requested as part of the given [`RouteParameters::final_value_msat`] if
+       /// we had to reach the [`htlc_minimum_msat`] limits.
+       ///
+       /// [`htlc_minimum_msat`]: https://github.com/lightning/bolts/blob/master/07-routing-gossip.md#the-channel_update-message
        pub fn get_total_amount(&self) -> u64 {
                self.paths.iter().map(|path| path.final_value_msat()).sum()
        }
@@ -383,8 +404,11 @@ impl Writeable for Route {
                        }
                }
                write_tlv_fields!(writer, {
-                       (1, self.payment_params, option),
+                       // For compatibility with LDK versions prior to 0.0.117, we take the individual
+                       // RouteParameters' fields and reconstruct them on read.
+                       (1, self.route_params.as_ref().map(|p| &p.payment_params), option),
                        (2, blinded_tails, optional_vec),
+                       (3, self.route_params.as_ref().map(|p| p.final_value_msat), option),
                });
                Ok(())
        }
@@ -411,6 +435,7 @@ impl Readable for Route {
                _init_and_read_len_prefixed_tlv_fields!(reader, {
                        (1, payment_params, (option: ReadableArgs, min_final_cltv_expiry_delta)),
                        (2, blinded_tails, optional_vec),
+                       (3, final_value_msat, option),
                });
                let blinded_tails = blinded_tails.unwrap_or(Vec::new());
                if blinded_tails.len() != 0 {
@@ -419,14 +444,23 @@ impl Readable for Route {
                                path.blinded_tail = blinded_tail_opt;
                        }
                }
-               Ok(Route { paths, payment_params })
+
+               // If we previously wrote the corresponding fields, reconstruct RouteParameters.
+               let route_params = match (payment_params, final_value_msat) {
+                       (Some(payment_params), Some(final_value_msat)) => {
+                               Some(RouteParameters { payment_params, final_value_msat })
+                       }
+                       _ => None,
+               };
+
+               Ok(Route { paths, route_params })
        }
 }
 
 /// Parameters needed to find a [`Route`].
 ///
 /// Passed to [`find_route`] and [`build_route_from_hops`].
-#[derive(Clone, Debug, PartialEq, Eq)]
+#[derive(Clone, Debug, Hash, PartialEq, Eq)]
 pub struct RouteParameters {
        /// The parameters of the failed payment path.
        pub payment_params: PaymentParameters,
@@ -2448,9 +2482,27 @@ where L::Target: Logger {
        let mut paths = Vec::new();
        for payment_path in selected_route {
                let mut hops = Vec::with_capacity(payment_path.hops.len());
+               let mut prev_hop_node_id = our_node_id;
                for (hop, node_features) in payment_path.hops.iter()
                        .filter(|(h, _)| h.candidate.short_channel_id().is_some())
                {
+                       let maybe_announced_channel = if let CandidateRouteHop::PublicHop { .. } = hop.candidate {
+                               // If we sourced the hop from the graph we're sure the target node is announced.
+                               true
+                       } else if let CandidateRouteHop::FirstHop { details } = hop.candidate {
+                               // If this is a first hop we also know if it's announced.
+                               details.is_public
+                       } else {
+                               // If we sourced it any other way, we double-check the network graph to see if
+                               // there are announced channels between the endpoints. If so, the hop might be
+                               // referring to any of the announced channels, as its `short_channel_id` might be
+                               // an alias, in which case we don't take any chances here.
+                               network_graph.node(&hop.node_id).map_or(false, |hop_node|
+                                       hop_node.channels.iter().any(|scid| network_graph.channel(*scid)
+                                                       .map_or(false, |c| c.as_directed_from(&prev_hop_node_id).is_some()))
+                               )
+                       };
+
                        hops.push(RouteHop {
                                pubkey: PublicKey::from_slice(hop.node_id.as_slice()).map_err(|_| LightningError{err: format!("Public key {:?} is invalid", &hop.node_id), action: ErrorAction::IgnoreAndLog(Level::Trace)})?,
                                node_features: node_features.clone(),
@@ -2458,7 +2510,10 @@ where L::Target: Logger {
                                channel_features: hop.candidate.features(),
                                fee_msat: hop.fee_msat,
                                cltv_expiry_delta: hop.candidate.cltv_expiry_delta(),
+                               maybe_announced_channel,
                        });
+
+                       prev_hop_node_id = hop.node_id;
                }
                let mut final_cltv_delta = final_cltv_expiry_delta;
                let blinded_tail = payment_path.hops.last().and_then(|(h, _)| {
@@ -2489,7 +2544,7 @@ where L::Target: Logger {
                }
        }
 
-       let route = Route { paths, payment_params: Some(payment_params.clone()) };
+       let route = Route { paths, route_params: Some(route_params.clone()) };
        log_info!(logger, "Got route: {}", log_route!(route));
        Ok(route)
 }
@@ -5940,20 +5995,20 @@ mod tests {
                                RouteHop {
                                        pubkey: PublicKey::from_slice(&hex::decode("02eec7245d6b7d2ccb30380bfbe2a3648cd7a942653f5aa340edcea1f283686619").unwrap()[..]).unwrap(),
                                        channel_features: ChannelFeatures::empty(), node_features: NodeFeatures::empty(),
-                                       short_channel_id: 0, fee_msat: 100, cltv_expiry_delta: 0
+                                       short_channel_id: 0, fee_msat: 100, cltv_expiry_delta: 0, maybe_announced_channel: true,
                                },
                                RouteHop {
                                        pubkey: PublicKey::from_slice(&hex::decode("0324653eac434488002cc06bbfb7f10fe18991e35f9fe4302dbea6d2353dc0ab1c").unwrap()[..]).unwrap(),
                                        channel_features: ChannelFeatures::empty(), node_features: NodeFeatures::empty(),
-                                       short_channel_id: 0, fee_msat: 150, cltv_expiry_delta: 0
+                                       short_channel_id: 0, fee_msat: 150, cltv_expiry_delta: 0, maybe_announced_channel: true,
                                },
                                RouteHop {
                                        pubkey: PublicKey::from_slice(&hex::decode("027f31ebc5462c1fdce1b737ecff52d37d75dea43ce11c74d25aa297165faa2007").unwrap()[..]).unwrap(),
                                        channel_features: ChannelFeatures::empty(), node_features: NodeFeatures::empty(),
-                                       short_channel_id: 0, fee_msat: 225, cltv_expiry_delta: 0
+                                       short_channel_id: 0, fee_msat: 225, cltv_expiry_delta: 0, maybe_announced_channel: true,
                                },
                        ], blinded_tail: None }],
-                       payment_params: None,
+                       route_params: None,
                };
 
                assert_eq!(route.get_total_fees(), 250);
@@ -5967,26 +6022,26 @@ mod tests {
                                RouteHop {
                                        pubkey: PublicKey::from_slice(&hex::decode("02eec7245d6b7d2ccb30380bfbe2a3648cd7a942653f5aa340edcea1f283686619").unwrap()[..]).unwrap(),
                                        channel_features: ChannelFeatures::empty(), node_features: NodeFeatures::empty(),
-                                       short_channel_id: 0, fee_msat: 100, cltv_expiry_delta: 0
+                                       short_channel_id: 0, fee_msat: 100, cltv_expiry_delta: 0, maybe_announced_channel: true,
                                },
                                RouteHop {
                                        pubkey: PublicKey::from_slice(&hex::decode("0324653eac434488002cc06bbfb7f10fe18991e35f9fe4302dbea6d2353dc0ab1c").unwrap()[..]).unwrap(),
                                        channel_features: ChannelFeatures::empty(), node_features: NodeFeatures::empty(),
-                                       short_channel_id: 0, fee_msat: 150, cltv_expiry_delta: 0
+                                       short_channel_id: 0, fee_msat: 150, cltv_expiry_delta: 0, maybe_announced_channel: true,
                                },
                        ], blinded_tail: None }, Path { hops: vec![
                                RouteHop {
                                        pubkey: PublicKey::from_slice(&hex::decode("02eec7245d6b7d2ccb30380bfbe2a3648cd7a942653f5aa340edcea1f283686619").unwrap()[..]).unwrap(),
                                        channel_features: ChannelFeatures::empty(), node_features: NodeFeatures::empty(),
-                                       short_channel_id: 0, fee_msat: 100, cltv_expiry_delta: 0
+                                       short_channel_id: 0, fee_msat: 100, cltv_expiry_delta: 0, maybe_announced_channel: true,
                                },
                                RouteHop {
                                        pubkey: PublicKey::from_slice(&hex::decode("0324653eac434488002cc06bbfb7f10fe18991e35f9fe4302dbea6d2353dc0ab1c").unwrap()[..]).unwrap(),
                                        channel_features: ChannelFeatures::empty(), node_features: NodeFeatures::empty(),
-                                       short_channel_id: 0, fee_msat: 150, cltv_expiry_delta: 0
+                                       short_channel_id: 0, fee_msat: 150, cltv_expiry_delta: 0, maybe_announced_channel: true,
                                },
                        ], blinded_tail: None }],
-                       payment_params: None,
+                       route_params: None,
                };
 
                assert_eq!(route.get_total_fees(), 200);
@@ -5998,7 +6053,7 @@ mod tests {
                // In an earlier version of `Route::get_total_fees` and `Route::get_total_amount`, they
                // would both panic if the route was completely empty. We test to ensure they return 0
                // here, even though its somewhat nonsensical as a route.
-               let route = Route { paths: Vec::new(), payment_params: None };
+               let route = Route { paths: Vec::new(), route_params: None };
 
                assert_eq!(route.get_total_fees(), 0);
                assert_eq!(route.get_total_amount(), 0);
@@ -6582,6 +6637,7 @@ mod tests {
                                channel_features: ChannelFeatures::empty(),
                                fee_msat: 100,
                                cltv_expiry_delta: 0,
+                               maybe_announced_channel: true,
                        }],
                        blinded_tail: Some(BlindedTail {
                                hops: blinded_path_1.blinded_hops,
@@ -6596,8 +6652,9 @@ mod tests {
                                channel_features: ChannelFeatures::empty(),
                                fee_msat: 100,
                                cltv_expiry_delta: 0,
+                               maybe_announced_channel: true,
                        }], blinded_tail: None }],
-                       payment_params: None,
+                       route_params: None,
                };
                let encoded_route = route.encode();
                let decoded_route: Route = Readable::read(&mut Cursor::new(&encoded_route[..])).unwrap();
@@ -6635,6 +6692,7 @@ mod tests {
                                channel_features: ChannelFeatures::empty(),
                                fee_msat: 100,
                                cltv_expiry_delta: 0,
+                               maybe_announced_channel: false,
                        },
                        RouteHop {
                                pubkey: blinded_path.introduction_node_id,
@@ -6643,6 +6701,7 @@ mod tests {
                                channel_features: ChannelFeatures::empty(),
                                fee_msat: 1,
                                cltv_expiry_delta: 0,
+                               maybe_announced_channel: false,
                        }],
                        blinded_tail: Some(BlindedTail {
                                hops: blinded_path.blinded_hops,
@@ -6675,6 +6734,7 @@ mod tests {
                                channel_features: ChannelFeatures::empty(),
                                fee_msat: 100,
                                cltv_expiry_delta: 0,
+                               maybe_announced_channel: false,
                        },
                        RouteHop {
                                pubkey: blinded_path.introduction_node_id,
@@ -6683,6 +6743,7 @@ mod tests {
                                channel_features: ChannelFeatures::empty(),
                                fee_msat: 1,
                                cltv_expiry_delta: 0,
+                               maybe_announced_channel: false,
                        }
                        ],
                        blinded_tail: Some(BlindedTail {
@@ -6691,7 +6752,7 @@ mod tests {
                                excess_final_cltv_expiry_delta: 0,
                                final_value_msat: 200,
                        }),
-               }], payment_params: None};
+               }], route_params: None};
 
                let payment_params = PaymentParameters::from_node_id(ln_test_utils::pubkey(47), 18);
                let (_, network_graph, _, _, _) = build_line_graph();