Skip to content

Set max path length when paying BOLT 12 invoices. #3156

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
54 changes: 52 additions & 2 deletions lightning/src/ln/max_payment_path_len_tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,18 +10,21 @@
//! 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;
Expand Down Expand Up @@ -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);
}
14 changes: 14 additions & 0 deletions lightning/src/ln/outbound_payment.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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);
}
Expand Down
19 changes: 15 additions & 4 deletions lightning/src/util/test_utils.rs
Original file line number Diff line number Diff line change
Expand Up @@ -116,9 +116,9 @@ pub struct TestRouter<'a> {
(),
TestScorer,
>,
//pub entropy_source: &'a RandomBytes,
pub network_graph: Arc<NetworkGraph<&'a TestLogger>>,
pub next_routes: Mutex<VecDeque<(RouteParameters, Option<Result<Route, LightningError>>)>>,
pub next_blinded_payment_paths: Mutex<Vec<(BlindedPayInfo, BlindedPath)>>,
pub scorer: &'a RwLock<TestScorer>,
}

Expand All @@ -132,6 +132,7 @@ impl<'a> TestRouter<'a> {
router: DefaultRouter::new(network_graph.clone(), logger, entropy_source, scorer, ()),
network_graph,
next_routes: Mutex::new(VecDeque::new()),
next_blinded_payment_paths: Mutex::new(Vec::new()),
scorer,
}
}
Expand All @@ -145,6 +146,11 @@ impl<'a> TestRouter<'a> {
let mut expected_routes = self.next_routes.lock().unwrap();
expected_routes.push_back((query, None));
}

pub fn expect_blinded_payment_paths(&self, mut paths: Vec<(BlindedPayInfo, BlindedPath)>) {
let mut expected_paths = self.next_blinded_payment_paths.lock().unwrap();
core::mem::swap(&mut *expected_paths, &mut paths);
}
}

impl<'a> Router for TestRouter<'a> {
Expand Down Expand Up @@ -236,9 +242,14 @@ impl<'a> Router for TestRouter<'a> {
&self, recipient: PublicKey, first_hops: Vec<ChannelDetails>, tlvs: ReceiveTlvs,
amount_msats: u64, secp_ctx: &Secp256k1<T>,
) -> Result<Vec<(BlindedPayInfo, BlindedPath)>, ()> {
self.router.create_blinded_payment_paths(
recipient, first_hops, tlvs, amount_msats, secp_ctx
)
let mut expected_paths = self.next_blinded_payment_paths.lock().unwrap();
if expected_paths.is_empty() {
self.router.create_blinded_payment_paths(
recipient, first_hops, tlvs, amount_msats, secp_ctx
)
} else {
Ok(core::mem::take(&mut *expected_paths))
}
}
}

Expand Down
Loading