Merge pull request #2951 from jkczyz/2024-03-fix-sender-is-intro-node
authorMatt Corallo <649246+TheBlueMatt@users.noreply.github.com>
Wed, 20 Mar 2024 14:35:35 +0000 (14:35 +0000)
committerGitHub <noreply@github.com>
Wed, 20 Mar 2024 14:35:35 +0000 (14:35 +0000)
Fix sender is the introduction node onion messages

17 files changed:
ci/ci-tests.sh
lightning/src/blinded_path/payment.rs
lightning/src/events/mod.rs
lightning/src/ln/blinded_payment_tests.rs
lightning/src/ln/chanmon_update_fail_tests.rs
lightning/src/ln/channelmanager.rs
lightning/src/ln/functional_test_utils.rs
lightning/src/ln/msgs.rs
lightning/src/ln/offers_tests.rs
lightning/src/ln/reload_tests.rs
lightning/src/ln/shutdown_tests.rs
lightning/src/offers/offer.rs
lightning/src/onion_message/messenger.rs
lightning/src/routing/gossip.rs
lightning/src/routing/router.rs
pending_changelog/blinded-hop-features-optional.txt [new file with mode: 0644]
pending_changelog/relay-constraints-ser.txt [new file with mode: 0644]

index 59acb833f1f2d4fe5d7b35a03ef835bb1f682e85..5cae6d45de5f56a778fc95b2e78b5c5fd9f5ffb6 100755 (executable)
@@ -173,5 +173,7 @@ fi
 
 echo -e "\n\nTest cfg-flag builds"
 RUSTFLAGS="--cfg=taproot" cargo test --verbose --color always -p lightning
+[ "$CI_MINIMIZE_DISK_USAGE" != "" ] && cargo clean
 RUSTFLAGS="--cfg=async_signing" cargo test --verbose --color always -p lightning
+[ "$CI_MINIMIZE_DISK_USAGE" != "" ] && cargo clean
 RUSTFLAGS="--cfg=dual_funding" cargo test --verbose --color always -p lightning
index 106cd802caac2abb860a19ab0366eea69370bfd2..6467af568886bd1891a7e8c52ae6b5956736f6bd 100644 (file)
@@ -13,7 +13,7 @@ use crate::ln::features::BlindedHopFeatures;
 use crate::ln::msgs::DecodeError;
 use crate::offers::invoice::BlindedPayInfo;
 use crate::prelude::*;
-use crate::util::ser::{Readable, Writeable, Writer};
+use crate::util::ser::{HighZeroBytesDroppedBigSize, Readable, Writeable, Writer};
 
 use core::convert::TryFrom;
 
@@ -120,11 +120,14 @@ impl TryFrom<CounterpartyForwardingInfo> for PaymentRelay {
 
 impl Writeable for ForwardTlvs {
        fn write<W: Writer>(&self, w: &mut W) -> Result<(), io::Error> {
+               let features_opt =
+                       if self.features == BlindedHopFeatures::empty() { None }
+                       else { Some(&self.features) };
                encode_tlv_stream!(w, {
                        (2, self.short_channel_id, required),
                        (10, self.payment_relay, required),
                        (12, self.payment_constraints, required),
-                       (14, self.features, required)
+                       (14, features_opt, option)
                });
                Ok(())
        }
@@ -169,7 +172,7 @@ impl Readable for BlindedPaymentTlvs {
                                short_channel_id,
                                payment_relay: payment_relay.ok_or(DecodeError::InvalidValue)?,
                                payment_constraints: payment_constraints.0.unwrap(),
-                               features: features.ok_or(DecodeError::InvalidValue)?,
+                               features: features.unwrap_or_else(BlindedHopFeatures::empty),
                        }))
                } else {
                        if payment_relay.is_some() || features.is_some() { return Err(DecodeError::InvalidValue) }
@@ -276,16 +279,35 @@ pub(super) fn compute_payinfo(
        })
 }
 
-impl_writeable_msg!(PaymentRelay, {
-       cltv_expiry_delta,
-       fee_proportional_millionths,
-       fee_base_msat
-}, {});
+impl Writeable for PaymentRelay {
+       fn write<W: Writer>(&self, w: &mut W) -> Result<(), io::Error> {
+               self.cltv_expiry_delta.write(w)?;
+               self.fee_proportional_millionths.write(w)?;
+               HighZeroBytesDroppedBigSize(self.fee_base_msat).write(w)
+       }
+}
+impl Readable for PaymentRelay {
+       fn read<R: io::Read>(r: &mut R) -> Result<Self, DecodeError> {
+               let cltv_expiry_delta: u16 = Readable::read(r)?;
+               let fee_proportional_millionths: u32 = Readable::read(r)?;
+               let fee_base_msat: HighZeroBytesDroppedBigSize<u32> = Readable::read(r)?;
+               Ok(Self { cltv_expiry_delta, fee_proportional_millionths, fee_base_msat: fee_base_msat.0 })
+       }
+}
 
