]> git.bitcoin.ninja Git - rust-lightning/commitdiff
Set max path length when paying BOLT 12 invoices.
authorValentine Wallace <vwallace@protonmail.com>
Wed, 3 Jul 2024 18:31:33 +0000 (14:31 -0400)
committerValentine Wallace <vwallace@protonmail.com>
Mon, 8 Jul 2024 14:35:48 +0000 (10:35 -0400)
lightning/src/ln/max_payment_path_len_tests.rs
lightning/src/ln/outbound_payment.rs

index 7bd605c9ddfeeeee275fb636b8ae38f8a1b4ff30..a77d91e89c7447239f1ed6d05eae67dcc02d6e8b 100644 (file)
 //! 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 bitcoin::secp256k1::{Secp256k1, PublicKey};
+use crate::blinded_path::{BlindedHop, BlindedPath, IntroductionNode};
 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::features::BlindedHopFeatures;
 use crate::ln::functional_test_utils::*;
 use crate::ln::msgs;
+use crate::ln::msgs::OnionMessageHandler;
 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::offers::invoice::BlindedPayInfo;
 use crate::prelude::*;
 use crate::routing::router::{DEFAULT_MAX_TOTAL_CLTV_EXPIRY_DELTA, PaymentParameters, RouteParameters};
 use crate::util::errors::APIError;
@@ -340,3 +343,50 @@ fn blinded_path_with_custom_tlv() {
                        .with_custom_tlvs(recipient_onion_allows_2_hops.custom_tlvs)
        );
 }
+
+#[test]
+fn bolt12_invoice_too_large_blinded_paths() {
+       // Check that we'll fail paying BOLT 12 invoices with too-large blinded paths prior to
+       // pathfinding.
+       let chanmon_cfgs = create_chanmon_cfgs(2);
+       let node_cfgs = create_node_cfgs(2, &chanmon_cfgs);
+       let node_chanmgrs = create_node_chanmgrs(2, &node_cfgs, &[None, None]);
+       let nodes = create_network(2, &node_cfgs, &node_chanmgrs);
+       create_announced_chan_between_nodes(&nodes, 0, 1);
+
+       nodes[1].router.expect_blinded_payment_paths(vec![(
+               BlindedPayInfo {
+                       fee_base_msat: 42,
+                       fee_proportional_millionths: 42,
+                       cltv_expiry_delta: 42,
+                       htlc_minimum_msat: 42,
+                       htlc_maximum_msat: 42_000_000,
+                       features: BlindedHopFeatures::empty(),
+               },
+               BlindedPath {
+                       introduction_node: IntroductionNode::NodeId(PublicKey::from_slice(&[2; 33]).unwrap()),
+                       blinding_point: PublicKey::from_slice(&[2; 33]).unwrap(),
+                       blinded_hops: vec![
+                               BlindedHop {
+                                       blinded_node_id: PublicKey::from_slice(&[2; 33]).unwrap(),
+                                       encrypted_payload: vec![42; 1300],
+                               },
+                               BlindedHop {
+                                       blinded_node_id: PublicKey::from_slice(&[2; 33]).unwrap(),
+                                       encrypted_payload: vec![42; 1300],
+                               },
+                       ],
+               }
+       )]);
+
+       let offer = nodes[1].node.create_offer_builder(None).unwrap().build().unwrap();
+       let payment_id = PaymentId([1; 32]);
+       nodes[0].node.pay_for_offer(&offer, None, Some(5000), None, payment_id, Retry::Attempts(0), None).unwrap();
+       let invreq_om = nodes[0].onion_messenger.next_onion_message_for_peer(nodes[1].node.get_our_node_id()).unwrap();
+       nodes[1].onion_messenger.handle_onion_message(&nodes[0].node.get_our_node_id(), &invreq_om);
+
+       let invoice_om = nodes[1].onion_messenger.next_onion_message_for_peer(nodes[0].node.get_our_node_id()).unwrap();
+       nodes[0].onion_messenger.handle_onion_message(&nodes[1].node.get_our_node_id(), &invoice_om);
+       // TODO: assert on the invoice error once we support replying to invoice OMs with failure info
+       nodes[0].logger.assert_log_contains("lightning::ln::channelmanager", "Failed paying invoice: OnionPacketSizeExceeded", 1);
+}
index 1915a4c5a14db61b4fe00c5ace6f36203f5791c2..443a7b2c3a285098f849f2b01756182fdf363043 100644 (file)
@@ -510,6 +510,11 @@ pub enum Bolt12PaymentError {
        UnexpectedInvoice,
        /// Payment for an invoice with the corresponding [`PaymentId`] was already initiated.
        DuplicateInvoice,
+       /// The [`BlindedPath`]s provided are too large and caused us to exceed the maximum onion hop data
+       /// size of 1300 bytes.
+       ///
+       /// [`BlindedPath`]: crate::blinded_path::BlindedPath
+       OnionPacketSizeExceeded,
 }
 
 /// Indicates that we failed to send a payment probe. Further errors may be surfaced later via
@@ -837,6 +842,15 @@ impl OutboundPayments {
                let mut route_params = RouteParameters::from_payment_params_and_value(
                        payment_params, amount_msat
                );
+               onion_utils::set_max_path_length(
+                       &mut route_params, &RecipientOnionFields::spontaneous_empty(), None, 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);
+                               Bolt12PaymentError::OnionPacketSizeExceeded
+                       })?;
+
                if let Some(max_fee_msat) = max_total_routing_fee_msat {
                        route_params.max_total_routing_fee_msat = Some(max_fee_msat);
                }