mirror of
https://github.com/lightningdevkit/rust-lightning.git
synced 2025-02-24 23:08:36 +01:00
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:
commit
37bf61c49b
15 changed files with 697 additions and 159 deletions
|
@ -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| {
|
||||
|
|
|
@ -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
|
||||
})
|
||||
}
|
||||
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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 {
|
||||
|
|
326
lightning/src/ln/max_payment_path_len_tests.rs
Normal file
326
lightning/src/ln/max_payment_path_len_tests.rs
Normal 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);
|
||||
}
|
|
@ -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)]
|
||||
|
|
|
@ -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,
|
||||
};
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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(|_| {
|
||||
|
|
|
@ -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 })
|
||||
}
|
||||
|
||||
|
|
|
@ -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();
|
||||
|
||||
|
|
|
@ -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]
|
||||
|
|
|
@ -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));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
Loading…
Add table
Reference in a new issue