-impl_writeable_msg!(PaymentConstraints, {
-       max_cltv_expiry,
-       htlc_minimum_msat
-}, {});
+impl Writeable for PaymentConstraints {
+       fn write<W: Writer>(&self, w: &mut W) -> Result<(), io::Error> {
+               self.max_cltv_expiry.write(w)?;
+               HighZeroBytesDroppedBigSize(self.htlc_minimum_msat).write(w)
+       }
+}
+impl Readable for PaymentConstraints {
+       fn read<R: io::Read>(r: &mut R) -> Result<Self, DecodeError> {
+               let max_cltv_expiry: u32 = Readable::read(r)?;
+               let htlc_minimum_msat: HighZeroBytesDroppedBigSize<u64> = Readable::read(r)?;
+               Ok(Self { max_cltv_expiry, htlc_minimum_msat: htlc_minimum_msat.0 })
+       }
+}
 
 #[cfg(test)]
 mod tests {
index 485a23e0292e520c28d47a226fe7de51abdd9690..fcd37914e3dd99d6340925629cb5d3dfa7553c7a 100644 (file)
@@ -797,12 +797,24 @@ pub enum Event {
        /// This event is generated when a payment has been successfully forwarded through us and a
        /// forwarding fee earned.
        PaymentForwarded {
-               /// The incoming channel between the previous node and us. This is only `None` for events
-               /// generated or serialized by versions prior to 0.0.107.
+               /// The channel id of the incoming channel between the previous node and us.
+               ///
+               /// This is only `None` for events generated or serialized by versions prior to 0.0.107.
                prev_channel_id: Option<ChannelId>,
-               /// The outgoing channel between the next node and us. This is only `None` for events
-               /// generated or serialized by versions prior to 0.0.107.
+               /// The channel id of the outgoing channel between the next node and us.
+               ///
+               /// This is only `None` for events generated or serialized by versions prior to 0.0.107.
                next_channel_id: Option<ChannelId>,
+               /// The `user_channel_id` of the incoming channel between the previous node and us.
+               ///
+               /// This is only `None` for events generated or serialized by versions prior to 0.0.122.
+               prev_user_channel_id: Option<u128>,
+               /// The `user_channel_id` of the outgoing channel between the next node and us.
+               ///
+               /// This will be `None` if the payment was settled via an on-chain transaction. See the
+               /// caveat described for the `total_fee_earned_msat` field. Moreover it will be `None` for
+               /// events generated or serialized by versions prior to 0.0.122.
+               next_user_channel_id: Option<u128>,
                /// The total fee, in milli-satoshis, which was earned as a result of the payment.
                ///
                /// Note that if we force-closed the channel over which we forwarded an HTLC while the HTLC
@@ -1121,8 +1133,9 @@ impl Writeable for Event {
                                });
                        }
                        &Event::PaymentForwarded {
-                               total_fee_earned_msat, prev_channel_id, claim_from_onchain_tx,
-                               next_channel_id, outbound_amount_forwarded_msat, skimmed_fee_msat,
+                               prev_channel_id, next_channel_id, prev_user_channel_id, next_user_channel_id,
+                               total_fee_earned_msat, skimmed_fee_msat, claim_from_onchain_tx,
+                               outbound_amount_forwarded_msat,
                        } => {
                                7u8.write(writer)?;
                                write_tlv_fields!(writer, {
@@ -1132,6 +1145,8 @@ impl Writeable for Event {
                                        (3, next_channel_id, option),
                                        (5, outbound_amount_forwarded_msat, option),
                                        (7, skimmed_fee_msat, option),
+                                       (9, prev_user_channel_id, option),
+                                       (11, next_user_channel_id, option),
                                });
                        },
                        &Event::ChannelClosed { ref channel_id, ref user_channel_id, ref reason,
@@ -1427,12 +1442,14 @@ impl MaybeReadable for Event {
                        },
                        7u8 => {
                                let f = || {
-                                       let mut total_fee_earned_msat = None;
                                        let mut prev_channel_id = None;
-                                       let mut claim_from_onchain_tx = false;
                                        let mut next_channel_id = None;
-                                       let mut outbound_amount_forwarded_msat = None;
+                                       let mut prev_user_channel_id = None;
+                                       let mut next_user_channel_id = None;
+                                       let mut total_fee_earned_msat = None;
                                        let mut skimmed_fee_msat = None;
+                                       let mut claim_from_onchain_tx = false;
+                                       let mut outbound_amount_forwarded_msat = None;
                                        read_tlv_fields!(reader, {
                                                (0, total_fee_earned_msat, option),
                                                (1, prev_channel_id, option),
@@ -1440,10 +1457,13 @@ impl MaybeReadable for Event {
                                                (3, next_channel_id, option),
                                                (5, outbound_amount_forwarded_msat, option),
                                                (7, skimmed_fee_msat, option),
+                                               (9, prev_user_channel_id, option),
+                                               (11, next_user_channel_id, option),
                                        });
                                        Ok(Some(Event::PaymentForwarded {
-                                               total_fee_earned_msat, prev_channel_id, claim_from_onchain_tx, next_channel_id,
-                                               outbound_amount_forwarded_msat, skimmed_fee_msat,
+                                               prev_channel_id, next_channel_id, prev_user_channel_id,
+                                               next_user_channel_id, total_fee_earned_msat, skimmed_fee_msat,
+                                               claim_from_onchain_tx, outbound_amount_forwarded_msat,
                                        }))
                                };
                                f()
index 267929517d228ff36bfaca3df58e7d3fc7ddf7e0..7adac5472b543c911352bb068b0cb1f59247aebf 100644 (file)
@@ -20,7 +20,7 @@ use crate::ln::msgs;
 use crate::ln::msgs::ChannelMessageHandler;
 use crate::ln::onion_utils;
 use crate::ln::onion_utils::INVALID_ONION_BLINDING;
-use crate::ln::outbound_payment::Retry;
+use crate::ln::outbound_payment::{Retry, IDEMPOTENCY_TIMEOUT_TICKS};
 use crate::offers::invoice::BlindedPayInfo;
 use crate::prelude::*;
 use crate::routing::router::{Payee, PaymentParameters, RouteParameters};
@@ -28,11 +28,14 @@ use crate::util::config::UserConfig;
 use crate::util::test_utils;
 
 fn blinded_payment_path(
-       payment_secret: PaymentSecret, node_ids: Vec<PublicKey>,
-       channel_upds: &[&msgs::UnsignedChannelUpdate], keys_manager: &test_utils::TestKeysInterface
+       payment_secret: PaymentSecret, intro_node_min_htlc: u64, intro_node_max_htlc: u64,
+       node_ids: Vec<PublicKey>, channel_upds: &[&msgs::UnsignedChannelUpdate],
+       keys_manager: &test_utils::TestKeysInterface
 ) -> (BlindedPayInfo, BlindedPath) {
        let mut intermediate_nodes = Vec::new();
-       for (node_id, chan_upd) in node_ids.iter().zip(channel_upds) {
+       let mut intro_node_min_htlc_opt = Some(intro_node_min_htlc);
+       let mut intro_node_max_htlc_opt = Some(intro_node_max_htlc);
+       for (idx, (node_id, chan_upd)) in node_ids.iter().zip(channel_upds).enumerate() {
                intermediate_nodes.push(ForwardNode {
                        node_id: *node_id,
                        tlvs: ForwardTlvs {
@@ -44,34 +47,42 @@ fn blinded_payment_path(
                                },
                                payment_constraints: PaymentConstraints {
                                        max_cltv_expiry: u32::max_value(),
-                                       htlc_minimum_msat: chan_upd.htlc_minimum_msat,
+                                       htlc_minimum_msat: intro_node_min_htlc_opt.take()
+                                               .unwrap_or_else(|| channel_upds[idx - 1].htlc_minimum_msat),
                                },
                                features: BlindedHopFeatures::empty(),
                        },
-                       htlc_maximum_msat: chan_upd.htlc_maximum_msat,
+                       htlc_maximum_msat: intro_node_max_htlc_opt.take()
+                               .unwrap_or_else(|| channel_upds[idx - 1].htlc_maximum_msat),
                });
        }
        let payee_tlvs = ReceiveTlvs {
                payment_secret,
                payment_constraints: PaymentConstraints {
                        max_cltv_expiry: u32::max_value(),
-                       htlc_minimum_msat: channel_upds.last().unwrap().htlc_minimum_msat,
+                       htlc_minimum_msat:
+                               intro_node_min_htlc_opt.unwrap_or_else(|| channel_upds.last().unwrap().htlc_minimum_msat),
                },
        };
        let mut secp_ctx = Secp256k1::new();
        BlindedPath::new_for_payment(
                &intermediate_nodes[..], *node_ids.last().unwrap(), payee_tlvs,
-               channel_upds.last().unwrap().htlc_maximum_msat, TEST_FINAL_CLTV as u16, keys_manager, &secp_ctx
+               intro_node_max_htlc_opt.unwrap_or_else(|| channel_upds.last().unwrap().htlc_maximum_msat),
+               TEST_FINAL_CLTV as u16, keys_manager, &secp_ctx
        ).unwrap()
 }
 
 pub fn get_blinded_route_parameters(
-       amt_msat: u64, payment_secret: PaymentSecret, node_ids: Vec<PublicKey>,
-       channel_upds: &[&msgs::UnsignedChannelUpdate], keys_manager: &test_utils::TestKeysInterface
+       amt_msat: u64, payment_secret: PaymentSecret, intro_node_min_htlc: u64, intro_node_max_htlc: u64,
+       node_ids: Vec<PublicKey>, channel_upds: &[&msgs::UnsignedChannelUpdate],
+       keys_manager: &test_utils::TestKeysInterface
 ) -> RouteParameters {
        RouteParameters::from_payment_params_and_value(
                PaymentParameters::blinded(vec![
-                       blinded_payment_path(payment_secret, node_ids, channel_upds, keys_manager)
+                       blinded_payment_path(
+                               payment_secret, intro_node_min_htlc, intro_node_max_htlc, node_ids, channel_upds,
+                               keys_manager
+                       )
                ]), amt_msat
        )
 }
@@ -169,6 +180,70 @@ fn mpp_to_one_hop_blinded_path() {
        claim_payment_along_route(&nodes[0], expected_route, false, payment_preimage);
 }
 
+#[test]
+fn mpp_to_three_hop_blinded_paths() {
+       let chanmon_cfgs = create_chanmon_cfgs(6);
+       let node_cfgs = create_node_cfgs(6, &chanmon_cfgs);
+       let node_chanmgrs = create_node_chanmgrs(6, &node_cfgs, &[None, None, None, None, None, None]);
+       let nodes = create_network(6, &node_cfgs, &node_chanmgrs);
+
+       // Create this network topology so node 0 MPP's over 2 3-hop blinded paths:
+       //     n1 -- n3
+       //    /        \
+       // n0           n5
+       //    \        /
+       //     n2 -- n4
+       create_announced_chan_between_nodes(&nodes, 0, 1);
+       create_announced_chan_between_nodes(&nodes, 0, 2);
+       let chan_upd_1_3 = create_announced_chan_between_nodes(&nodes, 1, 3).0.contents;
+       let chan_upd_2_4 = create_announced_chan_between_nodes(&nodes, 2, 4).0.contents;
+       let chan_upd_3_5 = create_announced_chan_between_nodes(&nodes, 3, 5).0.contents;
+       let chan_upd_4_5 = create_announced_chan_between_nodes(&nodes, 4, 5).0.contents;
+
+       let amt_msat = 15_000_000;
+       let (payment_preimage, payment_hash, payment_secret) = get_payment_preimage_hash(&nodes[5], Some(amt_msat), None);
+       let route_params = {
+               let path_1_params = get_blinded_route_parameters(
+                       amt_msat, payment_secret, 1, 1_0000_0000, vec![
+                               nodes[1].node.get_our_node_id(), nodes[3].node.get_our_node_id(),
+                               nodes[5].node.get_our_node_id()
+                       ], &[&chan_upd_1_3, &chan_upd_3_5], &chanmon_cfgs[5].keys_manager
+               );
+               let path_2_params = get_blinded_route_parameters(
+                       amt_msat, payment_secret, 1, 1_0000_0000, vec![
+                               nodes[2].node.get_our_node_id(), nodes[4].node.get_our_node_id(),
+                               nodes[5].node.get_our_node_id()
+                       ], &[&chan_upd_2_4, &chan_upd_4_5], &chanmon_cfgs[5].keys_manager
+               );
+               let pay_params = PaymentParameters::blinded(
+                       vec![
+                               path_1_params.payment_params.payee.blinded_route_hints()[0].clone(),
+                               path_2_params.payment_params.payee.blinded_route_hints()[0].clone()
+                       ]
+               )
+                       .with_bolt12_features(channelmanager::provided_bolt12_invoice_features(&UserConfig::default()))
+                       .unwrap();
+               RouteParameters::from_payment_params_and_value(pay_params, amt_msat)
+       };
+
+       nodes[0].node.send_payment(payment_hash, RecipientOnionFields::spontaneous_empty(),
+               PaymentId(payment_hash.0), route_params, Retry::Attempts(0)).unwrap();
+       check_added_monitors(&nodes[0], 2);
+
+       let expected_route: &[&[&Node]] = &[&[&nodes[1], &nodes[3], &nodes[5]], &[&nodes[2], &nodes[4], &nodes[5]]];
+       let mut events = nodes[0].node.get_and_clear_pending_msg_events();
+       assert_eq!(events.len(), 2);
+
+       let ev = remove_first_msg_event_to_node(&nodes[1].node.get_our_node_id(), &mut events);
+       pass_along_path(&nodes[0], expected_route[0], amt_msat, payment_hash.clone(),
+               Some(payment_secret), ev.clone(), false, None);
+
+       let ev = remove_first_msg_event_to_node(&nodes[2].node.get_our_node_id(), &mut events);
+       pass_along_path(&nodes[0], expected_route[1], amt_msat, payment_hash.clone(),
+               Some(payment_secret), ev.clone(), true, None);
+       claim_payment_along_route(&nodes[0], expected_route, false, payment_preimage);
+}
+
 enum ForwardCheckFail {
        // Fail a check on the inbound onion payload. In this case, we underflow when calculating the
        // outgoing cltv_expiry.
@@ -181,63 +256,108 @@ enum ForwardCheckFail {
 
 #[test]
 fn forward_checks_failure() {
-       do_forward_checks_failure(ForwardCheckFail::InboundOnionCheck);
-       do_forward_checks_failure(ForwardCheckFail::ForwardPayloadEncodedAsReceive);
-       do_forward_checks_failure(ForwardCheckFail::OutboundChannelCheck);
+       do_forward_checks_failure(ForwardCheckFail::InboundOnionCheck, true);
+       do_forward_checks_failure(ForwardCheckFail::InboundOnionCheck, false);
+       do_forward_checks_failure(ForwardCheckFail::ForwardPayloadEncodedAsReceive, true);
+       do_forward_checks_failure(ForwardCheckFail::ForwardPayloadEncodedAsReceive, false);
+       do_forward_checks_failure(ForwardCheckFail::OutboundChannelCheck, true);
+       do_forward_checks_failure(ForwardCheckFail::OutboundChannelCheck, false);
 }
 
-fn do_forward_checks_failure(check: ForwardCheckFail) {
+fn do_forward_checks_failure(check: ForwardCheckFail, intro_fails: bool) {
        // Ensure we'll fail backwards properly if a forwarding check fails on initial update_add
        // receipt.
-       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 mut nodes = create_network(3, &node_cfgs, &node_chanmgrs);
+       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 mut nodes = create_network(4, &node_cfgs, &node_chanmgrs);
        // We need the session priv to construct a bogus onion packet later.
        *nodes[0].keys_manager.override_random_bytes.lock().unwrap() = Some([3; 32]);
        create_announced_chan_between_nodes_with_value(&nodes, 0, 1, 1_000_000, 0);
        let chan_upd_1_2 = create_announced_chan_between_nodes_with_value(&nodes, 1, 2, 1_000_000, 0).0.contents;
+       let chan_upd_2_3 = create_announced_chan_between_nodes_with_value(&nodes, 2, 3, 1_000_000, 0).0.contents;
 
        let amt_msat = 5000;
-       let (_, payment_hash, payment_secret) = get_payment_preimage_hash(&nodes[2], Some(amt_msat), None);
-       let route_params = get_blinded_route_parameters(amt_msat, payment_secret,
-               nodes.iter().skip(1).map(|n| n.node.get_our_node_id()).collect(), &[&chan_upd_1_2],
-               &chanmon_cfgs[2].keys_manager);
+       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,
+               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);
 
        let route = get_route(&nodes[0], &route_params).unwrap();
        node_cfgs[0].router.expect_find_route(route_params.clone(), Ok(route.clone()));
        nodes[0].node.send_payment(payment_hash, RecipientOnionFields::spontaneous_empty(), PaymentId(payment_hash.0), route_params, 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 ev = remove_first_msg_event_to_node(&nodes[1].node.get_our_node_id(), &mut events);
-       let mut payment_event = SendEvent::from_event(ev);
+       macro_rules! cause_error {
+               ($src_node_idx: expr, $target_node_idx: expr, $update_add: expr) => {
+                       match check {
+                               ForwardCheckFail::InboundOnionCheck => {
+                                       $update_add.cltv_expiry = 10; // causes outbound CLTV expiry to underflow
+                               },
+                               ForwardCheckFail::ForwardPayloadEncodedAsReceive => {
+                                       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();
+                                       // 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();
+                                       if $target_node_idx + 1 < nodes.len() {
+                                               onion_payloads.pop();
+                                       }
+                                       $update_add.onion_routing_packet = onion_utils::construct_onion_packet(onion_payloads, onion_keys, [0; 32], &payment_hash).unwrap();
+                               },
+                               ForwardCheckFail::OutboundChannelCheck => {
+                                       // The intro node will see that the next-hop peer is disconnected and fail the HTLC backwards.
+                                       nodes[$src_node_idx].node.peer_disconnected(&nodes[$target_node_idx].node.get_our_node_id());
+                               }
+                       }
+               }
+       }
 
-       let mut update_add = &mut payment_event.msgs[0];
-       match check {
-               ForwardCheckFail::InboundOnionCheck => {
-                       update_add.cltv_expiry = 10; // causes outbound CLTV expiry to underflow
-               },
-               ForwardCheckFail::ForwardPayloadEncodedAsReceive => {
-                       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();
-                       // 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();
-                       update_add.onion_routing_packet = onion_utils::construct_onion_packet(onion_payloads, onion_keys, [0; 32], &payment_hash).unwrap();
-               },
-               ForwardCheckFail::OutboundChannelCheck => {
-                       // The intro node will see that the next-hop peer is disconnected and fail the HTLC backwards.
-                       nodes[1].node.peer_disconnected(&nodes[2].node.get_our_node_id());
-               },
+       let mut updates_0_1 = get_htlc_update_msgs!(nodes[0], nodes[1].node.get_our_node_id());
+       let update_add = &mut updates_0_1.update_add_htlcs[0];
+
+       if intro_fails {
+               cause_error!(1, 2, update_add);
        }
-       nodes[1].node.handle_update_add_htlc(&nodes[0].node.get_our_node_id(), &payment_event.msgs[0]);
+
+       nodes[1].node.handle_update_add_htlc(&nodes[0].node.get_our_node_id(), &update_add);
        check_added_monitors!(nodes[1], 0);
-       do_commitment_signed_dance(&nodes[1], &nodes[0], &payment_event.commitment_msg, true, true);
+       do_commitment_signed_dance(&nodes[1], &nodes[0], &updates_0_1.commitment_signed, true, true);
+
+       if intro_fails {
+               let mut updates = get_htlc_update_msgs!(nodes[1], nodes[0].node.get_our_node_id());
+               nodes[0].node.handle_update_fail_htlc(&nodes[1].node.get_our_node_id(), &updates.update_fail_htlcs[0]);
+               do_commitment_signed_dance(&nodes[0], &nodes[1], &updates.commitment_signed, false, false);
+               expect_payment_failed_conditions(&nodes[0], payment_hash, false,
+                       PaymentFailedConditions::new().expected_htlc_error_data(INVALID_ONION_BLINDING, &[0; 32]));
+               return
+       }
+
+       expect_pending_htlcs_forwardable!(nodes[1]);
+       check_added_monitors!(nodes[1], 1);
+
+       let mut updates_1_2 = get_htlc_update_msgs!(nodes[1], nodes[2].node.get_our_node_id());
+       let mut update_add = &mut updates_1_2.update_add_htlcs[0];
+
+       cause_error!(2, 3, update_add);
+
+       nodes[2].node.handle_update_add_htlc(&nodes[1].node.get_our_node_id(), &update_add);
+       check_added_monitors!(nodes[2], 0);
+       do_commitment_signed_dance(&nodes[2], &nodes[1], &updates_1_2.commitment_signed, true, true);
+
+       let mut updates = get_htlc_update_msgs!(nodes[2], nodes[1].node.get_our_node_id());
+       let update_malformed = &mut updates.update_fail_malformed_htlcs[0];
+       assert_eq!(update_malformed.failure_code, INVALID_ONION_BLINDING);
+       assert_eq!(update_malformed.sha256_of_onion, [0; 32]);
+
+       // Ensure the intro node will properly blind the error if its downstream node failed to do so.
+       update_malformed.sha256_of_onion = [1; 32];
+       update_malformed.failure_code = INVALID_ONION_BLINDING ^ 1;
+       nodes[1].node.handle_update_fail_malformed_htlc(&nodes[2].node.get_our_node_id(), update_malformed);
+       do_commitment_signed_dance(&nodes[1], &nodes[2], &updates.commitment_signed, true, false);
 
        let mut updates = get_htlc_update_msgs!(nodes[1], nodes[0].node.get_our_node_id());
        nodes[0].node.handle_update_fail_htlc(&nodes[1].node.get_our_node_id(), &updates.update_fail_htlcs[0]);
@@ -259,7 +379,7 @@ fn failed_backwards_to_intro_node() {
 
        let amt_msat = 5000;
        let (_, payment_hash, payment_secret) = get_payment_preimage_hash(&nodes[2], Some(amt_msat), None);
-       let route_params = get_blinded_route_parameters(amt_msat, payment_secret,
+       let 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],
                &chanmon_cfgs[2].keys_manager);
 
@@ -315,26 +435,32 @@ enum ProcessPendingHTLCsCheck {
 
 #[test]
 fn forward_fail_in_process_pending_htlc_fwds() {
-       do_forward_fail_in_process_pending_htlc_fwds(ProcessPendingHTLCsCheck::FwdPeerDisconnected);
-       do_forward_fail_in_process_pending_htlc_fwds(ProcessPendingHTLCsCheck::FwdChannelClosed);
+       do_forward_fail_in_process_pending_htlc_fwds(ProcessPendingHTLCsCheck::FwdPeerDisconnected, true);
+       do_forward_fail_in_process_pending_htlc_fwds(ProcessPendingHTLCsCheck::FwdPeerDisconnected, false);
+       do_forward_fail_in_process_pending_htlc_fwds(ProcessPendingHTLCsCheck::FwdChannelClosed, true);
+       do_forward_fail_in_process_pending_htlc_fwds(ProcessPendingHTLCsCheck::FwdChannelClosed, false);
 }
-fn do_forward_fail_in_process_pending_htlc_fwds(check: ProcessPendingHTLCsCheck) {
+fn do_forward_fail_in_process_pending_htlc_fwds(check: ProcessPendingHTLCsCheck, intro_fails: bool) {
        // Ensure the intro node will error backwards properly if the HTLC fails in
        // process_pending_htlc_forwards.
-       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 mut nodes = create_network(3, &node_cfgs, &node_chanmgrs);
+       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 mut nodes = create_network(4, &node_cfgs, &node_chanmgrs);
        create_announced_chan_between_nodes_with_value(&nodes, 0, 1, 1_000_000, 0);
-       let (chan_upd_1_2, channel_id) = {
+       let (chan_upd_1_2, chan_id_1_2) = {
                let chan = create_announced_chan_between_nodes_with_value(&nodes, 1, 2, 1_000_000, 0);
                (chan.0.contents, chan.2)
        };
+       let (chan_upd_2_3, chan_id_2_3) = {
+               let chan = create_announced_chan_between_nodes_with_value(&nodes, 2, 3, 1_000_000, 0);
+               (chan.0.contents, chan.2)
+       };
 
        let amt_msat = 5000;
        let (_, payment_hash, payment_secret) = get_payment_preimage_hash(&nodes[2], Some(amt_msat), None);
-       let route_params = get_blinded_route_parameters(amt_msat, payment_secret,
-               nodes.iter().skip(1).map(|n| n.node.get_our_node_id()).collect(), &[&chan_upd_1_2],
+       let 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[2].keys_manager);
 
        nodes[0].node.send_payment(payment_hash, RecipientOnionFields::spontaneous_empty(), PaymentId(payment_hash.0), route_params, Retry::Attempts(0)).unwrap();
@@ -349,43 +475,80 @@ fn do_forward_fail_in_process_pending_htlc_fwds(check: ProcessPendingHTLCsCheck)
        check_added_monitors!(nodes[1], 0);
        do_commitment_signed_dance(&nodes[1], &nodes[0], &payment_event.commitment_msg, false, false);
 
-       match check {
-               ProcessPendingHTLCsCheck::FwdPeerDisconnected => {
-                       // Disconnect the next-hop peer so when we go to forward in process_pending_htlc_forwards, the
-                       // intro node will error backwards.
-                       nodes[1].node.peer_disconnected(&nodes[2].node.get_our_node_id());
-                       expect_pending_htlcs_forwardable!(nodes[1]);
-                       expect_pending_htlcs_forwardable_and_htlc_handling_failed_ignore!(nodes[1],
-                               vec![HTLCDestination::NextHopChannel { node_id: Some(nodes[2].node.get_our_node_id()), channel_id }]);
-               },
-               ProcessPendingHTLCsCheck::FwdChannelClosed => {
-                       // Force close the next-hop channel so when we go to forward in process_pending_htlc_forwards,
-                       // the intro node will error backwards.
-                       nodes[1].node.force_close_broadcasting_latest_txn(&channel_id, &nodes[2].node.get_our_node_id()).unwrap();
-                       let events = nodes[1].node.get_and_clear_pending_events();
-                       match events[0] {
-                               crate::events::Event::PendingHTLCsForwardable { .. } => {},
-                               _ => panic!("Unexpected event {:?}", events),
-                       };
-                       match events[1] {
-                               crate::events::Event::ChannelClosed { .. } => {},
-                               _ => panic!("Unexpected event {:?}", events),
+       macro_rules! cause_error {
+               ($prev_node: expr, $curr_node: expr, $next_node: expr, $failed_chan_id: expr, $failed_scid: expr) => {
+                       match check {
+                               ProcessPendingHTLCsCheck::FwdPeerDisconnected => {
+                                       // Disconnect the next-hop peer so when we go to forward in process_pending_htlc_forwards, the
+                                       // intro node will error backwards.
+                                       $curr_node.node.peer_disconnected(&$next_node.node.get_our_node_id());
+                                       expect_pending_htlcs_forwardable!($curr_node);
+                                       expect_pending_htlcs_forwardable_and_htlc_handling_failed_ignore!($curr_node,
+                                               vec![HTLCDestination::NextHopChannel { node_id: Some($next_node.node.get_our_node_id()), channel_id: $failed_chan_id }]);
+                               },
+                               ProcessPendingHTLCsCheck::FwdChannelClosed => {
+                                       // Force close the next-hop channel so when we go to forward in process_pending_htlc_forwards,
+                                       // the intro node will error backwards.
+                                       $curr_node.node.force_close_broadcasting_latest_txn(&$failed_chan_id, &$next_node.node.get_our_node_id()).unwrap();
+                                       let events = $curr_node.node.get_and_clear_pending_events();
+                                       match events[0] {
+                                               crate::events::Event::PendingHTLCsForwardable { .. } => {},
+                                               _ => panic!("Unexpected event {:?}", events),
+                                       };
+                                       match events[1] {
+                                               crate::events::Event::ChannelClosed { .. } => {},
+                                               _ => panic!("Unexpected event {:?}", events),
+                                       }
+
+                                       $curr_node.node.process_pending_htlc_forwards();
+                                       expect_pending_htlcs_forwardable_and_htlc_handling_failed_ignore!($curr_node,
+                                               vec![HTLCDestination::UnknownNextHop { requested_forward_scid: $failed_scid }]);
+                                       check_closed_broadcast(&$curr_node, 1, true);
+                                       check_added_monitors!($curr_node, 1);
+                                       $curr_node.node.process_pending_htlc_forwards();
+                               },
                        }
+               }
+       }
 
-                       nodes[1].node.process_pending_htlc_forwards();
-                       expect_pending_htlcs_forwardable_and_htlc_handling_failed_ignore!(nodes[1],
-                               vec![HTLCDestination::UnknownNextHop { requested_forward_scid: chan_upd_1_2.short_channel_id }]);
-                       check_closed_broadcast(&nodes[1], 1, true);
-                       check_added_monitors!(nodes[1], 1);
-                       nodes[1].node.process_pending_htlc_forwards();
-               },
+       if intro_fails {
+               cause_error!(nodes[0], nodes[1], nodes[2], chan_id_1_2, chan_upd_1_2.short_channel_id);
+               let mut updates = get_htlc_update_msgs!(nodes[1], nodes[0].node.get_our_node_id());
+               nodes[0].node.handle_update_fail_htlc(&nodes[1].node.get_our_node_id(), &updates.update_fail_htlcs[0]);
+               check_added_monitors!(nodes[1], 1);
+               do_commitment_signed_dance(&nodes[0], &nodes[1], &updates.commitment_signed, false, false);
+
+               expect_payment_failed_conditions(&nodes[0], payment_hash, false,
+                       PaymentFailedConditions::new().expected_htlc_error_data(INVALID_ONION_BLINDING, &[0; 32]));
+               return
        }
 
+       expect_pending_htlcs_forwardable!(nodes[1]);
+       check_added_monitors!(nodes[1], 1);
+
+       let mut updates_1_2 = get_htlc_update_msgs!(nodes[1], nodes[2].node.get_our_node_id());
+       let mut update_add = &mut updates_1_2.update_add_htlcs[0];
+       nodes[2].node.handle_update_add_htlc(&nodes[1].node.get_our_node_id(), &update_add);
+       check_added_monitors!(nodes[2], 0);
+       do_commitment_signed_dance(&nodes[2], &nodes[1], &updates_1_2.commitment_signed, true, true);
+
+       cause_error!(nodes[1], nodes[2], nodes[3], chan_id_2_3, chan_upd_2_3.short_channel_id);
+       check_added_monitors!(nodes[2], 1);
+
+       let mut updates = get_htlc_update_msgs!(nodes[2], nodes[1].node.get_our_node_id());
+       let update_malformed = &mut updates.update_fail_malformed_htlcs[0];
+       assert_eq!(update_malformed.failure_code, INVALID_ONION_BLINDING);
+       assert_eq!(update_malformed.sha256_of_onion, [0; 32]);
+
+       // Ensure the intro node will properly blind the error if its downstream node failed to do so.
+       update_malformed.sha256_of_onion = [1; 32];
+       update_malformed.failure_code = INVALID_ONION_BLINDING ^ 1;
+       nodes[1].node.handle_update_fail_malformed_htlc(&nodes[2].node.get_our_node_id(), update_malformed);
+       do_commitment_signed_dance(&nodes[1], &nodes[2], &updates.commitment_signed, true, false);
+
        let mut updates = get_htlc_update_msgs!(nodes[1], nodes[0].node.get_our_node_id());
        nodes[0].node.handle_update_fail_htlc(&nodes[1].node.get_our_node_id(), &updates.update_fail_htlcs[0]);
-       check_added_monitors!(nodes[1], 1);
        do_commitment_signed_dance(&nodes[0], &nodes[1], &updates.commitment_signed, false, false);
-
        expect_payment_failed_conditions(&nodes[0], payment_hash, false,
                PaymentFailedConditions::new().expected_htlc_error_data(INVALID_ONION_BLINDING, &[0; 32]));
 }
@@ -413,7 +576,7 @@ fn do_blinded_intercept_payment(intercept_node_fails: bool) {
        let intercept_scid = nodes[1].node.get_intercept_scid();
        let mut intercept_chan_upd = chan_upd;
        intercept_chan_upd.short_channel_id = intercept_scid;
-       let route_params = get_blinded_route_parameters(amt_msat, payment_secret,
+       let 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(), &[&intercept_chan_upd],
                &chanmon_cfgs[2].keys_manager);
 
@@ -490,7 +653,7 @@ fn two_hop_blinded_path_success() {
 
        let amt_msat = 5000;
        let (payment_preimage, payment_hash, payment_secret) = get_payment_preimage_hash(&nodes[2], Some(amt_msat), None);
-       let route_params = get_blinded_route_parameters(amt_msat, payment_secret,
+       let 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],
                &chanmon_cfgs[2].keys_manager);
 
@@ -519,7 +682,7 @@ fn three_hop_blinded_path_success() {
 
        let amt_msat = 5000;
        let (payment_preimage, payment_hash, payment_secret) = get_payment_preimage_hash(&nodes[4], Some(amt_msat), None);
-       let route_params = get_blinded_route_parameters(amt_msat, payment_secret,
+       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, &chan_upd_3_4], &chanmon_cfgs[4].keys_manager);
 
@@ -529,6 +692,59 @@ fn three_hop_blinded_path_success() {
        claim_payment(&nodes[0], &[&nodes[1], &nodes[2], &nodes[3], &nodes[4]], payment_preimage);
 }
 
+#[test]
+fn three_hop_blinded_path_fail() {
+       // Test that an intermediate blinded forwarding node gets failed back to with
+       // malformed and also fails back themselves with malformed.
+       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 mut nodes = create_network(4, &node_cfgs, &node_chanmgrs);
+       create_announced_chan_between_nodes_with_value(&nodes, 0, 1, 1_000_000, 0);
+       let chan_upd_1_2 = create_announced_chan_between_nodes_with_value(&nodes, 1, 2, 1_000_000, 0).0.contents;
+       let chan_upd_2_3 = create_announced_chan_between_nodes_with_value(&nodes, 2, 3, 1_000_000, 0).0.contents;
+
+       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,
+               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);
+
+       nodes[0].node.send_payment(payment_hash, RecipientOnionFields::spontaneous_empty(), PaymentId(payment_hash.0), route_params, Retry::Attempts(0)).unwrap();
+       check_added_monitors(&nodes[0], 1);
+       pass_along_route(&nodes[0], &[&[&nodes[1], &nodes[2], &nodes[3]]], amt_msat, payment_hash, payment_secret);
+
+       nodes[3].node.fail_htlc_backwards(&payment_hash);
+       expect_pending_htlcs_forwardable_conditions(
+               nodes[3].node.get_and_clear_pending_events(), &[HTLCDestination::FailedPayment { payment_hash }]
+       );
+       nodes[3].node.process_pending_htlc_forwards();
+       check_added_monitors!(nodes[3], 1);
+
+       let updates_3_2 = get_htlc_update_msgs!(nodes[3], nodes[2].node.get_our_node_id());
+       assert_eq!(updates_3_2.update_fail_malformed_htlcs.len(), 1);
+       let update_malformed = &updates_3_2.update_fail_malformed_htlcs[0];
+       assert_eq!(update_malformed.sha256_of_onion, [0; 32]);
+       assert_eq!(update_malformed.failure_code, INVALID_ONION_BLINDING);
+       nodes[2].node.handle_update_fail_malformed_htlc(&nodes[3].node.get_our_node_id(), update_malformed);
+       do_commitment_signed_dance(&nodes[2], &nodes[3], &updates_3_2.commitment_signed, true, false);
+
+       let updates_2_1 = get_htlc_update_msgs!(nodes[2], nodes[1].node.get_our_node_id());
+       assert_eq!(updates_2_1.update_fail_malformed_htlcs.len(), 1);
+       let update_malformed = &updates_2_1.update_fail_malformed_htlcs[0];
+       assert_eq!(update_malformed.sha256_of_onion, [0; 32]);
+       assert_eq!(update_malformed.failure_code, INVALID_ONION_BLINDING);
+       nodes[1].node.handle_update_fail_malformed_htlc(&nodes[2].node.get_our_node_id(), update_malformed);
+       do_commitment_signed_dance(&nodes[1], &nodes[2], &updates_2_1.commitment_signed, true, false);
+
+       let updates_1_0 = get_htlc_update_msgs!(nodes[1], nodes[0].node.get_our_node_id());
+       assert_eq!(updates_1_0.update_fail_htlcs.len(), 1);
+       nodes[0].node.handle_update_fail_htlc(&nodes[1].node.get_our_node_id(), &updates_1_0.update_fail_htlcs[0]);
+       do_commitment_signed_dance(&nodes[0], &nodes[1], &updates_1_0.commitment_signed, false, false);
+       expect_payment_failed_conditions(&nodes[0], payment_hash, false,
+               PaymentFailedConditions::new().expected_htlc_error_data(INVALID_ONION_BLINDING, &[0; 32]));
+}
+
 #[derive(PartialEq)]
 enum ReceiveCheckFail {
        // The recipient fails the payment upon `PaymentClaimable`.
@@ -581,7 +797,7 @@ fn do_multi_hop_receiver_fail(check: ReceiveCheckFail) {
                Some(TEST_FINAL_CLTV as u16 - 2)
        } else { None };
        let (_, payment_hash, payment_secret) = get_payment_preimage_hash(&nodes[2], Some(amt_msat), excess_final_cltv_delta_opt);
-       let mut route_params = get_blinded_route_parameters(amt_msat, payment_secret,
+       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],
                &chanmon_cfgs[2].keys_manager);
 
@@ -598,7 +814,7 @@ fn do_multi_hop_receiver_fail(check: ReceiveCheckFail) {
                let high_htlc_min_bp = {
                        let mut high_htlc_minimum_upd = chan_upd_1_2.clone();
                        high_htlc_minimum_upd.htlc_minimum_msat = amt_msat + 1000;
-                       let high_htlc_min_params = get_blinded_route_parameters(amt_msat, payment_secret,
+                       let high_htlc_min_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(), &[&high_htlc_minimum_upd],
                                &chanmon_cfgs[2].keys_manager);
                        if let Payee::Blinded { route_hints, .. } = high_htlc_min_params.payment_params.payee {
@@ -768,11 +984,11 @@ fn blinded_path_retries() {
        let route_params = {
                let pay_params = PaymentParameters::blinded(
                        vec![
-                               blinded_payment_path(payment_secret,
+                               blinded_payment_path(payment_secret, 1, 1_0000_0000,
                                        vec![nodes[1].node.get_our_node_id(), nodes[3].node.get_our_node_id()], &[&chan_1_3.0.contents],
                                        &chanmon_cfgs[3].keys_manager
                                ),
-                               blinded_payment_path(payment_secret,
+                               blinded_payment_path(payment_secret, 1, 1_0000_0000,
                                        vec![nodes[2].node.get_our_node_id(), nodes[3].node.get_our_node_id()], &[&chan_2_3.0.contents],
                                        &chanmon_cfgs[3].keys_manager
                                ),
@@ -846,3 +1062,124 @@ fn blinded_path_retries() {
                _ => panic!()
        }
 }
+
+#[test]
+fn min_htlc() {
+       // The min htlc of a blinded path is the max (htlc_min - following_fees) along the path. Make sure
+       // the payment succeeds when we calculate the min htlc this way.
+       let chanmon_cfgs = create_chanmon_cfgs(4);
+       let node_cfgs = create_node_cfgs(4, &chanmon_cfgs);
+       let mut node_1_cfg = test_default_channel_config();
+       node_1_cfg.channel_handshake_config.our_htlc_minimum_msat = 2000;
+       node_1_cfg.channel_config.forwarding_fee_base_msat = 1000;
+       node_1_cfg.channel_config.forwarding_fee_proportional_millionths = 100_000;
+       let mut node_2_cfg = test_default_channel_config();
+       node_2_cfg.channel_handshake_config.our_htlc_minimum_msat = 5000;
+       node_2_cfg.channel_config.forwarding_fee_base_msat = 200;
+       node_2_cfg.channel_config.forwarding_fee_proportional_millionths = 150_000;
+       let mut node_3_cfg = test_default_channel_config();
+       node_3_cfg.channel_handshake_config.our_htlc_minimum_msat = 2000;
+       let node_chanmgrs = create_node_chanmgrs(4, &node_cfgs, &[None, Some(node_1_cfg), Some(node_2_cfg), Some(node_3_cfg)]);
+       let nodes = create_network(4, &node_cfgs, &node_chanmgrs);
+       create_announced_chan_between_nodes_with_value(&nodes, 0, 1, 1_000_000, 0);
+       let chan_1_2 = create_announced_chan_between_nodes_with_value(&nodes, 1, 2, 1_000_000, 0);
+       let chan_2_3 = create_announced_chan_between_nodes_with_value(&nodes, 2, 3, 1_000_000, 0);
+
+       let min_htlc_msat = {
+               // The min htlc for this setup is nodes[2]'s htlc_minimum_msat minus the
+               // following fees.
+               let post_base_fee = chan_2_3.1.contents.htlc_minimum_msat - chan_2_3.0.contents.fee_base_msat as u64;
+               let prop_fee = chan_2_3.0.contents.fee_proportional_millionths as u64;
+               (post_base_fee * 1_000_000 + 1_000_000 + prop_fee - 1) / (prop_fee + 1_000_000)
+       };
+       let (payment_preimage, payment_hash, payment_secret) = get_payment_preimage_hash(&nodes[3], Some(min_htlc_msat), None);
+       let mut route_params = get_blinded_route_parameters(
+               min_htlc_msat, payment_secret, chan_1_2.1.contents.htlc_minimum_msat,
+               chan_1_2.1.contents.htlc_maximum_msat, vec![nodes[1].node.get_our_node_id(),
+               nodes[2].node.get_our_node_id(), nodes[3].node.get_our_node_id()],
+               &[&chan_1_2.0.contents, &chan_2_3.0.contents], &chanmon_cfgs[3].keys_manager);
+       assert_eq!(min_htlc_msat,
+               route_params.payment_params.payee.blinded_route_hints()[0].0.htlc_minimum_msat);
+
+       nodes[0].node.send_payment(payment_hash, RecipientOnionFields::spontaneous_empty(), PaymentId(payment_hash.0), route_params.clone(), Retry::Attempts(0)).unwrap();
+       check_added_monitors(&nodes[0], 1);
+       pass_along_route(&nodes[0], &[&[&nodes[1], &nodes[2], &nodes[3]]], min_htlc_msat, payment_hash, payment_secret);
+       claim_payment(&nodes[0], &[&nodes[1], &nodes[2], &nodes[3]], payment_preimage);
+
+       // Paying 1 less than the min fails.
+       for _ in 0..IDEMPOTENCY_TIMEOUT_TICKS + 1 {
+               nodes[0].node.timer_tick_occurred();
+       }
+       if let Payee::Blinded { ref mut route_hints, .. } = route_params.payment_params.payee {
+               route_hints[0].0.htlc_minimum_msat -= 1;
+       } else { panic!() }
+       route_params.final_value_msat -= 1;
+       nodes[0].node.send_payment(payment_hash, RecipientOnionFields::spontaneous_empty(), PaymentId(payment_hash.0), route_params, Retry::Attempts(0)).unwrap();
+       check_added_monitors(&nodes[0], 1);
+
+       let mut payment_event_0_1 = {
+               let mut events = nodes[0].node.get_and_clear_pending_msg_events();
+               assert_eq!(events.len(), 1);
+               let ev = remove_first_msg_event_to_node(&nodes[1].node.get_our_node_id(), &mut events);
+               SendEvent::from_event(ev)
+       };
+       nodes[1].node.handle_update_add_htlc(&nodes[0].node.get_our_node_id(), &payment_event_0_1.msgs[0]);
+       check_added_monitors!(nodes[1], 0);
+       do_commitment_signed_dance(&nodes[1], &nodes[0], &payment_event_0_1.commitment_msg, true, true);
+       let mut updates = get_htlc_update_msgs!(nodes[1], nodes[0].node.get_our_node_id());
+       nodes[0].node.handle_update_fail_htlc(&nodes[1].node.get_our_node_id(), &updates.update_fail_htlcs[0]);
+       do_commitment_signed_dance(&nodes[0], &nodes[1], &updates.commitment_signed, false, false);
+       expect_payment_failed_conditions(&nodes[0], payment_hash, false,
+               PaymentFailedConditions::new().expected_htlc_error_data(INVALID_ONION_BLINDING, &[0; 32]));
+}
+
+#[test]
+fn conditionally_round_fwd_amt() {
+       // Previously, the (rng-found) feerates below caught a bug where an intermediate node would
+       // calculate an amt_to_forward that underpaid them by 1 msat, caused by rounding up the outbound
+       // amount on top of an already rounded-up total routing fee. Ensure that we'll conditionally round
+       // down intermediate nodes' outbound amounts based on whether rounding up will result in
+       // undercharging for relay.
+       let chanmon_cfgs = create_chanmon_cfgs(5);
+       let node_cfgs = create_node_cfgs(5, &chanmon_cfgs);
+
+       let mut node_1_cfg = test_default_channel_config();
+       node_1_cfg.channel_config.forwarding_fee_base_msat = 247371;
+       node_1_cfg.channel_config.forwarding_fee_proportional_millionths = 86552;
+
+       let mut node_2_cfg = test_default_channel_config();
+       node_2_cfg.channel_config.forwarding_fee_base_msat = 198921;
+       node_2_cfg.channel_config.forwarding_fee_proportional_millionths = 681759;
+
+       let mut node_3_cfg = test_default_channel_config();
+       node_3_cfg.channel_config.forwarding_fee_base_msat = 132845;
+       node_3_cfg.channel_config.forwarding_fee_proportional_millionths = 552561;
+
+       let node_chanmgrs = create_node_chanmgrs(5, &node_cfgs, &[None, Some(node_1_cfg), Some(node_2_cfg), Some(node_3_cfg), None]);
+       let nodes = create_network(5, &node_cfgs, &node_chanmgrs);
+       create_announced_chan_between_nodes_with_value(&nodes, 0, 1, 1_000_000, 0);
+       let chan_1_2 = create_announced_chan_between_nodes_with_value(&nodes, 1, 2, 1_000_000, 0);
+       let chan_2_3 = create_announced_chan_between_nodes_with_value(&nodes, 2, 3, 1_000_000, 0);
+       let chan_3_4 = create_announced_chan_between_nodes_with_value(&nodes, 3, 4, 1_000_000, 0);
+
+       let amt_msat = 100_000;
+       let (payment_preimage, payment_hash, payment_secret) = get_payment_preimage_hash(&nodes[4], Some(amt_msat), None);
+       let mut route_params = get_blinded_route_parameters(amt_msat, payment_secret,
+               chan_1_2.1.contents.htlc_minimum_msat, chan_1_2.1.contents.htlc_maximum_msat,
+               vec![nodes[1].node.get_our_node_id(), nodes[2].node.get_our_node_id(),
+               nodes[3].node.get_our_node_id(), nodes[4].node.get_our_node_id()],
+               &[&chan_1_2.0.contents, &chan_2_3.0.contents, &chan_3_4.0.contents],
+               &chanmon_cfgs[4].keys_manager);
+       route_params.max_total_routing_fee_msat = None;
+
+       nodes[0].node.send_payment(payment_hash, RecipientOnionFields::spontaneous_empty(), PaymentId(payment_hash.0), route_params, Retry::Attempts(0)).unwrap();
+       check_added_monitors(&nodes[0], 1);
+       pass_along_route(&nodes[0], &[&[&nodes[1], &nodes[2], &nodes[3], &nodes[4]]], amt_msat, payment_hash, payment_secret);
+       nodes[4].node.claim_funds(payment_preimage);
+       let expected_path = &[&nodes[1], &nodes[2], &nodes[3], &nodes[4]];
+       let expected_route = &[&expected_path[..]];
+       let mut args = ClaimAlongRouteArgs::new(&nodes[0], &expected_route[..], payment_preimage)
+               .allow_1_msat_fee_overpay();
+       let expected_fee = pass_claimed_payment_along_route(args);
+       expect_payment_sent(&nodes[0], payment_preimage, Some(Some(expected_fee)), true, true);
+}
index 4d9316db79f8f9157aff3989001a116283e23d82..2e95f5c63ff7e173bb5a107f0719fb89ca3e3301 100644 (file)
@@ -3405,7 +3405,7 @@ fn do_test_reload_mon_update_completion_actions(close_during_reload: bool) {
        let mut events = nodes[1].node.get_and_clear_pending_events();
        assert_eq!(events.len(), if close_during_reload { 2 } else { 1 });
        expect_payment_forwarded(events.pop().unwrap(), &nodes[1], &nodes[0], &nodes[2], Some(1000),
-               None, close_during_reload, false);
+               None, close_during_reload, false, false);
        if close_during_reload {
                match events[0] {
                        Event::ChannelClosed { .. } => {},
index 8dfd0c8fcaa32538de27b5f255a8b013e3939e26..53cdf17ecba1aacf5efc6b448bca3bcc8d942742 100644 (file)
@@ -5740,7 +5740,7 @@ where
        fn claim_funds_internal(&self, source: HTLCSource, payment_preimage: PaymentPreimage,
                forwarded_htlc_value_msat: Option<u64>, skimmed_fee_msat: Option<u64>, from_onchain: bool,
                startup_replay: bool, next_channel_counterparty_node_id: Option<PublicKey>,
-               next_channel_outpoint: OutPoint, next_channel_id: ChannelId,
+               next_channel_outpoint: OutPoint, next_channel_id: ChannelId, next_user_channel_id: Option<u128>,
        ) {
                match source {
                        HTLCSource::OutboundRoute { session_priv, payment_id, path, .. } => {
@@ -5759,11 +5759,10 @@ where
                        },
                        HTLCSource::PreviousHopData(hop_data) => {
                                let prev_channel_id = hop_data.channel_id;
+                               let prev_user_channel_id = hop_data.user_channel_id;
                                let completed_blocker = RAAMonitorUpdateBlockingAction::from_prev_hop_data(&hop_data);
                                #[cfg(debug_assertions)]
                                let claiming_chan_funding_outpoint = hop_data.outpoint;
-                               #[cfg(debug_assertions)]
-                               let claiming_channel_id = hop_data.channel_id;
                                let res = self.claim_funds_from_hop(hop_data, payment_preimage,
                                        |htlc_claim_value_msat, definitely_duplicate| {
                                                let chan_to_release =
@@ -5821,7 +5820,7 @@ where
                                                                                BackgroundEvent::MonitorUpdatesComplete {
                                                                                        channel_id, ..
                                                                                } =>
-                                                                                       *channel_id == claiming_channel_id,
+                                                                                       *channel_id == prev_channel_id,
                                                                        }
                                                                }), "{:?}", *background_events);
                                                        }
@@ -5845,12 +5844,14 @@ where
                                                                "skimmed_fee_msat must always be included in total_fee_earned_msat");
                                                        Some(MonitorUpdateCompletionAction::EmitEventAndFreeOtherChannel {
                                                                event: events::Event::PaymentForwarded {
-                                                                       total_fee_earned_msat,
-                                                                       claim_from_onchain_tx: from_onchain,
                                                                        prev_channel_id: Some(prev_channel_id),
                                                                        next_channel_id: Some(next_channel_id),
-                                                                       outbound_amount_forwarded_msat: forwarded_htlc_value_msat,
+                                                                       prev_user_channel_id,
+                                                                       next_user_channel_id,
+                                                                       total_fee_earned_msat,
                                                                        skimmed_fee_msat,
+                                                                       claim_from_onchain_tx: from_onchain,
+                                                                       outbound_amount_forwarded_msat: forwarded_htlc_value_msat,
                                                                },
                                                                downstream_counterparty_and_funding_outpoint: chan_to_release,
                                                        })
@@ -6824,6 +6825,7 @@ where
 
        fn internal_update_fulfill_htlc(&self, counterparty_node_id: &PublicKey, msg: &msgs::UpdateFulfillHTLC) -> Result<(), MsgHandleErrInternal> {
                let funding_txo;
+               let next_user_channel_id;
                let (htlc_source, forwarded_htlc_value, skimmed_fee_msat) = {
                        let per_peer_state = self.per_peer_state.read().unwrap();
                        let peer_state_mutex = per_peer_state.get(counterparty_node_id)
@@ -6853,6 +6855,7 @@ where
                                                // outbound HTLC is claimed. This is guaranteed to all complete before we
                                                // process the RAA as messages are processed from single peers serially.
                                                funding_txo = chan.context.get_funding_txo().expect("We won't accept a fulfill until funded");
+                                               next_user_channel_id = chan.context.get_user_id();
                                                res
                                        } else {
                                                return try_chan_phase_entry!(self, Err(ChannelError::Close(
@@ -6864,7 +6867,7 @@ where
                };
                self.claim_funds_internal(htlc_source, msg.payment_preimage.clone(),
                        Some(forwarded_htlc_value), skimmed_fee_msat, false, false, Some(*counterparty_node_id),
-                       funding_txo, msg.channel_id
+                       funding_txo, msg.channel_id, Some(next_user_channel_id),
                );
 
                Ok(())
@@ -7366,7 +7369,7 @@ where
                                                        log_trace!(logger, "Claiming HTLC with preimage {} from our monitor", preimage);
                                                        self.claim_funds_internal(htlc_update.source, preimage,
                                                                htlc_update.htlc_value_satoshis.map(|v| v * 1000), None, true,
-                                                               false, counterparty_node_id, funding_outpoint, channel_id);
+                                                               false, counterparty_node_id, funding_outpoint, channel_id, None);
                                                } else {
                                                        log_trace!(logger, "Failing HTLC with hash {} from our monitor", &htlc_update.payment_hash);
                                                        let receiver = HTLCDestination::NextHopChannel { node_id: counterparty_node_id, channel_id };
@@ -7755,6 +7758,8 @@ macro_rules! create_refund_builder { ($self: ident, $builder: ty) => {
                        .absolute_expiry(absolute_expiry)
                        .path(path);
 
+               let _persistence_guard = PersistenceNotifierGuard::notify_on_drop($self);
+
                let expiration = StaleExpiration::AbsoluteTimeout(absolute_expiry);
                $self.pending_outbound_payments
                        .add_new_awaiting_invoice(
@@ -7830,6 +7835,7 @@ where
        /// Errors if:
        /// - a duplicate `payment_id` is provided given the caveats in the aforementioned link,
        /// - the provided parameters are invalid for the offer,
+       /// - the offer is for an unsupported chain, or
        /// - the parameterized [`Router`] is unable to create a blinded reply path for the invoice
        ///   request.
        ///
@@ -7869,6 +7875,8 @@ where
                let invoice_request = builder.build_and_sign()?;
                let reply_path = self.create_blinded_path().map_err(|_| Bolt12SemanticError::MissingPaths)?;
 
+               let _persistence_guard = PersistenceNotifierGuard::notify_on_drop(self);
+
                let expiration = StaleExpiration::TimerTicks(1);
                self.pending_outbound_payments
                        .add_new_awaiting_invoice(
@@ -7918,8 +7926,10 @@ where
        ///
        /// # Errors
        ///
-       /// Errors if the parameterized [`Router`] is unable to create a blinded payment path or reply
-       /// path for the invoice.
+       /// Errors if:
+       /// - the refund is for an unsupported chain, or
+       /// - the parameterized [`Router`] is unable to create a blinded payment path or reply path for
+       ///   the invoice.
        ///
        /// [`Bolt12Invoice`]: crate::offers::invoice::Bolt12Invoice
        pub fn request_refund_payment(&self, refund: &Refund) -> Result<(), Bolt12SemanticError> {
@@ -7930,6 +7940,12 @@ where
                let amount_msats = refund.amount_msats();
                let relative_expiry = DEFAULT_RELATIVE_EXPIRY.as_secs() as u32;
 
+               if refund.chain() != self.chain_hash {
+                       return Err(Bolt12SemanticError::UnsupportedChain);
+               }
+
+               let _persistence_guard = PersistenceNotifierGuard::notify_on_drop(self);
+
                match self.create_inbound_payment(Some(amount_msats), relative_expiry, None) {
                        Ok((payment_hash, payment_secret)) => {
                                let payment_paths = self.create_blinded_payment_paths(amount_msats, payment_secret)
@@ -9229,8 +9245,6 @@ where
        }
 
        fn handle_error(&self, counterparty_node_id: &PublicKey, msg: &msgs::ErrorMessage) {
-               let _persistence_guard = PersistenceNotifierGuard::notify_on_drop(self);
-
                match &msg.data as &str {
                        "cannot co-op close channel w/ active htlcs"|
                        "link failed to shutdown" =>
@@ -9243,34 +9257,45 @@ where
                                // We're not going to bother handling this in a sensible way, instead simply
                                // repeating the Shutdown message on repeat until morale improves.
                                if !msg.channel_id.is_zero() {
-                                       let per_peer_state = self.per_peer_state.read().unwrap();
-                                       let peer_state_mutex_opt = per_peer_state.get(counterparty_node_id);
-                                       if peer_state_mutex_opt.is_none() { return; }
-                                       let mut peer_state = peer_state_mutex_opt.unwrap().lock().unwrap();
-                                       if let Some(ChannelPhase::Funded(chan)) = peer_state.channel_by_id.get(&msg.channel_id) {
-                                               if let Some(msg) = chan.get_outbound_shutdown() {
-                                                       peer_state.pending_msg_events.push(events::MessageSendEvent::SendShutdown {
-                                                               node_id: *counterparty_node_id,
-                                                               msg,
-                                                       });
-                                               }
-                                               peer_state.pending_msg_events.push(events::MessageSendEvent::HandleError {
-                                                       node_id: *counterparty_node_id,
-                                                       action: msgs::ErrorAction::SendWarningMessage {
-                                                               msg: msgs::WarningMessage {
-                                                                       channel_id: msg.channel_id,
-                                                                       data: "You appear to be exhibiting LND bug 6039, we'll keep sending you shutdown messages until you handle them correctly".to_owned()
-                                                               },
-                                                               log_level: Level::Trace,
+                                       PersistenceNotifierGuard::optionally_notify(
+                                               self,
+                                               || -> NotifyOption {
+                                                       let per_peer_state = self.per_peer_state.read().unwrap();
+                                                       let peer_state_mutex_opt = per_peer_state.get(counterparty_node_id);
+                                                       if peer_state_mutex_opt.is_none() { return NotifyOption::SkipPersistNoEvents; }
+                                                       let mut peer_state = peer_state_mutex_opt.unwrap().lock().unwrap();
+                                                       if let Some(ChannelPhase::Funded(chan)) = peer_state.channel_by_id.get(&msg.channel_id) {
+                                                               if let Some(msg) = chan.get_outbound_shutdown() {
+                                                                       peer_state.pending_msg_events.push(events::MessageSendEvent::SendShutdown {
+                                                                               node_id: *counterparty_node_id,
+                                                                               msg,
+                                                                       });
+                                                               }
+                                                               peer_state.pending_msg_events.push(events::MessageSendEvent::HandleError {
+                                                                       node_id: *counterparty_node_id,
+                                                                       action: msgs::ErrorAction::SendWarningMessage {
+                                                                               msg: msgs::WarningMessage {
+                                                                                       channel_id: msg.channel_id,
+                                                                                       data: "You appear to be exhibiting LND bug 6039, we'll keep sending you shutdown messages until you handle them correctly".to_owned()
+                                                                               },
+                                                                               log_level: Level::Trace,
+                                                                       }
+                                                               });
+                                                               // This can happen in a fairly tight loop, so we absolutely cannot trigger
+                                                               // a `ChannelManager` write here.
+                                                               return NotifyOption::SkipPersistHandleEvents;
                                                        }
-                                               });
-                                       }
+                                                       NotifyOption::SkipPersistNoEvents
+                                               }
+                                       );
                                }
                                return;
                        }
                        _ => {}
                }
 
+               let _persistence_guard = PersistenceNotifierGuard::notify_on_drop(self);
+
                if msg.channel_id.is_zero() {
                        let channel_ids: Vec<ChannelId> = {
                                let per_peer_state = self.per_peer_state.read().unwrap();
@@ -11364,7 +11389,9 @@ where
                        // don't remember in the `ChannelMonitor` where we got a preimage from, but if the
                        // channel is closed we just assume that it probably came from an on-chain claim.
                        channel_manager.claim_funds_internal(source, preimage, Some(downstream_value), None,
-                               downstream_closed, true, downstream_node_id, downstream_funding, downstream_channel_id);
+                               downstream_closed, true, downstream_node_id, downstream_funding,
+                               downstream_channel_id, None
+                       );
                }
 
                //TODO: Broadcast channel update for closed channels, but only after we've made a
index adf2768b4152700ed82172e9a44ab75c6c3475a2..5840fead943049a6ac2a26eb4e784253ad3bcffb 100644 (file)
@@ -2223,31 +2223,60 @@ macro_rules! expect_payment_path_successful {
        }
 }
 
+/// Returns the total fee earned by this HTLC forward, in msat.
 pub fn expect_payment_forwarded<CM: AChannelManager, H: NodeHolder<CM=CM>>(
        event: Event, node: &H, prev_node: &H, next_node: &H, expected_fee: Option<u64>,
        expected_extra_fees_msat: Option<u64>, upstream_force_closed: bool,
-       downstream_force_closed: bool
-) {
+       downstream_force_closed: bool, allow_1_msat_fee_overpay: bool,
+) -> Option<u64> {
        match event {
                Event::PaymentForwarded {
-                       total_fee_earned_msat, prev_channel_id, claim_from_onchain_tx, next_channel_id,
-                       outbound_amount_forwarded_msat: _, skimmed_fee_msat
+                       prev_channel_id, next_channel_id, prev_user_channel_id, next_user_channel_id,
+                       total_fee_earned_msat, skimmed_fee_msat, claim_from_onchain_tx, ..
                } => {
-                       assert_eq!(total_fee_earned_msat, expected_fee);
+                       if allow_1_msat_fee_overpay {
+                               // Aggregating fees for blinded paths may result in a rounding error, causing slight
+                               // overpayment in fees.
+                               let actual_fee = total_fee_earned_msat.unwrap();
+                               let expected_fee = expected_fee.unwrap();
+                               assert!(actual_fee == expected_fee || actual_fee == expected_fee + 1);
+                       } else {
+                               assert_eq!(total_fee_earned_msat, expected_fee);
+                       }
 
                        // Check that the (knowingly) withheld amount is always less or equal to the expected
                        // overpaid amount.
                        assert!(skimmed_fee_msat == expected_extra_fees_msat);
                        if !upstream_force_closed {
                                // Is the event prev_channel_id in one of the channels between the two nodes?
-                               assert!(node.node().list_channels().iter().any(|x| x.counterparty.node_id == prev_node.node().get_our_node_id() && x.channel_id == prev_channel_id.unwrap()));
+                               assert!(node.node().list_channels().iter().any(|x|
+                                       x.counterparty.node_id == prev_node.node().get_our_node_id() &&
+                                       x.channel_id == prev_channel_id.unwrap() &&
+                                       x.user_channel_id == prev_user_channel_id.unwrap()
+                               ));
                        }
                        // We check for force closures since a force closed channel is removed from the
                        // node's channel list
                        if !downstream_force_closed {
-                               assert!(node.node().list_channels().iter().any(|x| x.counterparty.node_id == next_node.node().get_our_node_id() && x.channel_id == next_channel_id.unwrap()));
+                               // As documented, `next_user_channel_id` will only be `Some` if we didn't settle via an
+                               // onchain transaction, just as the `total_fee_earned_msat` field. Rather than
+                               // introducing yet another variable, we use the latter's state as a flag to detect
+                               // this and only check if it's `Some`.
+                               if total_fee_earned_msat.is_none() {
+                                       assert!(node.node().list_channels().iter().any(|x|
+                                               x.counterparty.node_id == next_node.node().get_our_node_id() &&
+                                               x.channel_id == next_channel_id.unwrap()
+                                       ));
+                               } else {
+                                       assert!(node.node().list_channels().iter().any(|x|
+                                               x.counterparty.node_id == next_node.node().get_our_node_id() &&
+                                               x.channel_id == next_channel_id.unwrap() &&
+                                               x.user_channel_id == next_user_channel_id.unwrap()
+                                       ));
+                               }
                        }
                        assert_eq!(claim_from_onchain_tx, downstream_force_closed);
+                       total_fee_earned_msat
                },
                _ => panic!("Unexpected event"),
        }
@@ -2260,7 +2289,7 @@ macro_rules! expect_payment_forwarded {
                assert_eq!(events.len(), 1);
                $crate::ln::functional_test_utils::expect_payment_forwarded(
                        events.pop().unwrap(), &$node, &$prev_node, &$next_node, $expected_fee, None,
-                       $upstream_force_closed, $downstream_force_closed
+                       $upstream_force_closed, $downstream_force_closed, false
                );
        }
 }
@@ -2472,7 +2501,60 @@ fn fail_payment_along_path<'a, 'b, 'c>(expected_path: &[&Node<'a, 'b, 'c>]) {
        }
 }
 
-pub fn do_pass_along_path<'a, 'b, 'c>(origin_node: &Node<'a, 'b, 'c>, expected_path: &[&Node<'a, 'b, 'c>], recv_value: u64, our_payment_hash: PaymentHash, our_payment_secret: Option<PaymentSecret>, ev: MessageSendEvent, payment_claimable_expected: bool, clear_recipient_events: bool, expected_preimage: Option<PaymentPreimage>, is_probe: bool) -> Option<Event> {
+pub struct PassAlongPathArgs<'a, 'b, 'c, 'd> {
+       pub origin_node: &'a Node<'b, 'c, 'd>,
+       pub expected_path: &'a [&'a Node<'b, 'c, 'd>],
+       pub recv_value: u64,
+       pub payment_hash: PaymentHash,
+       pub payment_secret: Option<PaymentSecret>,
+       pub event: MessageSendEvent,
+       pub payment_claimable_expected: bool,
+       pub clear_recipient_events: bool,
+       pub expected_preimage: Option<PaymentPreimage>,
+       pub is_probe: bool,
+}
+
+impl<'a, 'b, 'c, 'd> PassAlongPathArgs<'a, 'b, 'c, 'd> {
+       pub fn new(
+               origin_node: &'a Node<'b, 'c, 'd>, expected_path: &'a [&'a Node<'b, 'c, 'd>], recv_value: u64,
+               payment_hash: PaymentHash, event: MessageSendEvent,
+       ) -> Self {
+               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,
+               }
+       }
+       pub fn without_clearing_recipient_events(mut self) -> Self {
+               self.clear_recipient_events = false;
+               self
+       }
+       pub fn is_probe(mut self) -> Self {
+               self.payment_claimable_expected = false;
+               self.is_probe = true;
+               self
+       }
+       pub fn without_claimable_event(mut self) -> Self {
+               self.payment_claimable_expected = false;
+               self
+       }
+       pub fn with_payment_secret(mut self, payment_secret: PaymentSecret) -> Self {
+               self.payment_secret = Some(payment_secret);
+               self
+       }
+       pub fn with_payment_preimage(mut self, payment_preimage: PaymentPreimage) -> Self {
+               self.expected_preimage = Some(payment_preimage);
+               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
+       } = args;
+
        let mut payment_event = SendEvent::from_event(ev);
        let mut prev_node = origin_node;
        let mut event = None;
@@ -2539,7 +2621,17 @@ pub fn do_pass_along_path<'a, 'b, 'c>(origin_node: &Node<'a, 'b, 'c>, expected_p
 }
 
 pub fn pass_along_path<'a, 'b, 'c>(origin_node: &Node<'a, 'b, 'c>, expected_path: &[&Node<'a, 'b, 'c>], recv_value: u64, our_payment_hash: PaymentHash, our_payment_secret: Option<PaymentSecret>, ev: MessageSendEvent, payment_claimable_expected: bool, expected_preimage: Option<PaymentPreimage>) -> Option<Event> {
-       do_pass_along_path(origin_node, expected_path, recv_value, our_payment_hash, our_payment_secret, ev, payment_claimable_expected, true, expected_preimage, false)
+       let mut args = PassAlongPathArgs::new(origin_node, expected_path, recv_value, our_payment_hash, ev);
+       if !payment_claimable_expected {
+               args = args.without_claimable_event();
+       }
+       if let Some(payment_secret) = our_payment_secret {
+               args = args.with_payment_secret(payment_secret);
+       }
+       if let Some(payment_preimage) = expected_preimage {
+               args = args.with_payment_preimage(payment_preimage);
+       }
+       do_pass_along_path(args)
 }
 
 pub fn send_probe_along_route<'a, 'b, 'c>(origin_node: &Node<'a, 'b, 'c>, expected_route: &[&[&Node<'a, 'b, 'c>]]) {
@@ -2551,7 +2643,10 @@ pub fn send_probe_along_route<'a, 'b, 'c>(origin_node: &Node<'a, 'b, 'c>, expect
        for path in expected_route.iter() {
                let ev = remove_first_msg_event_to_node(&path[0].node.get_our_node_id(), &mut events);
 
-               do_pass_along_path(origin_node, path, 0, PaymentHash([0_u8; 32]), None, ev, false, false, None, true);
+               do_pass_along_path(PassAlongPathArgs::new(origin_node, path, 0, PaymentHash([0_u8; 32]), ev)
+                       .is_probe()
+                       .without_clearing_recipient_events());
+
                let nodes_to_fail_payment: Vec<_> = vec![origin_node].into_iter().chain(path.iter().cloned()).collect();
 
                fail_payment_along_path(nodes_to_fail_payment.as_slice());
@@ -2598,6 +2693,14 @@ pub struct ClaimAlongRouteArgs<'a, 'b, 'c, 'd> {
        pub expected_min_htlc_overpay: Vec<u32>,
        pub skip_last: bool,
        pub payment_preimage: PaymentPreimage,
+       // Allow forwarding nodes to have taken 1 msat more fee than expected based on the downstream
+       // fulfill amount.
+       //
+       // Necessary because our test utils calculate the expected fee for an intermediate node based on
+       // the amount was claimed in their downstream peer's fulfill, but blinded intermediate nodes
+       // calculate their fee based on the inbound amount from their upstream peer, causing a difference
+       // in rounding.
+       pub allow_1_msat_fee_overpay: bool,
 }
 
 impl<'a, 'b, 'c, 'd> ClaimAlongRouteArgs<'a, 'b, 'c, 'd> {
@@ -2608,6 +2711,7 @@ impl<'a, 'b, 'c, 'd> ClaimAlongRouteArgs<'a, 'b, 'c, 'd> {
                Self {
                        origin_node, expected_paths, expected_extra_fees: vec![0; expected_paths.len()],
                        expected_min_htlc_overpay: vec![0; expected_paths.len()], skip_last: false, payment_preimage,
+                       allow_1_msat_fee_overpay: false,
                }
        }
        pub fn skip_last(mut self, skip_last: bool) -> Self {
@@ -2622,15 +2726,21 @@ impl<'a, 'b, 'c, 'd> ClaimAlongRouteArgs<'a, 'b, 'c, 'd> {
                self.expected_min_htlc_overpay = extra_fees;
                self
        }
+       pub fn allow_1_msat_fee_overpay(mut self) -> Self {
+               self.allow_1_msat_fee_overpay = true;
+               self
+       }
 }
 
 pub fn pass_claimed_payment_along_route<'a, 'b, 'c, 'd>(args: ClaimAlongRouteArgs) -> u64 {
        let ClaimAlongRouteArgs {
                origin_node, expected_paths, expected_extra_fees, expected_min_htlc_overpay, skip_last,
-               payment_preimage: our_payment_preimage
+               payment_preimage: our_payment_preimage, allow_1_msat_fee_overpay,
        } = args;
        let claim_event = expected_paths[0].last().unwrap().node.get_and_clear_pending_events();
        assert_eq!(claim_event.len(), 1);
+       #[allow(unused)]
+       let mut fwd_amt_msat = 0;
        match claim_event[0] {
                Event::PaymentClaimed {
                        purpose: PaymentPurpose::SpontaneousPayment(preimage),
@@ -2647,6 +2757,7 @@ pub fn pass_claimed_payment_along_route<'a, 'b, 'c, 'd>(args: ClaimAlongRouteArg
                        assert_eq!(htlcs.len(), expected_paths.len());  // One per path.
                        assert_eq!(htlcs.iter().map(|h| h.value_msat).sum::<u64>(), amount_msat);
                        expected_paths.iter().zip(htlcs).for_each(|(path, htlc)| check_claimed_htlc_channel(origin_node, path, htlc));
+                       fwd_amt_msat = amount_msat;
                },
                Event::PaymentClaimed {
                        purpose: PaymentPurpose::InvoicePayment { .. },
@@ -2659,6 +2770,7 @@ pub fn pass_claimed_payment_along_route<'a, 'b, 'c, 'd>(args: ClaimAlongRouteArg
                        assert_eq!(htlcs.len(), expected_paths.len());  // One per path.
                        assert_eq!(htlcs.iter().map(|h| h.value_msat).sum::<u64>(), amount_msat);
                        expected_paths.iter().zip(htlcs).for_each(|(path, htlc)| check_claimed_htlc_channel(origin_node, path, htlc));
+                       fwd_amt_msat = amount_msat;
                }
                _ => panic!(),
        }
@@ -2690,8 +2802,12 @@ pub fn pass_claimed_payment_along_route<'a, 'b, 'c, 'd>(args: ClaimAlongRouteArg
                per_path_msgs.push(msgs_from_ev!(&events[0]));
        } else {
                for expected_path in expected_paths.iter() {
-                       // For MPP payments, we always want the message to the first node in the path.
-                       let ev = remove_first_msg_event_to_node(&expected_path[0].node.get_our_node_id(), &mut events);
+                       // For MPP payments, we want the fulfill message from the payee to the penultimate hop in the
+                       // path.
+                       let penultimate_hop_node_id = expected_path.iter().rev().skip(1).next()
+                               .map(|n| n.node.get_our_node_id())
+                               .unwrap_or(origin_node.node.get_our_node_id());
+                       let ev = remove_first_msg_event_to_node(&penultimate_hop_node_id, &mut events);
                        per_path_msgs.push(msgs_from_ev!(&ev));
                }
        }
@@ -2715,15 +2831,20 @@ pub fn pass_claimed_payment_along_route<'a, 'b, 'c, 'd>(args: ClaimAlongRouteArg
                                {
                                        $node.node.handle_update_fulfill_htlc(&$prev_node.node.get_our_node_id(), &next_msgs.as_ref().unwrap().0);
                                        let mut fee = {
-                                               let per_peer_state = $node.node.per_peer_state.read().unwrap();
-                                               let peer_state = per_peer_state.get(&$prev_node.node.get_our_node_id())
-                                                       .unwrap().lock().unwrap();
-                                               let channel = peer_state.channel_by_id.get(&next_msgs.as_ref().unwrap().0.channel_id).unwrap();
-                                               if let Some(prev_config) = channel.context().prev_config() {
-                                                       prev_config.forwarding_fee_base_msat
-                                               } else {
-                                                       channel.context().config().forwarding_fee_base_msat
-                                               }
+                                               let (base_fee, prop_fee) = {
+                                                       let per_peer_state = $node.node.per_peer_state.read().unwrap();
+                                                       let peer_state = per_peer_state.get(&$prev_node.node.get_our_node_id())
+                                                               .unwrap().lock().unwrap();
+                                                       let channel = peer_state.channel_by_id.get(&next_msgs.as_ref().unwrap().0.channel_id).unwrap();
+                                                       if let Some(prev_config) = channel.context().prev_config() {
+                                                               (prev_config.forwarding_fee_base_msat as u64,
+                                                                prev_config.forwarding_fee_proportional_millionths as u64)
+                                                       } else {
+                                                               (channel.context().config().forwarding_fee_base_msat as u64,
+                                                                channel.context().config().forwarding_fee_proportional_millionths as u64)
+                                                       }
+                                               };
+                                               ((fwd_amt_msat * prop_fee / 1_000_000) + base_fee) as u32
                                        };
 
                                        let mut expected_extra_fee = None;
@@ -2734,9 +2855,10 @@ pub fn pass_claimed_payment_along_route<'a, 'b, 'c, 'd>(args: ClaimAlongRouteArg
                                        }
                                        let mut events = $node.node.get_and_clear_pending_events();
                                        assert_eq!(events.len(), 1);
-                                       expect_payment_forwarded(events.pop().unwrap(), *$node, $next_node, $prev_node,
-                                               Some(fee as u64), expected_extra_fee, false, false);
-                                       expected_total_fee_msat += fee as u64;
+                                       let actual_fee = expect_payment_forwarded(events.pop().unwrap(), *$node, $next_node, $prev_node,
+                                               Some(fee as u64), expected_extra_fee, false, false, allow_1_msat_fee_overpay);
+                                       expected_total_fee_msat += actual_fee.unwrap();
+                                       fwd_amt_msat += actual_fee.unwrap();
                                        check_added_monitors!($node, 1);
                                        let new_next_msgs = if $new_msgs {
                                                let events = $node.node.get_and_clear_pending_msg_events();
index d291ac8664aceec5fdaff3b0d2ce20077e727028..260d39a5a4cefa4e021426b55be37866a6a51127 100644 (file)
@@ -1136,8 +1136,16 @@ pub struct UnsignedNodeAnnouncement {
        pub alias: NodeAlias,
        /// List of addresses on which this node is reachable
        pub addresses: Vec<SocketAddress>,
-       pub(crate) excess_address_data: Vec<u8>,
-       pub(crate) excess_data: Vec<u8>,
+       /// Excess address data which was signed as a part of the message which we do not (yet) understand how
+       /// to decode.
+       ///
+       /// This is stored to ensure forward-compatibility as new address types are added to the lightning gossip protocol.
+       pub excess_address_data: Vec<u8>,
+       /// Excess data which was signed as a part of the message which we do not (yet) understand how
+       /// to decode.
+       ///
+       /// This is stored to ensure forward-compatibility as new fields are added to the lightning gossip protocol.
+       pub excess_data: Vec<u8>,
 }
 #[derive(Clone, Debug, Hash, PartialEq, Eq)]
 /// A [`node_announcement`] message to be sent to or received from a peer.
index e16c0ed515f5836195d867fed906ea203e763007..e75bd2c70e1e9c3d6b68945fa0ea424a694d4f7d 100644 (file)
@@ -40,6 +40,7 @@
 //! Nodes without channels are disconnected and connected as needed to ensure that deterministic
 //! blinded paths are used.
 
+use bitcoin::network::constants::Network;
 use core::time::Duration;
 use crate::blinded_path::BlindedPath;
 use crate::events::{Event, MessageSendEventsProvider, PaymentPurpose};
@@ -145,9 +146,9 @@ fn route_bolt12_payment<'a, 'b, 'c>(
        // invoice contains the payment_hash but it was encrypted inside an onion message.
        let amount_msats = invoice.amount_msats();
        let payment_hash = invoice.payment_hash();
-       do_pass_along_path(
-               node, path, amount_msats, payment_hash, None, ev, false, false, None, false
-       );
+       let args = PassAlongPathArgs::new(node, path, amount_msats, payment_hash, ev)
+               .without_clearing_recipient_events();
+       do_pass_along_path(args);
 }
 
 fn claim_bolt12_payment<'a, 'b, 'c>(node: &Node<'a, 'b, 'c>, path: &[&Node<'a, 'b, 'c>]) {
@@ -732,6 +733,61 @@ fn fails_creating_refund_without_blinded_paths() {
        assert!(nodes[0].node.list_recent_payments().is_empty());
 }
 
+/// Fails creating an invoice request when the offer contains an unsupported chain.
+#[test]
+fn fails_creating_invoice_request_for_unsupported_chain() {
+       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_with_value(&nodes, 0, 1, 10_000_000, 1_000_000_000);
+
+       let alice = &nodes[0];
+       let bob = &nodes[1];
+
+       let offer = alice.node
+               .create_offer_builder("coffee".to_string()).unwrap()
+               .clear_chains()
+               .chain(Network::Signet)
+               .build().unwrap();
+
+       let payment_id = PaymentId([1; 32]);
+       match bob.node.pay_for_offer(&offer, None, None, None, payment_id, Retry::Attempts(0), None) {
+               Ok(_) => panic!("Expected error"),
+               Err(e) => assert_eq!(e, Bolt12SemanticError::UnsupportedChain),
+       }
+}
+
+/// Fails requesting a payment when the refund contains an unsupported chain.
+#[test]
+fn fails_sending_invoice_with_unsupported_chain_for_refund() {
+       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_with_value(&nodes, 0, 1, 10_000_000, 1_000_000_000);
+
+       let alice = &nodes[0];
+       let bob = &nodes[1];
+
+       let absolute_expiry = Duration::from_secs(u64::MAX);
+       let payment_id = PaymentId([1; 32]);
+       let refund = bob.node
+               .create_refund_builder(
+                       "refund".to_string(), 10_000_000, absolute_expiry, payment_id, Retry::Attempts(0), None
+               )
+               .unwrap()
+               .chain(Network::Signet)
+               .build().unwrap();
+
+       match alice.node.request_refund_payment(&refund) {
+               Ok(_) => panic!("Expected error"),
+               Err(e) => assert_eq!(e, Bolt12SemanticError::UnsupportedChain),
+       }
+}
+
 /// Fails creating an invoice request when a blinded reply path cannot be created without exposing
 /// the node's id.
 #[test]
index 76dd78ae2ad6a712560c5d6d6aa1a1c0b2e182e5..4422af1e8c70c63a81b93ece1aac38e2553d00a0 100644 (file)
@@ -823,8 +823,12 @@ fn do_test_partial_claim_before_restart(persist_both_monitors: bool) {
        assert_eq!(send_events.len(), 2);
        let node_1_msgs = remove_first_msg_event_to_node(&nodes[1].node.get_our_node_id(), &mut send_events);
        let node_2_msgs = remove_first_msg_event_to_node(&nodes[2].node.get_our_node_id(), &mut send_events);
-       do_pass_along_path(&nodes[0], &[&nodes[1], &nodes[3]], 15_000_000, payment_hash, Some(payment_secret), node_1_msgs, true, false, None, false);
-       do_pass_along_path(&nodes[0], &[&nodes[2], &nodes[3]], 15_000_000, payment_hash, Some(payment_secret), node_2_msgs, true, false, None, false);
+       do_pass_along_path(PassAlongPathArgs::new(&nodes[0],&[&nodes[1], &nodes[3]], 15_000_000, payment_hash, node_1_msgs)
+               .with_payment_secret(payment_secret)
+               .without_clearing_recipient_events());
+       do_pass_along_path(PassAlongPathArgs::new(&nodes[0], &[&nodes[2], &nodes[3]], 15_000_000, payment_hash, node_2_msgs)
+               .with_payment_secret(payment_secret)
+               .without_clearing_recipient_events());
 
        // Now that we have an MPP payment pending, get the latest encoded copies of nodes[3]'s
        // monitors and ChannelManager, for use later, if we don't want to persist both monitors.
index ee6a6afb6ecda2ae2a919a610b9ba9e978d5e5e9..c6d0cc58b9bf299a1fc2d6e06a34a9a6bc0b3958 100644 (file)
@@ -437,7 +437,7 @@ fn do_htlc_fail_async_shutdown(blinded_recipient: bool) {
        let (_, our_payment_hash, our_payment_secret) = get_payment_preimage_hash(&nodes[2], Some(amt_msat), None);
        let route_params = if blinded_recipient {
                crate::ln::blinded_payment_tests::get_blinded_route_parameters(
-                       amt_msat, our_payment_secret,
+                       amt_msat, our_payment_secret, 1, 100000000,
                        nodes.iter().skip(1).map(|n| n.node.get_our_node_id()).collect(), &[&chan_2.0.contents],
                        &chanmon_cfgs[2].keys_manager)
        } else {
index e9e35836a96e2bec19b5bbe73c390ad78481e4bf..bf5e50deb77b8c290c1385f3459b76fb17bfbd6d 100644 (file)
@@ -390,6 +390,12 @@ macro_rules! offer_builder_test_methods { (
                $return_value
        }
 
+       #[cfg_attr(c_bindings, allow(dead_code))]
+       pub(crate) fn clear_chains($($self_mut)* $self: $self_type) -> $return_type {
+               $self.offer.chains = None;
+               $return_value
+       }
+
        #[cfg_attr(c_bindings, allow(dead_code))]
        pub(crate) fn clear_paths($($self_mut)* $self: $self_type) -> $return_type {
                $self.offer.paths = None;
index 15dc12f6bea7f6f89fd101dfaeeafb7a36126c0d..e213bcbb0e1dc50d9fda8983f164e82611db26d9 100644 (file)
@@ -28,7 +28,7 @@ use super::packet::OnionMessageContents;
 use super::packet::ParsedOnionMessageContents;
 use super::offers::OffersMessageHandler;
 use super::packet::{BIG_PACKET_HOP_DATA_LEN, ForwardControlTlvs, Packet, Payload, ReceiveControlTlvs, SMALL_PACKET_HOP_DATA_LEN};
-use crate::util::logger::Logger;
+use crate::util::logger::{Logger, WithContext};
 use crate::util::ser::Writeable;
 
 use core::fmt;
@@ -748,25 +748,31 @@ where
                &self, contents: T, destination: Destination, reply_path: Option<BlindedPath>,
                log_suffix: fmt::Arguments
        ) -> Result<SendSuccess, SendError> {
+               let mut logger = WithContext::from(&self.logger, None, None);
                let result = self.find_path(destination)
-                       .and_then(|path| self.enqueue_onion_message(path, contents, reply_path, log_suffix));
+                       .and_then(|path| {
+                               let first_hop = path.intermediate_nodes.get(0).map(|p| *p);
+                               logger = WithContext::from(&self.logger, first_hop, None);
+                               self.enqueue_onion_message(path, contents, reply_path, log_suffix)
+                       });
 
                match result.as_ref() {
                        Err(SendError::GetNodeIdFailed) => {
-                               log_warn!(self.logger, "Unable to retrieve node id {}", log_suffix);
+                               log_warn!(logger, "Unable to retrieve node id {}", log_suffix);
                        },
                        Err(SendError::PathNotFound) => {
-                               log_trace!(self.logger, "Failed to find path {}", log_suffix);
+                               log_trace!(logger, "Failed to find path {}", log_suffix);
                        },
                        Err(e) => {
-                               log_trace!(self.logger, "Failed sending onion message {}: {:?}", log_suffix, e);
+                               log_trace!(logger, "Failed sending onion message {}: {:?}", log_suffix, e);
                        },
                        Ok(SendSuccess::Buffered) => {
-                               log_trace!(self.logger, "Buffered onion message {}", log_suffix);
+                               log_trace!(logger, "Buffered onion message {}", log_suffix);
                        },
                        Ok(SendSuccess::BufferedAwaitingConnection(node_id)) => {
                                log_trace!(
-                                       self.logger, "Buffered onion message waiting on peer connection {}: {:?}",
+                                       logger,
+                                       "Buffered onion message waiting on peer connection {}: {}",
                                        log_suffix, node_id
                                );
                        },
@@ -925,12 +931,13 @@ where
        OMH::Target: OffersMessageHandler,
        CMH::Target: CustomOnionMessageHandler,
 {
-       fn handle_onion_message(&self, _peer_node_id: &PublicKey, msg: &OnionMessage) {
+       fn handle_onion_message(&self, peer_node_id: &PublicKey, msg: &OnionMessage) {
+               let logger = WithContext::from(&self.logger, Some(*peer_node_id), None);
                match self.peel_onion_message(msg) {
                        Ok(PeeledOnion::Receive(message, path_id, reply_path)) => {
                                log_trace!(
-                                       self.logger,
-                                  "Received an onion message with path_id {:02x?} and {} reply_path: {:?}",
+                                       logger,
+                                       "Received an onion message with path_id {:02x?} and {} reply_path: {:?}",
                                        path_id, if reply_path.is_some() { "a" } else { "no" }, message);
 
                                match message {
@@ -957,7 +964,10 @@ where
                        Ok(PeeledOnion::Forward(next_node_id, onion_message)) => {
                                let mut message_recipients = self.message_recipients.lock().unwrap();
                                if outbound_buffer_full(&next_node_id, &message_recipients) {
-                                       log_trace!(self.logger, "Dropping forwarded onion message to peer {:?}: outbound buffer full", next_node_id);
+                                       log_trace!(
+                                               logger,
+                                               "Dropping forwarded onion message to peer {}: outbound buffer full",
+                                               next_node_id);
                                        return
                                }
 
@@ -971,16 +981,19 @@ where
                                                e.get(), OnionMessageRecipient::ConnectedPeer(..)
                                        ) => {
                                                e.get_mut().enqueue_message(onion_message);
-                                               log_trace!(self.logger, "Forwarding an onion message to peer {}", next_node_id);
+                                               log_trace!(logger, "Forwarding an onion message to peer {}", next_node_id);
                                        },
                                        _ => {
-                                               log_trace!(self.logger, "Dropping forwarded onion message to disconnected peer {:?}", next_node_id);
+                                               log_trace!(
+                                                       logger,
+                                                       "Dropping forwarded onion message to disconnected peer {}",
+                                                       next_node_id);
                                                return
                                        },
                                }
                        },
                        Err(e) => {
-                               log_error!(self.logger, "Failed to process onion message {:?}", e);
+                               log_error!(logger, "Failed to process onion message {:?}", e);
                        }
                }
        }
index 806aadd583c02491df9b87b1076a4166b65d71c1..045772486ba7aa150636a1e7b5420a606e87ce64 100644 (file)
@@ -74,6 +74,16 @@ impl NodeId {
                NodeId(pubkey.serialize())
        }
 
+       /// Create a new NodeId from a slice of bytes
+       pub fn from_slice(bytes: &[u8]) -> Result<Self, DecodeError> {
+               if bytes.len() != PUBLIC_KEY_SIZE {
+                       return Err(DecodeError::InvalidValue);
+               }
+               let mut data = [0; PUBLIC_KEY_SIZE];
+               data.copy_from_slice(bytes);
+               Ok(NodeId(data))
+       }
+
        /// Get the public key slice from this NodeId
        pub fn as_slice(&self) -> &[u8] {
                &self.0
index 8e59c9bd46f5ff5edeed994adb96d2667a0acfc3..8d37182266b11debdda0d96787358c36c87dcd59 100644 (file)
@@ -986,7 +986,7 @@ impl Payee {
                        _ => None,
                }
        }
-       fn blinded_route_hints(&self) -> &[(BlindedPayInfo, BlindedPath)] {
+       pub(crate) fn blinded_route_hints(&self) -> &[(BlindedPayInfo, BlindedPath)] {
                match self {
                        Self::Blinded { route_hints, .. } => &route_hints[..],
                        Self::Clear { .. } => &[]
diff --git a/pending_changelog/blinded-hop-features-optional.txt b/pending_changelog/blinded-hop-features-optional.txt
new file mode 100644 (file)
index 0000000..f8967f1
--- /dev/null
@@ -0,0 +1,5 @@
+## Bug Fixes
+
+* LDK previously would fail to forward an intermediate blinded payment
+       if the blinded hop features were absent, potentially breaking
+       interoperability.
diff --git a/pending_changelog/relay-constraints-ser.txt b/pending_changelog/relay-constraints-ser.txt
new file mode 100644 (file)
index 0000000..f0da509
--- /dev/null
@@ -0,0 +1,7 @@
+## Bug fixes
+
+* LDK previously serialized `PaymentRelay::fee_base_msat` as a u32 when it
+       should have been serialized as a tu32. Similarly, we were serializing
+       `PaymentConstraints::htlc_minimum_msat` as a u64 when we should have been
+       serializing it as tu64. This caused lack of interoperability when using other
+       implementations as forwarding nodes along blinded payment paths.