Merge pull request #3026 from valentinewallace/2024-04-limit-path-len

Limit payment path length based on `payment_metadata`, custom TLVs, etc.
This commit is contained in:
Matt Corallo 2024-05-21 07:19:37 -07:00 committed by GitHub
commit 37bf61c49b
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
15 changed files with 697 additions and 159 deletions

View file

@ -282,9 +282,10 @@ fn do_forward_checks_failure(check: ForwardCheckFail, intro_fails: bool) {
let amt_msat = 5000;
let (_, payment_hash, payment_secret) = get_payment_preimage_hash(&nodes[3], Some(amt_msat), None);
let route_params = get_blinded_route_parameters(amt_msat, payment_secret, 1, 1_0000_0000,
let mut route_params = get_blinded_route_parameters(amt_msat, payment_secret, 1, 1_0000_0000,
nodes.iter().skip(1).map(|n| n.node.get_our_node_id()).collect(),
&[&chan_upd_1_2, &chan_upd_2_3], &chanmon_cfgs[3].keys_manager);
route_params.payment_params.max_path_length = 18;
let route = get_route(&nodes[0], &route_params).unwrap();
node_cfgs[0].router.expect_find_route(route_params.clone(), Ok(route.clone()));
@ -298,11 +299,12 @@ fn do_forward_checks_failure(check: ForwardCheckFail, intro_fails: bool) {
$update_add.cltv_expiry = 10; // causes outbound CLTV expiry to underflow
},
ForwardCheckFail::ForwardPayloadEncodedAsReceive => {
let recipient_onion_fields = RecipientOnionFields::spontaneous_empty();
let session_priv = SecretKey::from_slice(&[3; 32]).unwrap();
let onion_keys = onion_utils::construct_onion_keys(&Secp256k1::new(), &route.paths[0], &session_priv).unwrap();
let cur_height = nodes[0].best_block_info().1;
let (mut onion_payloads, ..) = onion_utils::build_onion_payloads(
&route.paths[0], amt_msat, RecipientOnionFields::spontaneous_empty(), cur_height, &None).unwrap();
&route.paths[0], amt_msat, &recipient_onion_fields, cur_height, &None).unwrap();
// Remove the receive payload so the blinded forward payload is encoded as a final payload
// (i.e. next_hop_hmac == [0; 32])
onion_payloads.pop();
@ -875,8 +877,9 @@ fn do_multi_hop_receiver_fail(check: ReceiveCheckFail) {
let session_priv = SecretKey::from_slice(&session_priv).unwrap();
let mut onion_keys = onion_utils::construct_onion_keys(&Secp256k1::new(), &route.paths[0], &session_priv).unwrap();
let cur_height = nodes[0].best_block_info().1;
let recipient_onion_fields = RecipientOnionFields::spontaneous_empty();
let (mut onion_payloads, ..) = onion_utils::build_onion_payloads(
&route.paths[0], amt_msat, RecipientOnionFields::spontaneous_empty(), cur_height, &None).unwrap();
&route.paths[0], amt_msat, &recipient_onion_fields, cur_height, &None).unwrap();
let update_add = &mut payment_event_1_2.msgs[0];
onion_payloads.last_mut().map(|p| {

View file

@ -4084,8 +4084,8 @@ where
pub(crate) fn test_send_payment_along_path(&self, path: &Path, payment_hash: &PaymentHash, recipient_onion: RecipientOnionFields, total_value: u64, cur_height: u32, payment_id: PaymentId, keysend_preimage: &Option<PaymentPreimage>, session_priv_bytes: [u8; 32]) -> Result<(), APIError> {
let _lck = self.total_consistency_lock.read().unwrap();
self.send_payment_along_path(SendAlongPathArgs {
path, payment_hash, recipient_onion, total_value, cur_height, payment_id, keysend_preimage,
session_priv_bytes
path, payment_hash, recipient_onion: &recipient_onion, total_value,
cur_height, payment_id, keysend_preimage, session_priv_bytes
})
}

View file

@ -2535,6 +2535,7 @@ pub struct PassAlongPathArgs<'a, 'b, 'c, 'd> {
pub expected_preimage: Option<PaymentPreimage>,
pub is_probe: bool,
pub custom_tlvs: Vec<(u64, Vec<u8>)>,
pub payment_metadata: Option<Vec<u8>>,
}
impl<'a, 'b, 'c, 'd> PassAlongPathArgs<'a, 'b, 'c, 'd> {
@ -2545,7 +2546,7 @@ impl<'a, 'b, 'c, 'd> PassAlongPathArgs<'a, 'b, 'c, 'd> {
Self {
origin_node, expected_path, recv_value, payment_hash, payment_secret: None, event,
payment_claimable_expected: true, clear_recipient_events: true, expected_preimage: None,
is_probe: false, custom_tlvs: Vec::new(),
is_probe: false, custom_tlvs: Vec::new(), payment_metadata: None,
}
}
pub fn without_clearing_recipient_events(mut self) -> Self {
@ -2573,13 +2574,17 @@ impl<'a, 'b, 'c, 'd> PassAlongPathArgs<'a, 'b, 'c, 'd> {
self.custom_tlvs = custom_tlvs;
self
}
pub fn with_payment_metadata(mut self, payment_metadata: Vec<u8>) -> Self {
self.payment_metadata = Some(payment_metadata);
self
}
}
pub fn do_pass_along_path<'a, 'b, 'c>(args: PassAlongPathArgs) -> Option<Event> {
let PassAlongPathArgs {
origin_node, expected_path, recv_value, payment_hash: our_payment_hash,
payment_secret: our_payment_secret, event: ev, payment_claimable_expected,
clear_recipient_events, expected_preimage, is_probe, custom_tlvs
clear_recipient_events, expected_preimage, is_probe, custom_tlvs, payment_metadata,
} = args;
let mut payment_event = SendEvent::from_event(ev);
@ -2613,6 +2618,7 @@ pub fn do_pass_along_path<'a, 'b, 'c>(args: PassAlongPathArgs) -> Option<Event>
assert_eq!(node.node.get_our_node_id(), receiver_node_id.unwrap());
assert!(onion_fields.is_some());
assert_eq!(onion_fields.as_ref().unwrap().custom_tlvs, custom_tlvs);
assert_eq!(onion_fields.as_ref().unwrap().payment_metadata, payment_metadata);
match &purpose {
PaymentPurpose::Bolt11InvoicePayment { payment_preimage, payment_secret, .. } => {
assert_eq!(expected_preimage, *payment_preimage);

View file

@ -1401,8 +1401,9 @@ fn test_fee_spike_violation_fails_htlc() {
let cur_height = nodes[1].node.best_block.read().unwrap().height + 1;
let onion_keys = onion_utils::construct_onion_keys(&secp_ctx, &route.paths[0], &session_priv).unwrap();
let recipient_onion_fields = RecipientOnionFields::secret_only(payment_secret);
let (onion_payloads, htlc_msat, htlc_cltv) = onion_utils::build_onion_payloads(&route.paths[0],
3460001, RecipientOnionFields::secret_only(payment_secret), cur_height, &None).unwrap();
3460001, &recipient_onion_fields, cur_height, &None).unwrap();
let onion_packet = onion_utils::construct_onion_packet(onion_payloads, onion_keys, [0; 32], &payment_hash).unwrap();
let msg = msgs::UpdateAddHTLC {
channel_id: chan.2,
@ -1598,8 +1599,9 @@ fn test_chan_reserve_violation_inbound_htlc_outbound_channel() {
let session_priv = SecretKey::from_slice(&[42; 32]).unwrap();
let cur_height = nodes[1].node.best_block.read().unwrap().height + 1;
let onion_keys = onion_utils::construct_onion_keys(&secp_ctx, &route.paths[0], &session_priv).unwrap();
let recipient_onion_fields = RecipientOnionFields::secret_only(payment_secret);
let (onion_payloads, htlc_msat, htlc_cltv) = onion_utils::build_onion_payloads(&route.paths[0],
700_000, RecipientOnionFields::secret_only(payment_secret), cur_height, &None).unwrap();
700_000, &recipient_onion_fields, cur_height, &None).unwrap();
let onion_packet = onion_utils::construct_onion_packet(onion_payloads, onion_keys, [0; 32], &payment_hash).unwrap();
let msg = msgs::UpdateAddHTLC {
channel_id: chan.2,
@ -1777,8 +1779,9 @@ fn test_chan_reserve_violation_inbound_htlc_inbound_chan() {
let session_priv = SecretKey::from_slice(&[42; 32]).unwrap();
let cur_height = nodes[0].node.best_block.read().unwrap().height + 1;
let onion_keys = onion_utils::construct_onion_keys(&secp_ctx, &route_2.paths[0], &session_priv).unwrap();
let recipient_onion_fields = RecipientOnionFields::spontaneous_empty();
let (onion_payloads, htlc_msat, htlc_cltv) = onion_utils::build_onion_payloads(
&route_2.paths[0], recv_value_2, RecipientOnionFields::spontaneous_empty(), cur_height, &None).unwrap();
&route_2.paths[0], recv_value_2, &recipient_onion_fields, cur_height, &None).unwrap();
let onion_packet = onion_utils::construct_onion_packet(onion_payloads, onion_keys, [0; 32], &our_payment_hash_1).unwrap();
let msg = msgs::UpdateAddHTLC {
channel_id: chan.2,
@ -3501,8 +3504,9 @@ fn fail_backward_pending_htlc_upon_channel_failure() {
let secp_ctx = Secp256k1::new();
let session_priv = SecretKey::from_slice(&[42; 32]).unwrap();
let current_height = nodes[1].node.best_block.read().unwrap().height + 1;
let recipient_onion_fields = RecipientOnionFields::secret_only(payment_secret);
let (onion_payloads, _amount_msat, cltv_expiry) = onion_utils::build_onion_payloads(
&route.paths[0], 50_000, RecipientOnionFields::secret_only(payment_secret), current_height, &None).unwrap();
&route.paths[0], 50_000, &recipient_onion_fields, current_height, &None).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).unwrap();
@ -6487,8 +6491,9 @@ fn test_update_add_htlc_bolt2_receiver_check_max_htlc_limit() {
let session_priv = SecretKey::from_slice(&[42; 32]).unwrap();
let cur_height = nodes[0].node.best_block.read().unwrap().height + 1;
let onion_keys = onion_utils::construct_onion_keys(&Secp256k1::signing_only(), &route.paths[0], &session_priv).unwrap();
let recipient_onion_fields = RecipientOnionFields::secret_only(our_payment_secret);
let (onion_payloads, _htlc_msat, htlc_cltv) = onion_utils::build_onion_payloads(
&route.paths[0], send_amt, RecipientOnionFields::secret_only(our_payment_secret), cur_height, &None).unwrap();
&route.paths[0], send_amt, &recipient_onion_fields, cur_height, &None).unwrap();
let onion_packet = onion_utils::construct_onion_packet(onion_payloads, onion_keys, [0; 32], &our_payment_hash).unwrap();
let mut msg = msgs::UpdateAddHTLC {
@ -8222,8 +8227,9 @@ fn test_onion_value_mpp_set_calculation() {
let height = nodes[0].best_block_info().1;
let session_priv = SecretKey::from_slice(&session_priv).unwrap();
let mut onion_keys = onion_utils::construct_onion_keys(&Secp256k1::new(), &route.paths[0], &session_priv).unwrap();
let recipient_onion_fields = RecipientOnionFields::secret_only(our_payment_secret);
let (mut onion_payloads, _, _) = onion_utils::build_onion_payloads(&route.paths[0], 100_000,
RecipientOnionFields::secret_only(our_payment_secret), height + 1, &None).unwrap();
&recipient_onion_fields, height + 1, &None).unwrap();
// Edit amt_to_forward to simulate the sender having set
// the final amount and the routing node taking less fee
if let msgs::OutboundOnionPayload::Receive {

View file

@ -0,0 +1,326 @@
// This file is Copyright its original authors, visible in version control
// history.
//
// This file is licensed under the Apache License, Version 2.0 <LICENSE-APACHE
// or http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your option.
// You may not use this file except in accordance with one or both of these
// licenses.
//! Tests for calculating the maximum length of a path based on the payment metadata, custom TLVs,
//! and/or blinded paths present.
use bitcoin::secp256k1::Secp256k1;
use crate::blinded_path::BlindedPath;
use crate::blinded_path::payment::{PaymentConstraints, PaymentContext, ReceiveTlvs};
use crate::events::MessageSendEventsProvider;
use crate::ln::PaymentSecret;
use crate::ln::blinded_payment_tests::get_blinded_route_parameters;
use crate::ln::channelmanager::PaymentId;
use crate::ln::functional_test_utils::*;
use crate::ln::msgs;
use crate::ln::onion_utils;
use crate::ln::onion_utils::MIN_FINAL_VALUE_ESTIMATE_WITH_OVERPAY;
use crate::ln::outbound_payment::{RecipientOnionFields, Retry, RetryableSendFailure};
use crate::prelude::*;
use crate::routing::router::{DEFAULT_MAX_TOTAL_CLTV_EXPIRY_DELTA, PaymentParameters, RouteParameters};
use crate::util::errors::APIError;
use crate::util::ser::Writeable;
use crate::util::test_utils;
// 3+32 (payload length and HMAC) + 2+8 (amt_to_forward) +
// 2+4 (outgoing_cltv_value) + 2+8 (short_channel_id)
const INTERMED_PAYLOAD_LEN_ESTIMATE: usize = 61;
// Length of the HMAC of an onion payload when encoded into the packet.
const PAYLOAD_HMAC_LEN: usize = 32;
#[test]
fn large_payment_metadata() {
// Test that we'll limit our maximum path length based on the size of the provided
// payment_metadata, and refuse to send at all prior to pathfinding if it's too large.
let chanmon_cfgs = create_chanmon_cfgs(3);
let node_cfgs = create_node_cfgs(3, &chanmon_cfgs);
let node_chanmgrs = create_node_chanmgrs(3, &node_cfgs, &[None, None, None]);
let nodes = create_network(3, &node_cfgs, &node_chanmgrs);
create_announced_chan_between_nodes(&nodes, 0, 1);
create_announced_chan_between_nodes(&nodes, 1, 2);
let amt_msat = 100_000;
// Construct payment_metadata such that we can send the payment to the next hop but no further
// without exceeding the max onion packet size.
let final_payload_len_without_metadata = msgs::OutboundOnionPayload::Receive {
payment_data: Some(msgs::FinalOnionHopData {
payment_secret: PaymentSecret([0; 32]), total_msat: MIN_FINAL_VALUE_ESTIMATE_WITH_OVERPAY
}),
payment_metadata: None,
keysend_preimage: None,
custom_tlvs: &Vec::new(),
sender_intended_htlc_amt_msat: MIN_FINAL_VALUE_ESTIMATE_WITH_OVERPAY,
cltv_expiry_height: nodes[0].best_block_info().1 + DEFAULT_MAX_TOTAL_CLTV_EXPIRY_DELTA,
}.serialized_length();
let max_metadata_len = 1300
- 1 // metadata type
- crate::util::ser::BigSize(1200).serialized_length() // metadata length
- 2 // onion payload varint prefix increased ser size due to metadata
- PAYLOAD_HMAC_LEN
- final_payload_len_without_metadata;
let mut payment_metadata = vec![42; max_metadata_len];
// Check that the maximum-size metadata is sendable.
let (mut route_0_1, payment_hash, payment_preimage, payment_secret) = get_route_and_payment_hash!(&nodes[0], &nodes[1], amt_msat);
let mut recipient_onion_max_md_size = RecipientOnionFields {
payment_secret: Some(payment_secret),
payment_metadata: Some(payment_metadata.clone()),
custom_tlvs: Vec::new(),
};
nodes[0].node.send_payment(payment_hash, recipient_onion_max_md_size.clone(), PaymentId(payment_hash.0), route_0_1.route_params.clone().unwrap(), Retry::Attempts(0)).unwrap();
check_added_monitors!(nodes[0], 1);
let mut events = nodes[0].node.get_and_clear_pending_msg_events();
assert_eq!(events.len(), 1);
let path = &[&nodes[1]];
let args = PassAlongPathArgs::new(&nodes[0], path, amt_msat, payment_hash, events.pop().unwrap())
.with_payment_secret(payment_secret)
.with_payment_metadata(payment_metadata.clone());
do_pass_along_path(args);
claim_payment_along_route(&nodes[0], &[&[&nodes[1]]], false, payment_preimage);
// Check that the payment parameter for max path length will prevent us from routing past our
// next-hop peer given the payment_metadata size.
let (mut route_0_2, payment_hash_2, payment_preimage_2, payment_secret_2) = get_route_and_payment_hash!(&nodes[0], &nodes[2], amt_msat);
let mut route_params_0_2 = route_0_2.route_params.clone().unwrap();
route_params_0_2.payment_params.max_path_length = 1;
nodes[0].router.expect_find_route_query(route_params_0_2);
let err = nodes[0].node.send_payment(payment_hash_2, recipient_onion_max_md_size.clone(), PaymentId(payment_hash_2.0), route_0_2.route_params.clone().unwrap(), Retry::Attempts(0)).unwrap_err();
assert_eq!(err, RetryableSendFailure::RouteNotFound);
// If our payment_metadata contains 1 additional byte, we'll fail prior to pathfinding.
let mut recipient_onion_too_large_md = recipient_onion_max_md_size.clone();
recipient_onion_too_large_md.payment_metadata.as_mut().map(|mut md| md.push(42));
let err = nodes[0].node.send_payment(payment_hash, recipient_onion_too_large_md.clone(), PaymentId(payment_hash.0), route_0_1.route_params.clone().unwrap(), Retry::Attempts(0)).unwrap_err();
assert_eq!(err, RetryableSendFailure::OnionPacketSizeExceeded);
// Confirm that we'll fail to construct an onion packet given this payment_metadata that's too
// large for even a 1-hop path.
let secp_ctx = Secp256k1::signing_only();
route_0_1.paths[0].hops[0].fee_msat = MIN_FINAL_VALUE_ESTIMATE_WITH_OVERPAY;
route_0_1.paths[0].hops[0].cltv_expiry_delta = DEFAULT_MAX_TOTAL_CLTV_EXPIRY_DELTA;
let err = onion_utils::create_payment_onion(&secp_ctx, &route_0_1.paths[0], &test_utils::privkey(42), MIN_FINAL_VALUE_ESTIMATE_WITH_OVERPAY, &recipient_onion_too_large_md, nodes[0].best_block_info().1 + DEFAULT_MAX_TOTAL_CLTV_EXPIRY_DELTA, &payment_hash, &None, [0; 32]).unwrap_err();
match err {
APIError::InvalidRoute { err } => {
assert_eq!(err, "Route size too large considering onion data");
},
_ => panic!(),
}
// If we remove enough payment_metadata bytes to allow for 2 hops, we're now able to send to
// nodes[2].
let mut recipient_onion_allows_2_hops = RecipientOnionFields {
payment_secret: Some(payment_secret_2),
payment_metadata: Some(vec![42; max_metadata_len - INTERMED_PAYLOAD_LEN_ESTIMATE]),
custom_tlvs: Vec::new(),
};
let mut route_params_0_2 = route_0_2.route_params.clone().unwrap();
route_params_0_2.payment_params.max_path_length = 2;
nodes[0].router.expect_find_route_query(route_params_0_2);
nodes[0].node.send_payment(payment_hash_2, recipient_onion_allows_2_hops.clone(), PaymentId(payment_hash_2.0), route_0_2.route_params.unwrap(), Retry::Attempts(0)).unwrap();
check_added_monitors!(nodes[0], 1);
let mut events = nodes[0].node.get_and_clear_pending_msg_events();
assert_eq!(events.len(), 1);
let path = &[&nodes[1], &nodes[2]];
let args = PassAlongPathArgs::new(&nodes[0], path, amt_msat, payment_hash_2, events.pop().unwrap())
.with_payment_secret(payment_secret_2)
.with_payment_metadata(recipient_onion_allows_2_hops.payment_metadata.unwrap());
do_pass_along_path(args);
claim_payment_along_route(&nodes[0], &[&[&nodes[1], &nodes[2]]], false, payment_preimage_2);
}
#[test]
fn one_hop_blinded_path_with_custom_tlv() {
// Test that we'll limit our maximum path length when paying to a 1-hop blinded path based on the
// size of the provided custom TLV, and refuse to send at all prior to pathfinding if it's too
// large.
let chanmon_cfgs = create_chanmon_cfgs(3);
let node_cfgs = create_node_cfgs(3, &chanmon_cfgs);
let node_chanmgrs = create_node_chanmgrs(3, &node_cfgs, &[None, None, None]);
let nodes = create_network(3, &node_cfgs, &node_chanmgrs);
create_announced_chan_between_nodes(&nodes, 0, 1);
let chan_upd_1_2 = create_announced_chan_between_nodes_with_value(&nodes, 1, 2, 1_000_000, 0).0.contents;
// Construct the route parameters for sending to nodes[2]'s 1-hop blinded path.
let amt_msat = 100_000;
let (payment_preimage, payment_hash, payment_secret) = get_payment_preimage_hash(&nodes[2], Some(amt_msat), None);
let payee_tlvs = ReceiveTlvs {
payment_secret,
payment_constraints: PaymentConstraints {
max_cltv_expiry: u32::max_value(),
htlc_minimum_msat: chan_upd_1_2.htlc_minimum_msat,
},
payment_context: PaymentContext::unknown(),
};
let mut secp_ctx = Secp256k1::new();
let blinded_path = BlindedPath::one_hop_for_payment(
nodes[2].node.get_our_node_id(), payee_tlvs, TEST_FINAL_CLTV as u16,
&chanmon_cfgs[2].keys_manager, &secp_ctx
).unwrap();
let route_params = RouteParameters::from_payment_params_and_value(
PaymentParameters::blinded(vec![blinded_path.clone()]),
amt_msat,
);
// Calculate the maximum custom TLV value size where a valid onion packet is still possible.
const CUSTOM_TLV_TYPE: u64 = 65537;
let final_payload_len_without_custom_tlv = msgs::OutboundOnionPayload::BlindedReceive {
sender_intended_htlc_amt_msat: MIN_FINAL_VALUE_ESTIMATE_WITH_OVERPAY,
total_msat: MIN_FINAL_VALUE_ESTIMATE_WITH_OVERPAY,
cltv_expiry_height: nodes[0].best_block_info().1 + DEFAULT_MAX_TOTAL_CLTV_EXPIRY_DELTA,
encrypted_tlvs: &blinded_path.1.blinded_hops[0].encrypted_payload,
intro_node_blinding_point: Some(blinded_path.1.blinding_point),
keysend_preimage: None,
custom_tlvs: &Vec::new()
}.serialized_length();
let max_custom_tlv_len = 1300
- crate::util::ser::BigSize(CUSTOM_TLV_TYPE).serialized_length() // custom TLV type
- crate::util::ser::BigSize(1200).serialized_length() // custom TLV length
- 1 // onion payload varint prefix increased ser size due to custom TLV
- PAYLOAD_HMAC_LEN
- final_payload_len_without_custom_tlv;
// Check that we can send the maximum custom TLV with 1 blinded hop.
let recipient_onion_max_custom_tlv_size = RecipientOnionFields::spontaneous_empty()
.with_custom_tlvs(vec![(CUSTOM_TLV_TYPE, vec![42; max_custom_tlv_len])])
.unwrap();
nodes[1].node.send_payment(payment_hash, recipient_onion_max_custom_tlv_size.clone(), PaymentId(payment_hash.0), route_params.clone(), Retry::Attempts(0)).unwrap();
check_added_monitors(&nodes[1], 1);
let mut events = nodes[1].node.get_and_clear_pending_msg_events();
assert_eq!(events.len(), 1);
let path = &[&nodes[2]];
let args = PassAlongPathArgs::new(&nodes[1], path, amt_msat, payment_hash, events.pop().unwrap())
.with_payment_secret(payment_secret)
.with_custom_tlvs(recipient_onion_max_custom_tlv_size.custom_tlvs.clone());
do_pass_along_path(args);
claim_payment_along_route(&nodes[1], &[&[&nodes[2]]], false, payment_preimage);
// If 1 byte is added to the custom TLV value, we'll fail to send prior to pathfinding.
let mut recipient_onion_too_large_custom_tlv = recipient_onion_max_custom_tlv_size.clone();
recipient_onion_too_large_custom_tlv.custom_tlvs[0].1.push(42);
let err = nodes[1].node.send_payment(payment_hash, recipient_onion_too_large_custom_tlv, PaymentId(payment_hash.0), route_params.clone(), Retry::Attempts(0)).unwrap_err();
assert_eq!(err, RetryableSendFailure::OnionPacketSizeExceeded);
// With the maximum-size custom TLV, our max path length is limited to 1, so attempting to route
// nodes[0] -> nodes[2] will fail.
let err = nodes[0].node.send_payment(payment_hash, recipient_onion_max_custom_tlv_size.clone(), PaymentId(payment_hash.0), route_params.clone(), Retry::Attempts(0)).unwrap_err();
assert_eq!(err, RetryableSendFailure::RouteNotFound);
// If we remove enough custom TLV bytes to allow for 1 intermediate unblinded hop, we're now able
// to send nodes[0] -> nodes[2].
let mut recipient_onion_allows_2_hops = recipient_onion_max_custom_tlv_size.clone();
recipient_onion_allows_2_hops.custom_tlvs[0].1.resize(max_custom_tlv_len - INTERMED_PAYLOAD_LEN_ESTIMATE, 0);
nodes[0].node.send_payment(payment_hash, recipient_onion_allows_2_hops.clone(), PaymentId(payment_hash.0), route_params.clone(), Retry::Attempts(0)).unwrap();
check_added_monitors(&nodes[0], 1);
let mut events = nodes[0].node.get_and_clear_pending_msg_events();
assert_eq!(events.len(), 1);
let path = &[&nodes[1], &nodes[2]];
let args = PassAlongPathArgs::new(&nodes[0], path, amt_msat, payment_hash, events.pop().unwrap())
.with_payment_secret(payment_secret)
.with_custom_tlvs(recipient_onion_allows_2_hops.custom_tlvs);
do_pass_along_path(args);
claim_payment_along_route(&nodes[0], &[&[&nodes[1], &nodes[2]]], false, payment_preimage);
}
#[test]
fn blinded_path_with_custom_tlv() {
// Test that we'll limit our maximum path length when paying to a blinded path based on the size
// of the provided custom TLV, and refuse to send at all prior to pathfinding if it's too large.
let chanmon_cfgs = create_chanmon_cfgs(4);
let node_cfgs = create_node_cfgs(4, &chanmon_cfgs);
let node_chanmgrs = create_node_chanmgrs(4, &node_cfgs, &[None, None, None, None]);
let nodes = create_network(4, &node_cfgs, &node_chanmgrs);
create_announced_chan_between_nodes(&nodes, 0, 1);
create_announced_chan_between_nodes(&nodes, 1, 2);
let chan_upd_2_3 = create_announced_chan_between_nodes_with_value(&nodes, 2, 3, 1_000_000, 0).0.contents;
// Construct the route parameters for sending to nodes[3]'s blinded path.
let amt_msat = 100_000;
let (payment_preimage, payment_hash, payment_secret) = get_payment_preimage_hash(&nodes[3], Some(amt_msat), None);
let route_params = get_blinded_route_parameters(amt_msat, payment_secret, 1, 1_0000_0000,
nodes.iter().skip(2).map(|n| n.node.get_our_node_id()).collect(), &[&chan_upd_2_3],
&chanmon_cfgs[3].keys_manager);
// Calculate the maximum custom TLV value size where a valid onion packet is still possible.
const CUSTOM_TLV_TYPE: u64 = 65537;
let mut route = get_route(&nodes[1], &route_params).unwrap();
let reserved_packet_bytes_without_custom_tlv: usize = onion_utils::build_onion_payloads(
&route.paths[0], MIN_FINAL_VALUE_ESTIMATE_WITH_OVERPAY,
&RecipientOnionFields::spontaneous_empty(),
nodes[0].best_block_info().1 + DEFAULT_MAX_TOTAL_CLTV_EXPIRY_DELTA, &None
)
.unwrap()
.0
.iter()
.map(|payload| payload.serialized_length() + PAYLOAD_HMAC_LEN)
.sum();
let max_custom_tlv_len = 1300
- crate::util::ser::BigSize(CUSTOM_TLV_TYPE).serialized_length() // custom TLV type
- crate::util::ser::BigSize(1200).serialized_length() // custom TLV length
- 2 // onion payload varint prefix increased ser size due to custom TLV
- reserved_packet_bytes_without_custom_tlv;
// Check that we can send the maximum custom TLV size with 0 intermediate unblinded hops.
let recipient_onion_max_custom_tlv_size = RecipientOnionFields::spontaneous_empty()
.with_custom_tlvs(vec![(CUSTOM_TLV_TYPE, vec![42; max_custom_tlv_len])])
.unwrap();
nodes[1].node.send_payment(payment_hash, recipient_onion_max_custom_tlv_size.clone(), PaymentId(payment_hash.0), route_params.clone(), Retry::Attempts(0)).unwrap();
check_added_monitors(&nodes[1], 1);
let mut events = nodes[1].node.get_and_clear_pending_msg_events();
assert_eq!(events.len(), 1);
let path = &[&nodes[2], &nodes[3]];
let args = PassAlongPathArgs::new(&nodes[1], path, amt_msat, payment_hash, events.pop().unwrap())
.with_payment_secret(payment_secret)
.with_custom_tlvs(recipient_onion_max_custom_tlv_size.custom_tlvs.clone());
do_pass_along_path(args);
claim_payment_along_route(&nodes[1], &[&[&nodes[2], &nodes[3]]], false, payment_preimage);
// If 1 byte is added to the custom TLV value, we'll fail to send prior to pathfinding.
let mut recipient_onion_too_large_custom_tlv = recipient_onion_max_custom_tlv_size.clone();
recipient_onion_too_large_custom_tlv.custom_tlvs[0].1.push(42);
let err = nodes[1].node.send_payment(payment_hash, recipient_onion_too_large_custom_tlv.clone(), PaymentId(payment_hash.0), route_params.clone(), Retry::Attempts(0)).unwrap_err();
assert_eq!(err, RetryableSendFailure::OnionPacketSizeExceeded);
// Confirm that we can't construct an onion packet given this too-large custom TLV.
let secp_ctx = Secp256k1::signing_only();
route.paths[0].hops[0].fee_msat = MIN_FINAL_VALUE_ESTIMATE_WITH_OVERPAY;
route.paths[0].hops[0].cltv_expiry_delta = DEFAULT_MAX_TOTAL_CLTV_EXPIRY_DELTA;
let err = onion_utils::create_payment_onion(&secp_ctx, &route.paths[0], &test_utils::privkey(42), MIN_FINAL_VALUE_ESTIMATE_WITH_OVERPAY, &recipient_onion_too_large_custom_tlv, nodes[0].best_block_info().1 + DEFAULT_MAX_TOTAL_CLTV_EXPIRY_DELTA, &payment_hash, &None, [0; 32]).unwrap_err();
match err {
APIError::InvalidRoute { err } => {
assert_eq!(err, "Route size too large considering onion data");
},
_ => panic!(),
}
// With the maximum-size custom TLV, we can't have any intermediate unblinded hops, so attempting
// to route nodes[0] -> nodes[3] will fail.
let err = nodes[0].node.send_payment(payment_hash, recipient_onion_max_custom_tlv_size.clone(), PaymentId(payment_hash.0), route_params.clone(), Retry::Attempts(0)).unwrap_err();
assert_eq!(err, RetryableSendFailure::RouteNotFound);
// If we remove enough custom TLV bytes to allow for 1 intermediate unblinded hop, we're now able
// to send nodes[0] -> nodes[3].
let mut recipient_onion_allows_2_hops = recipient_onion_max_custom_tlv_size.clone();
recipient_onion_allows_2_hops.custom_tlvs[0].1.resize(max_custom_tlv_len - INTERMED_PAYLOAD_LEN_ESTIMATE, 0);
nodes[0].node.send_payment(payment_hash, recipient_onion_allows_2_hops.clone(), PaymentId(payment_hash.0), route_params.clone(), Retry::Attempts(0)).unwrap();
check_added_monitors(&nodes[0], 1);
let mut events = nodes[0].node.get_and_clear_pending_msg_events();
assert_eq!(events.len(), 1);
let path = &[&nodes[1], &nodes[2], &nodes[3]];
let args = PassAlongPathArgs::new(&nodes[0], path, amt_msat, payment_hash, events.pop().unwrap())
.with_payment_secret(payment_secret)
.with_custom_tlvs(recipient_onion_allows_2_hops.custom_tlvs);
do_pass_along_path(args);
claim_payment_along_route(&nodes[0], &[&[&nodes[1], &nodes[2], &nodes[3]]], false, payment_preimage);
}

View file

@ -53,6 +53,9 @@ mod blinded_payment_tests;
mod functional_tests;
#[cfg(test)]
#[allow(unused_mut)]
mod max_payment_path_len_tests;
#[cfg(test)]
#[allow(unused_mut)]
mod payment_tests;
#[cfg(test)]
#[allow(unused_mut)]

View file

@ -1723,7 +1723,7 @@ mod fuzzy_internal_msgs {
}
}
pub(crate) enum OutboundOnionPayload {
pub(crate) enum OutboundOnionPayload<'a> {
Forward {
short_channel_id: u64,
/// The value, in msat, of the payment after this hop's fee is deducted.
@ -1739,24 +1739,24 @@ mod fuzzy_internal_msgs {
},
Receive {
payment_data: Option<FinalOnionHopData>,
payment_metadata: Option<Vec<u8>>,
payment_metadata: Option<&'a Vec<u8>>,
keysend_preimage: Option<PaymentPreimage>,
custom_tlvs: Vec<(u64, Vec<u8>)>,
custom_tlvs: &'a Vec<(u64, Vec<u8>)>,
sender_intended_htlc_amt_msat: u64,
cltv_expiry_height: u32,
},
BlindedForward {
encrypted_tlvs: Vec<u8>,
encrypted_tlvs: &'a Vec<u8>,
intro_node_blinding_point: Option<PublicKey>,
},
BlindedReceive {
sender_intended_htlc_amt_msat: u64,
total_msat: u64,
cltv_expiry_height: u32,
encrypted_tlvs: Vec<u8>,
encrypted_tlvs: &'a Vec<u8>,
intro_node_blinding_point: Option<PublicKey>, // Set if the introduction node of the blinded path is the final node
keysend_preimage: Option<PaymentPreimage>,
custom_tlvs: Vec<(u64, Vec<u8>)>,
custom_tlvs: &'a Vec<(u64, Vec<u8>)>,
}
}
@ -2569,7 +2569,7 @@ impl Readable for FinalOnionHopData {
}
}
impl Writeable for OutboundOnionPayload {
impl<'a> Writeable for OutboundOnionPayload<'a> {
fn write<W: Writer>(&self, w: &mut W) -> Result<(), io::Error> {
match self {
Self::Forward { short_channel_id, amt_to_forward, outgoing_cltv_value } => {
@ -2604,12 +2604,12 @@ impl Writeable for OutboundOnionPayload {
(2, HighZeroBytesDroppedBigSize(*sender_intended_htlc_amt_msat), required),
(4, HighZeroBytesDroppedBigSize(*cltv_expiry_height), required),
(8, payment_data, option),
(16, payment_metadata.as_ref().map(|m| WithoutLength(m)), option)
(16, payment_metadata.map(|m| WithoutLength(m)), option)
}, custom_tlvs.iter());
},
Self::BlindedForward { encrypted_tlvs, intro_node_blinding_point } => {
_encode_varint_length_prefixed_tlv!(w, {
(10, *encrypted_tlvs, required_vec),
(10, **encrypted_tlvs, required_vec),
(12, intro_node_blinding_point, option)
});
},
@ -2626,7 +2626,7 @@ impl Writeable for OutboundOnionPayload {
_encode_varint_length_prefixed_tlv!(w, {
(2, HighZeroBytesDroppedBigSize(*sender_intended_htlc_amt_msat), required),
(4, HighZeroBytesDroppedBigSize(*cltv_expiry_height), required),
(10, *encrypted_tlvs, required_vec),
(10, **encrypted_tlvs, required_vec),
(12, intro_node_blinding_point, option),
(18, HighZeroBytesDroppedBigSize(*total_msat), required)
}, custom_tlvs.iter());
@ -4359,7 +4359,7 @@ mod tests {
keysend_preimage: None,
sender_intended_htlc_amt_msat: 0x0badf00d01020304,
cltv_expiry_height: 0xffffffff,
custom_tlvs: vec![],
custom_tlvs: &vec![],
};
let encoded_value = outbound_msg.encode();
let target_value = <Vec<u8>>::from_hex("1002080badf00d010203040404ffffffff").unwrap();
@ -4387,7 +4387,7 @@ mod tests {
keysend_preimage: None,
sender_intended_htlc_amt_msat: 0x0badf00d01020304,
cltv_expiry_height: 0xffffffff,
custom_tlvs: vec![],
custom_tlvs: &vec![],
};
let encoded_value = outbound_msg.encode();
let target_value = <Vec<u8>>::from_hex("3602080badf00d010203040404ffffffff082442424242424242424242424242424242424242424242424242424242424242421badca1f").unwrap();
@ -4424,7 +4424,7 @@ mod tests {
payment_data: None,
payment_metadata: None,
keysend_preimage: None,
custom_tlvs: bad_type_range_tlvs,
custom_tlvs: &bad_type_range_tlvs,
sender_intended_htlc_amt_msat: 0x0badf00d01020304,
cltv_expiry_height: 0xffffffff,
};
@ -4436,7 +4436,7 @@ mod tests {
((1 << 16) - 1, vec![42; 32]),
];
if let msgs::OutboundOnionPayload::Receive { ref mut custom_tlvs, .. } = msg {
*custom_tlvs = good_type_range_tlvs.clone();
*custom_tlvs = &good_type_range_tlvs;
}
let encoded_value = msg.encode();
let inbound_msg = ReadableArgs::read(&mut Cursor::new(&encoded_value[..]), (None, &&node_signer)).unwrap();
@ -4456,7 +4456,7 @@ mod tests {
payment_data: None,
payment_metadata: None,
keysend_preimage: None,
custom_tlvs: expected_custom_tlvs.clone(),
custom_tlvs: &expected_custom_tlvs,
sender_intended_htlc_amt_msat: 0x0badf00d01020304,
cltv_expiry_height: 0xffffffff,
};

View file

@ -536,7 +536,7 @@ mod tests {
let path = Path { hops, blinded_tail: None, };
let onion_keys = super::onion_utils::construct_onion_keys(&secp_ctx, &path, &session_priv).unwrap();
let (onion_payloads, ..) = super::onion_utils::build_onion_payloads(
&path, total_amt_msat, recipient_onion, cur_height + 1, &Some(keysend_preimage)
&path, total_amt_msat, &recipient_onion, cur_height + 1, &Some(keysend_preimage)
).unwrap();
assert!(super::onion_utils::construct_onion_packet(
@ -563,8 +563,8 @@ mod tests {
};
let (onion, amount_msat, cltv_expiry) = create_payment_onion(
&secp_ctx, &path, &session_priv, total_amt_msat, recipient_onion, cur_height,
&payment_hash, &Some(preimage), prng_seed
&secp_ctx, &path, &session_priv, total_amt_msat, &recipient_onion,
cur_height, &payment_hash, &Some(preimage), prng_seed
).unwrap();
let msg = make_update_add_msg(amount_msat, cltv_expiry, payment_hash, onion);

View file

@ -357,8 +357,9 @@ fn test_onion_failure() {
let session_priv = SecretKey::from_slice(&[3; 32]).unwrap();
let cur_height = nodes[0].best_block_info().1 + 1;
let onion_keys = onion_utils::construct_onion_keys(&Secp256k1::new(), &route.paths[0], &session_priv).unwrap();
let recipient_onion_fields = RecipientOnionFields::spontaneous_empty();
let (mut onion_payloads, _htlc_msat, _htlc_cltv) = onion_utils::build_onion_payloads(
&route.paths[0], 40000, RecipientOnionFields::spontaneous_empty(), cur_height, &None).unwrap();
&route.paths[0], 40000, &recipient_onion_fields, cur_height, &None).unwrap();
let mut new_payloads = Vec::new();
for payload in onion_payloads.drain(..) {
new_payloads.push(BogusOnionHopData::new(payload));
@ -375,8 +376,9 @@ fn test_onion_failure() {
let session_priv = SecretKey::from_slice(&[3; 32]).unwrap();
let cur_height = nodes[0].best_block_info().1 + 1;
let onion_keys = onion_utils::construct_onion_keys(&Secp256k1::new(), &route.paths[0], &session_priv).unwrap();
let recipient_onion_fields = RecipientOnionFields::spontaneous_empty();
let (mut onion_payloads, _htlc_msat, _htlc_cltv) = onion_utils::build_onion_payloads(
&route.paths[0], 40000, RecipientOnionFields::spontaneous_empty(), cur_height, &None).unwrap();
&route.paths[0], 40000, &recipient_onion_fields, cur_height, &None).unwrap();
let mut new_payloads = Vec::new();
for payload in onion_payloads.drain(..) {
new_payloads.push(BogusOnionHopData::new(payload));
@ -612,8 +614,9 @@ fn test_onion_failure() {
let height = nodes[2].best_block_info().1;
route.paths[0].hops[1].cltv_expiry_delta += CLTV_FAR_FAR_AWAY + route.paths[0].hops[0].cltv_expiry_delta + 1;
let onion_keys = onion_utils::construct_onion_keys(&Secp256k1::new(), &route.paths[0], &session_priv).unwrap();
let recipient_onion_fields = RecipientOnionFields::spontaneous_empty();
let (onion_payloads, _, htlc_cltv) = onion_utils::build_onion_payloads(
&route.paths[0], 40000, RecipientOnionFields::spontaneous_empty(), height, &None).unwrap();
&route.paths[0], 40000, &recipient_onion_fields, height, &None).unwrap();
let onion_packet = onion_utils::construct_onion_packet(onion_payloads, onion_keys, [0; 32], &payment_hash).unwrap();
msg.cltv_expiry = htlc_cltv;
msg.onion_routing_packet = onion_packet;
@ -948,8 +951,9 @@ fn test_always_create_tlv_format_onion_payloads() {
assert!(!hops[1].node_features.supports_variable_length_onion());
let cur_height = nodes[0].best_block_info().1 + 1;
let recipient_onion_fields = RecipientOnionFields::spontaneous_empty();
let (onion_payloads, _htlc_msat, _htlc_cltv) = onion_utils::build_onion_payloads(
&route.paths[0], 40000, RecipientOnionFields::spontaneous_empty(), cur_height, &None).unwrap();
&route.paths[0], 40000, &recipient_onion_fields, cur_height, &None).unwrap();
match onion_payloads[0] {
msgs::OutboundOnionPayload::Forward {..} => {},
@ -1203,9 +1207,10 @@ fn test_phantom_invalid_onion_payload() {
let height = nodes[0].best_block_info().1;
let session_priv = SecretKey::from_slice(&session_priv).unwrap();
let mut onion_keys = onion_utils::construct_onion_keys(&Secp256k1::new(), &route.paths[0], &session_priv).unwrap();
let recipient_onion_fields = RecipientOnionFields::secret_only(payment_secret);
let (mut onion_payloads, _, _) = onion_utils::build_onion_payloads(
&route.paths[0], msgs::MAX_VALUE_MSAT + 1,
RecipientOnionFields::secret_only(payment_secret), height + 1, &None).unwrap();
&recipient_onion_fields, height + 1, &None).unwrap();
// We only want to construct the onion packet for the last hop, not the entire route, so
// remove the first hop's payload and its keys.
onion_keys.remove(0);

View file

@ -7,14 +7,17 @@
// You may not use this file except in accordance with one or both of these
// licenses.
use crate::blinded_path::BlindedHop;
use crate::crypto::chacha20::ChaCha20;
use crate::crypto::streams::ChaChaReader;
use crate::ln::channel::TOTAL_BITCOIN_SUPPLY_SATOSHIS;
use crate::ln::channelmanager::{HTLCSource, RecipientOnionFields};
use crate::ln::features::{ChannelFeatures, NodeFeatures};
use crate::ln::msgs;
use crate::ln::types::{PaymentHash, PaymentPreimage};
use crate::ln::wire::Encode;
use crate::routing::gossip::NetworkUpdate;
use crate::routing::router::{BlindedTail, Path, RouteHop};
use crate::routing::router::{Path, RouteHop, RouteParameters};
use crate::sign::NodeSigner;
use crate::util::errors::{self, APIError};
use crate::util::logger::Logger;
@ -174,18 +177,60 @@ pub(super) fn construct_onion_keys<T: secp256k1::Signing>(
}
/// returns the hop data, as well as the first-hop value_msat and CLTV value we should send.
pub(super) fn build_onion_payloads(
path: &Path, total_msat: u64, mut recipient_onion: RecipientOnionFields,
pub(super) fn build_onion_payloads<'a>(
path: &'a Path, total_msat: u64, recipient_onion: &'a RecipientOnionFields,
starting_htlc_offset: u32, keysend_preimage: &Option<PaymentPreimage>,
) -> Result<(Vec<msgs::OutboundOnionPayload>, u64, u32), APIError> {
let mut cur_value_msat = 0u64;
let mut cur_cltv = starting_htlc_offset;
let mut last_short_channel_id = 0;
) -> Result<(Vec<msgs::OutboundOnionPayload<'a>>, u64, u32), APIError> {
let mut res: Vec<msgs::OutboundOnionPayload> = Vec::with_capacity(
path.hops.len() + path.blinded_tail.as_ref().map_or(0, |t| t.hops.len()),
);
let blinded_tail_with_hop_iter = path.blinded_tail.as_ref().map(|bt| BlindedTailHopIter {
hops: bt.hops.iter(),
blinding_point: bt.blinding_point,
final_value_msat: bt.final_value_msat,
excess_final_cltv_expiry_delta: bt.excess_final_cltv_expiry_delta,
});
for (idx, hop) in path.hops.iter().rev().enumerate() {
let (value_msat, cltv) = build_onion_payloads_callback(
path.hops.iter(),
blinded_tail_with_hop_iter,
total_msat,
recipient_onion,
starting_htlc_offset,
keysend_preimage,
|action, payload| match action {
PayloadCallbackAction::PushBack => res.push(payload),
PayloadCallbackAction::PushFront => res.insert(0, payload),
},
)?;
Ok((res, value_msat, cltv))
}
struct BlindedTailHopIter<'a, I: Iterator<Item = &'a BlindedHop>> {
hops: I,
blinding_point: PublicKey,
final_value_msat: u64,
excess_final_cltv_expiry_delta: u32,
}
enum PayloadCallbackAction {
PushBack,
PushFront,
}
fn build_onion_payloads_callback<'a, H, B, F>(
hops: H, mut blinded_tail: Option<BlindedTailHopIter<'a, B>>, total_msat: u64,
recipient_onion: &'a RecipientOnionFields, starting_htlc_offset: u32,
keysend_preimage: &Option<PaymentPreimage>, mut callback: F,
) -> Result<(u64, u32), APIError>
where
H: DoubleEndedIterator<Item = &'a RouteHop>,
B: ExactSizeIterator<Item = &'a BlindedHop>,
F: FnMut(PayloadCallbackAction, msgs::OutboundOnionPayload<'a>),
{
let mut cur_value_msat = 0u64;
let mut cur_cltv = starting_htlc_offset;
let mut last_short_channel_id = 0;
for (idx, hop) in hops.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).
@ -196,47 +241,55 @@ pub(super) fn build_onion_payloads(
cur_cltv
};
if idx == 0 {
if let Some(BlindedTail {
if let Some(BlindedTailHopIter {
blinding_point,
hops,
final_value_msat,
excess_final_cltv_expiry_delta,
..
}) = &path.blinded_tail
}) = blinded_tail.take()
{
let mut blinding_point = Some(*blinding_point);
for (i, blinded_hop) in hops.iter().enumerate() {
if i == hops.len() - 1 {
let mut blinding_point = Some(blinding_point);
let hops_len = hops.len();
for (i, blinded_hop) in hops.enumerate() {
if i == hops_len - 1 {
cur_value_msat += final_value_msat;
res.push(msgs::OutboundOnionPayload::BlindedReceive {
sender_intended_htlc_amt_msat: *final_value_msat,
total_msat,
cltv_expiry_height: cur_cltv + excess_final_cltv_expiry_delta,
encrypted_tlvs: blinded_hop.encrypted_payload.clone(),
intro_node_blinding_point: blinding_point.take(),
keysend_preimage: *keysend_preimage,
custom_tlvs: recipient_onion.custom_tlvs.clone(),
});
callback(
PayloadCallbackAction::PushBack,
msgs::OutboundOnionPayload::BlindedReceive {
sender_intended_htlc_amt_msat: final_value_msat,
total_msat,
cltv_expiry_height: cur_cltv + excess_final_cltv_expiry_delta,
encrypted_tlvs: &blinded_hop.encrypted_payload,
intro_node_blinding_point: blinding_point.take(),
keysend_preimage: *keysend_preimage,
custom_tlvs: &recipient_onion.custom_tlvs,
},
);
} else {
res.push(msgs::OutboundOnionPayload::BlindedForward {
encrypted_tlvs: blinded_hop.encrypted_payload.clone(),
intro_node_blinding_point: blinding_point.take(),
});
callback(
PayloadCallbackAction::PushBack,
msgs::OutboundOnionPayload::BlindedForward {
encrypted_tlvs: &blinded_hop.encrypted_payload,
intro_node_blinding_point: blinding_point.take(),
},
);
}
}
} else {
res.push(msgs::OutboundOnionPayload::Receive {
payment_data: if let Some(secret) = recipient_onion.payment_secret.take() {
Some(msgs::FinalOnionHopData { payment_secret: secret, total_msat })
} else {
None
callback(
PayloadCallbackAction::PushBack,
msgs::OutboundOnionPayload::Receive {
payment_data: recipient_onion.payment_secret.map(|payment_secret| {
msgs::FinalOnionHopData { payment_secret, total_msat }
}),
payment_metadata: recipient_onion.payment_metadata.as_ref(),
keysend_preimage: *keysend_preimage,
custom_tlvs: &recipient_onion.custom_tlvs,
sender_intended_htlc_amt_msat: value_msat,
cltv_expiry_height: cltv,
},
payment_metadata: recipient_onion.payment_metadata.take(),
keysend_preimage: *keysend_preimage,
custom_tlvs: recipient_onion.custom_tlvs.clone(),
sender_intended_htlc_amt_msat: value_msat,
cltv_expiry_height: cltv,
});
);
}
} else {
let payload = msgs::OutboundOnionPayload::Forward {
@ -244,7 +297,7 @@ pub(super) fn build_onion_payloads(
amt_to_forward: value_msat,
outgoing_cltv_value: cltv,
};
res.insert(0, payload);
callback(PayloadCallbackAction::PushFront, payload);
}
cur_value_msat += hop.fee_msat;
if cur_value_msat >= 21000000 * 100000000 * 1000 {
@ -256,7 +309,78 @@ pub(super) fn build_onion_payloads(
}
last_short_channel_id = hop.short_channel_id;
}
Ok((res, cur_value_msat, cur_cltv))
Ok((cur_value_msat, cur_cltv))
}
pub(crate) const MIN_FINAL_VALUE_ESTIMATE_WITH_OVERPAY: u64 = 100_000_000;
pub(crate) fn set_max_path_length(
route_params: &mut RouteParameters, recipient_onion: &RecipientOnionFields,
keysend_preimage: Option<PaymentPreimage>, best_block_height: u32,
) -> Result<(), ()> {
const PAYLOAD_HMAC_LEN: usize = 32;
let unblinded_intermed_payload_len = msgs::OutboundOnionPayload::Forward {
short_channel_id: 42,
amt_to_forward: TOTAL_BITCOIN_SUPPLY_SATOSHIS,
outgoing_cltv_value: route_params.payment_params.max_total_cltv_expiry_delta,
}
.serialized_length()
.saturating_add(PAYLOAD_HMAC_LEN);
const OVERPAY_ESTIMATE_MULTIPLER: u64 = 3;
let final_value_msat_with_overpay_buffer = core::cmp::max(
route_params.final_value_msat.saturating_mul(OVERPAY_ESTIMATE_MULTIPLER),
MIN_FINAL_VALUE_ESTIMATE_WITH_OVERPAY,
);
let blinded_tail_opt = route_params
.payment_params
.payee
.blinded_route_hints()
.iter()
.map(|(_, path)| path)
.max_by_key(|path| path.serialized_length())
.map(|largest_path| BlindedTailHopIter {
hops: largest_path.blinded_hops.iter(),
blinding_point: largest_path.blinding_point,
final_value_msat: final_value_msat_with_overpay_buffer,
excess_final_cltv_expiry_delta: 0,
});
let unblinded_route_hop = RouteHop {
pubkey: PublicKey::from_slice(&[2; 33]).unwrap(),
node_features: NodeFeatures::empty(),
short_channel_id: 42,
channel_features: ChannelFeatures::empty(),
fee_msat: final_value_msat_with_overpay_buffer,
cltv_expiry_delta: route_params.payment_params.max_total_cltv_expiry_delta,
maybe_announced_channel: false,
};
let mut num_reserved_bytes: usize = 0;
let build_payloads_res = build_onion_payloads_callback(
core::iter::once(&unblinded_route_hop),
blinded_tail_opt,
final_value_msat_with_overpay_buffer,
&recipient_onion,
best_block_height,
&keysend_preimage,
|_, payload| {
num_reserved_bytes = num_reserved_bytes
.saturating_add(payload.serialized_length())
.saturating_add(PAYLOAD_HMAC_LEN);
},
);
debug_assert!(build_payloads_res.is_ok());
let max_path_length = 1300usize
.checked_sub(num_reserved_bytes)
.map(|p| p / unblinded_intermed_payload_len)
.and_then(|l| u8::try_from(l.saturating_add(1)).ok())
.ok_or(())?;
route_params.payment_params.max_path_length =
core::cmp::min(max_path_length, route_params.payment_params.max_path_length);
Ok(())
}
/// Length of the onion data packet. Before TLV-based onions this was 20 65-byte hops, though now
@ -1124,7 +1248,7 @@ where
/// `cur_block_height` should be set to the best known block height + 1.
pub fn create_payment_onion<T: secp256k1::Signing>(
secp_ctx: &Secp256k1<T>, path: &Path, session_priv: &SecretKey, total_msat: u64,
recipient_onion: RecipientOnionFields, cur_block_height: u32, payment_hash: &PaymentHash,
recipient_onion: &RecipientOnionFields, cur_block_height: u32, payment_hash: &PaymentHash,
keysend_preimage: &Option<PaymentPreimage>, prng_seed: [u8; 32],
) -> Result<(msgs::OnionPacket, u64, u32), APIError> {
let onion_keys = construct_onion_keys(&secp_ctx, &path, &session_priv).map_err(|_| {

View file

@ -17,6 +17,7 @@ use crate::sign::{EntropySource, NodeSigner, Recipient};
use crate::events::{self, PaymentFailureReason};
use crate::ln::types::{PaymentHash, PaymentPreimage, PaymentSecret};
use crate::ln::channelmanager::{ChannelDetails, EventCompletionAction, HTLCSource, PaymentId};
use crate::ln::onion_utils;
use crate::ln::onion_utils::{DecodedOnionFailure, HTLCFailReason};
use crate::offers::invoice::Bolt12Invoice;
use crate::routing::router::{BlindedTail, InFlightHtlcs, Path, PaymentParameters, Route, RouteParameters, Router};
@ -421,6 +422,12 @@ pub enum RetryableSendFailure {
/// [`Event::PaymentSent`]: crate::events::Event::PaymentSent
/// [`Event::PaymentFailed`]: crate::events::Event::PaymentFailed
DuplicatePayment,
/// The [`RecipientOnionFields::payment_metadata`], [`RecipientOnionFields::custom_tlvs`], or
/// [`BlindedPath`]s provided are too large and caused us to exceed the maximum onion packet size
/// of 1300 bytes.
///
/// [`BlindedPath`]: crate::blinded_path::BlindedPath
OnionPacketSizeExceeded,
}
/// If a payment fails to send with [`ChannelManager::send_payment_with_route`], it can be in one
@ -659,7 +666,7 @@ impl RecipientOnionFields {
pub(super) struct SendAlongPathArgs<'a> {
pub path: &'a Path,
pub payment_hash: &'a PaymentHash,
pub recipient_onion: RecipientOnionFields,
pub recipient_onion: &'a RecipientOnionFields,
pub total_value: u64,
pub cur_height: u32,
pub payment_id: PaymentId,
@ -711,7 +718,7 @@ impl OutboundPayments {
F: Fn(SendAlongPathArgs) -> Result<(), APIError>
{
let onion_session_privs = self.add_new_pending_payment(payment_hash, recipient_onion.clone(), payment_id, None, route, None, None, entropy_source, best_block_height)?;
self.pay_route_internal(route, payment_hash, recipient_onion, None, payment_id, None,
self.pay_route_internal(route, payment_hash, &recipient_onion, None, payment_id, None,
onion_session_privs, node_signer, best_block_height, &send_payment_along_path)
.map_err(|e| { self.remove_outbound_if_all_failed(payment_id, &e); e })
}
@ -756,7 +763,7 @@ impl OutboundPayments {
let onion_session_privs = self.add_new_pending_payment(payment_hash, recipient_onion.clone(),
payment_id, Some(preimage), &route, None, None, entropy_source, best_block_height)?;
match self.pay_route_internal(route, payment_hash, recipient_onion, Some(preimage),
match self.pay_route_internal(route, payment_hash, &recipient_onion, Some(preimage),
payment_id, None, onion_session_privs, node_signer, best_block_height, &send_payment_along_path
) {
Ok(()) => Ok(payment_hash),
@ -886,7 +893,7 @@ impl OutboundPayments {
/// [`Event::PaymentFailed`]: crate::events::Event::PaymentFailed
fn send_payment_internal<R: Deref, NS: Deref, ES: Deref, IH, SP, L: Deref>(
&self, payment_id: PaymentId, payment_hash: PaymentHash, recipient_onion: RecipientOnionFields,
keysend_preimage: Option<PaymentPreimage>, retry_strategy: Retry, route_params: RouteParameters,
keysend_preimage: Option<PaymentPreimage>, retry_strategy: Retry, mut route_params: RouteParameters,
router: &R, first_hops: Vec<ChannelDetails>, inflight_htlcs: IH, entropy_source: &ES,
node_signer: &NS, best_block_height: u32, logger: &L,
pending_events: &Mutex<VecDeque<(events::Event, Option<EventCompletionAction>)>>, send_payment_along_path: SP,
@ -907,6 +914,15 @@ impl OutboundPayments {
}
}
onion_utils::set_max_path_length(
&mut route_params, &recipient_onion, keysend_preimage, best_block_height
)
.map_err(|()| {
log_error!(logger, "Can't construct an onion packet without exceeding 1300-byte onion \
hop_data length for payment with id {} and hash {}", payment_id, payment_hash);
RetryableSendFailure::OnionPacketSizeExceeded
})?;
let mut route = router.find_route_with_id(
&node_signer.get_node_id(Recipient::Node).unwrap(), &route_params,
Some(&first_hops.iter().collect::<Vec<_>>()), inflight_htlcs(),
@ -932,8 +948,9 @@ impl OutboundPayments {
RetryableSendFailure::DuplicatePayment
})?;
let res = self.pay_route_internal(&route, payment_hash, recipient_onion, keysend_preimage, payment_id, None,
onion_session_privs, node_signer, best_block_height, &send_payment_along_path);
let res = self.pay_route_internal(&route, payment_hash, &recipient_onion,
keysend_preimage, payment_id, None, onion_session_privs, node_signer,
best_block_height, &send_payment_along_path);
log_info!(logger, "Sending payment with id {} and hash {} returned {:?}",
payment_id, payment_hash, res);
if let Err(e) = res {
@ -1090,7 +1107,7 @@ impl OutboundPayments {
}
}
};
let res = self.pay_route_internal(&route, payment_hash, recipient_onion, keysend_preimage,
let res = self.pay_route_internal(&route, payment_hash, &recipient_onion, keysend_preimage,
payment_id, Some(total_msat), onion_session_privs, node_signer, best_block_height,
&send_payment_along_path);
log_info!(logger, "Result retrying payment id {}: {:?}", &payment_id, res);
@ -1201,7 +1218,8 @@ impl OutboundPayments {
RecipientOnionFields::secret_only(payment_secret), payment_id, None, &route, None, None,
entropy_source, best_block_height)?;
match self.pay_route_internal(&route, payment_hash, RecipientOnionFields::spontaneous_empty(),
let recipient_onion_fields = RecipientOnionFields::spontaneous_empty();
match self.pay_route_internal(&route, payment_hash, &recipient_onion_fields,
None, payment_id, None, onion_session_privs, node_signer, best_block_height, &send_payment_along_path
) {
Ok(()) => Ok((payment_hash, payment_id)),
@ -1309,7 +1327,7 @@ impl OutboundPayments {
}
fn pay_route_internal<NS: Deref, F>(
&self, route: &Route, payment_hash: PaymentHash, recipient_onion: RecipientOnionFields,
&self, route: &Route, payment_hash: PaymentHash, recipient_onion: &RecipientOnionFields,
keysend_preimage: Option<PaymentPreimage>, payment_id: PaymentId, recv_value_msat: Option<u64>,
onion_session_privs: Vec<[u8; 32]>, node_signer: &NS, best_block_height: u32,
send_payment_along_path: &F
@ -1364,8 +1382,9 @@ impl OutboundPayments {
debug_assert_eq!(route.paths.len(), onion_session_privs.len());
for (path, session_priv_bytes) in route.paths.iter().zip(onion_session_privs.into_iter()) {
let mut path_res = send_payment_along_path(SendAlongPathArgs {
path: &path, payment_hash: &payment_hash, recipient_onion: recipient_onion.clone(),
total_value, cur_height, payment_id, keysend_preimage: &keysend_preimage, session_priv_bytes
path: &path, payment_hash: &payment_hash, recipient_onion, total_value,
cur_height, payment_id, keysend_preimage: &keysend_preimage,
session_priv_bytes
});
match path_res {
Ok(_) => {},
@ -1448,9 +1467,9 @@ impl OutboundPayments {
NS::Target: NodeSigner,
F: Fn(SendAlongPathArgs) -> Result<(), APIError>,
{
self.pay_route_internal(route, payment_hash, recipient_onion, keysend_preimage, payment_id,
recv_value_msat, onion_session_privs, node_signer, best_block_height,
&send_payment_along_path)
self.pay_route_internal(route, payment_hash, &recipient_onion,
keysend_preimage, payment_id, recv_value_msat, onion_session_privs,
node_signer, best_block_height, &send_payment_along_path)
.map_err(|e| { self.remove_outbound_if_all_failed(payment_id, &e); e })
}

View file

@ -4228,7 +4228,7 @@ fn peel_payment_onion_custom_tlvs() {
let payment_hash = PaymentHash(Sha256::hash(&keysend_preimage.0).to_byte_array());
let (onion_routing_packet, first_hop_msat, cltv_expiry) = onion_utils::create_payment_onion(
&secp_ctx, &route.paths[0], &session_priv, amt_msat, recipient_onion.clone(),
&secp_ctx, &route.paths[0], &session_priv, amt_msat, &recipient_onion,
nodes[0].best_block_info().1, &payment_hash, &Some(keysend_preimage), prng_seed
).unwrap();

View file

@ -13,10 +13,11 @@ use bitcoin::secp256k1::{PublicKey, Secp256k1, self};
use crate::blinded_path::{BlindedHop, BlindedPath, Direction, IntroductionNode};
use crate::blinded_path::payment::{ForwardNode, ForwardTlvs, PaymentConstraints, PaymentRelay, ReceiveTlvs};
use crate::ln::types::PaymentHash;
use crate::ln::channelmanager::{ChannelDetails, PaymentId, MIN_FINAL_CLTV_EXPIRY_DELTA};
use crate::ln::{PaymentHash, PaymentPreimage};
use crate::ln::channelmanager::{ChannelDetails, PaymentId, MIN_FINAL_CLTV_EXPIRY_DELTA, RecipientOnionFields};
use crate::ln::features::{BlindedHopFeatures, Bolt11InvoiceFeatures, Bolt12InvoiceFeatures, ChannelFeatures, NodeFeatures};
use crate::ln::msgs::{DecodeError, ErrorAction, LightningError, MAX_VALUE_MSAT};
use crate::ln::onion_utils;
use crate::offers::invoice::{BlindedPayInfo, Bolt12Invoice};
use crate::onion_message::messenger::{DefaultMessageRouter, Destination, MessageRouter, OnionMessagePath};
use crate::routing::gossip::{DirectedChannelInfo, EffectiveCapacity, ReadOnlyNetworkGraph, NetworkGraph, NodeId, RoutingFees};
@ -603,6 +604,17 @@ impl RouteParameters {
pub fn from_payment_params_and_value(payment_params: PaymentParameters, final_value_msat: u64) -> Self {
Self { payment_params, final_value_msat, max_total_routing_fee_msat: Some(final_value_msat / 100 + 50_000) }
}
/// Sets the maximum number of hops that can be included in a payment path, based on the provided
/// [`RecipientOnionFields`] and blinded paths.
pub fn set_max_path_length(
&mut self, recipient_onion: &RecipientOnionFields, is_keysend: bool, best_block_height: u32
) -> Result<(), ()> {
let keysend_preimage_opt = is_keysend.then(|| PaymentPreimage([42; 32]));
onion_utils::set_max_path_length(
self, recipient_onion, keysend_preimage_opt, best_block_height
)
}
}
impl Writeable for RouteParameters {
@ -654,6 +666,8 @@ const DEFAULT_MAX_CHANNEL_SATURATION_POW_HALF: u8 = 2;
// The median hop CLTV expiry delta currently seen in the network.
const MEDIAN_HOP_CLTV_EXPIRY_DELTA: u32 = 40;
/// Estimated maximum number of hops that can be included in a payment path. May be inaccurate if
/// payment metadata, custom TLVs, or blinded paths are included in the payment.
// During routing, we only consider paths shorter than our maximum length estimate.
// In the TLV onion format, there is no fixed maximum length, but the `hop_payloads`
// field is always 1300 bytes. As the `tlv_payload` for each hop may vary in length, we have to
@ -665,7 +679,7 @@ const MEDIAN_HOP_CLTV_EXPIRY_DELTA: u32 = 40;
// (payment_secret and total_msat) = 93 bytes for the final hop.
// Since the length of the potentially included `payment_metadata` is unknown to us, we round
// down from (1300-93) / 61 = 19.78... to arrive at a conservative estimate of 19.
const MAX_PATH_LENGTH_ESTIMATE: u8 = 19;
pub const MAX_PATH_LENGTH_ESTIMATE: u8 = 19;
/// Information used to route a payment.
#[derive(Clone, Debug, Hash, PartialEq, Eq)]
@ -684,6 +698,10 @@ pub struct PaymentParameters {
/// Defaults to [`DEFAULT_MAX_PATH_COUNT`].
pub max_path_count: u8,
/// The maximum number of [`Path::hops`] in any returned path.
/// Defaults to [`MAX_PATH_LENGTH_ESTIMATE`].
pub max_path_length: u8,
/// Selects the maximum share of a channel's total capacity which will be sent over a channel,
/// as a power of 1/2. A higher value prefers to send the payment using more MPP parts whereas
/// a lower value prefers to send larger MPP parts, potentially saturating channels and
@ -730,6 +748,7 @@ impl Writeable for PaymentParameters {
(8, *blinded_hints, optional_vec),
(9, self.payee.final_cltv_expiry_delta(), option),
(11, self.previously_failed_blinded_path_idxs, required_vec),
(13, self.max_path_length, required),
});
Ok(())
}
@ -749,6 +768,7 @@ impl ReadableArgs<u32> for PaymentParameters {
(8, blinded_route_hints, optional_vec),
(9, final_cltv_expiry_delta, (default_value, default_final_cltv_expiry_delta)),
(11, previously_failed_blinded_path_idxs, optional_vec),
(13, max_path_length, (default_value, MAX_PATH_LENGTH_ESTIMATE)),
});
let blinded_route_hints = blinded_route_hints.unwrap_or(vec![]);
let payee = if blinded_route_hints.len() != 0 {
@ -773,6 +793,7 @@ impl ReadableArgs<u32> for PaymentParameters {
expiry_time,
previously_failed_channels: previously_failed_channels.unwrap_or(Vec::new()),
previously_failed_blinded_path_idxs: previously_failed_blinded_path_idxs.unwrap_or(Vec::new()),
max_path_length: _init_tlv_based_struct_field!(max_path_length, (default_value, unused)),
})
}
}
@ -789,6 +810,7 @@ impl PaymentParameters {
expiry_time: None,
max_total_cltv_expiry_delta: DEFAULT_MAX_TOTAL_CLTV_EXPIRY_DELTA,
max_path_count: DEFAULT_MAX_PATH_COUNT,
max_path_length: MAX_PATH_LENGTH_ESTIMATE,
max_channel_saturation_power_of_half: DEFAULT_MAX_CHANNEL_SATURATION_POW_HALF,
previously_failed_channels: Vec::new(),
previously_failed_blinded_path_idxs: Vec::new(),
@ -828,6 +850,7 @@ impl PaymentParameters {
expiry_time: None,
max_total_cltv_expiry_delta: DEFAULT_MAX_TOTAL_CLTV_EXPIRY_DELTA,
max_path_count: DEFAULT_MAX_PATH_COUNT,
max_path_length: MAX_PATH_LENGTH_ESTIMATE,
max_channel_saturation_power_of_half: DEFAULT_MAX_CHANNEL_SATURATION_POW_HALF,
previously_failed_channels: Vec::new(),
previously_failed_blinded_path_idxs: Vec::new(),
@ -1849,6 +1872,7 @@ pub(crate) fn get_route<L: Deref, S: ScoreLookUp>(
where L::Target: Logger {
let payment_params = &route_params.payment_params;
let max_path_length = core::cmp::min(payment_params.max_path_length, MAX_PATH_LENGTH_ESTIMATE);
let final_value_msat = route_params.final_value_msat;
// If we're routing to a blinded recipient, we won't have their node id. Therefore, keep the
// unblinded payee id as an option. We also need a non-optional "payee id" for path construction,
@ -2145,8 +2169,9 @@ where L::Target: Logger {
// Verify the liquidity offered by this channel complies to the minimal contribution.
let contributes_sufficient_value = available_value_contribution_msat >= minimal_value_contribution_msat;
// Do not consider candidate hops that would exceed the maximum path length.
let path_length_to_node = $next_hops_path_length + 1;
let exceeds_max_path_length = path_length_to_node > MAX_PATH_LENGTH_ESTIMATE;
let path_length_to_node = $next_hops_path_length
+ if $candidate.blinded_hint_idx().is_some() { 0 } else { 1 };
let exceeds_max_path_length = path_length_to_node > max_path_length;
// Do not consider candidates that exceed the maximum total cltv expiry limit.
// In order to already account for some of the privacy enhancing random CLTV
@ -2600,9 +2625,8 @@ where L::Target: Logger {
};
let path_min = candidate.htlc_minimum_msat().saturating_add(
compute_fees_saturating(candidate.htlc_minimum_msat(), candidate.fees()));
add_entry!(&first_hop_candidate, blinded_path_fee,
path_contribution_msat, path_min, 0_u64, candidate.cltv_expiry_delta(),
candidate.blinded_path().map_or(1, |bp| bp.blinded_hops.len() as u8));
add_entry!(&first_hop_candidate, blinded_path_fee, path_contribution_msat, path_min,
0_u64, candidate.cltv_expiry_delta(), 0);
}
}
}
@ -3362,7 +3386,7 @@ mod tests {
fn simple_route_test() {
let (secp_ctx, network_graph, _, _, logger) = build_graph();
let (_, our_id, _, nodes) = get_nodes(&secp_ctx);
let payment_params = PaymentParameters::from_node_id(nodes[2], 42);
let mut payment_params = PaymentParameters::from_node_id(nodes[2], 42);
let scorer = ln_test_utils::TestScorer::new();
let keys_manager = ln_test_utils::TestKeysInterface::new(&[0u8; 32], Network::Testnet);
let random_seed_bytes = keys_manager.get_secure_random_bytes();
@ -3377,7 +3401,8 @@ mod tests {
assert_eq!(err, "Cannot send a payment of 0 msat");
} else { panic!(); }
let route_params = RouteParameters::from_payment_params_and_value(payment_params, 100);
payment_params.max_path_length = 2;
let mut route_params = RouteParameters::from_payment_params_and_value(payment_params, 100);
let route = get_route(&our_id, &route_params, &network_graph.read_only(), None,
Arc::clone(&logger), &scorer, &Default::default(), &random_seed_bytes).unwrap();
assert_eq!(route.paths[0].hops.len(), 2);
@ -3395,6 +3420,10 @@ mod tests {
assert_eq!(route.paths[0].hops[1].cltv_expiry_delta, 42);
assert_eq!(route.paths[0].hops[1].node_features.le_flags(), &id_to_feature_flags(3));
assert_eq!(route.paths[0].hops[1].channel_features.le_flags(), &id_to_feature_flags(4));
route_params.payment_params.max_path_length = 1;
get_route(&our_id, &route_params, &network_graph.read_only(), None,
Arc::clone(&logger), &scorer, &Default::default(), &random_seed_bytes).unwrap_err();
}
#[test]
@ -3799,7 +3828,7 @@ mod tests {
});
// If all the channels require some features we don't understand, route should fail
let route_params = RouteParameters::from_payment_params_and_value(payment_params, 100);
let mut route_params = RouteParameters::from_payment_params_and_value(payment_params, 100);
if let Err(LightningError{err, action: ErrorAction::IgnoreError}) = get_route(&our_id,
&route_params, &network_graph.read_only(), None, Arc::clone(&logger), &scorer,
&Default::default(), &random_seed_bytes) {
@ -3809,6 +3838,7 @@ mod tests {
// If we specify a channel to node7, that overrides our local channel view and that gets used
let our_chans = vec![get_channel_details(Some(42), nodes[7].clone(),
InitFeatures::from_le_bytes(vec![0b11]), 250_000_000)];
route_params.payment_params.max_path_length = 2;
let route = get_route(&our_id, &route_params, &network_graph.read_only(),
Some(&our_chans.iter().collect::<Vec<_>>()), Arc::clone(&logger), &scorer,
&Default::default(), &random_seed_bytes).unwrap();
@ -4055,8 +4085,9 @@ mod tests {
} else { panic!(); }
}
let payment_params = PaymentParameters::from_node_id(nodes[6], 42)
let mut payment_params = PaymentParameters::from_node_id(nodes[6], 42)
.with_route_hints(last_hops_multi_private_channels(&nodes)).unwrap();
payment_params.max_path_length = 5;
let route_params = RouteParameters::from_payment_params_and_value(payment_params, 100);
let route = get_route(&our_id, &route_params, &network_graph.read_only(), None,
Arc::clone(&logger), &scorer, &Default::default(), &random_seed_bytes).unwrap();
@ -4214,7 +4245,8 @@ mod tests {
let keys_manager = ln_test_utils::TestKeysInterface::new(&[0u8; 32], Network::Testnet);
let random_seed_bytes = keys_manager.get_secure_random_bytes();
// Test through channels 2, 3, 0xff00, 0xff01.
// Test shows that multiple hop hints are considered.
// Test shows that multi-hop route hints are considered and factored correctly into the
// max path length.
// Disabling channels 6 & 7 by flags=2
update_channel(&gossip_sync, &secp_ctx, &privkeys[2], UnsignedChannelUpdate {
@ -4242,7 +4274,8 @@ mod tests {
excess_data: Vec::new()
});
let route_params = RouteParameters::from_payment_params_and_value(payment_params, 100);
let mut route_params = RouteParameters::from_payment_params_and_value(payment_params, 100);
route_params.payment_params.max_path_length = 4;
let route = get_route(&our_id, &route_params, &network_graph.read_only(), None,
Arc::clone(&logger), &scorer, &Default::default(), &random_seed_bytes).unwrap();
assert_eq!(route.paths[0].hops.len(), 4);
@ -4274,6 +4307,9 @@ mod tests {
assert_eq!(route.paths[0].hops[3].cltv_expiry_delta, 42);
assert_eq!(route.paths[0].hops[3].node_features.le_flags(), default_node_features().le_flags()); // We dont pass flags in from invoices yet
assert_eq!(route.paths[0].hops[3].channel_features.le_flags(), &Vec::<u8>::new()); // We can't learn any flags from invoices, sadly
route_params.payment_params.max_path_length = 3;
get_route(&our_id, &route_params, &network_graph.read_only(), None,
Arc::clone(&logger), &scorer, &Default::default(), &random_seed_bytes).unwrap_err();
}
#[test]

View file

@ -112,7 +112,7 @@ pub struct TestRouter<'a> {
>,
//pub entropy_source: &'a RandomBytes,
pub network_graph: Arc<NetworkGraph<&'a TestLogger>>,
pub next_routes: Mutex<VecDeque<(RouteParameters, Result<Route, LightningError>)>>,
pub next_routes: Mutex<VecDeque<(RouteParameters, Option<Result<Route, LightningError>>)>>,
pub scorer: &'a RwLock<TestScorer>,
}
@ -132,7 +132,12 @@ impl<'a> TestRouter<'a> {
pub fn expect_find_route(&self, query: RouteParameters, result: Result<Route, LightningError>) {
let mut expected_routes = self.next_routes.lock().unwrap();
expected_routes.push_back((query, result));
expected_routes.push_back((query, Some(result)));
}
pub fn expect_find_route_query(&self, query: RouteParameters) {
let mut expected_routes = self.next_routes.lock().unwrap();
expected_routes.push_back((query, None));
}
}
@ -145,63 +150,67 @@ impl<'a> Router for TestRouter<'a> {
let next_route_opt = self.next_routes.lock().unwrap().pop_front();
if let Some((find_route_query, find_route_res)) = next_route_opt {
assert_eq!(find_route_query, *params);
if let Ok(ref route) = find_route_res {
assert_eq!(route.route_params, Some(find_route_query));
let scorer = self.scorer.read().unwrap();
let scorer = ScorerAccountingForInFlightHtlcs::new(scorer, &inflight_htlcs);
for path in &route.paths {
let mut aggregate_msat = 0u64;
let mut prev_hop_node = payer;
for (idx, hop) in path.hops.iter().rev().enumerate() {
aggregate_msat += hop.fee_msat;
let usage = ChannelUsage {
amount_msat: aggregate_msat,
inflight_htlc_msat: 0,
effective_capacity: EffectiveCapacity::Unknown,
};
if let Some(res) = find_route_res {
if let Ok(ref route) = res {
assert_eq!(route.route_params, Some(find_route_query));
let scorer = self.scorer.read().unwrap();
let scorer = ScorerAccountingForInFlightHtlcs::new(scorer, &inflight_htlcs);
for path in &route.paths {
let mut aggregate_msat = 0u64;
let mut prev_hop_node = payer;
for (idx, hop) in path.hops.iter().rev().enumerate() {
aggregate_msat += hop.fee_msat;
let usage = ChannelUsage {
amount_msat: aggregate_msat,
inflight_htlc_msat: 0,
effective_capacity: EffectiveCapacity::Unknown,
};
if idx == path.hops.len() - 1 {
if let Some(first_hops) = first_hops {
if let Some(idx) = first_hops.iter().position(|h| h.get_outbound_payment_scid() == Some(hop.short_channel_id)) {
let node_id = NodeId::from_pubkey(payer);
let candidate = CandidateRouteHop::FirstHop(FirstHopCandidate {
details: first_hops[idx],
payer_node_id: &node_id,
});
scorer.channel_penalty_msat(&candidate, usage, &Default::default());
continue;
if idx == path.hops.len() - 1 {
if let Some(first_hops) = first_hops {
if let Some(idx) = first_hops.iter().position(|h| h.get_outbound_payment_scid() == Some(hop.short_channel_id)) {
let node_id = NodeId::from_pubkey(payer);
let candidate = CandidateRouteHop::FirstHop(FirstHopCandidate {
details: first_hops[idx],
payer_node_id: &node_id,
});
scorer.channel_penalty_msat(&candidate, usage, &Default::default());
continue;
}
}
}
let network_graph = self.network_graph.read_only();
if let Some(channel) = network_graph.channel(hop.short_channel_id) {
let (directed, _) = channel.as_directed_to(&NodeId::from_pubkey(&hop.pubkey)).unwrap();
let candidate = CandidateRouteHop::PublicHop(PublicHopCandidate {
info: directed,
short_channel_id: hop.short_channel_id,
});
scorer.channel_penalty_msat(&candidate, usage, &Default::default());
} else {
let target_node_id = NodeId::from_pubkey(&hop.pubkey);
let route_hint = RouteHintHop {
src_node_id: *prev_hop_node,
short_channel_id: hop.short_channel_id,
fees: RoutingFees { base_msat: 0, proportional_millionths: 0 },
cltv_expiry_delta: 0,
htlc_minimum_msat: None,
htlc_maximum_msat: None,
};
let candidate = CandidateRouteHop::PrivateHop(PrivateHopCandidate {
hint: &route_hint,
target_node_id: &target_node_id,
});
scorer.channel_penalty_msat(&candidate, usage, &Default::default());
}
prev_hop_node = &hop.pubkey;
}
let network_graph = self.network_graph.read_only();
if let Some(channel) = network_graph.channel(hop.short_channel_id) {
let (directed, _) = channel.as_directed_to(&NodeId::from_pubkey(&hop.pubkey)).unwrap();
let candidate = CandidateRouteHop::PublicHop(PublicHopCandidate {
info: directed,
short_channel_id: hop.short_channel_id,
});
scorer.channel_penalty_msat(&candidate, usage, &Default::default());
} else {
let target_node_id = NodeId::from_pubkey(&hop.pubkey);
let route_hint = RouteHintHop {
src_node_id: *prev_hop_node,
short_channel_id: hop.short_channel_id,
fees: RoutingFees { base_msat: 0, proportional_millionths: 0 },
cltv_expiry_delta: 0,
htlc_minimum_msat: None,
htlc_maximum_msat: None,
};
let candidate = CandidateRouteHop::PrivateHop(PrivateHopCandidate {
hint: &route_hint,
target_node_id: &target_node_id,
});
scorer.channel_penalty_msat(&candidate, usage, &Default::default());
}
prev_hop_node = &hop.pubkey;
}
}
route_res = res;
} else {
route_res = self.router.find_route(payer, params, first_hops, inflight_htlcs);
}
route_res = find_route_res;
} else {
route_res = self.router.find_route(payer, params, first_hops, inflight_htlcs);
};
@ -552,7 +561,7 @@ impl<Signer: sign::ecdsa::EcdsaChannelSigner> chainmonitor::Persist<Signer> for
ret
}
fn archive_persisted_channel(&self, funding_txo: OutPoint) {
fn archive_persisted_channel(&self, funding_txo: OutPoint) {
// remove the channel from the offchain_monitor_updates map
self.offchain_monitor_updates.lock().unwrap().remove(&funding_txo);
}
@ -1371,7 +1380,7 @@ impl TestChainSource {
}
}
pub fn remove_watched_txn_and_outputs(&self, outpoint: OutPoint, script_pubkey: ScriptBuf) {
self.watched_outputs.lock().unwrap().remove(&(outpoint, script_pubkey.clone()));
self.watched_outputs.lock().unwrap().remove(&(outpoint, script_pubkey.clone()));
self.watched_txn.lock().unwrap().remove(&(outpoint.txid, script_pubkey));
}
}

View file

@ -196,6 +196,7 @@
./lightning/src/ln/functional_test_utils.rs
./lightning/src/ln/functional_tests.rs
./lightning/src/ln/inbound_payment.rs
./lightning/src/ln/max_payment_path_len_tests.rs
./lightning/src/ln/mod.rs
./lightning/src/ln/monitor_tests.rs
./lightning/src/ln/msgs.rs