let payment_hash = Sha256::hash(&[payment_id; 1]);
payment_id = payment_id.wrapping_add(1);
if let Err(_) = $source.send_payment(Route {
- hops: vec![RouteHop {
+ paths: vec![vec![RouteHop {
pubkey: $dest.0.get_our_node_id(),
node_features: NodeFeatures::empty(),
short_channel_id: $dest.1,
channel_features: ChannelFeatures::empty(),
fee_msat: 5000000,
cltv_expiry_delta: 200,
- }],
+ }]],
}, PaymentHash(payment_hash.into_inner()), &None) {
// Probably ran out of funds
test_return!();
let payment_hash = Sha256::hash(&[payment_id; 1]);
payment_id = payment_id.wrapping_add(1);
if let Err(_) = $source.send_payment(Route {
- hops: vec![RouteHop {
+ paths: vec![vec![RouteHop {
pubkey: $middle.0.get_our_node_id(),
node_features: NodeFeatures::empty(),
short_channel_id: $middle.1,
channel_features: ChannelFeatures::empty(),
fee_msat: 5000000,
cltv_expiry_delta: 200,
- }],
+ }]],
}, PaymentHash(payment_hash.into_inner()), &None) {
// Probably ran out of funds
test_return!();
use ln::channel::{Channel, ChannelError};
use ln::channelmonitor::{ChannelMonitor, ChannelMonitorUpdate, ChannelMonitorUpdateErr, ManyChannelMonitor, CLTV_CLAIM_BUFFER, LATENCY_GRACE_PERIOD_BLOCKS, ANTI_REORG_DELAY};
use ln::features::{InitFeatures, NodeFeatures};
-use ln::router::Route;
+use ln::router::{Route, RouteHop};
use ln::msgs;
use ln::onion_utils;
use ln::msgs::{ChannelMessageHandler, DecodeError, LightningError};
pub(super) enum HTLCSource {
PreviousHopData(HTLCPreviousHopData),
OutboundRoute {
- route: Route,
+ path: Vec<RouteHop>,
session_priv: SecretKey,
/// Technically we can recalculate this from the route, but we cache it here to avoid
/// doing a double-pass on route when we get a failure back
impl HTLCSource {
pub fn dummy() -> Self {
HTLCSource::OutboundRoute {
- route: Route { hops: Vec::new() },
+ path: Vec::new(),
session_priv: SecretKey::from_slice(&[1; 32]).unwrap(),
first_hop_htlc_msat: 0,
}
/// bit set (either as required or as available). If multiple paths are present in the Route,
/// we assume the invoice had the basic_mpp feature set.
pub fn send_payment(&self, route: Route, payment_hash: PaymentHash, payment_secret: &Option<PaymentSecret>) -> Result<(), APIError> {
- if route.hops.len() < 1 || route.hops.len() > 20 {
- return Err(APIError::RouteError{err: "Route didn't go anywhere/had bogus size"});
+ if route.paths.len() < 1 || route.paths.len() > 1 {
+ return Err(APIError::RouteError{err: "We currently don't support MPP, and we need at least one path"});
+ }
+ if route.paths[0].len() < 1 || route.paths[0].len() > 20 {
+ return Err(APIError::RouteError{err: "Path didn't go anywhere/had bogus size"});
}
let our_node_id = self.get_our_node_id();
- for (idx, hop) in route.hops.iter().enumerate() {
- if idx != route.hops.len() - 1 && hop.pubkey == our_node_id {
- return Err(APIError::RouteError{err: "Route went through us but wasn't a simple rebalance loop to us"});
+ for (idx, hop) in route.paths[0].iter().enumerate() {
+ if idx != route.paths[0].len() - 1 && hop.pubkey == our_node_id {
+ return Err(APIError::RouteError{err: "Path went through us but wasn't a simple rebalance loop to us"});
}
}
let cur_height = self.latest_block_height.load(Ordering::Acquire) as u32 + 1;
- let onion_keys = secp_call!(onion_utils::construct_onion_keys(&self.secp_ctx, &route, &session_priv),
+ let onion_keys = secp_call!(onion_utils::construct_onion_keys(&self.secp_ctx, &route.paths[0], &session_priv),
APIError::RouteError{err: "Pubkey along hop was maliciously selected"});
- let (onion_payloads, htlc_msat, htlc_cltv) = onion_utils::build_onion_payloads(&route, payment_secret, cur_height)?;
+ let (onion_payloads, htlc_msat, htlc_cltv) = onion_utils::build_onion_payloads(&route.paths[0], payment_secret, cur_height)?;
if onion_utils::route_size_insane(&onion_payloads) {
return Err(APIError::RouteError{err: "Route size too large considering onion data"});
}
let err: Result<(), _> = loop {
let mut channel_lock = self.channel_state.lock().unwrap();
- let id = match channel_lock.short_to_id.get(&route.hops.first().unwrap().short_channel_id) {
+ let id = match channel_lock.short_to_id.get(&route.paths[0].first().unwrap().short_channel_id) {
None => return Err(APIError::ChannelUnavailable{err: "No channel available with first hop!"}),
Some(id) => id.clone(),
};
let channel_state = &mut *channel_lock;
if let hash_map::Entry::Occupied(mut chan) = channel_state.by_id.entry(id) {
match {
- if chan.get().get_their_node_id() != route.hops.first().unwrap().pubkey {
+ if chan.get().get_their_node_id() != route.paths[0].first().unwrap().pubkey {
return Err(APIError::RouteError{err: "Node ID mismatch on first hop!"});
}
if !chan.get().is_live() {
return Err(APIError::ChannelUnavailable{err: "Peer for first hop currently disconnected/pending monitor update!"});
}
break_chan_entry!(self, chan.get_mut().send_htlc_and_commit(htlc_msat, payment_hash.clone(), htlc_cltv, HTLCSource::OutboundRoute {
- route: route.clone(),
+ path: route.paths[0].clone(),
session_priv: session_priv.clone(),
first_hop_htlc_msat: htlc_msat,
}, onion_packet), channel_state, chan)
}
channel_state.pending_msg_events.push(events::MessageSendEvent::UpdateHTLCs {
- node_id: route.hops.first().unwrap().pubkey,
+ node_id: route.paths[0].first().unwrap().pubkey,
updates: msgs::CommitmentUpdate {
update_add_htlcs: vec![update_add],
update_fulfill_htlcs: Vec::new(),
return Ok(());
};
- match handle_error!(self, err, route.hops.first().unwrap().pubkey) {
+ match handle_error!(self, err, route.paths[0].first().unwrap().pubkey) {
Ok(_) => unreachable!(),
Err(e) => { Err(APIError::ChannelUnavailable { err: e.err }) }
}
//between the branches here. We should make this async and move it into the forward HTLCs
//timer handling.
match source {
- HTLCSource::OutboundRoute { ref route, .. } => {
+ HTLCSource::OutboundRoute { ref path, .. } => {
log_trace!(self, "Failing outbound payment HTLC with payment_hash {}", log_bytes!(payment_hash.0));
mem::drop(channel_state_lock);
match &onion_error {
self.pending_events.lock().unwrap().push(
events::Event::PaymentFailed {
payment_hash: payment_hash.clone(),
- rejected_by_dest: route.hops.len() == 1,
+ rejected_by_dest: path.len() == 1,
#[cfg(test)]
error_code: Some(*failure_code),
}
let mut channel_state = Some(self.channel_state.lock().unwrap());
let removed_source = channel_state.as_mut().unwrap().claimable_htlcs.remove(&(payment_hash, *payment_secret));
if let Some(mut sources) = removed_source {
+ assert!(!sources.is_empty());
+ let valid_mpp_amount = if let &Some(ref data) = &sources[0].payment_data {
+ assert!(payment_secret.is_some());
+ data.total_msat == expected_amount
+ } else {
+ assert!(payment_secret.is_none());
+ false
+ };
+
+ let mut claimed_any_htlcs = false;
for htlc in sources.drain(..) {
if channel_state.is_none() { channel_state = Some(self.channel_state.lock().unwrap()); }
- if htlc.value < expected_amount || htlc.value > expected_amount * 2 {
+ if !valid_mpp_amount && (htlc.value < expected_amount || htlc.value > expected_amount * 2) {
let mut htlc_msat_data = byte_utils::be64_to_array(htlc.value).to_vec();
let mut height_data = byte_utils::be32_to_array(self.latest_block_height.load(Ordering::Acquire) as u32).to_vec();
htlc_msat_data.append(&mut height_data);
HTLCFailReason::Reason { failure_code: 0x4000|15, data: htlc_msat_data });
} else {
self.claim_funds_internal(channel_state.take().unwrap(), HTLCSource::PreviousHopData(htlc.prev_hop), payment_preimage);
+ claimed_any_htlcs = true;
}
}
- true
+ claimed_any_htlcs
} else { false }
}
fn claim_funds_internal(&self, mut channel_state_lock: MutexGuard<ChannelHolder<ChanSigner>>, source: HTLCSource, payment_preimage: PaymentPreimage) {
0u8.write(writer)?;
hop_data.write(writer)?;
},
- &HTLCSource::OutboundRoute { ref route, ref session_priv, ref first_hop_htlc_msat } => {
+ &HTLCSource::OutboundRoute { ref path, ref session_priv, ref first_hop_htlc_msat } => {
1u8.write(writer)?;
- route.write(writer)?;
+ path.write(writer)?;
session_priv.write(writer)?;
first_hop_htlc_msat.write(writer)?;
}
match <u8 as Readable>::read(reader)? {
0 => Ok(HTLCSource::PreviousHopData(Readable::read(reader)?)),
1 => Ok(HTLCSource::OutboundRoute {
- route: Readable::read(reader)?,
+ path: Readable::read(reader)?,
session_priv: Readable::read(reader)?,
first_hop_htlc_msat: Readable::read(reader)?,
}),
pub fn route_payment<'a, 'b, 'c>(origin_node: &Node<'a, 'b, 'c>, expected_route: &[&Node<'a, 'b, 'c>], recv_value: u64) -> (PaymentPreimage, PaymentHash) {
let route = origin_node.router.get_route(&expected_route.last().unwrap().node.get_our_node_id(), None, &Vec::new(), recv_value, TEST_FINAL_CLTV).unwrap();
- assert_eq!(route.hops.len(), expected_route.len());
- for (node, hop) in expected_route.iter().zip(route.hops.iter()) {
+ assert_eq!(route.paths.len(), 1);
+ assert_eq!(route.paths[0].len(), expected_route.len());
+ for (node, hop) in expected_route.iter().zip(route.paths[0].iter()) {
assert_eq!(hop.pubkey, node.node.get_our_node_id());
}
pub fn route_over_limit<'a, 'b, 'c>(origin_node: &Node<'a, 'b, 'c>, expected_route: &[&Node<'a, 'b, 'c>], recv_value: u64) {
let route = origin_node.router.get_route(&expected_route.last().unwrap().node.get_our_node_id(), None, &Vec::new(), recv_value, TEST_FINAL_CLTV).unwrap();
- assert_eq!(route.hops.len(), expected_route.len());
- for (node, hop) in expected_route.iter().zip(route.hops.iter()) {
+ assert_eq!(route.paths.len(), 1);
+ assert_eq!(route.paths[0].len(), expected_route.len());
+ for (node, hop) in expected_route.iter().zip(route.paths[0].iter()) {
assert_eq!(hop.pubkey, node.node.get_our_node_id());
}
});
hops[1].fee_msat = chan_4.1.contents.fee_base_msat as u64 + chan_4.1.contents.fee_proportional_millionths as u64 * hops[2].fee_msat as u64 / 1000000;
hops[0].fee_msat = chan_3.0.contents.fee_base_msat as u64 + chan_3.0.contents.fee_proportional_millionths as u64 * hops[1].fee_msat as u64 / 1000000;
- let payment_preimage_1 = send_along_route(&nodes[1], Route { hops }, &vec!(&nodes[2], &nodes[3], &nodes[1])[..], 1000000).0;
+ let payment_preimage_1 = send_along_route(&nodes[1], Route { paths: vec![hops] }, &vec!(&nodes[2], &nodes[3], &nodes[1])[..], 1000000).0;
let mut hops = Vec::with_capacity(3);
hops.push(RouteHop {
});
hops[1].fee_msat = chan_2.1.contents.fee_base_msat as u64 + chan_2.1.contents.fee_proportional_millionths as u64 * hops[2].fee_msat as u64 / 1000000;
hops[0].fee_msat = chan_3.1.contents.fee_base_msat as u64 + chan_3.1.contents.fee_proportional_millionths as u64 * hops[1].fee_msat as u64 / 1000000;
- let payment_hash_2 = send_along_route(&nodes[1], Route { hops }, &vec!(&nodes[3], &nodes[2], &nodes[1])[..], 1000000).1;
+ let payment_hash_2 = send_along_route(&nodes[1], Route { paths: vec![hops] }, &vec!(&nodes[3], &nodes[2], &nodes[1])[..], 1000000).1;
// Claim the rebalances...
fail_payment(&nodes[1], &vec!(&nodes[3], &nodes[2], &nodes[1])[..], payment_hash_2);
// attempt to send amt_msat > their_max_htlc_value_in_flight_msat
{
let (route, our_payment_hash, _) = get_route_and_payment_hash!(recv_value_0 + 1);
- assert!(route.hops.iter().rev().skip(1).all(|h| h.fee_msat == feemsat));
+ assert!(route.paths[0].iter().rev().skip(1).all(|h| h.fee_msat == feemsat));
let err = nodes[0].node.send_payment(route, our_payment_hash, &None).err().unwrap();
match err {
APIError::ChannelUnavailable{err} => assert_eq!(err, "Cannot send value that would put us over the max HTLC value in flight our peer will accept"),
}).expect("RNG is bad!");
let cur_height = nodes[0].node.latest_block_height.load(Ordering::Acquire) as u32 + 1;
- let onion_keys = onion_utils::construct_onion_keys(&secp_ctx, &route, &session_priv).unwrap();
- let (onion_payloads, htlc_msat, htlc_cltv) = onion_utils::build_onion_payloads(&route, &None, cur_height).unwrap();
+ let onion_keys = onion_utils::construct_onion_keys(&secp_ctx, &route.paths[0], &session_priv).unwrap();
+ let (onion_payloads, htlc_msat, htlc_cltv) = onion_utils::build_onion_payloads(&route.paths[0], &None, cur_height).unwrap();
let onion_packet = onion_utils::construct_onion_packet(onion_payloads, onion_keys, [0; 32], &our_payment_hash);
let msg = msgs::UpdateAddHTLC {
channel_id: chan_1.2,
};
let current_height = nodes[1].node.latest_block_height.load(Ordering::Acquire) as u32 + 1;
- let (onion_payloads, _amount_msat, cltv_expiry) = onion_utils::build_onion_payloads(&route, &None, current_height).unwrap();
- let onion_keys = onion_utils::construct_onion_keys(&secp_ctx, &route, &session_priv).unwrap();
+ let (onion_payloads, _amount_msat, cltv_expiry) = onion_utils::build_onion_payloads(&route.paths[0], &None, current_height).unwrap();
+ let onion_keys = onion_utils::construct_onion_keys(&secp_ctx, &route.paths[0], &session_priv).unwrap();
let onion_routing_packet = onion_utils::construct_onion_packet(onion_payloads, onion_keys, [0; 32], &payment_hash);
// Send a 0-msat update_add_htlc to fail the channel.
run_onion_failure_test("invalid_realm", 0, &nodes, &route, &payment_hash, |msg| {
let session_priv = SecretKey::from_slice(&[3; 32]).unwrap();
let cur_height = nodes[0].node.latest_block_height.load(Ordering::Acquire) as u32 + 1;
- let onion_keys = onion_utils::construct_onion_keys(&Secp256k1::new(), &route, &session_priv).unwrap();
- let (mut onion_payloads, _htlc_msat, _htlc_cltv) = onion_utils::build_onion_payloads(&route, &None, cur_height).unwrap();
+ let onion_keys = onion_utils::construct_onion_keys(&Secp256k1::new(), &route.paths[0], &session_priv).unwrap();
+ let (mut onion_payloads, _htlc_msat, _htlc_cltv) = onion_utils::build_onion_payloads(&route.paths[0], &None, cur_height).unwrap();
let mut new_payloads = Vec::new();
for payload in onion_payloads.drain(..) {
new_payloads.push(BogusOnionHopData::new(payload));
run_onion_failure_test("invalid_realm", 3, &nodes, &route, &payment_hash, |msg| {
let session_priv = SecretKey::from_slice(&[3; 32]).unwrap();
let cur_height = nodes[0].node.latest_block_height.load(Ordering::Acquire) as u32 + 1;
- let onion_keys = onion_utils::construct_onion_keys(&Secp256k1::new(), &route, &session_priv).unwrap();
- let (mut onion_payloads, _htlc_msat, _htlc_cltv) = onion_utils::build_onion_payloads(&route, &None, cur_height).unwrap();
+ let onion_keys = onion_utils::construct_onion_keys(&Secp256k1::new(), &route.paths[0], &session_priv).unwrap();
+ let (mut onion_payloads, _htlc_msat, _htlc_cltv) = onion_utils::build_onion_payloads(&route.paths[0], &None, cur_height).unwrap();
let mut new_payloads = Vec::new();
for payload in onion_payloads.drain(..) {
new_payloads.push(BogusOnionHopData::new(payload));
}, |msg| {
// and tamper returning error message
let session_priv = SecretKey::from_slice(&[3; 32]).unwrap();
- let onion_keys = onion_utils::construct_onion_keys(&Secp256k1::new(), &route, &session_priv).unwrap();
+ let onion_keys = onion_utils::construct_onion_keys(&Secp256k1::new(), &route.paths[0], &session_priv).unwrap();
msg.reason = onion_utils::build_first_hop_failure_packet(&onion_keys[0].shared_secret[..], NODE|2, &[0;0]);
- }, ||{}, true, Some(NODE|2), Some(msgs::HTLCFailChannelUpdate::NodeFailure{node_id: route.hops[0].pubkey, is_permanent: false}));
+ }, ||{}, true, Some(NODE|2), Some(msgs::HTLCFailChannelUpdate::NodeFailure{node_id: route.paths[0][0].pubkey, is_permanent: false}));
// final node failure
run_onion_failure_test_with_fail_intercept("temporary_node_failure", 200, &nodes, &route, &payment_hash, |_msg| {}, |msg| {
// and tamper returning error message
let session_priv = SecretKey::from_slice(&[3; 32]).unwrap();
- let onion_keys = onion_utils::construct_onion_keys(&Secp256k1::new(), &route, &session_priv).unwrap();
+ let onion_keys = onion_utils::construct_onion_keys(&Secp256k1::new(), &route.paths[0], &session_priv).unwrap();
msg.reason = onion_utils::build_first_hop_failure_packet(&onion_keys[1].shared_secret[..], NODE|2, &[0;0]);
}, ||{
nodes[2].node.fail_htlc_backwards(&payment_hash, &None);
- }, true, Some(NODE|2), Some(msgs::HTLCFailChannelUpdate::NodeFailure{node_id: route.hops[1].pubkey, is_permanent: false}));
+ }, true, Some(NODE|2), Some(msgs::HTLCFailChannelUpdate::NodeFailure{node_id: route.paths[0][1].pubkey, is_permanent: false}));
// intermediate node failure
run_onion_failure_test_with_fail_intercept("permanent_node_failure", 100, &nodes, &route, &payment_hash, |msg| {
msg.amount_msat -= 1;
}, |msg| {
let session_priv = SecretKey::from_slice(&[3; 32]).unwrap();
- let onion_keys = onion_utils::construct_onion_keys(&Secp256k1::new(), &route, &session_priv).unwrap();
+ let onion_keys = onion_utils::construct_onion_keys(&Secp256k1::new(), &route.paths[0], &session_priv).unwrap();
msg.reason = onion_utils::build_first_hop_failure_packet(&onion_keys[0].shared_secret[..], PERM|NODE|2, &[0;0]);
- }, ||{}, true, Some(PERM|NODE|2), Some(msgs::HTLCFailChannelUpdate::NodeFailure{node_id: route.hops[0].pubkey, is_permanent: true}));
+ }, ||{}, true, Some(PERM|NODE|2), Some(msgs::HTLCFailChannelUpdate::NodeFailure{node_id: route.paths[0][0].pubkey, is_permanent: true}));
// final node failure
run_onion_failure_test_with_fail_intercept("permanent_node_failure", 200, &nodes, &route, &payment_hash, |_msg| {}, |msg| {
let session_priv = SecretKey::from_slice(&[3; 32]).unwrap();
- let onion_keys = onion_utils::construct_onion_keys(&Secp256k1::new(), &route, &session_priv).unwrap();
+ let onion_keys = onion_utils::construct_onion_keys(&Secp256k1::new(), &route.paths[0], &session_priv).unwrap();
msg.reason = onion_utils::build_first_hop_failure_packet(&onion_keys[1].shared_secret[..], PERM|NODE|2, &[0;0]);
}, ||{
nodes[2].node.fail_htlc_backwards(&payment_hash, &None);
- }, false, Some(PERM|NODE|2), Some(msgs::HTLCFailChannelUpdate::NodeFailure{node_id: route.hops[1].pubkey, is_permanent: true}));
+ }, false, Some(PERM|NODE|2), Some(msgs::HTLCFailChannelUpdate::NodeFailure{node_id: route.paths[0][1].pubkey, is_permanent: true}));
// intermediate node failure
run_onion_failure_test_with_fail_intercept("required_node_feature_missing", 100, &nodes, &route, &payment_hash, |msg| {
msg.amount_msat -= 1;
}, |msg| {
let session_priv = SecretKey::from_slice(&[3; 32]).unwrap();
- let onion_keys = onion_utils::construct_onion_keys(&Secp256k1::new(), &route, &session_priv).unwrap();
+ let onion_keys = onion_utils::construct_onion_keys(&Secp256k1::new(), &route.paths[0], &session_priv).unwrap();
msg.reason = onion_utils::build_first_hop_failure_packet(&onion_keys[0].shared_secret[..], PERM|NODE|3, &[0;0]);
}, ||{
nodes[2].node.fail_htlc_backwards(&payment_hash, &None);
- }, true, Some(PERM|NODE|3), Some(msgs::HTLCFailChannelUpdate::NodeFailure{node_id: route.hops[0].pubkey, is_permanent: true}));
+ }, true, Some(PERM|NODE|3), Some(msgs::HTLCFailChannelUpdate::NodeFailure{node_id: route.paths[0][0].pubkey, is_permanent: true}));
// final node failure
run_onion_failure_test_with_fail_intercept("required_node_feature_missing", 200, &nodes, &route, &payment_hash, |_msg| {}, |msg| {
let session_priv = SecretKey::from_slice(&[3; 32]).unwrap();
- let onion_keys = onion_utils::construct_onion_keys(&Secp256k1::new(), &route, &session_priv).unwrap();
+ let onion_keys = onion_utils::construct_onion_keys(&Secp256k1::new(), &route.paths[0], &session_priv).unwrap();
msg.reason = onion_utils::build_first_hop_failure_packet(&onion_keys[1].shared_secret[..], PERM|NODE|3, &[0;0]);
}, ||{
nodes[2].node.fail_htlc_backwards(&payment_hash, &None);
- }, false, Some(PERM|NODE|3), Some(msgs::HTLCFailChannelUpdate::NodeFailure{node_id: route.hops[1].pubkey, is_permanent: true}));
+ }, false, Some(PERM|NODE|3), Some(msgs::HTLCFailChannelUpdate::NodeFailure{node_id: route.paths[0][1].pubkey, is_permanent: true}));
run_onion_failure_test("invalid_onion_version", 0, &nodes, &route, &payment_hash, |msg| { msg.onion_routing_packet.version = 1; }, ||{}, true,
Some(BADONION|PERM|4), None);
msg.amount_msat -= 1;
}, |msg| {
let session_priv = SecretKey::from_slice(&[3; 32]).unwrap();
- let onion_keys = onion_utils::construct_onion_keys(&Secp256k1::new(), &route, &session_priv).unwrap();
+ let onion_keys = onion_utils::construct_onion_keys(&Secp256k1::new(), &route.paths[0], &session_priv).unwrap();
msg.reason = onion_utils::build_first_hop_failure_packet(&onion_keys[0].shared_secret[..], UPDATE|7, &ChannelUpdate::dummy().encode_with_len()[..]);
}, ||{}, true, Some(UPDATE|7), Some(msgs::HTLCFailChannelUpdate::ChannelUpdateMessage{msg: ChannelUpdate::dummy()}));
msg.amount_msat -= 1;
}, |msg| {
let session_priv = SecretKey::from_slice(&[3; 32]).unwrap();
- let onion_keys = onion_utils::construct_onion_keys(&Secp256k1::new(), &route, &session_priv).unwrap();
+ let onion_keys = onion_utils::construct_onion_keys(&Secp256k1::new(), &route.paths[0], &session_priv).unwrap();
msg.reason = onion_utils::build_first_hop_failure_packet(&onion_keys[0].shared_secret[..], PERM|8, &[0;0]);
// short_channel_id from the processing node
}, ||{}, true, Some(PERM|8), Some(msgs::HTLCFailChannelUpdate::ChannelClosed{short_channel_id: channels[1].0.contents.short_channel_id, is_permanent: true}));
msg.amount_msat -= 1;
}, |msg| {
let session_priv = SecretKey::from_slice(&[3; 32]).unwrap();
- let onion_keys = onion_utils::construct_onion_keys(&Secp256k1::new(), &route, &session_priv).unwrap();
+ let onion_keys = onion_utils::construct_onion_keys(&Secp256k1::new(), &route.paths[0], &session_priv).unwrap();
msg.reason = onion_utils::build_first_hop_failure_packet(&onion_keys[0].shared_secret[..], PERM|9, &[0;0]);
// short_channel_id from the processing node
}, ||{}, true, Some(PERM|9), Some(msgs::HTLCFailChannelUpdate::ChannelClosed{short_channel_id: channels[1].0.contents.short_channel_id, is_permanent: true}));
let mut bogus_route = route.clone();
- bogus_route.hops[1].short_channel_id -= 1;
+ bogus_route.paths[0][1].short_channel_id -= 1;
run_onion_failure_test("unknown_next_peer", 0, &nodes, &bogus_route, &payment_hash, |_| {}, ||{}, true, Some(PERM|10),
- Some(msgs::HTLCFailChannelUpdate::ChannelClosed{short_channel_id: bogus_route.hops[1].short_channel_id, is_permanent:true}));
+ Some(msgs::HTLCFailChannelUpdate::ChannelClosed{short_channel_id: bogus_route.paths[0][1].short_channel_id, is_permanent:true}));
let amt_to_forward = nodes[1].node.channel_state.lock().unwrap().by_id.get(&channels[1].2).unwrap().get_their_htlc_minimum_msat() - 1;
let mut bogus_route = route.clone();
- let route_len = bogus_route.hops.len();
- bogus_route.hops[route_len-1].fee_msat = amt_to_forward;
+ let route_len = bogus_route.paths[0].len();
+ bogus_route.paths[0][route_len-1].fee_msat = amt_to_forward;
run_onion_failure_test("amount_below_minimum", 0, &nodes, &bogus_route, &payment_hash, |_| {}, ||{}, true, Some(UPDATE|11), Some(msgs::HTLCFailChannelUpdate::ChannelUpdateMessage{msg: ChannelUpdate::dummy()}));
//TODO: with new config API, we will be able to generate both valid and
let session_priv = SecretKey::from_slice(&[3; 32]).unwrap();
let mut route = route.clone();
let height = 1;
- route.hops[1].cltv_expiry_delta += CLTV_FAR_FAR_AWAY + route.hops[0].cltv_expiry_delta + 1;
- let onion_keys = onion_utils::construct_onion_keys(&Secp256k1::new(), &route, &session_priv).unwrap();
- let (onion_payloads, _, htlc_cltv) = onion_utils::build_onion_payloads(&route, &None, height).unwrap();
+ route.paths[0][1].cltv_expiry_delta += CLTV_FAR_FAR_AWAY + route.paths[0][0].cltv_expiry_delta + 1;
+ let onion_keys = onion_utils::construct_onion_keys(&Secp256k1::new(), &route.paths[0], &session_priv).unwrap();
+ let (onion_payloads, _, htlc_cltv) = onion_utils::build_onion_payloads(&route.paths[0], &None, height).unwrap();
let onion_packet = onion_utils::construct_onion_packet(onion_payloads, onion_keys, [0; 32], &payment_hash);
msg.cltv_expiry = htlc_cltv;
msg.onion_routing_packet = onion_packet;
let mut route = nodes[0].router.get_route(&nodes[1].node.get_our_node_id(), None, &[], 100000, TEST_FINAL_CLTV).unwrap();
let (_, our_payment_hash) = get_payment_preimage_hash!(nodes[0]);
- route.hops[0].fee_msat = 100;
+ route.paths[0][0].fee_msat = 100;
let err = nodes[0].node.send_payment(route, our_payment_hash, &None);
let mut route = nodes[0].router.get_route(&nodes[1].node.get_our_node_id(), None, &[], 100000, TEST_FINAL_CLTV).unwrap();
let (_, our_payment_hash) = get_payment_preimage_hash!(nodes[0]);
- route.hops[0].fee_msat = 0;
+ route.paths[0][0].fee_msat = 0;
let err = nodes[0].node.send_payment(route, our_payment_hash, &None);
}).expect("RNG is bad!");
let cur_height = nodes[0].node.latest_block_height.load(Ordering::Acquire) as u32 + 1;
- let onion_keys = onion_utils::construct_onion_keys(&Secp256k1::signing_only(), &route, &session_priv).unwrap();
- let (onion_payloads, _htlc_msat, htlc_cltv) = onion_utils::build_onion_payloads(&route, &None, cur_height).unwrap();
+ let onion_keys = onion_utils::construct_onion_keys(&Secp256k1::signing_only(), &route.paths[0], &session_priv).unwrap();
+ let (onion_payloads, _htlc_msat, htlc_cltv) = onion_utils::build_onion_payloads(&route.paths[0], &None, cur_height).unwrap();
let onion_packet = onion_utils::construct_onion_packet(onion_payloads, onion_keys, [0; 32], &our_payment_hash);
let mut msg = msgs::UpdateAddHTLC {
use ln::channelmanager::{PaymentHash, PaymentSecret, HTLCSource};
use ln::msgs;
-use ln::router::{Route,RouteHop};
+use ln::router::RouteHop;
use util::byte_utils;
use util::chacha20::ChaCha20;
use util::errors::{self, APIError};
// can only fail if an intermediary hop has an invalid public key or session_priv is invalid
#[inline]
-pub(super) fn construct_onion_keys_callback<T: secp256k1::Signing, FType: FnMut(SharedSecret, [u8; 32], PublicKey, &RouteHop)> (secp_ctx: &Secp256k1<T>, route: &Route, session_priv: &SecretKey, mut callback: FType) -> Result<(), secp256k1::Error> {
+pub(super) fn construct_onion_keys_callback<T: secp256k1::Signing, FType: FnMut(SharedSecret, [u8; 32], PublicKey, &RouteHop)> (secp_ctx: &Secp256k1<T>, path: &Vec<RouteHop>, session_priv: &SecretKey, mut callback: FType) -> Result<(), secp256k1::Error> {
let mut blinded_priv = session_priv.clone();
let mut blinded_pub = PublicKey::from_secret_key(secp_ctx, &blinded_priv);
- for hop in route.hops.iter() {
+ for hop in path.iter() {
let shared_secret = SharedSecret::new(&hop.pubkey, &blinded_priv);
let mut sha = Sha256::engine();
}
// can only fail if an intermediary hop has an invalid public key or session_priv is invalid
-pub(super) fn construct_onion_keys<T: secp256k1::Signing>(secp_ctx: &Secp256k1<T>, route: &Route, session_priv: &SecretKey) -> Result<Vec<OnionKeys>, secp256k1::Error> {
- let mut res = Vec::with_capacity(route.hops.len());
+pub(super) fn construct_onion_keys<T: secp256k1::Signing>(secp_ctx: &Secp256k1<T>, path: &Vec<RouteHop>, session_priv: &SecretKey) -> Result<Vec<OnionKeys>, secp256k1::Error> {
+ let mut res = Vec::with_capacity(path.len());
- construct_onion_keys_callback(secp_ctx, route, session_priv, |shared_secret, _blinding_factor, ephemeral_pubkey, _| {
+ construct_onion_keys_callback(secp_ctx, path, session_priv, |shared_secret, _blinding_factor, ephemeral_pubkey, _| {
let (rho, mu) = gen_rho_mu_from_shared_secret(&shared_secret[..]);
res.push(OnionKeys {
}
/// returns the hop data, as well as the first-hop value_msat and CLTV value we should send.
-pub(super) fn build_onion_payloads(route: &Route, payment_secret_option: &Option<PaymentSecret>, starting_htlc_offset: u32) -> Result<(Vec<msgs::OnionHopData>, u64, u32), APIError> {
+pub(super) fn build_onion_payloads(path: &Vec<RouteHop>, payment_secret_option: &Option<PaymentSecret>, starting_htlc_offset: u32) -> Result<(Vec<msgs::OnionHopData>, u64, u32), APIError> {
let mut cur_value_msat = 0u64;
let mut cur_cltv = starting_htlc_offset;
let mut last_short_channel_id = 0;
- let mut res: Vec<msgs::OnionHopData> = Vec::with_capacity(route.hops.len());
+ let mut res: Vec<msgs::OnionHopData> = Vec::with_capacity(path.len());
- for (idx, hop) in route.hops.iter().rev().enumerate() {
+ for (idx, hop) in path.iter().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).
/// OutboundRoute).
/// Returns update, a boolean indicating that the payment itself failed, and the error code.
pub(super) fn process_onion_failure<T: secp256k1::Signing>(secp_ctx: &Secp256k1<T>, logger: &Arc<Logger>, htlc_source: &HTLCSource, mut packet_decrypted: Vec<u8>) -> (Option<msgs::HTLCFailChannelUpdate>, bool, Option<u16>) {
- if let &HTLCSource::OutboundRoute { ref route, ref session_priv, ref first_hop_htlc_msat } = htlc_source {
+ if let &HTLCSource::OutboundRoute { ref path, ref session_priv, ref first_hop_htlc_msat } = htlc_source {
let mut res = None;
let mut htlc_msat = *first_hop_htlc_msat;
let mut error_code_ret = None;
let mut is_from_final_node = false;
// Handle packed channel/node updates for passing back for the route handler
- construct_onion_keys_callback(secp_ctx, route, session_priv, |shared_secret, _, _, route_hop| {
+ construct_onion_keys_callback(secp_ctx, path, session_priv, |shared_secret, _, _, route_hop| {
next_route_hop_ix += 1;
if res.is_some() { return; }
chacha.process(&packet_decrypted, &mut decryption_tmp[..]);
packet_decrypted = decryption_tmp;
- is_from_final_node = route.hops.last().unwrap().pubkey == route_hop.pubkey;
+ is_from_final_node = path.last().unwrap().pubkey == route_hop.pubkey;
if let Ok(err_packet) = msgs::DecodedOnionErrorPacket::read(&mut Cursor::new(&packet_decrypted)) {
let um = gen_um_from_shared_secret(&shared_secret[..]);
}
else if error_code & PERM == PERM {
fail_channel_update = if payment_failed {None} else {Some(msgs::HTLCFailChannelUpdate::ChannelClosed {
- short_channel_id: route.hops[next_route_hop_ix - if next_route_hop_ix == route.hops.len() { 1 } else { 0 }].short_channel_id,
+ short_channel_id: path[next_route_hop_ix - if next_route_hop_ix == path.len() { 1 } else { 0 }].short_channel_id,
is_permanent: true,
})};
}
let secp_ctx = Secp256k1::new();
let route = Route {
- hops: vec!(
+ paths: vec![vec![
RouteHop {
pubkey: PublicKey::from_slice(&hex::decode("02eec7245d6b7d2ccb30380bfbe2a3648cd7a942653f5aa340edcea1f283686619").unwrap()[..]).unwrap(),
channel_features: ChannelFeatures::empty(), node_features: NodeFeatures::empty(),
channel_features: ChannelFeatures::empty(), node_features: NodeFeatures::empty(),
short_channel_id: 0, fee_msat: 0, cltv_expiry_delta: 0 // Test vectors are garbage and not generateble from a RouteHop, we fill in payloads manually
},
- ),
+ ]],
};
let session_priv = SecretKey::from_slice(&hex::decode("4141414141414141414141414141414141414141414141414141414141414141").unwrap()[..]).unwrap();
- let onion_keys = super::construct_onion_keys(&secp_ctx, &route, &session_priv).unwrap();
- assert_eq!(onion_keys.len(), route.hops.len());
+ let onion_keys = super::construct_onion_keys(&secp_ctx, &route.paths[0], &session_priv).unwrap();
+ assert_eq!(onion_keys.len(), route.paths[0].len());
onion_keys
}
pub cltv_expiry_delta: u32,
}
-/// A route from us through the network to a destination
-#[derive(Clone, PartialEq)]
-pub struct Route {
- /// The list of hops, NOT INCLUDING our own, where the last hop is the destination. Thus, this
- /// must always be at least length one. By protocol rules, this may not currently exceed 20 in
- /// length.
- pub hops: Vec<RouteHop>,
-}
-
-impl Writeable for Route {
+impl Writeable for Vec<RouteHop> {
fn write<W: ::util::ser::Writer>(&self, writer: &mut W) -> Result<(), ::std::io::Error> {
- (self.hops.len() as u8).write(writer)?;
- for hop in self.hops.iter() {
+ (self.len() as u8).write(writer)?;
+ for hop in self.iter() {
hop.pubkey.write(writer)?;
hop.node_features.write(writer)?;
hop.short_channel_id.write(writer)?;
}
}
-impl Readable for Route {
- fn read<R: ::std::io::Read>(reader: &mut R) -> Result<Route, DecodeError> {
+impl Readable for Vec<RouteHop> {
+ fn read<R: ::std::io::Read>(reader: &mut R) -> Result<Vec<RouteHop>, DecodeError> {
let hops_count: u8 = Readable::read(reader)?;
let mut hops = Vec::with_capacity(hops_count as usize);
for _ in 0..hops_count {
cltv_expiry_delta: Readable::read(reader)?,
});
}
- Ok(Route {
- hops
- })
+ Ok(hops)
+ }
+}
+
+/// A route directs a payment from the sender (us) to the recipient. If the recipient supports MPP,
+/// it can take multiple paths. Each path is composed of one or more hops through the network.
+#[derive(Clone, PartialEq)]
+pub struct Route {
+ /// The list of routes taken for a single (potentially-)multi-part payment. The pubkey of the
+ /// last RouteHop in each path must be the same.
+ /// Each entry represents a list of hops, NOT INCLUDING our own, where the last hop is the
+ /// destination. Thus, this must always be at least length one. While the maximum length of any
+ /// given path is variable, keeping the length of any path to less than 20 should currently
+ /// ensure it is viable.
+ pub paths: Vec<Vec<RouteHop>>,
+}
+
+impl Writeable for Route {
+ fn write<W: ::util::ser::Writer>(&self, writer: &mut W) -> Result<(), ::std::io::Error> {
+ (self.paths.len() as u64).write(writer)?;
+ for hops in self.paths.iter() {
+ hops.write(writer)?;
+ }
+ Ok(())
+ }
+}
+
+impl Readable for Route {
+ fn read<R: ::std::io::Read>(reader: &mut R) -> Result<Route, DecodeError> {
+ let path_count: u64 = Readable::read(reader)?;
+ let mut paths = Vec::with_capacity(cmp::min(path_count, 128) as usize);
+ for _ in 0..path_count {
+ paths.push(Readable::read(reader)?);
+ }
+ Ok(Route { paths })
}
}
let short_channel_id = chan.short_channel_id.expect("first_hops should be filled in with usable channels, not pending ones");
if chan.remote_network_id == *target {
return Ok(Route {
- hops: vec![RouteHop {
+ paths: vec![vec![RouteHop {
pubkey: chan.remote_network_id,
node_features: NodeFeatures::with_known_relevant_init_flags(&chan.counterparty_features),
short_channel_id,
channel_features: ChannelFeatures::with_known_relevant_init_flags(&chan.counterparty_features),
fee_msat: final_value_msat,
cltv_expiry_delta: final_cltv,
- }],
+ }]],
});
}
first_hop_targets.insert(chan.remote_network_id, (short_channel_id, chan.counterparty_features.clone()));
}
res.last_mut().unwrap().fee_msat = final_value_msat;
res.last_mut().unwrap().cltv_expiry_delta = final_cltv;
- let route = Route { hops: res };
+ let route = Route { paths: vec![res] };
log_trace!(self, "Got route: {}", log_route!(route));
return Ok(route);
}
{ // Simple route to 3 via 2
let route = router.get_route(&node3, None, &Vec::new(), 100, 42).unwrap();
- assert_eq!(route.hops.len(), 2);
+ assert_eq!(route.paths[0].len(), 2);
- assert_eq!(route.hops[0].pubkey, node2);
- assert_eq!(route.hops[0].short_channel_id, 2);
- assert_eq!(route.hops[0].fee_msat, 100);
- assert_eq!(route.hops[0].cltv_expiry_delta, (4 << 8) | 1);
- assert_eq!(route.hops[0].node_features.le_flags(), &id_to_feature_flags!(2));
- assert_eq!(route.hops[0].channel_features.le_flags(), &id_to_feature_flags!(2));
+ assert_eq!(route.paths[0][0].pubkey, node2);
+ assert_eq!(route.paths[0][0].short_channel_id, 2);
+ assert_eq!(route.paths[0][0].fee_msat, 100);
+ assert_eq!(route.paths[0][0].cltv_expiry_delta, (4 << 8) | 1);
+ assert_eq!(route.paths[0][0].node_features.le_flags(), &id_to_feature_flags!(2));
+ assert_eq!(route.paths[0][0].channel_features.le_flags(), &id_to_feature_flags!(2));
- assert_eq!(route.hops[1].pubkey, node3);
- assert_eq!(route.hops[1].short_channel_id, 4);
- assert_eq!(route.hops[1].fee_msat, 100);
- assert_eq!(route.hops[1].cltv_expiry_delta, 42);
- assert_eq!(route.hops[1].node_features.le_flags(), &id_to_feature_flags!(3));
- assert_eq!(route.hops[1].channel_features.le_flags(), &id_to_feature_flags!(4));
+ assert_eq!(route.paths[0][1].pubkey, node3);
+ assert_eq!(route.paths[0][1].short_channel_id, 4);
+ assert_eq!(route.paths[0][1].fee_msat, 100);
+ assert_eq!(route.paths[0][1].cltv_expiry_delta, 42);
+ assert_eq!(route.paths[0][1].node_features.le_flags(), &id_to_feature_flags!(3));
+ assert_eq!(route.paths[0][1].channel_features.le_flags(), &id_to_feature_flags!(4));
}
{ // Disable channels 4 and 12 by requiring unknown feature bits
is_live: true,
}];
let route = router.get_route(&node3, Some(&our_chans), &Vec::new(), 100, 42).unwrap();
- assert_eq!(route.hops.len(), 2);
+ assert_eq!(route.paths[0].len(), 2);
- assert_eq!(route.hops[0].pubkey, node8);
- assert_eq!(route.hops[0].short_channel_id, 42);
- assert_eq!(route.hops[0].fee_msat, 200);
- assert_eq!(route.hops[0].cltv_expiry_delta, (13 << 8) | 1);
- assert_eq!(route.hops[0].node_features.le_flags(), &vec![0b11]); // it should also override our view of their features
- assert_eq!(route.hops[0].channel_features.le_flags(), &Vec::new()); // No feature flags will meet the relevant-to-channel conversion
+ assert_eq!(route.paths[0][0].pubkey, node8);
+ assert_eq!(route.paths[0][0].short_channel_id, 42);
+ assert_eq!(route.paths[0][0].fee_msat, 200);
+ assert_eq!(route.paths[0][0].cltv_expiry_delta, (13 << 8) | 1);
+ assert_eq!(route.paths[0][0].node_features.le_flags(), &vec![0b11]); // it should also override our view of their features
+ assert_eq!(route.paths[0][0].channel_features.le_flags(), &Vec::new()); // No feature flags will meet the relevant-to-channel conversion
- assert_eq!(route.hops[1].pubkey, node3);
- assert_eq!(route.hops[1].short_channel_id, 13);
- assert_eq!(route.hops[1].fee_msat, 100);
- assert_eq!(route.hops[1].cltv_expiry_delta, 42);
- assert_eq!(route.hops[1].node_features.le_flags(), &id_to_feature_flags!(3));
- assert_eq!(route.hops[1].channel_features.le_flags(), &id_to_feature_flags!(13));
+ assert_eq!(route.paths[0][1].pubkey, node3);
+ assert_eq!(route.paths[0][1].short_channel_id, 13);
+ assert_eq!(route.paths[0][1].fee_msat, 100);
+ assert_eq!(route.paths[0][1].cltv_expiry_delta, 42);
+ assert_eq!(route.paths[0][1].node_features.le_flags(), &id_to_feature_flags!(3));
+ assert_eq!(route.paths[0][1].channel_features.le_flags(), &id_to_feature_flags!(13));
}
{ // Re-enable channels 4 and 12 by wiping the unknown feature bits
is_live: true,
}];
let route = router.get_route(&node3, Some(&our_chans), &Vec::new(), 100, 42).unwrap();
- assert_eq!(route.hops.len(), 2);
+ assert_eq!(route.paths[0].len(), 2);
- assert_eq!(route.hops[0].pubkey, node8);
- assert_eq!(route.hops[0].short_channel_id, 42);
- assert_eq!(route.hops[0].fee_msat, 200);
- assert_eq!(route.hops[0].cltv_expiry_delta, (13 << 8) | 1);
- assert_eq!(route.hops[0].node_features.le_flags(), &vec![0b11]); // it should also override our view of their features
- assert_eq!(route.hops[0].channel_features.le_flags(), &Vec::new()); // No feature flags will meet the relevant-to-channel conversion
+ assert_eq!(route.paths[0][0].pubkey, node8);
+ assert_eq!(route.paths[0][0].short_channel_id, 42);
+ assert_eq!(route.paths[0][0].fee_msat, 200);
+ assert_eq!(route.paths[0][0].cltv_expiry_delta, (13 << 8) | 1);
+ assert_eq!(route.paths[0][0].node_features.le_flags(), &vec![0b11]); // it should also override our view of their features
+ assert_eq!(route.paths[0][0].channel_features.le_flags(), &Vec::new()); // No feature flags will meet the relevant-to-channel conversion
- assert_eq!(route.hops[1].pubkey, node3);
- assert_eq!(route.hops[1].short_channel_id, 13);
- assert_eq!(route.hops[1].fee_msat, 100);
- assert_eq!(route.hops[1].cltv_expiry_delta, 42);
- assert_eq!(route.hops[1].node_features.le_flags(), &id_to_feature_flags!(3));
- assert_eq!(route.hops[1].channel_features.le_flags(), &id_to_feature_flags!(13));
+ assert_eq!(route.paths[0][1].pubkey, node3);
+ assert_eq!(route.paths[0][1].short_channel_id, 13);
+ assert_eq!(route.paths[0][1].fee_msat, 100);
+ assert_eq!(route.paths[0][1].cltv_expiry_delta, 42);
+ assert_eq!(route.paths[0][1].node_features.le_flags(), &id_to_feature_flags!(3));
+ assert_eq!(route.paths[0][1].channel_features.le_flags(), &id_to_feature_flags!(13));
}
{ // Re-enable nodes 1, 2, and 8
{ // Route to 1 via 2 and 3 because our channel to 1 is disabled
let route = router.get_route(&node1, None, &Vec::new(), 100, 42).unwrap();
- assert_eq!(route.hops.len(), 3);
-
- assert_eq!(route.hops[0].pubkey, node2);
- assert_eq!(route.hops[0].short_channel_id, 2);
- assert_eq!(route.hops[0].fee_msat, 200);
- assert_eq!(route.hops[0].cltv_expiry_delta, (4 << 8) | 1);
- assert_eq!(route.hops[0].node_features.le_flags(), &id_to_feature_flags!(2));
- assert_eq!(route.hops[0].channel_features.le_flags(), &id_to_feature_flags!(2));
-
- assert_eq!(route.hops[1].pubkey, node3);
- assert_eq!(route.hops[1].short_channel_id, 4);
- assert_eq!(route.hops[1].fee_msat, 100);
- assert_eq!(route.hops[1].cltv_expiry_delta, (3 << 8) | 2);
- assert_eq!(route.hops[1].node_features.le_flags(), &id_to_feature_flags!(3));
- assert_eq!(route.hops[1].channel_features.le_flags(), &id_to_feature_flags!(4));
-
- assert_eq!(route.hops[2].pubkey, node1);
- assert_eq!(route.hops[2].short_channel_id, 3);
- assert_eq!(route.hops[2].fee_msat, 100);
- assert_eq!(route.hops[2].cltv_expiry_delta, 42);
- assert_eq!(route.hops[2].node_features.le_flags(), &id_to_feature_flags!(1));
- assert_eq!(route.hops[2].channel_features.le_flags(), &id_to_feature_flags!(3));
+ assert_eq!(route.paths[0].len(), 3);
+
+ assert_eq!(route.paths[0][0].pubkey, node2);
+ assert_eq!(route.paths[0][0].short_channel_id, 2);
+ assert_eq!(route.paths[0][0].fee_msat, 200);
+ assert_eq!(route.paths[0][0].cltv_expiry_delta, (4 << 8) | 1);
+ assert_eq!(route.paths[0][0].node_features.le_flags(), &id_to_feature_flags!(2));
+ assert_eq!(route.paths[0][0].channel_features.le_flags(), &id_to_feature_flags!(2));
+
+ assert_eq!(route.paths[0][1].pubkey, node3);
+ assert_eq!(route.paths[0][1].short_channel_id, 4);
+ assert_eq!(route.paths[0][1].fee_msat, 100);
+ assert_eq!(route.paths[0][1].cltv_expiry_delta, (3 << 8) | 2);
+ assert_eq!(route.paths[0][1].node_features.le_flags(), &id_to_feature_flags!(3));
+ assert_eq!(route.paths[0][1].channel_features.le_flags(), &id_to_feature_flags!(4));
+
+ assert_eq!(route.paths[0][2].pubkey, node1);
+ assert_eq!(route.paths[0][2].short_channel_id, 3);
+ assert_eq!(route.paths[0][2].fee_msat, 100);
+ assert_eq!(route.paths[0][2].cltv_expiry_delta, 42);
+ assert_eq!(route.paths[0][2].node_features.le_flags(), &id_to_feature_flags!(1));
+ assert_eq!(route.paths[0][2].channel_features.le_flags(), &id_to_feature_flags!(3));
}
{ // If we specify a channel to node8, that overrides our local channel view and that gets used
is_live: true,
}];
let route = router.get_route(&node3, Some(&our_chans), &Vec::new(), 100, 42).unwrap();
- assert_eq!(route.hops.len(), 2);
+ assert_eq!(route.paths[0].len(), 2);
- assert_eq!(route.hops[0].pubkey, node8);
- assert_eq!(route.hops[0].short_channel_id, 42);
- assert_eq!(route.hops[0].fee_msat, 200);
- assert_eq!(route.hops[0].cltv_expiry_delta, (13 << 8) | 1);
- assert_eq!(route.hops[0].node_features.le_flags(), &vec![0b11]);
- assert_eq!(route.hops[0].channel_features.le_flags(), &Vec::new()); // No feature flags will meet the relevant-to-channel conversion
+ assert_eq!(route.paths[0][0].pubkey, node8);
+ assert_eq!(route.paths[0][0].short_channel_id, 42);
+ assert_eq!(route.paths[0][0].fee_msat, 200);
+ assert_eq!(route.paths[0][0].cltv_expiry_delta, (13 << 8) | 1);
+ assert_eq!(route.paths[0][0].node_features.le_flags(), &vec![0b11]);
+ assert_eq!(route.paths[0][0].channel_features.le_flags(), &Vec::new()); // No feature flags will meet the relevant-to-channel conversion
- assert_eq!(route.hops[1].pubkey, node3);
- assert_eq!(route.hops[1].short_channel_id, 13);
- assert_eq!(route.hops[1].fee_msat, 100);
- assert_eq!(route.hops[1].cltv_expiry_delta, 42);
- assert_eq!(route.hops[1].node_features.le_flags(), &id_to_feature_flags!(3));
- assert_eq!(route.hops[1].channel_features.le_flags(), &id_to_feature_flags!(13));
+ assert_eq!(route.paths[0][1].pubkey, node3);
+ assert_eq!(route.paths[0][1].short_channel_id, 13);
+ assert_eq!(route.paths[0][1].fee_msat, 100);
+ assert_eq!(route.paths[0][1].cltv_expiry_delta, 42);
+ assert_eq!(route.paths[0][1].node_features.le_flags(), &id_to_feature_flags!(3));
+ assert_eq!(route.paths[0][1].channel_features.le_flags(), &id_to_feature_flags!(13));
}
let mut last_hops = vec!(RouteHint {
{ // Simple test across 2, 3, 5, and 4 via a last_hop channel
let route = router.get_route(&node7, None, &last_hops, 100, 42).unwrap();
- assert_eq!(route.hops.len(), 5);
-
- assert_eq!(route.hops[0].pubkey, node2);
- assert_eq!(route.hops[0].short_channel_id, 2);
- assert_eq!(route.hops[0].fee_msat, 100);
- assert_eq!(route.hops[0].cltv_expiry_delta, (4 << 8) | 1);
- assert_eq!(route.hops[0].node_features.le_flags(), &id_to_feature_flags!(2));
- assert_eq!(route.hops[0].channel_features.le_flags(), &id_to_feature_flags!(2));
-
- assert_eq!(route.hops[1].pubkey, node3);
- assert_eq!(route.hops[1].short_channel_id, 4);
- assert_eq!(route.hops[1].fee_msat, 0);
- assert_eq!(route.hops[1].cltv_expiry_delta, (6 << 8) | 1);
- assert_eq!(route.hops[1].node_features.le_flags(), &id_to_feature_flags!(3));
- assert_eq!(route.hops[1].channel_features.le_flags(), &id_to_feature_flags!(4));
-
- assert_eq!(route.hops[2].pubkey, node5);
- assert_eq!(route.hops[2].short_channel_id, 6);
- assert_eq!(route.hops[2].fee_msat, 0);
- assert_eq!(route.hops[2].cltv_expiry_delta, (11 << 8) | 1);
- assert_eq!(route.hops[2].node_features.le_flags(), &id_to_feature_flags!(5));
- assert_eq!(route.hops[2].channel_features.le_flags(), &id_to_feature_flags!(6));
-
- assert_eq!(route.hops[3].pubkey, node4);
- assert_eq!(route.hops[3].short_channel_id, 11);
- assert_eq!(route.hops[3].fee_msat, 0);
- assert_eq!(route.hops[3].cltv_expiry_delta, (8 << 8) | 1);
+ assert_eq!(route.paths[0].len(), 5);
+
+ assert_eq!(route.paths[0][0].pubkey, node2);
+ assert_eq!(route.paths[0][0].short_channel_id, 2);
+ assert_eq!(route.paths[0][0].fee_msat, 100);
+ assert_eq!(route.paths[0][0].cltv_expiry_delta, (4 << 8) | 1);
+ assert_eq!(route.paths[0][0].node_features.le_flags(), &id_to_feature_flags!(2));
+ assert_eq!(route.paths[0][0].channel_features.le_flags(), &id_to_feature_flags!(2));
+
+ assert_eq!(route.paths[0][1].pubkey, node3);
+ assert_eq!(route.paths[0][1].short_channel_id, 4);
+ assert_eq!(route.paths[0][1].fee_msat, 0);
+ assert_eq!(route.paths[0][1].cltv_expiry_delta, (6 << 8) | 1);
+ assert_eq!(route.paths[0][1].node_features.le_flags(), &id_to_feature_flags!(3));
+ assert_eq!(route.paths[0][1].channel_features.le_flags(), &id_to_feature_flags!(4));
+
+ assert_eq!(route.paths[0][2].pubkey, node5);
+ assert_eq!(route.paths[0][2].short_channel_id, 6);
+ assert_eq!(route.paths[0][2].fee_msat, 0);
+ assert_eq!(route.paths[0][2].cltv_expiry_delta, (11 << 8) | 1);
+ assert_eq!(route.paths[0][2].node_features.le_flags(), &id_to_feature_flags!(5));
+ assert_eq!(route.paths[0][2].channel_features.le_flags(), &id_to_feature_flags!(6));
+
+ assert_eq!(route.paths[0][3].pubkey, node4);
+ assert_eq!(route.paths[0][3].short_channel_id, 11);
+ assert_eq!(route.paths[0][3].fee_msat, 0);
+ assert_eq!(route.paths[0][3].cltv_expiry_delta, (8 << 8) | 1);
// If we have a peer in the node map, we'll use their features here since we don't have
// a way of figuring out their features from the invoice:
- assert_eq!(route.hops[3].node_features.le_flags(), &id_to_feature_flags!(4));
- assert_eq!(route.hops[3].channel_features.le_flags(), &id_to_feature_flags!(11));
+ assert_eq!(route.paths[0][3].node_features.le_flags(), &id_to_feature_flags!(4));
+ assert_eq!(route.paths[0][3].channel_features.le_flags(), &id_to_feature_flags!(11));
- assert_eq!(route.hops[4].pubkey, node7);
- assert_eq!(route.hops[4].short_channel_id, 8);
- assert_eq!(route.hops[4].fee_msat, 100);
- assert_eq!(route.hops[4].cltv_expiry_delta, 42);
- assert_eq!(route.hops[4].node_features.le_flags(), &Vec::new()); // We dont pass flags in from invoices yet
- assert_eq!(route.hops[4].channel_features.le_flags(), &Vec::new()); // We can't learn any flags from invoices, sadly
+ assert_eq!(route.paths[0][4].pubkey, node7);
+ assert_eq!(route.paths[0][4].short_channel_id, 8);
+ assert_eq!(route.paths[0][4].fee_msat, 100);
+ assert_eq!(route.paths[0][4].cltv_expiry_delta, 42);
+ assert_eq!(route.paths[0][4].node_features.le_flags(), &Vec::new()); // We dont pass flags in from invoices yet
+ assert_eq!(route.paths[0][4].channel_features.le_flags(), &Vec::new()); // We can't learn any flags from invoices, sadly
}
{ // Simple test with outbound channel to 4 to test that last_hops and first_hops connect
is_live: true,
}];
let route = router.get_route(&node7, Some(&our_chans), &last_hops, 100, 42).unwrap();
- assert_eq!(route.hops.len(), 2);
+ assert_eq!(route.paths[0].len(), 2);
- assert_eq!(route.hops[0].pubkey, node4);
- assert_eq!(route.hops[0].short_channel_id, 42);
- assert_eq!(route.hops[0].fee_msat, 0);
- assert_eq!(route.hops[0].cltv_expiry_delta, (8 << 8) | 1);
- assert_eq!(route.hops[0].node_features.le_flags(), &vec![0b11]);
- assert_eq!(route.hops[0].channel_features.le_flags(), &Vec::new()); // No feature flags will meet the relevant-to-channel conversion
+ assert_eq!(route.paths[0][0].pubkey, node4);
+ assert_eq!(route.paths[0][0].short_channel_id, 42);
+ assert_eq!(route.paths[0][0].fee_msat, 0);
+ assert_eq!(route.paths[0][0].cltv_expiry_delta, (8 << 8) | 1);
+ assert_eq!(route.paths[0][0].node_features.le_flags(), &vec![0b11]);
+ assert_eq!(route.paths[0][0].channel_features.le_flags(), &Vec::new()); // No feature flags will meet the relevant-to-channel conversion
- assert_eq!(route.hops[1].pubkey, node7);
- assert_eq!(route.hops[1].short_channel_id, 8);
- assert_eq!(route.hops[1].fee_msat, 100);
- assert_eq!(route.hops[1].cltv_expiry_delta, 42);
- assert_eq!(route.hops[1].node_features.le_flags(), &Vec::new()); // We dont pass flags in from invoices yet
- assert_eq!(route.hops[1].channel_features.le_flags(), &Vec::new()); // We can't learn any flags from invoices, sadly
+ assert_eq!(route.paths[0][1].pubkey, node7);
+ assert_eq!(route.paths[0][1].short_channel_id, 8);
+ assert_eq!(route.paths[0][1].fee_msat, 100);
+ assert_eq!(route.paths[0][1].cltv_expiry_delta, 42);
+ assert_eq!(route.paths[0][1].node_features.le_flags(), &Vec::new()); // We dont pass flags in from invoices yet
+ assert_eq!(route.paths[0][1].channel_features.le_flags(), &Vec::new()); // We can't learn any flags from invoices, sadly
}
last_hops[0].fee_base_msat = 1000;
{ // Revert to via 6 as the fee on 8 goes up
let route = router.get_route(&node7, None, &last_hops, 100, 42).unwrap();
- assert_eq!(route.hops.len(), 4);
-
- assert_eq!(route.hops[0].pubkey, node2);
- assert_eq!(route.hops[0].short_channel_id, 2);
- assert_eq!(route.hops[0].fee_msat, 200); // fee increased as its % of value transferred across node
- assert_eq!(route.hops[0].cltv_expiry_delta, (4 << 8) | 1);
- assert_eq!(route.hops[0].node_features.le_flags(), &id_to_feature_flags!(2));
- assert_eq!(route.hops[0].channel_features.le_flags(), &id_to_feature_flags!(2));
-
- assert_eq!(route.hops[1].pubkey, node3);
- assert_eq!(route.hops[1].short_channel_id, 4);
- assert_eq!(route.hops[1].fee_msat, 100);
- assert_eq!(route.hops[1].cltv_expiry_delta, (7 << 8) | 1);
- assert_eq!(route.hops[1].node_features.le_flags(), &id_to_feature_flags!(3));
- assert_eq!(route.hops[1].channel_features.le_flags(), &id_to_feature_flags!(4));
-
- assert_eq!(route.hops[2].pubkey, node6);
- assert_eq!(route.hops[2].short_channel_id, 7);
- assert_eq!(route.hops[2].fee_msat, 0);
- assert_eq!(route.hops[2].cltv_expiry_delta, (10 << 8) | 1);
+ assert_eq!(route.paths[0].len(), 4);
+
+ assert_eq!(route.paths[0][0].pubkey, node2);
+ assert_eq!(route.paths[0][0].short_channel_id, 2);
+ assert_eq!(route.paths[0][0].fee_msat, 200); // fee increased as its % of value transferred across node
+ assert_eq!(route.paths[0][0].cltv_expiry_delta, (4 << 8) | 1);
+ assert_eq!(route.paths[0][0].node_features.le_flags(), &id_to_feature_flags!(2));
+ assert_eq!(route.paths[0][0].channel_features.le_flags(), &id_to_feature_flags!(2));
+
+ assert_eq!(route.paths[0][1].pubkey, node3);
+ assert_eq!(route.paths[0][1].short_channel_id, 4);
+ assert_eq!(route.paths[0][1].fee_msat, 100);
+ assert_eq!(route.paths[0][1].cltv_expiry_delta, (7 << 8) | 1);
+ assert_eq!(route.paths[0][1].node_features.le_flags(), &id_to_feature_flags!(3));
+ assert_eq!(route.paths[0][1].channel_features.le_flags(), &id_to_feature_flags!(4));
+
+ assert_eq!(route.paths[0][2].pubkey, node6);
+ assert_eq!(route.paths[0][2].short_channel_id, 7);
+ assert_eq!(route.paths[0][2].fee_msat, 0);
+ assert_eq!(route.paths[0][2].cltv_expiry_delta, (10 << 8) | 1);
// If we have a peer in the node map, we'll use their features here since we don't have
// a way of figuring out their features from the invoice:
- assert_eq!(route.hops[2].node_features.le_flags(), &id_to_feature_flags!(6));
- assert_eq!(route.hops[2].channel_features.le_flags(), &id_to_feature_flags!(7));
+ assert_eq!(route.paths[0][2].node_features.le_flags(), &id_to_feature_flags!(6));
+ assert_eq!(route.paths[0][2].channel_features.le_flags(), &id_to_feature_flags!(7));
- assert_eq!(route.hops[3].pubkey, node7);
- assert_eq!(route.hops[3].short_channel_id, 10);
- assert_eq!(route.hops[3].fee_msat, 100);
- assert_eq!(route.hops[3].cltv_expiry_delta, 42);
- assert_eq!(route.hops[3].node_features.le_flags(), &Vec::new()); // We dont pass flags in from invoices yet
- assert_eq!(route.hops[3].channel_features.le_flags(), &Vec::new()); // We can't learn any flags from invoices, sadly
+ assert_eq!(route.paths[0][3].pubkey, node7);
+ assert_eq!(route.paths[0][3].short_channel_id, 10);
+ assert_eq!(route.paths[0][3].fee_msat, 100);
+ assert_eq!(route.paths[0][3].cltv_expiry_delta, 42);
+ assert_eq!(route.paths[0][3].node_features.le_flags(), &Vec::new()); // We dont pass flags in from invoices yet
+ assert_eq!(route.paths[0][3].channel_features.le_flags(), &Vec::new()); // We can't learn any flags from invoices, sadly
}
{ // ...but still use 8 for larger payments as 6 has a variable feerate
let route = router.get_route(&node7, None, &last_hops, 2000, 42).unwrap();
- assert_eq!(route.hops.len(), 5);
-
- assert_eq!(route.hops[0].pubkey, node2);
- assert_eq!(route.hops[0].short_channel_id, 2);
- assert_eq!(route.hops[0].fee_msat, 3000);
- assert_eq!(route.hops[0].cltv_expiry_delta, (4 << 8) | 1);
- assert_eq!(route.hops[0].node_features.le_flags(), &id_to_feature_flags!(2));
- assert_eq!(route.hops[0].channel_features.le_flags(), &id_to_feature_flags!(2));
-
- assert_eq!(route.hops[1].pubkey, node3);
- assert_eq!(route.hops[1].short_channel_id, 4);
- assert_eq!(route.hops[1].fee_msat, 0);
- assert_eq!(route.hops[1].cltv_expiry_delta, (6 << 8) | 1);
- assert_eq!(route.hops[1].node_features.le_flags(), &id_to_feature_flags!(3));
- assert_eq!(route.hops[1].channel_features.le_flags(), &id_to_feature_flags!(4));
-
- assert_eq!(route.hops[2].pubkey, node5);
- assert_eq!(route.hops[2].short_channel_id, 6);
- assert_eq!(route.hops[2].fee_msat, 0);
- assert_eq!(route.hops[2].cltv_expiry_delta, (11 << 8) | 1);
- assert_eq!(route.hops[2].node_features.le_flags(), &id_to_feature_flags!(5));
- assert_eq!(route.hops[2].channel_features.le_flags(), &id_to_feature_flags!(6));
-
- assert_eq!(route.hops[3].pubkey, node4);
- assert_eq!(route.hops[3].short_channel_id, 11);
- assert_eq!(route.hops[3].fee_msat, 1000);
- assert_eq!(route.hops[3].cltv_expiry_delta, (8 << 8) | 1);
+ assert_eq!(route.paths[0].len(), 5);
+
+ assert_eq!(route.paths[0][0].pubkey, node2);
+ assert_eq!(route.paths[0][0].short_channel_id, 2);
+ assert_eq!(route.paths[0][0].fee_msat, 3000);
+ assert_eq!(route.paths[0][0].cltv_expiry_delta, (4 << 8) | 1);
+ assert_eq!(route.paths[0][0].node_features.le_flags(), &id_to_feature_flags!(2));
+ assert_eq!(route.paths[0][0].channel_features.le_flags(), &id_to_feature_flags!(2));
+
+ assert_eq!(route.paths[0][1].pubkey, node3);
+ assert_eq!(route.paths[0][1].short_channel_id, 4);
+ assert_eq!(route.paths[0][1].fee_msat, 0);
+ assert_eq!(route.paths[0][1].cltv_expiry_delta, (6 << 8) | 1);
+ assert_eq!(route.paths[0][1].node_features.le_flags(), &id_to_feature_flags!(3));
+ assert_eq!(route.paths[0][1].channel_features.le_flags(), &id_to_feature_flags!(4));
+
+ assert_eq!(route.paths[0][2].pubkey, node5);
+ assert_eq!(route.paths[0][2].short_channel_id, 6);
+ assert_eq!(route.paths[0][2].fee_msat, 0);
+ assert_eq!(route.paths[0][2].cltv_expiry_delta, (11 << 8) | 1);
+ assert_eq!(route.paths[0][2].node_features.le_flags(), &id_to_feature_flags!(5));
+ assert_eq!(route.paths[0][2].channel_features.le_flags(), &id_to_feature_flags!(6));
+
+ assert_eq!(route.paths[0][3].pubkey, node4);
+ assert_eq!(route.paths[0][3].short_channel_id, 11);
+ assert_eq!(route.paths[0][3].fee_msat, 1000);
+ assert_eq!(route.paths[0][3].cltv_expiry_delta, (8 << 8) | 1);
// If we have a peer in the node map, we'll use their features here since we don't have
// a way of figuring out their features from the invoice:
- assert_eq!(route.hops[3].node_features.le_flags(), &id_to_feature_flags!(4));
- assert_eq!(route.hops[3].channel_features.le_flags(), &id_to_feature_flags!(11));
-
- assert_eq!(route.hops[4].pubkey, node7);
- assert_eq!(route.hops[4].short_channel_id, 8);
- assert_eq!(route.hops[4].fee_msat, 2000);
- assert_eq!(route.hops[4].cltv_expiry_delta, 42);
- assert_eq!(route.hops[4].node_features.le_flags(), &Vec::new()); // We dont pass flags in from invoices yet
- assert_eq!(route.hops[4].channel_features.le_flags(), &Vec::new()); // We can't learn any flags from invoices, sadly
+ assert_eq!(route.paths[0][3].node_features.le_flags(), &id_to_feature_flags!(4));
+ assert_eq!(route.paths[0][3].channel_features.le_flags(), &id_to_feature_flags!(11));
+
+ assert_eq!(route.paths[0][4].pubkey, node7);
+ assert_eq!(route.paths[0][4].short_channel_id, 8);
+ assert_eq!(route.paths[0][4].fee_msat, 2000);
+ assert_eq!(route.paths[0][4].cltv_expiry_delta, 42);
+ assert_eq!(route.paths[0][4].node_features.le_flags(), &Vec::new()); // We dont pass flags in from invoices yet
+ assert_eq!(route.paths[0][4].channel_features.le_flags(), &Vec::new()); // We can't learn any flags from invoices, sadly
}
{ // Test Router serialization/deserialization
pub(crate) struct DebugRoute<'a>(pub &'a Route);
impl<'a> std::fmt::Display for DebugRoute<'a> {
fn fmt(&self, f: &mut std::fmt::Formatter) -> Result<(), std::fmt::Error> {
- for h in self.0.hops.iter() {
- write!(f, "node_id: {}, short_channel_id: {}, fee_msat: {}, cltv_expiry_delta: {}\n", log_pubkey!(h.pubkey), h.short_channel_id, h.fee_msat, h.cltv_expiry_delta)?;
+ for (idx, p) in self.0.paths.iter().enumerate() {
+ write!(f, "path {}:\n", idx)?;
+ for h in p.iter() {
+ write!(f, " node_id: {}, short_channel_id: {}, fee_msat: {}, cltv_expiry_delta: {}\n", log_pubkey!(h.pubkey), h.short_channel_id, h.fee_msat, h.cltv_expiry_delta)?;
+ }
}
Ok(())
}