mirror of
https://github.com/lightningdevkit/rust-lightning.git
synced 2025-02-24 15:02:20 +01:00
Expand the Route object to include multiple paths.
Rather big diff, but its all mechanical and doesn't introduce any new features.
This commit is contained in:
parent
d39f25839a
commit
5260e81033
7 changed files with 319 additions and 277 deletions
|
@ -409,14 +409,14 @@ pub fn do_test(data: &[u8]) {
|
|||
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!();
|
||||
|
@ -426,7 +426,7 @@ pub fn do_test(data: &[u8]) {
|
|||
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,
|
||||
|
@ -440,7 +440,7 @@ pub fn do_test(data: &[u8]) {
|
|||
channel_features: ChannelFeatures::empty(),
|
||||
fee_msat: 5000000,
|
||||
cltv_expiry_delta: 200,
|
||||
}],
|
||||
}]],
|
||||
}, PaymentHash(payment_hash.into_inner()), &None) {
|
||||
// Probably ran out of funds
|
||||
test_return!();
|
||||
|
|
|
@ -30,7 +30,7 @@ use chain::transaction::OutPoint;
|
|||
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};
|
||||
|
@ -136,7 +136,7 @@ struct ClaimableHTLC {
|
|||
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
|
||||
|
@ -147,7 +147,7 @@ pub(super) enum HTLCSource {
|
|||
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,
|
||||
}
|
||||
|
@ -1231,13 +1231,16 @@ impl<ChanSigner: ChannelKeys, M: Deref, T: Deref, K: Deref, F: Deref> ChannelMan
|
|||
/// 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"});
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1245,9 +1248,9 @@ impl<ChanSigner: ChannelKeys, M: Deref, T: Deref, K: Deref, F: Deref> ChannelMan
|
|||
|
||||
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"});
|
||||
}
|
||||
|
@ -1257,7 +1260,7 @@ impl<ChanSigner: ChannelKeys, M: Deref, T: Deref, K: Deref, F: Deref> ChannelMan
|
|||
|
||||
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(),
|
||||
};
|
||||
|
@ -1265,14 +1268,14 @@ impl<ChanSigner: ChannelKeys, M: Deref, T: Deref, K: Deref, F: Deref> ChannelMan
|
|||
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)
|
||||
|
@ -1288,7 +1291,7 @@ impl<ChanSigner: ChannelKeys, M: Deref, T: Deref, K: Deref, F: Deref> ChannelMan
|
|||
}
|
||||
|
||||
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(),
|
||||
|
@ -1305,7 +1308,7 @@ impl<ChanSigner: ChannelKeys, M: Deref, T: Deref, K: Deref, F: Deref> ChannelMan
|
|||
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 }) }
|
||||
}
|
||||
|
@ -1750,7 +1753,7 @@ impl<ChanSigner: ChannelKeys, M: Deref, T: Deref, K: Deref, F: Deref> ChannelMan
|
|||
//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 {
|
||||
|
@ -1792,7 +1795,7 @@ impl<ChanSigner: ChannelKeys, M: Deref, T: Deref, K: Deref, F: Deref> ChannelMan
|
|||
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),
|
||||
}
|
||||
|
@ -1856,9 +1859,19 @@ impl<ChanSigner: ChannelKeys, M: Deref, T: Deref, K: Deref, F: Deref> ChannelMan
|
|||
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);
|
||||
|
@ -1867,9 +1880,10 @@ impl<ChanSigner: ChannelKeys, M: Deref, T: Deref, K: Deref, F: Deref> ChannelMan
|
|||
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) {
|
||||
|
@ -3271,9 +3285,9 @@ impl Writeable for HTLCSource {
|
|||
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)?;
|
||||
}
|
||||
|
@ -3287,7 +3301,7 @@ impl Readable for HTLCSource {
|
|||
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)?,
|
||||
}),
|
||||
|
|
|
@ -897,8 +897,9 @@ pub const TEST_FINAL_CLTV: u32 = 32;
|
|||
|
||||
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());
|
||||
}
|
||||
|
||||
|
@ -907,8 +908,9 @@ pub fn route_payment<'a, 'b, 'c>(origin_node: &Node<'a, 'b, 'c>, expected_route:
|
|||
|
||||
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());
|
||||
}
|
||||
|
||||
|
|
|
@ -1226,7 +1226,7 @@ fn fake_network_test() {
|
|||
});
|
||||
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 {
|
||||
|
@ -1255,7 +1255,7 @@ fn fake_network_test() {
|
|||
});
|
||||
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);
|
||||
|
@ -1562,7 +1562,7 @@ fn do_channel_reserve_test(test_recv: bool) {
|
|||
// 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"),
|
||||
|
@ -1651,8 +1651,8 @@ fn do_channel_reserve_test(test_recv: bool) {
|
|||
}).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,
|
||||
|
@ -3038,8 +3038,8 @@ fn fail_backward_pending_htlc_upon_channel_failure() {
|
|||
};
|
||||
|
||||
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.
|
||||
|
@ -5470,8 +5470,8 @@ fn test_onion_failure() {
|
|||
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));
|
||||
|
@ -5486,8 +5486,8 @@ fn test_onion_failure() {
|
|||
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));
|
||||
|
@ -5507,57 +5507,57 @@ fn test_onion_failure() {
|
|||
}, |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);
|
||||
|
@ -5572,7 +5572,7 @@ fn test_onion_failure() {
|
|||
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()}));
|
||||
|
||||
|
@ -5580,7 +5580,7 @@ fn test_onion_failure() {
|
|||
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}));
|
||||
|
@ -5589,20 +5589,20 @@ fn test_onion_failure() {
|
|||
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
|
||||
|
@ -5670,9 +5670,9 @@ fn test_onion_failure() {
|
|||
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;
|
||||
|
@ -5762,7 +5762,7 @@ fn test_update_add_htlc_bolt2_sender_value_below_minimum_msat() {
|
|||
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);
|
||||
|
||||
|
@ -5786,7 +5786,7 @@ fn test_update_add_htlc_bolt2_sender_zero_value_msat() {
|
|||
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);
|
||||
|
||||
|
@ -5992,8 +5992,8 @@ fn test_update_add_htlc_bolt2_receiver_check_max_htlc_limit() {
|
|||
}).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 {
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
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};
|
||||
|
@ -63,11 +63,11 @@ pub(super) fn gen_ammag_from_shared_secret(shared_secret: &[u8]) -> [u8; 32] {
|
|||
|
||||
// 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();
|
||||
|
@ -87,10 +87,10 @@ pub(super) fn construct_onion_keys_callback<T: secp256k1::Signing, FType: FnMut(
|
|||
}
|
||||
|
||||
// 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 {
|
||||
|
@ -108,13 +108,13 @@ pub(super) fn construct_onion_keys<T: secp256k1::Signing>(secp_ctx: &Secp256k1<T
|
|||
}
|
||||
|
||||
/// 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).
|
||||
|
@ -318,7 +318,7 @@ pub(super) fn build_first_hop_failure_packet(shared_secret: &[u8], failure_type:
|
|||
/// 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;
|
||||
|
@ -326,7 +326,7 @@ pub(super) fn process_onion_failure<T: secp256k1::Signing>(secp_ctx: &Secp256k1<
|
|||
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; }
|
||||
|
||||
|
@ -341,7 +341,7 @@ pub(super) fn process_onion_failure<T: secp256k1::Signing>(secp_ctx: &Secp256k1<
|
|||
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[..]);
|
||||
|
@ -374,7 +374,7 @@ pub(super) fn process_onion_failure<T: secp256k1::Signing>(secp_ctx: &Secp256k1<
|
|||
}
|
||||
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,
|
||||
})};
|
||||
}
|
||||
|
@ -485,7 +485,7 @@ mod tests {
|
|||
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(),
|
||||
|
@ -511,13 +511,13 @@ mod tests {
|
|||
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
|
||||
}
|
||||
|
||||
|
|
|
@ -47,19 +47,10 @@ pub struct RouteHop {
|
|||
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)?;
|
||||
|
@ -71,8 +62,8 @@ impl Writeable for Route {
|
|||
}
|
||||
}
|
||||
|
||||
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 {
|
||||
|
@ -85,9 +76,41 @@ impl Readable for Route {
|
|||
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 })
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -868,14 +891,14 @@ impl Router {
|
|||
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()));
|
||||
|
@ -1032,7 +1055,7 @@ impl Router {
|
|||
}
|
||||
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);
|
||||
}
|
||||
|
@ -1497,21 +1520,21 @@ mod tests {
|
|||
|
||||
{ // 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
|
||||
|
@ -1539,21 +1562,21 @@ mod tests {
|
|||
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
|
||||
|
@ -1588,21 +1611,21 @@ mod tests {
|
|||
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
|
||||
|
@ -1618,28 +1641,28 @@ mod tests {
|
|||
|
||||
{ // 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.paths[0].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.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.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.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.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][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
|
||||
|
@ -1655,21 +1678,21 @@ mod tests {
|
|||
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 {
|
||||
|
@ -1697,44 +1720,44 @@ mod tests {
|
|||
|
||||
{ // 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.paths[0].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.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, 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.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.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.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.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][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
|
||||
|
@ -1750,100 +1773,100 @@ mod tests {
|
|||
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.paths[0].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.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.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.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.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][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.paths[0].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.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.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.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.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.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.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][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.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, 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][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
|
||||
|
|
|
@ -73,8 +73,11 @@ macro_rules! log_funding_info {
|
|||
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(())
|
||||
}
|
||||
|
|
Loading…
Add table
Reference in a new issue