/// Takes in a path with payer's node id and adds the path's details to `InFlightHtlcs`.
pub fn process_path(&mut self, path: &Path, payer_node_id: PublicKey) {
if path.hops.is_empty() { return };
+
+ let mut cumulative_msat = 0;
+ if let Some(tail) = &path.blinded_tail {
+ cumulative_msat += tail.final_value_msat;
+ }
+
// total_inflight_map needs to be direction-sensitive when keeping track of the HTLC value
// that is held up. However, the `hops` array, which is a path returned by `find_route` in
// the router excludes the payer node. In the following lines, the payer's information is
let reversed_hops_with_payer = path.hops.iter().rev().skip(1)
.map(|hop| hop.pubkey)
.chain(core::iter::once(payer_node_id));
- let mut cumulative_msat = 0;
// Taking the reversed vector from above, we zip it with just the reversed hops list to
// work "backwards" of the given path, since the last hop's `fee_msat` actually represents
fn write<W: crate::util::ser::Writer>(&self, writer: &mut W) -> Result<(), io::Error> {
write_ver_prefix!(writer, SERIALIZATION_VERSION, MIN_SERIALIZATION_VERSION);
(self.paths.len() as u64).write(writer)?;
+ let mut blinded_tails = Vec::new();
for path in self.paths.iter() {
(path.hops.len() as u8).write(writer)?;
- for hop in path.hops.iter() {
+ for (idx, hop) in path.hops.iter().enumerate() {
hop.write(writer)?;
+ if let Some(blinded_tail) = &path.blinded_tail {
+ if blinded_tails.is_empty() {
+ blinded_tails = Vec::with_capacity(path.hops.len());
+ for _ in 0..idx {
+ blinded_tails.push(None);
+ }
+ }
+ blinded_tails.push(Some(blinded_tail));
+ } else if !blinded_tails.is_empty() { blinded_tails.push(None); }
}
}
write_tlv_fields!(writer, {
(1, self.payment_params, option),
+ (2, blinded_tails, optional_vec),
});
Ok(())
}
cmp::min(min_final_cltv_expiry_delta, hops.last().unwrap().cltv_expiry_delta);
paths.push(Path { hops, blinded_tail: None });
}
- let mut payment_params = None;
- read_tlv_fields!(reader, {
+ _init_and_read_tlv_fields!(reader, {
(1, payment_params, (option: ReadableArgs, min_final_cltv_expiry_delta)),
+ (2, blinded_tails, optional_vec),
});
+ let blinded_tails = blinded_tails.unwrap_or(Vec::new());
+ if blinded_tails.len() != 0 {
+ if blinded_tails.len() != paths.len() { return Err(DecodeError::InvalidValue) }
+ for (mut path, blinded_tail_opt) in paths.iter_mut().zip(blinded_tails.into_iter()) {
+ path.blinded_tail = blinded_tail_opt;
+ }
+ }
Ok(Route { paths, payment_params })
}
}
shadow_ctlv_expiry_delta_offset = cmp::min(shadow_ctlv_expiry_delta_offset, max_path_offset);
// Add 'shadow' CLTV offset to the final hop
+ if let Some(tail) = path.blinded_tail.as_mut() {
+ tail.excess_final_cltv_expiry_delta = tail.excess_final_cltv_expiry_delta
+ .checked_add(shadow_ctlv_expiry_delta_offset).unwrap_or(tail.excess_final_cltv_expiry_delta);
+ }
if let Some(last_hop) = path.hops.last_mut() {
last_hop.cltv_expiry_delta = last_hop.cltv_expiry_delta
.checked_add(shadow_ctlv_expiry_delta_offset).unwrap_or(last_hop.cltv_expiry_delta);
#[cfg(test)]
mod tests {
+ use crate::blinded_path::{BlindedHop, BlindedPath};
use crate::routing::gossip::{NetworkGraph, P2PGossipSync, NodeId, EffectiveCapacity};
use crate::routing::utxo::UtxoResult;
use crate::routing::router::{get_route, build_route_from_hops_internal, add_random_cltv_offset, default_node_features,
- Path, PaymentParameters, Route, RouteHint, RouteHintHop, RouteHop, RoutingFees,
+ BlindedTail, InFlightHtlcs, Path, PaymentParameters, Route, RouteHint, RouteHintHop, RouteHop, RoutingFees,
DEFAULT_MAX_TOTAL_CLTV_EXPIRY_DELTA, MAX_PATH_LENGTH_ESTIMATE};
use crate::routing::scoring::{ChannelUsage, FixedPenaltyScorer, Score, ProbabilisticScorer, ProbabilisticScoringParameters};
use crate::routing::test_utils::{add_channel, add_or_update_node, build_graph, build_line_graph, id_to_feature_flags, get_nodes, update_channel};
use crate::util::config::UserConfig;
use crate::util::test_utils as ln_test_utils;
use crate::util::chacha20::ChaCha20;
+ use crate::util::ser::{Readable, Writeable};
#[cfg(c_bindings)]
- use crate::util::ser::{Writeable, Writer};
+ use crate::util::ser::Writer;
use bitcoin::hashes::Hash;
use bitcoin::network::constants::Network;
use bitcoin::secp256k1::{PublicKey,SecretKey};
use bitcoin::secp256k1::Secp256k1;
+ use crate::io::Cursor;
use crate::prelude::*;
use crate::sync::Arc;
let route = get_route(&our_id, &payment_params, &network_graph.read_only(), None, 100, 42, Arc::clone(&logger), &scorer, &random_seed_bytes);
assert!(route.is_ok());
}
+
+ #[test]
+ fn blinded_route_ser() {
+ let blinded_path_1 = BlindedPath {
+ introduction_node_id: ln_test_utils::pubkey(42),
+ blinding_point: ln_test_utils::pubkey(43),
+ blinded_hops: vec![
+ BlindedHop { blinded_node_id: ln_test_utils::pubkey(44), encrypted_payload: Vec::new() },
+ BlindedHop { blinded_node_id: ln_test_utils::pubkey(45), encrypted_payload: Vec::new() }
+ ],
+ };
+ let blinded_path_2 = BlindedPath {
+ introduction_node_id: ln_test_utils::pubkey(46),
+ blinding_point: ln_test_utils::pubkey(47),
+ blinded_hops: vec![
+ BlindedHop { blinded_node_id: ln_test_utils::pubkey(48), encrypted_payload: Vec::new() },
+ BlindedHop { blinded_node_id: ln_test_utils::pubkey(49), encrypted_payload: Vec::new() }
+ ],
+ };
+ // (De)serialize a Route with 1 blinded path out of two total paths.
+ let mut route = Route { paths: vec![Path {
+ hops: vec![RouteHop {
+ pubkey: ln_test_utils::pubkey(50),
+ node_features: NodeFeatures::empty(),
+ short_channel_id: 42,
+ channel_features: ChannelFeatures::empty(),
+ fee_msat: 100,
+ cltv_expiry_delta: 0,
+ }],
+ blinded_tail: Some(BlindedTail {
+ hops: blinded_path_1.blinded_hops,
+ blinding_point: blinded_path_1.blinding_point,
+ excess_final_cltv_expiry_delta: 40,
+ final_value_msat: 100,
+ })}, Path {
+ hops: vec![RouteHop {
+ pubkey: ln_test_utils::pubkey(51),
+ node_features: NodeFeatures::empty(),
+ short_channel_id: 43,
+ channel_features: ChannelFeatures::empty(),
+ fee_msat: 100,
+ cltv_expiry_delta: 0,
+ }], blinded_tail: None }],
+ payment_params: None,
+ };
+ let encoded_route = route.encode();
+ let decoded_route: Route = Readable::read(&mut Cursor::new(&encoded_route[..])).unwrap();
+ assert_eq!(decoded_route.paths[0].blinded_tail, route.paths[0].blinded_tail);
+ assert_eq!(decoded_route.paths[1].blinded_tail, route.paths[1].blinded_tail);
+
+ // (De)serialize a Route with two paths, each containing a blinded tail.
+ route.paths[1].blinded_tail = Some(BlindedTail {
+ hops: blinded_path_2.blinded_hops,
+ blinding_point: blinded_path_2.blinding_point,
+ excess_final_cltv_expiry_delta: 41,
+ final_value_msat: 101,
+ });
+ let encoded_route = route.encode();
+ let decoded_route: Route = Readable::read(&mut Cursor::new(&encoded_route[..])).unwrap();
+ assert_eq!(decoded_route.paths[0].blinded_tail, route.paths[0].blinded_tail);
+ assert_eq!(decoded_route.paths[1].blinded_tail, route.paths[1].blinded_tail);
+ }
+
+ #[test]
+ fn blinded_path_inflight_processing() {
+ // Ensure we'll score the channel that's inbound to a blinded path's introduction node, and
+ // account for the blinded tail's final amount_msat.
+ let mut inflight_htlcs = InFlightHtlcs::new();
+ let blinded_path = BlindedPath {
+ introduction_node_id: ln_test_utils::pubkey(43),
+ blinding_point: ln_test_utils::pubkey(48),
+ blinded_hops: vec![BlindedHop { blinded_node_id: ln_test_utils::pubkey(49), encrypted_payload: Vec::new() }],
+ };
+ let path = Path {
+ hops: vec![RouteHop {
+ pubkey: ln_test_utils::pubkey(42),
+ node_features: NodeFeatures::empty(),
+ short_channel_id: 42,
+ channel_features: ChannelFeatures::empty(),
+ fee_msat: 100,
+ cltv_expiry_delta: 0,
+ },
+ RouteHop {
+ pubkey: blinded_path.introduction_node_id,
+ node_features: NodeFeatures::empty(),
+ short_channel_id: 43,
+ channel_features: ChannelFeatures::empty(),
+ fee_msat: 1,
+ cltv_expiry_delta: 0,
+ }],
+ blinded_tail: Some(BlindedTail {
+ hops: blinded_path.blinded_hops,
+ blinding_point: blinded_path.blinding_point,
+ excess_final_cltv_expiry_delta: 0,
+ final_value_msat: 200,
+ }),
+ };
+ inflight_htlcs.process_path(&path, ln_test_utils::pubkey(44));
+ assert_eq!(*inflight_htlcs.0.get(&(42, true)).unwrap(), 301);
+ assert_eq!(*inflight_htlcs.0.get(&(43, false)).unwrap(), 201);
+ }
+
+ #[test]
+ fn blinded_path_cltv_shadow_offset() {
+ // Make sure we add a shadow offset when sending to blinded paths.
+ let blinded_path = BlindedPath {
+ introduction_node_id: ln_test_utils::pubkey(43),
+ blinding_point: ln_test_utils::pubkey(44),
+ blinded_hops: vec![
+ BlindedHop { blinded_node_id: ln_test_utils::pubkey(45), encrypted_payload: Vec::new() },
+ BlindedHop { blinded_node_id: ln_test_utils::pubkey(46), encrypted_payload: Vec::new() }
+ ],
+ };
+ let mut route = Route { paths: vec![Path {
+ hops: vec![RouteHop {
+ pubkey: ln_test_utils::pubkey(42),
+ node_features: NodeFeatures::empty(),
+ short_channel_id: 42,
+ channel_features: ChannelFeatures::empty(),
+ fee_msat: 100,
+ cltv_expiry_delta: 0,
+ },
+ RouteHop {
+ pubkey: blinded_path.introduction_node_id,
+ node_features: NodeFeatures::empty(),
+ short_channel_id: 43,
+ channel_features: ChannelFeatures::empty(),
+ fee_msat: 1,
+ cltv_expiry_delta: 0,
+ }
+ ],
+ blinded_tail: Some(BlindedTail {
+ hops: blinded_path.blinded_hops,
+ blinding_point: blinded_path.blinding_point,
+ excess_final_cltv_expiry_delta: 0,
+ final_value_msat: 200,
+ }),
+ }], payment_params: None};
+
+ let payment_params = PaymentParameters::from_node_id(ln_test_utils::pubkey(47), 18);
+ let (_, network_graph, _, _, _) = build_line_graph();
+ add_random_cltv_offset(&mut route, &payment_params, &network_graph.read_only(), &[0; 32]);
+ assert_eq!(route.paths[0].blinded_tail.as_ref().unwrap().excess_final_cltv_expiry_delta, 40);
+ assert_eq!(route.paths[0].hops.last().unwrap().cltv_expiry_delta, 40);
+ }
}
#[cfg(all(test, not(feature = "no-std")))]