///
/// [`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),
/// [`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()
}
}
}
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(())
}
_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 {
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,
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(),
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, _)| {
}
}
- 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)
}
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);
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);
// 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);
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,
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();
channel_features: ChannelFeatures::empty(),
fee_msat: 100,
cltv_expiry_delta: 0,
+ maybe_announced_channel: false,
},
RouteHop {
pubkey: blinded_path.introduction_node_id,
channel_features: ChannelFeatures::empty(),
fee_msat: 1,
cltv_expiry_delta: 0,
+ maybe_announced_channel: false,
}],
blinded_tail: Some(BlindedTail {
hops: blinded_path.blinded_hops,
channel_features: ChannelFeatures::empty(),
fee_msat: 100,
cltv_expiry_delta: 0,
+ maybe_announced_channel: false,
},
RouteHop {
pubkey: blinded_path.introduction_node_id,
channel_features: ChannelFeatures::empty(),
fee_msat: 1,
cltv_expiry_delta: 0,
+ maybe_announced_channel: false,
}
],
blinded_tail: Some(BlindedTail {
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();