Merge pull request #2688 from valentinewallace/2023-10-multihop-blinded-recv

Support receiving to multi-hop blinded paths
This commit is contained in:
valentinewallace 2023-12-13 10:31:02 -05:00 committed by GitHub
commit 9856fb6710
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
10 changed files with 757 additions and 102 deletions

View file

@ -16,16 +16,18 @@ use lightning::util::test_utils;
#[inline]
pub fn onion_hop_data_test<Out: test_logger::Output>(data: &[u8], _out: Out) {
use lightning::util::ser::ReadableArgs;
use bitcoin::secp256k1::PublicKey;
let mut r = ::std::io::Cursor::new(data);
let node_signer = test_utils::TestNodeSigner::new(test_utils::privkey(42));
let _ = <lightning::ln::msgs::InboundOnionPayload as ReadableArgs<&&test_utils::TestNodeSigner>>::read(&mut r, &&node_signer);
let _ = <lightning::ln::msgs::InboundOnionPayload as ReadableArgs<(Option<PublicKey>, &&test_utils::TestNodeSigner)>>::read(&mut r, (None, &&node_signer));
}
#[no_mangle]
pub extern "C" fn onion_hop_data_run(data: *const u8, datalen: usize) {
use lightning::util::ser::ReadableArgs;
use bitcoin::secp256k1::PublicKey;
let data = unsafe { std::slice::from_raw_parts(data, datalen) };
let mut r = ::std::io::Cursor::new(data);
let node_signer = test_utils::TestNodeSigner::new(test_utils::privkey(42));
let _ = <lightning::ln::msgs::InboundOnionPayload as ReadableArgs<&&test_utils::TestNodeSigner>>::read(&mut r, &&node_signer);
let _ = <lightning::ln::msgs::InboundOnionPayload as ReadableArgs<(Option<PublicKey>, &&test_utils::TestNodeSigner)>>::read(&mut r, (None, &&node_signer));
}

View file

@ -105,7 +105,7 @@ impl BlindedPath {
///
/// [`ForwardTlvs`]: crate::blinded_path::payment::ForwardTlvs
// TODO: make all payloads the same size with padding + add dummy hops
pub(crate) fn new_for_payment<ES: EntropySource + ?Sized, T: secp256k1::Signing + secp256k1::Verification>(
pub fn new_for_payment<ES: EntropySource + ?Sized, T: secp256k1::Signing + secp256k1::Verification>(
intermediate_nodes: &[payment::ForwardNode], payee_node_id: PublicKey,
payee_tlvs: payment::ReceiveTlvs, htlc_maximum_msat: u64, entropy_source: &ES,
secp_ctx: &Secp256k1<T>

View file

@ -10,7 +10,7 @@
use bitcoin::secp256k1::{PublicKey, Secp256k1, SecretKey};
use crate::blinded_path::BlindedPath;
use crate::blinded_path::payment::{ForwardNode, ForwardTlvs, PaymentConstraints, PaymentRelay, ReceiveTlvs};
use crate::events::{HTLCDestination, MessageSendEventsProvider};
use crate::events::{HTLCDestination, MessageSendEvent, MessageSendEventsProvider};
use crate::ln::PaymentSecret;
use crate::ln::channelmanager;
use crate::ln::channelmanager::{PaymentId, RecipientOnionFields};
@ -22,7 +22,7 @@ use crate::ln::onion_utils;
use crate::ln::onion_utils::INVALID_ONION_BLINDING;
use crate::ln::outbound_payment::Retry;
use crate::prelude::*;
use crate::routing::router::{PaymentParameters, RouteParameters};
use crate::routing::router::{Payee, PaymentParameters, RouteParameters};
use crate::util::config::UserConfig;
use crate::util::test_utils;
@ -281,7 +281,12 @@ fn failed_backwards_to_intro_node() {
let mut updates = get_htlc_update_msgs!(nodes[2], nodes[1].node.get_our_node_id());
let mut update_malformed = &mut updates.update_fail_malformed_htlcs[0];
// Ensure the final hop does not correctly blind their error.
// Check that the final node encodes its failure correctly.
assert_eq!(update_malformed.failure_code, INVALID_ONION_BLINDING);
assert_eq!(update_malformed.sha256_of_onion, [0; 32]);
// Modify such the final hop does not correctly blind their error so we can ensure the intro node
// converts it to the correct error.
update_malformed.sha256_of_onion = [1; 32];
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);
@ -377,6 +382,10 @@ fn do_forward_fail_in_process_pending_htlc_fwds(check: ProcessPendingHTLCsCheck)
#[test]
fn blinded_intercept_payment() {
do_blinded_intercept_payment(true);
do_blinded_intercept_payment(false);
}
fn do_blinded_intercept_payment(intercept_node_fails: bool) {
let chanmon_cfgs = create_chanmon_cfgs(3);
let node_cfgs = create_node_cfgs(3, &chanmon_cfgs);
let mut intercept_forwards_config = test_default_channel_config();
@ -384,10 +393,13 @@ fn blinded_intercept_payment() {
let node_chanmgrs = create_node_chanmgrs(3, &node_cfgs, &[None, Some(intercept_forwards_config), None]);
let nodes = create_network(3, &node_cfgs, &node_chanmgrs);
create_announced_chan_between_nodes_with_value(&nodes, 0, 1, 1_000_000, 0);
let chan_upd = create_announced_chan_between_nodes_with_value(&nodes, 1, 2, 1_000_000, 0).0.contents;
let (channel_id, chan_upd) = {
let chan = create_announced_chan_between_nodes_with_value(&nodes, 1, 2, 1_000_000, 0);
(chan.2, chan.0.contents)
};
let amt_msat = 5000;
let (_, payment_hash, payment_secret) = get_payment_preimage_hash(&nodes[2], Some(amt_msat), None);
let (payment_preimage, payment_hash, payment_secret) = get_payment_preimage_hash(&nodes[2], Some(amt_msat), None);
let intercept_scid = nodes[1].node.get_intercept_scid();
let mut intercept_chan_upd = chan_upd;
intercept_chan_upd.short_channel_id = intercept_scid;
@ -408,27 +420,283 @@ fn blinded_intercept_payment() {
let events = nodes[1].node.get_and_clear_pending_events();
assert_eq!(events.len(), 1);
let intercept_id = match events[0] {
let (intercept_id, expected_outbound_amount_msat) = match events[0] {
crate::events::Event::HTLCIntercepted {
intercept_id, payment_hash: pmt_hash,
requested_next_hop_scid: short_channel_id, ..
requested_next_hop_scid: short_channel_id, expected_outbound_amount_msat, ..
} => {
assert_eq!(pmt_hash, payment_hash);
assert_eq!(short_channel_id, intercept_scid);
intercept_id
(intercept_id, expected_outbound_amount_msat)
},
_ => panic!()
};
nodes[1].node.fail_intercepted_htlc(intercept_id).unwrap();
expect_pending_htlcs_forwardable_and_htlc_handling_failed_ignore!(nodes[1], vec![HTLCDestination::UnknownNextHop { requested_forward_scid: intercept_scid }]);
nodes[1].node.process_pending_htlc_forwards();
let update_fail = get_htlc_update_msgs!(nodes[1], nodes[0].node.get_our_node_id());
if intercept_node_fails {
nodes[1].node.fail_intercepted_htlc(intercept_id).unwrap();
expect_pending_htlcs_forwardable_and_htlc_handling_failed_ignore!(nodes[1], vec![HTLCDestination::UnknownNextHop { requested_forward_scid: intercept_scid }]);
nodes[1].node.process_pending_htlc_forwards();
let update_fail = get_htlc_update_msgs!(nodes[1], nodes[0].node.get_our_node_id());
check_added_monitors!(&nodes[1], 1);
assert!(update_fail.update_fail_htlcs.len() == 1);
let fail_msg = update_fail.update_fail_htlcs[0].clone();
nodes[0].node.handle_update_fail_htlc(&nodes[1].node.get_our_node_id(), &fail_msg);
commitment_signed_dance!(nodes[0], nodes[1], update_fail.commitment_signed, false);
expect_payment_failed_conditions(&nodes[0], payment_hash, false,
PaymentFailedConditions::new().expected_htlc_error_data(INVALID_ONION_BLINDING, &[0; 32]));
return
}
nodes[1].node.forward_intercepted_htlc(intercept_id, &channel_id, nodes[2].node.get_our_node_id(), expected_outbound_amount_msat).unwrap();
expect_pending_htlcs_forwardable!(nodes[1]);
let payment_event = {
{
let mut added_monitors = nodes[1].chain_monitor.added_monitors.lock().unwrap();
assert_eq!(added_monitors.len(), 1);
added_monitors.clear();
}
let mut events = nodes[1].node.get_and_clear_pending_msg_events();
assert_eq!(events.len(), 1);
SendEvent::from_event(events.remove(0))
};
nodes[2].node.handle_update_add_htlc(&nodes[1].node.get_our_node_id(), &payment_event.msgs[0]);
commitment_signed_dance!(nodes[2], nodes[1], &payment_event.commitment_msg, false, true);
expect_pending_htlcs_forwardable!(nodes[2]);
expect_payment_claimable!(&nodes[2], payment_hash, payment_secret, amt_msat, None, nodes[2].node.get_our_node_id());
do_claim_payment_along_route(&nodes[0], &vec!(&vec!(&nodes[1], &nodes[2])[..]), false, payment_preimage);
expect_payment_sent(&nodes[0], payment_preimage, Some(Some(1000)), true, true);
}
#[test]
fn two_hop_blinded_path_success() {
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);
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 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,
nodes.iter().skip(1).map(|n| n.node.get_our_node_id()).collect(), &[&chan_upd_1_2],
&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();
check_added_monitors(&nodes[0], 1);
pass_along_route(&nodes[0], &[&[&nodes[1], &nodes[2]]], amt_msat, payment_hash, payment_secret);
claim_payment(&nodes[0], &[&nodes[1], &nodes[2]], payment_preimage);
}
#[derive(PartialEq)]
enum ReceiveCheckFail {
// The recipient fails the payment upon `PaymentClaimable`.
RecipientFail,
// Failure to decode the recipient's onion payload.
OnionDecodeFail,
// The incoming HTLC did not satisfy our requirements; in this case it underpaid us according to
// the expected receive amount in the onion.
ReceiveRequirements,
// The incoming HTLC errors when added to the Channel, in this case due to the HTLC being
// delivered out-of-order with a shutdown message.
ChannelCheck,
// The HTLC is successfully added to the inbound channel but fails receive checks in
// process_pending_htlc_forwards.
ProcessPendingHTLCsCheck,
// The HTLC violates the `PaymentConstraints` contained within the receiver's encrypted payload.
PaymentConstraints,
}
#[test]
fn multi_hop_receiver_fail() {
do_multi_hop_receiver_fail(ReceiveCheckFail::RecipientFail);
do_multi_hop_receiver_fail(ReceiveCheckFail::OnionDecodeFail);
do_multi_hop_receiver_fail(ReceiveCheckFail::ReceiveRequirements);
do_multi_hop_receiver_fail(ReceiveCheckFail::ChannelCheck);
do_multi_hop_receiver_fail(ReceiveCheckFail::ProcessPendingHTLCsCheck);
do_multi_hop_receiver_fail(ReceiveCheckFail::PaymentConstraints);
}
fn do_multi_hop_receiver_fail(check: ReceiveCheckFail) {
// Test that the receiver to a multihop blinded path fails back correctly.
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);
// We need the session priv to construct an invalid onion packet later.
let session_priv = [3; 32];
*nodes[0].keys_manager.override_random_bytes.lock().unwrap() = Some(session_priv);
create_announced_chan_between_nodes_with_value(&nodes, 0, 1, 1_000_000, 0);
let (chan_upd_1_2, chan_id_1_2) = {
let (chan_upd, _, channel_id, ..) = create_announced_chan_between_nodes_with_value(
&nodes, 1, 2, 1_000_000, 0
);
(chan_upd.contents, channel_id)
};
let amt_msat = 5000;
let final_cltv_delta = if check == ReceiveCheckFail::ProcessPendingHTLCsCheck {
// Set the final CLTV expiry too low to trigger the failure in process_pending_htlc_forwards.
Some(TEST_FINAL_CLTV as u16 - 2)
} else { None };
let (_, payment_hash, payment_secret) = get_payment_preimage_hash(&nodes[2], Some(amt_msat), final_cltv_delta);
let mut 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 route = if check == ReceiveCheckFail::ProcessPendingHTLCsCheck {
let mut route = get_route(&nodes[0], &route_params).unwrap();
// Set the final CLTV expiry too low to trigger the failure in process_pending_htlc_forwards.
route.paths[0].blinded_tail.as_mut().map(|bt| bt.excess_final_cltv_expiry_delta = TEST_FINAL_CLTV - 2);
route
} else if check == ReceiveCheckFail::PaymentConstraints {
// Create a blinded path where the receiver's encrypted payload has an htlc_minimum_msat that is
// violated by `amt_msat`, and stick it in the route_params without changing the corresponding
// BlindedPayInfo (to ensure pathfinding still succeeds).
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,
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 {
route_hints[0].1.clone()
} else { panic!() }
};
if let Payee::Blinded { ref mut route_hints, .. } = route_params.payment_params.payee {
route_hints[0].1 = high_htlc_min_bp;
} else { panic!() }
find_route(&nodes[0], &route_params).unwrap()
} else {
find_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 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, false, false);
expect_pending_htlcs_forwardable!(nodes[1]);
check_added_monitors!(&nodes[1], 1);
assert!(update_fail.update_fail_htlcs.len() == 1);
let fail_msg = update_fail.update_fail_htlcs[0].clone();
nodes[0].node.handle_update_fail_htlc(&nodes[1].node.get_our_node_id(), &fail_msg);
commitment_signed_dance!(nodes[0], nodes[1], update_fail.commitment_signed, false);
let mut payment_event_1_2 = {
let mut events = nodes[1].node.get_and_clear_pending_msg_events();
assert_eq!(events.len(), 1);
let ev = remove_first_msg_event_to_node(&nodes[2].node.get_our_node_id(), &mut events);
SendEvent::from_event(ev)
};
match check {
ReceiveCheckFail::RecipientFail => {
nodes[2].node.handle_update_add_htlc(&nodes[1].node.get_our_node_id(), &payment_event_1_2.msgs[0]);
check_added_monitors!(nodes[2], 0);
do_commitment_signed_dance(&nodes[2], &nodes[1], &payment_event_1_2.commitment_msg, true, true);
expect_pending_htlcs_forwardable!(nodes[2]);
check_payment_claimable(
&nodes[2].node.get_and_clear_pending_events()[0], payment_hash, payment_secret, amt_msat,
None, nodes[2].node.get_our_node_id()
);
nodes[2].node.fail_htlc_backwards(&payment_hash);
expect_pending_htlcs_forwardable_conditions(
nodes[2].node.get_and_clear_pending_events(), &[HTLCDestination::FailedPayment { payment_hash }]
);
nodes[2].node.process_pending_htlc_forwards();
check_added_monitors!(nodes[2], 1);
},
ReceiveCheckFail::OnionDecodeFail => {
let session_priv = SecretKey::from_slice(&session_priv).unwrap();
let mut onion_keys = onion_utils::construct_onion_keys(&Secp256k1::new(), &route.paths[0], &session_priv).unwrap();
let cur_height = nodes[0].best_block_info().1;
let (mut onion_payloads, ..) = onion_utils::build_onion_payloads(
&route.paths[0], amt_msat, RecipientOnionFields::spontaneous_empty(), cur_height, &None).unwrap();
let update_add = &mut payment_event_1_2.msgs[0];
onion_payloads.last_mut().map(|p| {
if let msgs::OutboundOnionPayload::BlindedReceive { ref mut intro_node_blinding_point, .. } = p {
// The receiver should error if both the update_add blinding_point and the
// intro_node_blinding_point are set.
assert!(intro_node_blinding_point.is_none() && update_add.blinding_point.is_some());
*intro_node_blinding_point = Some(PublicKey::from_slice(&[2; 33]).unwrap());
} else { panic!() }
});
update_add.onion_routing_packet = onion_utils::construct_onion_packet(
vec![onion_payloads.pop().unwrap()], vec![onion_keys.pop().unwrap()], [0; 32],
&payment_hash
).unwrap();
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], &payment_event_1_2.commitment_msg, true, true);
},
ReceiveCheckFail::ReceiveRequirements => {
let update_add = &mut payment_event_1_2.msgs[0];
update_add.amount_msat -= 1;
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], &payment_event_1_2.commitment_msg, true, true);
},
ReceiveCheckFail::ChannelCheck => {
nodes[2].node.close_channel(&chan_id_1_2, &nodes[1].node.get_our_node_id()).unwrap();
let node_2_shutdown = get_event_msg!(nodes[2], MessageSendEvent::SendShutdown, nodes[1].node.get_our_node_id());
nodes[1].node.handle_shutdown(&nodes[2].node.get_our_node_id(), &node_2_shutdown);
let node_1_shutdown = get_event_msg!(nodes[1], MessageSendEvent::SendShutdown, nodes[2].node.get_our_node_id());
nodes[2].node.handle_update_add_htlc(&nodes[1].node.get_our_node_id(), &payment_event_1_2.msgs[0]);
nodes[2].node.handle_commitment_signed(&nodes[1].node.get_our_node_id(), &payment_event_1_2.commitment_msg);
check_added_monitors!(nodes[2], 1);
nodes[2].node.handle_shutdown(&nodes[1].node.get_our_node_id(), &node_1_shutdown);
commitment_signed_dance!(nodes[2], nodes[1], (), false, true, false, false);
},
ReceiveCheckFail::ProcessPendingHTLCsCheck => {
nodes[2].node.handle_update_add_htlc(&nodes[1].node.get_our_node_id(), &payment_event_1_2.msgs[0]);
check_added_monitors!(nodes[2], 0);
do_commitment_signed_dance(&nodes[2], &nodes[1], &payment_event_1_2.commitment_msg, true, true);
expect_pending_htlcs_forwardable!(nodes[2]);
expect_pending_htlcs_forwardable_and_htlc_handling_failed_ignore!(nodes[2],
vec![HTLCDestination::FailedPayment { payment_hash }]);
check_added_monitors!(nodes[2], 1);
},
ReceiveCheckFail::PaymentConstraints => {
nodes[2].node.handle_update_add_htlc(&nodes[1].node.get_our_node_id(), &payment_event_1_2.msgs[0]);
check_added_monitors!(nodes[2], 0);
do_commitment_signed_dance(&nodes[2], &nodes[1], &payment_event_1_2.commitment_msg, true, true);
}
}
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 = if check == ReceiveCheckFail::ChannelCheck {
let events = nodes[1].node.get_and_clear_pending_msg_events();
assert_eq!(events.len(), 2);
events.into_iter().find_map(|ev| {
match ev {
MessageSendEvent:: UpdateHTLCs { node_id, updates } => {
assert_eq!(node_id, nodes[0].node.get_our_node_id());
return Some(updates)
},
MessageSendEvent::SendClosingSigned { .. } => None,
_ => panic!()
}
}).unwrap()
} else { 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]));
}

View file

@ -259,6 +259,11 @@ enum HTLCUpdateAwaitingACK {
htlc_id: u64,
err_packet: msgs::OnionErrorPacket,
},
FailMalformedHTLC {
htlc_id: u64,
failure_code: u16,
sha256_of_onion: [u8; 32],
},
}
macro_rules! define_state_flags {
@ -2518,6 +2523,64 @@ struct CommitmentTxInfoCached {
feerate: u32,
}
/// Contents of a wire message that fails an HTLC backwards. Useful for [`Channel::fail_htlc`] to
/// fail with either [`msgs::UpdateFailMalformedHTLC`] or [`msgs::UpdateFailHTLC`] as needed.
trait FailHTLCContents {
type Message: FailHTLCMessageName;
fn to_message(self, htlc_id: u64, channel_id: ChannelId) -> Self::Message;
fn to_inbound_htlc_state(self) -> InboundHTLCState;
fn to_htlc_update_awaiting_ack(self, htlc_id: u64) -> HTLCUpdateAwaitingACK;
}
impl FailHTLCContents for msgs::OnionErrorPacket {
type Message = msgs::UpdateFailHTLC;
fn to_message(self, htlc_id: u64, channel_id: ChannelId) -> Self::Message {
msgs::UpdateFailHTLC { htlc_id, channel_id, reason: self }
}
fn to_inbound_htlc_state(self) -> InboundHTLCState {
InboundHTLCState::LocalRemoved(InboundHTLCRemovalReason::FailRelay(self))
}
fn to_htlc_update_awaiting_ack(self, htlc_id: u64) -> HTLCUpdateAwaitingACK {
HTLCUpdateAwaitingACK::FailHTLC { htlc_id, err_packet: self }
}
}
impl FailHTLCContents for (u16, [u8; 32]) {
type Message = msgs::UpdateFailMalformedHTLC; // (failure_code, sha256_of_onion)
fn to_message(self, htlc_id: u64, channel_id: ChannelId) -> Self::Message {
msgs::UpdateFailMalformedHTLC {
htlc_id,
channel_id,
failure_code: self.0,
sha256_of_onion: self.1
}
}
fn to_inbound_htlc_state(self) -> InboundHTLCState {
InboundHTLCState::LocalRemoved(
InboundHTLCRemovalReason::FailMalformed((self.1, self.0))
)
}
fn to_htlc_update_awaiting_ack(self, htlc_id: u64) -> HTLCUpdateAwaitingACK {
HTLCUpdateAwaitingACK::FailMalformedHTLC {
htlc_id,
failure_code: self.0,
sha256_of_onion: self.1
}
}
}
trait FailHTLCMessageName {
fn name() -> &'static str;
}
impl FailHTLCMessageName for msgs::UpdateFailHTLC {
fn name() -> &'static str {
"update_fail_htlc"
}
}
impl FailHTLCMessageName for msgs::UpdateFailMalformedHTLC {
fn name() -> &'static str {
"update_fail_malformed_htlc"
}
}
impl<SP: Deref> Channel<SP> where
SP::Target: SignerProvider,
<SP::Target as SignerProvider>::EcdsaSigner: WriteableEcdsaChannelSigner
@ -2719,7 +2782,9 @@ impl<SP: Deref> Channel<SP> where
return UpdateFulfillFetch::DuplicateClaim {};
}
},
&HTLCUpdateAwaitingACK::FailHTLC { htlc_id, .. } => {
&HTLCUpdateAwaitingACK::FailHTLC { htlc_id, .. } |
&HTLCUpdateAwaitingACK::FailMalformedHTLC { htlc_id, .. } =>
{
if htlc_id_arg == htlc_id {
log_warn!(logger, "Have preimage and want to fulfill HTLC with pending failure against channel {}", &self.context.channel_id());
// TODO: We may actually be able to switch to a fulfill here, though its
@ -2816,6 +2881,17 @@ impl<SP: Deref> Channel<SP> where
.map(|msg_opt| assert!(msg_opt.is_none(), "We forced holding cell?"))
}
/// Used for failing back with [`msgs::UpdateFailMalformedHTLC`]. For now, this is used when we
/// want to fail blinded HTLCs where we are not the intro node.
///
/// See [`Self::queue_fail_htlc`] for more info.
pub fn queue_fail_malformed_htlc<L: Deref>(
&mut self, htlc_id_arg: u64, failure_code: u16, sha256_of_onion: [u8; 32], logger: &L
) -> Result<(), ChannelError> where L::Target: Logger {
self.fail_htlc(htlc_id_arg, (failure_code, sha256_of_onion), true, logger)
.map(|msg_opt| assert!(msg_opt.is_none(), "We forced holding cell?"))
}
/// We can only have one resolution per HTLC. In some cases around reconnect, we may fulfill
/// an HTLC more than once or fulfill once and then attempt to fail after reconnect. We cannot,
/// however, fail more than once as we wait for an upstream failure to be irrevocably committed
@ -2824,8 +2900,10 @@ impl<SP: Deref> Channel<SP> where
/// If we do fail twice, we `debug_assert!(false)` and return `Ok(None)`. Thus, this will always
/// return `Ok(_)` if preconditions are met. In any case, `Err`s will only be
/// [`ChannelError::Ignore`].
fn fail_htlc<L: Deref>(&mut self, htlc_id_arg: u64, err_packet: msgs::OnionErrorPacket, mut force_holding_cell: bool, logger: &L)
-> Result<Option<msgs::UpdateFailHTLC>, ChannelError> where L::Target: Logger {
fn fail_htlc<L: Deref, E: FailHTLCContents + Clone>(
&mut self, htlc_id_arg: u64, err_packet: E, mut force_holding_cell: bool,
logger: &L
) -> Result<Option<E::Message>, ChannelError> where L::Target: Logger {
if !matches!(self.context.channel_state, ChannelState::ChannelReady(_)) {
panic!("Was asked to fail an HTLC when channel was not in an operational state");
}
@ -2878,7 +2956,9 @@ impl<SP: Deref> Channel<SP> where
return Ok(None);
}
},
&HTLCUpdateAwaitingACK::FailHTLC { htlc_id, .. } => {
&HTLCUpdateAwaitingACK::FailHTLC { htlc_id, .. } |
&HTLCUpdateAwaitingACK::FailMalformedHTLC { htlc_id, .. } =>
{
if htlc_id_arg == htlc_id {
debug_assert!(false, "Tried to fail an HTLC that was already failed");
return Err(ChannelError::Ignore("Unable to find a pending HTLC which matched the given HTLC ID".to_owned()));
@ -2888,24 +2968,18 @@ impl<SP: Deref> Channel<SP> where
}
}
log_trace!(logger, "Placing failure for HTLC ID {} in holding cell in channel {}.", htlc_id_arg, &self.context.channel_id());
self.context.holding_cell_htlc_updates.push(HTLCUpdateAwaitingACK::FailHTLC {
htlc_id: htlc_id_arg,
err_packet,
});
self.context.holding_cell_htlc_updates.push(err_packet.to_htlc_update_awaiting_ack(htlc_id_arg));
return Ok(None);
}
log_trace!(logger, "Failing HTLC ID {} back with a update_fail_htlc message in channel {}.", htlc_id_arg, &self.context.channel_id());
log_trace!(logger, "Failing HTLC ID {} back with {} message in channel {}.", htlc_id_arg,
E::Message::name(), &self.context.channel_id());
{
let htlc = &mut self.context.pending_inbound_htlcs[pending_idx];
htlc.state = InboundHTLCState::LocalRemoved(InboundHTLCRemovalReason::FailRelay(err_packet.clone()));
htlc.state = err_packet.clone().to_inbound_htlc_state();
}
Ok(Some(msgs::UpdateFailHTLC {
channel_id: self.context.channel_id(),
htlc_id: htlc_id_arg,
reason: err_packet
}))
Ok(Some(err_packet.to_message(htlc_id_arg, self.context.channel_id())))
}
// Message handlers:
@ -3563,6 +3637,20 @@ impl<SP: Deref> Channel<SP> where
}
}
},
&HTLCUpdateAwaitingACK::FailMalformedHTLC { htlc_id, failure_code, sha256_of_onion } => {
match self.fail_htlc(htlc_id, (failure_code, sha256_of_onion), false, logger) {
Ok(update_fail_malformed_opt) => {
debug_assert!(update_fail_malformed_opt.is_some()); // See above comment
update_fail_count += 1;
},
Err(e) => {
if let ChannelError::Ignore(_) = e {}
else {
panic!("Got a non-IgnoreError action trying to fail holding cell HTLC");
}
}
}
},
}
}
if update_add_count == 0 && update_fulfill_count == 0 && update_fail_count == 0 && self.context.holding_cell_update_fee.is_none() {
@ -7433,6 +7521,8 @@ impl<SP: Deref> Writeable for Channel<SP> where SP::Target: SignerProvider {
let mut holding_cell_skimmed_fees: Vec<Option<u64>> = Vec::new();
let mut holding_cell_blinding_points: Vec<Option<PublicKey>> = Vec::new();
// Vec of (htlc_id, failure_code, sha256_of_onion)
let mut malformed_htlcs: Vec<(u64, u16, [u8; 32])> = Vec::new();
(self.context.holding_cell_htlc_updates.len() as u64).write(writer)?;
for update in self.context.holding_cell_htlc_updates.iter() {
match update {
@ -7460,6 +7550,18 @@ impl<SP: Deref> Writeable for Channel<SP> where SP::Target: SignerProvider {
htlc_id.write(writer)?;
err_packet.write(writer)?;
}
&HTLCUpdateAwaitingACK::FailMalformedHTLC {
htlc_id, failure_code, sha256_of_onion
} => {
// We don't want to break downgrading by adding a new variant, so write a dummy
// `::FailHTLC` variant and write the real malformed error as an optional TLV.
malformed_htlcs.push((htlc_id, failure_code, sha256_of_onion));
let dummy_err_packet = msgs::OnionErrorPacket { data: Vec::new() };
2u8.write(writer)?;
htlc_id.write(writer)?;
dummy_err_packet.write(writer)?;
}
}
}
@ -7620,6 +7722,7 @@ impl<SP: Deref> Writeable for Channel<SP> where SP::Target: SignerProvider {
(38, self.context.is_batch_funding, option),
(39, pending_outbound_blinding_points, optional_vec),
(41, holding_cell_blinding_points, optional_vec),
(43, malformed_htlcs, optional_vec), // Added in 0.0.119
});
Ok(())
@ -7910,6 +8013,8 @@ impl<'a, 'b, 'c, ES: Deref, SP: Deref> ReadableArgs<(&'a ES, &'b SP, u32, &'c Ch
let mut pending_outbound_blinding_points_opt: Option<Vec<Option<PublicKey>>> = None;
let mut holding_cell_blinding_points_opt: Option<Vec<Option<PublicKey>>> = None;
let mut malformed_htlcs: Option<Vec<(u64, u16, [u8; 32])>> = None;
read_tlv_fields!(reader, {
(0, announcement_sigs, option),
(1, minimum_depth, option),
@ -7938,6 +8043,7 @@ impl<'a, 'b, 'c, ES: Deref, SP: Deref> ReadableArgs<(&'a ES, &'b SP, u32, &'c Ch
(38, is_batch_funding, option),
(39, pending_outbound_blinding_points_opt, optional_vec),
(41, holding_cell_blinding_points_opt, optional_vec),
(43, malformed_htlcs, optional_vec), // Added in 0.0.119
});
let (channel_keys_id, holder_signer) = if let Some(channel_keys_id) = channel_keys_id {
@ -8032,6 +8138,22 @@ impl<'a, 'b, 'c, ES: Deref, SP: Deref> ReadableArgs<(&'a ES, &'b SP, u32, &'c Ch
if iter.next().is_some() { return Err(DecodeError::InvalidValue) }
}
if let Some(malformed_htlcs) = malformed_htlcs {
for (malformed_htlc_id, failure_code, sha256_of_onion) in malformed_htlcs {
let htlc_idx = holding_cell_htlc_updates.iter().position(|htlc| {
if let HTLCUpdateAwaitingACK::FailHTLC { htlc_id, err_packet } = htlc {
let matches = *htlc_id == malformed_htlc_id;
if matches { debug_assert!(err_packet.data.is_empty()) }
matches
} else { false }
}).ok_or(DecodeError::InvalidValue)?;
let malformed_htlc = HTLCUpdateAwaitingACK::FailMalformedHTLC {
htlc_id: malformed_htlc_id, failure_code, sha256_of_onion
};
let _ = core::mem::replace(&mut holding_cell_htlc_updates[htlc_idx], malformed_htlc);
}
}
Ok(Channel {
context: ChannelContext {
user_id,
@ -8166,6 +8288,7 @@ mod tests {
use bitcoin::blockdata::transaction::{Transaction, TxOut};
use bitcoin::blockdata::opcodes;
use bitcoin::network::constants::Network;
use crate::ln::onion_utils::INVALID_ONION_BLINDING;
use crate::ln::{PaymentHash, PaymentPreimage};
use crate::ln::channel_keys::{RevocationKey, RevocationBasepoint};
use crate::ln::channelmanager::{self, HTLCSource, PaymentId};
@ -8702,8 +8825,9 @@ mod tests {
}
#[test]
fn blinding_point_skimmed_fee_ser() {
// Ensure that channel blinding points and skimmed fees are (de)serialized properly.
fn blinding_point_skimmed_fee_malformed_ser() {
// Ensure that channel blinding points, skimmed fees, and malformed HTLCs are (de)serialized
// properly.
let feeest = LowerBoundedFeeEstimator::new(&TestFeeEstimator{fee_est: 15000});
let secp_ctx = Secp256k1::new();
let seed = [42; 32];
@ -8768,13 +8892,19 @@ mod tests {
payment_preimage: PaymentPreimage([42; 32]),
htlc_id: 0,
};
let mut holding_cell_htlc_updates = Vec::with_capacity(10);
for i in 0..10 {
if i % 3 == 0 {
let dummy_holding_cell_failed_htlc = |htlc_id| HTLCUpdateAwaitingACK::FailHTLC {
htlc_id, err_packet: msgs::OnionErrorPacket { data: vec![42] }
};
let dummy_holding_cell_malformed_htlc = |htlc_id| HTLCUpdateAwaitingACK::FailMalformedHTLC {
htlc_id, failure_code: INVALID_ONION_BLINDING, sha256_of_onion: [0; 32],
};
let mut holding_cell_htlc_updates = Vec::with_capacity(12);
for i in 0..12 {
if i % 5 == 0 {
holding_cell_htlc_updates.push(dummy_holding_cell_add_htlc.clone());
} else if i % 3 == 1 {
} else if i % 5 == 1 {
holding_cell_htlc_updates.push(dummy_holding_cell_claim_htlc.clone());
} else {
} else if i % 5 == 2 {
let mut dummy_add = dummy_holding_cell_add_htlc.clone();
if let HTLCUpdateAwaitingACK::AddHTLC {
ref mut blinding_point, ref mut skimmed_fee_msat, ..
@ -8783,6 +8913,10 @@ mod tests {
*skimmed_fee_msat = Some(42);
} else { panic!() }
holding_cell_htlc_updates.push(dummy_add);
} else if i % 5 == 3 {
holding_cell_htlc_updates.push(dummy_holding_cell_malformed_htlc(i as u64));
} else {
holding_cell_htlc_updates.push(dummy_holding_cell_failed_htlc(i as u64));
}
}
chan.context.holding_cell_htlc_updates = holding_cell_htlc_updates.clone();

View file

@ -111,6 +111,7 @@ use crate::ln::script::ShutdownScript;
/// Information about where a received HTLC('s onion) has indicated the HTLC should go.
#[derive(Clone)] // See Channel::revoke_and_ack for why, tl;dr: Rust bug
#[cfg_attr(test, derive(Debug, PartialEq))]
pub enum PendingHTLCRouting {
/// An HTLC which should be forwarded on to another node.
Forward {
@ -155,6 +156,8 @@ pub enum PendingHTLCRouting {
/// [`Event::PaymentClaimable::onion_fields`] as
/// [`RecipientOnionFields::custom_tlvs`].
custom_tlvs: Vec<(u64, Vec<u8>)>,
/// Set if this HTLC is the final hop in a multi-hop blinded path.
requires_blinded_error: bool,
},
/// The onion indicates that this is for payment to us but which contains the preimage for
/// claiming included, and is unrelated to any invoice we'd previously generated (aka a
@ -187,7 +190,7 @@ pub enum PendingHTLCRouting {
}
/// Information used to forward or fail this HTLC that is being forwarded within a blinded path.
#[derive(Clone, Copy, Hash, PartialEq, Eq)]
#[derive(Clone, Copy, Debug, Hash, PartialEq, Eq)]
pub struct BlindedForward {
/// The `blinding_point` that was set in the inbound [`msgs::UpdateAddHTLC`], or in the inbound
/// onion payload if we're the introduction node. Useful for calculating the next hop's
@ -199,11 +202,11 @@ pub struct BlindedForward {
impl PendingHTLCRouting {
// Used to override the onion failure code and data if the HTLC is blinded.
fn blinded_failure(&self) -> Option<BlindedFailure> {
// TODO: needs update when we support receiving to multi-hop blinded paths
if let Self::Forward { blinded: Some(_), .. } = self {
Some(BlindedFailure::FromIntroductionNode)
} else {
None
// TODO: needs update when we support forwarding blinded HTLCs as non-intro node
match self {
Self::Forward { blinded: Some(_), .. } => Some(BlindedFailure::FromIntroductionNode),
Self::Receive { requires_blinded_error: true, .. } => Some(BlindedFailure::FromBlindedNode),
_ => None,
}
}
}
@ -211,6 +214,7 @@ impl PendingHTLCRouting {
/// Information about an incoming HTLC, including the [`PendingHTLCRouting`] describing where it
/// should go next.
#[derive(Clone)] // See Channel::revoke_and_ack for why, tl;dr: Rust bug
#[cfg_attr(test, derive(Debug, PartialEq))]
pub struct PendingHTLCInfo {
/// Further routing details based on whether the HTLC is being forwarded or received.
pub routing: PendingHTLCRouting,
@ -265,6 +269,7 @@ pub(super) enum PendingHTLCStatus {
Fail(HTLCFailureMsg),
}
#[cfg_attr(test, derive(Clone, Debug, PartialEq))]
pub(super) struct PendingAddHTLCInfo {
pub(super) forward_info: PendingHTLCInfo,
@ -280,19 +285,25 @@ pub(super) struct PendingAddHTLCInfo {
prev_user_channel_id: u128,
}
#[cfg_attr(test, derive(Clone, Debug, PartialEq))]
pub(super) enum HTLCForwardInfo {
AddHTLC(PendingAddHTLCInfo),
FailHTLC {
htlc_id: u64,
err_packet: msgs::OnionErrorPacket,
},
FailMalformedHTLC {
htlc_id: u64,
failure_code: u16,
sha256_of_onion: [u8; 32],
},
}
// Used for failing blinded HTLCs backwards correctly.
#[derive(Clone, Debug, Hash, PartialEq, Eq)]
#[derive(Clone, Copy, Debug, Hash, PartialEq, Eq)]
enum BlindedFailure {
FromIntroductionNode,
// Another variant will be added here for non-intro nodes.
FromBlindedNode,
}
/// Tracks the inbound corresponding to an outbound HTLC
@ -3016,11 +3027,12 @@ where
msg, &self.node_signer, &self.logger, &self.secp_ctx
)?;
let is_blinded = match next_hop {
let is_intro_node_forward = match next_hop {
onion_utils::Hop::Forward {
// TODO: update this when we support blinded forwarding as non-intro node
next_hop_data: msgs::InboundOnionPayload::BlindedForward { .. }, ..
} => true,
_ => false, // TODO: update this when we support receiving to multi-hop blinded paths
_ => false,
};
macro_rules! return_err {
@ -3030,7 +3042,17 @@ where
WithContext::from(&self.logger, Some(*counterparty_node_id), Some(msg.channel_id)),
"Failed to accept/forward incoming HTLC: {}", $msg
);
let (err_code, err_data) = if is_blinded {
// If `msg.blinding_point` is set, we must always fail with malformed.
if msg.blinding_point.is_some() {
return Err(HTLCFailureMsg::Malformed(msgs::UpdateFailMalformedHTLC {
channel_id: msg.channel_id,
htlc_id: msg.htlc_id,
sha256_of_onion: [0; 32],
failure_code: INVALID_ONION_BLINDING,
}));
}
let (err_code, err_data) = if is_intro_node_forward {
(INVALID_ONION_BLINDING, &[0; 32][..])
} else { ($err_code, $data) };
return Err(HTLCFailureMsg::Relay(msgs::UpdateFailHTLC {
@ -3183,6 +3205,16 @@ where
{
let logger = WithContext::from(&self.logger, Some(*counterparty_node_id), Some(msg.channel_id));
log_info!(logger, "Failed to accept/forward incoming HTLC: {}", $msg);
if msg.blinding_point.is_some() {
return PendingHTLCStatus::Fail(HTLCFailureMsg::Malformed(
msgs::UpdateFailMalformedHTLC {
channel_id: msg.channel_id,
htlc_id: msg.htlc_id,
sha256_of_onion: [0; 32],
failure_code: INVALID_ONION_BLINDING,
}
))
}
return PendingHTLCStatus::Fail(HTLCFailureMsg::Relay(msgs::UpdateFailHTLC {
channel_id: msg.channel_id,
htlc_id: msg.htlc_id,
@ -4246,7 +4278,7 @@ where
let phantom_shared_secret = self.node_signer.ecdh(Recipient::PhantomNode, &onion_packet.public_key.unwrap(), None).unwrap().secret_bytes();
let next_hop = match onion_utils::decode_next_payment_hop(
phantom_shared_secret, &onion_packet.hop_data, onion_packet.hmac,
payment_hash, &self.node_signer
payment_hash, None, &self.node_signer
) {
Ok(res) => res,
Err(onion_utils::OnionDecodeErr::Malformed { err_msg, err_code }) => {
@ -4282,7 +4314,7 @@ where
fail_forward!(format!("Unknown short channel id {} for forward HTLC", short_chan_id), 0x4000 | 10, Vec::new(), None);
}
},
HTLCForwardInfo::FailHTLC { .. } => {
HTLCForwardInfo::FailHTLC { .. } | HTLCForwardInfo::FailMalformedHTLC { .. } => {
// Channel went away before we could fail it. This implies
// the channel is now on chain and our counterparty is
// trying to broadcast the HTLC-Timeout, but that's their
@ -4378,6 +4410,20 @@ where
continue;
}
},
HTLCForwardInfo::FailMalformedHTLC { htlc_id, failure_code, sha256_of_onion } => {
log_trace!(self.logger, "Failing malformed HTLC back to channel with short id {} (backward HTLC ID {}) after delay", short_chan_id, htlc_id);
if let Err(e) = chan.queue_fail_malformed_htlc(htlc_id, failure_code, sha256_of_onion, &self.logger) {
if let ChannelError::Ignore(msg) = e {
log_trace!(self.logger, "Failed to fail HTLC with ID {} backwards to short_id {}: {}", htlc_id, short_chan_id, msg);
} else {
panic!("Stated return value requirements in queue_fail_malformed_htlc() were not met");
}
// fail-backs are best-effort, we probably already have one
// pending, and if not that's OK, if not, the channel is on
// the chain and sending the HTLC-Timeout is their problem.
continue;
}
},
}
}
} else {
@ -4396,7 +4442,10 @@ where
}) => {
let blinded_failure = routing.blinded_failure();
let (cltv_expiry, onion_payload, payment_data, phantom_shared_secret, mut onion_fields) = match routing {
PendingHTLCRouting::Receive { payment_data, payment_metadata, incoming_cltv_expiry, phantom_shared_secret, custom_tlvs } => {
PendingHTLCRouting::Receive {
payment_data, payment_metadata, incoming_cltv_expiry, phantom_shared_secret,
custom_tlvs, requires_blinded_error: _
} => {
let _legacy_hop_data = Some(payment_data.clone());
let onion_fields = RecipientOnionFields { payment_secret: Some(payment_data.payment_secret),
payment_metadata, custom_tlvs };
@ -4455,7 +4504,7 @@ where
htlc_id: $htlc.prev_hop.htlc_id,
incoming_packet_shared_secret: $htlc.prev_hop.incoming_packet_shared_secret,
phantom_shared_secret,
blinded_failure: None,
blinded_failure,
}), payment_hash,
HTLCFailReason::reason(0x4000 | 15, htlc_msat_height_data),
HTLCDestination::FailedPayment { payment_hash: $payment_hash },
@ -4629,7 +4678,7 @@ where
},
};
},
HTLCForwardInfo::FailHTLC { .. } => {
HTLCForwardInfo::FailHTLC { .. } | HTLCForwardInfo::FailMalformedHTLC { .. } => {
panic!("Got pending fail of our own HTLC");
}
}
@ -5234,15 +5283,26 @@ where
"Failing {}HTLC with payment_hash {} backwards from us: {:?}",
if blinded_failure.is_some() { "blinded " } else { "" }, &payment_hash, onion_error
);
let err_packet = match blinded_failure {
let failure = match blinded_failure {
Some(BlindedFailure::FromIntroductionNode) => {
let blinded_onion_error = HTLCFailReason::reason(INVALID_ONION_BLINDING, vec![0; 32]);
blinded_onion_error.get_encrypted_failure_packet(
let err_packet = blinded_onion_error.get_encrypted_failure_packet(
incoming_packet_shared_secret, phantom_shared_secret
)
);
HTLCForwardInfo::FailHTLC { htlc_id: *htlc_id, err_packet }
},
Some(BlindedFailure::FromBlindedNode) => {
HTLCForwardInfo::FailMalformedHTLC {
htlc_id: *htlc_id,
failure_code: INVALID_ONION_BLINDING,
sha256_of_onion: [0; 32]
}
},
None => {
onion_error.get_encrypted_failure_packet(incoming_packet_shared_secret, phantom_shared_secret)
let err_packet = onion_error.get_encrypted_failure_packet(
incoming_packet_shared_secret, phantom_shared_secret
);
HTLCForwardInfo::FailHTLC { htlc_id: *htlc_id, err_packet }
}
};
@ -5253,10 +5313,10 @@ where
}
match forward_htlcs.entry(*short_channel_id) {
hash_map::Entry::Occupied(mut entry) => {
entry.get_mut().push(HTLCForwardInfo::FailHTLC { htlc_id: *htlc_id, err_packet });
entry.get_mut().push(failure);
},
hash_map::Entry::Vacant(entry) => {
entry.insert(vec!(HTLCForwardInfo::FailHTLC { htlc_id: *htlc_id, err_packet }));
entry.insert(vec!(failure));
}
}
mem::drop(forward_htlcs);
@ -6556,6 +6616,16 @@ where
Err(e) => PendingHTLCStatus::Fail(e)
};
let create_pending_htlc_status = |chan: &Channel<SP>, pending_forward_info: PendingHTLCStatus, error_code: u16| {
if msg.blinding_point.is_some() {
return PendingHTLCStatus::Fail(HTLCFailureMsg::Malformed(
msgs::UpdateFailMalformedHTLC {
channel_id: msg.channel_id,
htlc_id: msg.htlc_id,
sha256_of_onion: [0; 32],
failure_code: INVALID_ONION_BLINDING,
}
))
}
// If the update_add is completely bogus, the call will Err and we will close,
// but if we've sent a shutdown and they haven't acknowledged it yet, we just
// want to reject the new HTLC and fail it backwards instead of forwarding.
@ -9371,6 +9441,7 @@ impl_writeable_tlv_based_enum!(PendingHTLCRouting,
(2, incoming_cltv_expiry, required),
(3, payment_metadata, option),
(5, custom_tlvs, optional_vec),
(7, requires_blinded_error, (default_value, false)),
},
(2, ReceiveKeysend) => {
(0, payment_preimage, required),
@ -9465,7 +9536,8 @@ impl_writeable_tlv_based_enum!(PendingHTLCStatus, ;
);
impl_writeable_tlv_based_enum!(BlindedFailure,
(0, FromIntroductionNode) => {}, ;
(0, FromIntroductionNode) => {},
(2, FromBlindedNode) => {}, ;
);
impl_writeable_tlv_based!(HTLCPreviousHopData, {
@ -9629,13 +9701,68 @@ impl_writeable_tlv_based!(PendingAddHTLCInfo, {
(6, prev_funding_outpoint, required),
});
impl_writeable_tlv_based_enum!(HTLCForwardInfo,
(1, FailHTLC) => {
(0, htlc_id, required),
(2, err_packet, required),
};
(0, AddHTLC)
);
impl Writeable for HTLCForwardInfo {
fn write<W: Writer>(&self, w: &mut W) -> Result<(), io::Error> {
const FAIL_HTLC_VARIANT_ID: u8 = 1;
match self {
Self::AddHTLC(info) => {
0u8.write(w)?;
info.write(w)?;
},
Self::FailHTLC { htlc_id, err_packet } => {
FAIL_HTLC_VARIANT_ID.write(w)?;
write_tlv_fields!(w, {
(0, htlc_id, required),
(2, err_packet, required),
});
},
Self::FailMalformedHTLC { htlc_id, failure_code, sha256_of_onion } => {
// Since this variant was added in 0.0.119, write this as `::FailHTLC` with an empty error
// packet so older versions have something to fail back with, but serialize the real data as
// optional TLVs for the benefit of newer versions.
FAIL_HTLC_VARIANT_ID.write(w)?;
let dummy_err_packet = msgs::OnionErrorPacket { data: Vec::new() };
write_tlv_fields!(w, {
(0, htlc_id, required),
(1, failure_code, required),
(2, dummy_err_packet, required),
(3, sha256_of_onion, required),
});
},
}
Ok(())
}
}
impl Readable for HTLCForwardInfo {
fn read<R: Read>(r: &mut R) -> Result<Self, DecodeError> {
let id: u8 = Readable::read(r)?;
Ok(match id {
0 => Self::AddHTLC(Readable::read(r)?),
1 => {
_init_and_read_len_prefixed_tlv_fields!(r, {
(0, htlc_id, required),
(1, malformed_htlc_failure_code, option),
(2, err_packet, required),
(3, sha256_of_onion, option),
});
if let Some(failure_code) = malformed_htlc_failure_code {
Self::FailMalformedHTLC {
htlc_id: _init_tlv_based_struct_field!(htlc_id, required),
failure_code,
sha256_of_onion: sha256_of_onion.ok_or(DecodeError::InvalidValue)?,
}
} else {
Self::FailHTLC {
htlc_id: _init_tlv_based_struct_field!(htlc_id, required),
err_packet: _init_tlv_based_struct_field!(err_packet, required),
}
}
},
_ => return Err(DecodeError::InvalidValue),
})
}
}
impl_writeable_tlv_based!(PendingInboundPayment, {
(0, payment_secret, required),
@ -10930,12 +11057,14 @@ mod tests {
use crate::events::{Event, HTLCDestination, MessageSendEvent, MessageSendEventsProvider, ClosureReason};
use crate::ln::{PaymentPreimage, PaymentHash, PaymentSecret};
use crate::ln::ChannelId;
use crate::ln::channelmanager::{create_recv_pending_htlc_info, inbound_payment, PaymentId, PaymentSendFailure, RecipientOnionFields, InterceptId};
use crate::ln::channelmanager::{create_recv_pending_htlc_info, HTLCForwardInfo, inbound_payment, PaymentId, PaymentSendFailure, RecipientOnionFields, InterceptId};
use crate::ln::functional_test_utils::*;
use crate::ln::msgs::{self, ErrorAction};
use crate::ln::msgs::ChannelMessageHandler;
use crate::prelude::*;
use crate::routing::router::{PaymentParameters, RouteParameters, find_route};
use crate::util::errors::APIError;
use crate::util::ser::Writeable;
use crate::util::test_utils;
use crate::util::config::{ChannelConfig, ChannelConfigUpdate};
use crate::sign::EntropySource;
@ -12210,6 +12339,63 @@ mod tests {
check_spends!(txn[0], funding_tx);
}
}
#[test]
fn test_malformed_forward_htlcs_ser() {
// Ensure that `HTLCForwardInfo::FailMalformedHTLC`s are (de)serialized properly.
let chanmon_cfg = create_chanmon_cfgs(1);
let node_cfg = create_node_cfgs(1, &chanmon_cfg);
let persister;
let chain_monitor;
let chanmgrs = create_node_chanmgrs(1, &node_cfg, &[None]);
let deserialized_chanmgr;
let mut nodes = create_network(1, &node_cfg, &chanmgrs);
let dummy_failed_htlc = |htlc_id| {
HTLCForwardInfo::FailHTLC { htlc_id, err_packet: msgs::OnionErrorPacket { data: vec![42] }, }
};
let dummy_malformed_htlc = |htlc_id| {
HTLCForwardInfo::FailMalformedHTLC { htlc_id, failure_code: 0x4000, sha256_of_onion: [0; 32] }
};
let dummy_htlcs_1: Vec<HTLCForwardInfo> = (1..10).map(|htlc_id| {
if htlc_id % 2 == 0 {
dummy_failed_htlc(htlc_id)
} else {
dummy_malformed_htlc(htlc_id)
}
}).collect();
let dummy_htlcs_2: Vec<HTLCForwardInfo> = (1..10).map(|htlc_id| {
if htlc_id % 2 == 1 {
dummy_failed_htlc(htlc_id)
} else {
dummy_malformed_htlc(htlc_id)
}
}).collect();
let (scid_1, scid_2) = (42, 43);
let mut forward_htlcs = HashMap::new();
forward_htlcs.insert(scid_1, dummy_htlcs_1.clone());
forward_htlcs.insert(scid_2, dummy_htlcs_2.clone());
let mut chanmgr_fwd_htlcs = nodes[0].node.forward_htlcs.lock().unwrap();
*chanmgr_fwd_htlcs = forward_htlcs.clone();
core::mem::drop(chanmgr_fwd_htlcs);
reload_node!(nodes[0], nodes[0].node.encode(), &[], persister, chain_monitor, deserialized_chanmgr);
let mut deserialized_fwd_htlcs = nodes[0].node.forward_htlcs.lock().unwrap();
for scid in [scid_1, scid_2].iter() {
let deserialized_htlcs = deserialized_fwd_htlcs.remove(scid).unwrap();
assert_eq!(forward_htlcs.remove(scid).unwrap(), deserialized_htlcs);
}
assert!(deserialized_fwd_htlcs.is_empty());
core::mem::drop(deserialized_fwd_htlcs);
expect_pending_htlcs_forwardable!(nodes[0]);
}
}
#[cfg(ldk_bench)]

View file

@ -1971,6 +1971,18 @@ pub fn get_route(send_node: &Node, route_params: &RouteParameters) -> Result<Rou
)
}
/// Like `get_route` above, but adds a random CLTV offset to the final hop.
pub fn find_route(send_node: &Node, route_params: &RouteParameters) -> Result<Route, msgs::LightningError> {
let scorer = TestScorer::new();
let keys_manager = TestKeysInterface::new(&[0u8; 32], bitcoin::network::constants::Network::Testnet);
let random_seed_bytes = keys_manager.get_secure_random_bytes();
router::find_route(
&send_node.node.get_our_node_id(), route_params, &send_node.network_graph,
Some(&send_node.node.list_usable_channels().iter().collect::<Vec<_>>()),
send_node.logger, &scorer, &Default::default(), &random_seed_bytes
)
}
/// Gets a route from the given sender to the node described in `payment_params`.
///
/// Don't use this, use the identically-named function instead.

View file

@ -1678,6 +1678,7 @@ mod fuzzy_internal_msgs {
// These types aren't intended to be pub, but are exposed for direct fuzzing (as we deserialize
// them from untrusted input):
#[derive(Clone)]
#[cfg_attr(test, derive(Debug, PartialEq))]
pub struct FinalOnionHopData {
pub payment_secret: PaymentSecret,
/// The total value, in msat, of the payment as received by the ultimate recipient.
@ -1713,7 +1714,7 @@ mod fuzzy_internal_msgs {
outgoing_cltv_value: u32,
payment_secret: PaymentSecret,
payment_constraints: PaymentConstraints,
intro_node_blinding_point: PublicKey,
intro_node_blinding_point: Option<PublicKey>,
}
}
@ -2319,8 +2320,10 @@ impl Writeable for OutboundOnionPayload {
}
}
impl<NS: Deref> ReadableArgs<&NS> for InboundOnionPayload where NS::Target: NodeSigner {
fn read<R: Read>(r: &mut R, node_signer: &NS) -> Result<Self, DecodeError> {
impl<NS: Deref> ReadableArgs<(Option<PublicKey>, &NS)> for InboundOnionPayload where NS::Target: NodeSigner {
fn read<R: Read>(r: &mut R, args: (Option<PublicKey>, &NS)) -> Result<Self, DecodeError> {
let (update_add_blinding_point, node_signer) = args;
let mut amt = None;
let mut cltv_value = None;
let mut short_id: Option<u64> = None;
@ -2354,9 +2357,14 @@ impl<NS: Deref> ReadableArgs<&NS> for InboundOnionPayload where NS::Target: Node
});
if amt.unwrap_or(0) > MAX_VALUE_MSAT { return Err(DecodeError::InvalidValue) }
if intro_node_blinding_point.is_some() && update_add_blinding_point.is_some() {
return Err(DecodeError::InvalidValue)
}
if let Some(blinding_point) = intro_node_blinding_point {
if short_id.is_some() || payment_data.is_some() || payment_metadata.is_some() {
if let Some(blinding_point) = intro_node_blinding_point.or(update_add_blinding_point) {
if short_id.is_some() || payment_data.is_some() || payment_metadata.is_some() ||
keysend_preimage.is_some()
{
return Err(DecodeError::InvalidValue)
}
let enc_tlvs = encrypted_tlvs_opt.ok_or(DecodeError::InvalidValue)?.0;
@ -2377,7 +2385,7 @@ impl<NS: Deref> ReadableArgs<&NS> for InboundOnionPayload where NS::Target: Node
payment_relay,
payment_constraints,
features,
intro_node_blinding_point: blinding_point,
intro_node_blinding_point: intro_node_blinding_point.ok_or(DecodeError::InvalidValue)?,
})
},
ChaChaPolyReadAdapter { readable: BlindedPaymentTlvs::Receive(ReceiveTlvs {
@ -2390,7 +2398,7 @@ impl<NS: Deref> ReadableArgs<&NS> for InboundOnionPayload where NS::Target: Node
outgoing_cltv_value: cltv_value.ok_or(DecodeError::InvalidValue)?,
payment_secret,
payment_constraints,
intro_node_blinding_point: blinding_point,
intro_node_blinding_point,
})
},
}
@ -3987,7 +3995,7 @@ mod tests {
assert_eq!(encoded_value, target_value);
let node_signer = test_utils::TestKeysInterface::new(&[42; 32], Network::Testnet);
let inbound_msg = ReadableArgs::read(&mut Cursor::new(&target_value[..]), &&node_signer).unwrap();
let inbound_msg = ReadableArgs::read(&mut Cursor::new(&target_value[..]), (None, &&node_signer)).unwrap();
if let msgs::InboundOnionPayload::Forward {
short_channel_id, amt_to_forward, outgoing_cltv_value
} = inbound_msg {
@ -4012,7 +4020,7 @@ mod tests {
assert_eq!(encoded_value, target_value);
let node_signer = test_utils::TestKeysInterface::new(&[42; 32], Network::Testnet);
let inbound_msg = ReadableArgs::read(&mut Cursor::new(&target_value[..]), &&node_signer).unwrap();
let inbound_msg = ReadableArgs::read(&mut Cursor::new(&target_value[..]), (None, &&node_signer)).unwrap();
if let msgs::InboundOnionPayload::Receive {
payment_data: None, amt_msat, outgoing_cltv_value, ..
} = inbound_msg {
@ -4040,7 +4048,7 @@ mod tests {
assert_eq!(encoded_value, target_value);
let node_signer = test_utils::TestKeysInterface::new(&[42; 32], Network::Testnet);
let inbound_msg = ReadableArgs::read(&mut Cursor::new(&target_value[..]), &&node_signer).unwrap();
let inbound_msg = ReadableArgs::read(&mut Cursor::new(&target_value[..]), (None, &&node_signer)).unwrap();
if let msgs::InboundOnionPayload::Receive {
payment_data: Some(FinalOnionHopData {
payment_secret,
@ -4076,7 +4084,7 @@ mod tests {
};
let encoded_value = msg.encode();
let node_signer = test_utils::TestKeysInterface::new(&[42; 32], Network::Testnet);
assert!(msgs::InboundOnionPayload::read(&mut Cursor::new(&encoded_value[..]), &&node_signer).is_err());
assert!(msgs::InboundOnionPayload::read(&mut Cursor::new(&encoded_value[..]), (None, &&node_signer)).is_err());
let good_type_range_tlvs = vec![
((1 << 16) - 3, vec![42]),
((1 << 16) - 1, vec![42; 32]),
@ -4085,7 +4093,7 @@ mod tests {
*custom_tlvs = good_type_range_tlvs.clone();
}
let encoded_value = msg.encode();
let inbound_msg = ReadableArgs::read(&mut Cursor::new(&encoded_value[..]), &&node_signer).unwrap();
let inbound_msg = ReadableArgs::read(&mut Cursor::new(&encoded_value[..]), (None, &&node_signer)).unwrap();
match inbound_msg {
msgs::InboundOnionPayload::Receive { custom_tlvs, .. } => assert!(custom_tlvs.is_empty()),
_ => panic!(),
@ -4110,7 +4118,7 @@ mod tests {
let target_value = <Vec<u8>>::from_hex("2e02080badf00d010203040404ffffffffff0000000146c6616b021234ff0000000146c6616f084242424242424242").unwrap();
assert_eq!(encoded_value, target_value);
let node_signer = test_utils::TestKeysInterface::new(&[42; 32], Network::Testnet);
let inbound_msg: msgs::InboundOnionPayload = ReadableArgs::read(&mut Cursor::new(&target_value[..]), &&node_signer).unwrap();
let inbound_msg: msgs::InboundOnionPayload = ReadableArgs::read(&mut Cursor::new(&target_value[..]), (None, &&node_signer)).unwrap();
if let msgs::InboundOnionPayload::Receive {
payment_data: None,
payment_metadata: None,
@ -4275,8 +4283,8 @@ mod tests {
let mut rd = Cursor::new(&big_payload[..]);
let node_signer = test_utils::TestKeysInterface::new(&[42; 32], Network::Testnet);
<msgs::InboundOnionPayload as ReadableArgs<&&test_utils::TestKeysInterface>>
::read(&mut rd, &&node_signer).unwrap();
<msgs::InboundOnionPayload as ReadableArgs<(Option<PublicKey>, &&test_utils::TestKeysInterface)>>
::read(&mut rd, (None, &&node_signer)).unwrap();
}
// see above test, needs to be a separate method for use of the serialization macros.
fn encode_big_payload() -> Result<Vec<u8>, io::Error> {

View file

@ -3,9 +3,10 @@
//! Primarily features [`peel_payment_onion`], which allows the decoding of an onion statelessly
//! and can be used to predict whether we'd accept a payment.
use bitcoin::hashes::Hash;
use bitcoin::hashes::{Hash, HashEngine};
use bitcoin::hashes::hmac::{Hmac, HmacEngine};
use bitcoin::hashes::sha256::Hash as Sha256;
use bitcoin::secp256k1::{self, Secp256k1, PublicKey};
use bitcoin::secp256k1::{self, PublicKey, Scalar, Secp256k1};
use crate::blinded_path;
use crate::blinded_path::payment::{PaymentConstraints, PaymentRelay};
@ -33,6 +34,15 @@ pub struct InboundOnionErr {
pub msg: &'static str,
}
fn check_blinded_payment_constraints(
amt_msat: u64, cltv_expiry: u32, constraints: &PaymentConstraints
) -> Result<(), ()> {
if amt_msat < constraints.htlc_minimum_msat ||
cltv_expiry > constraints.max_cltv_expiry
{ return Err(()) }
Ok(())
}
fn check_blinded_forward(
inbound_amt_msat: u64, inbound_cltv_expiry: u32, payment_relay: &PaymentRelay,
payment_constraints: &PaymentConstraints, features: &BlindedHopFeatures
@ -43,9 +53,8 @@ fn check_blinded_forward(
let outgoing_cltv_value = inbound_cltv_expiry.checked_sub(
payment_relay.cltv_expiry_delta as u32
).ok_or(())?;
if inbound_amt_msat < payment_constraints.htlc_minimum_msat ||
outgoing_cltv_value > payment_constraints.max_cltv_expiry
{ return Err(()) }
check_blinded_payment_constraints(inbound_amt_msat, outgoing_cltv_value, payment_constraints)?;
if features.requires_unknown_bits_from(&BlindedHopFeatures::empty()) { return Err(()) }
Ok((amt_to_forward, outgoing_cltv_value))
}
@ -112,16 +121,30 @@ pub(super) fn create_recv_pending_htlc_info(
amt_msat: u64, cltv_expiry: u32, phantom_shared_secret: Option<[u8; 32]>, allow_underpay: bool,
counterparty_skimmed_fee_msat: Option<u64>, current_height: u32, accept_mpp_keysend: bool,
) -> Result<PendingHTLCInfo, InboundOnionErr> {
let (payment_data, keysend_preimage, custom_tlvs, onion_amt_msat, outgoing_cltv_value, payment_metadata) = match hop_data {
let (
payment_data, keysend_preimage, custom_tlvs, onion_amt_msat, outgoing_cltv_value,
payment_metadata, requires_blinded_error
) = match hop_data {
msgs::InboundOnionPayload::Receive {
payment_data, keysend_preimage, custom_tlvs, amt_msat, outgoing_cltv_value, payment_metadata, ..
} =>
(payment_data, keysend_preimage, custom_tlvs, amt_msat, outgoing_cltv_value, payment_metadata),
(payment_data, keysend_preimage, custom_tlvs, amt_msat, outgoing_cltv_value, payment_metadata,
false),
msgs::InboundOnionPayload::BlindedReceive {
amt_msat, total_msat, outgoing_cltv_value, payment_secret, ..
amt_msat, total_msat, outgoing_cltv_value, payment_secret, intro_node_blinding_point,
payment_constraints, ..
} => {
check_blinded_payment_constraints(amt_msat, cltv_expiry, &payment_constraints)
.map_err(|()| {
InboundOnionErr {
err_code: INVALID_ONION_BLINDING,
err_data: vec![0; 32],
msg: "Amount or cltv_expiry violated blinded payment constraints",
}
})?;
let payment_data = msgs::FinalOnionHopData { payment_secret, total_msat };
(Some(payment_data), None, Vec::new(), amt_msat, outgoing_cltv_value, None)
(Some(payment_data), None, Vec::new(), amt_msat, outgoing_cltv_value, None,
intro_node_blinding_point.is_none())
}
msgs::InboundOnionPayload::Forward { .. } => {
return Err(InboundOnionErr {
@ -208,6 +231,7 @@ pub(super) fn create_recv_pending_htlc_info(
incoming_cltv_expiry: outgoing_cltv_value,
phantom_shared_secret,
custom_tlvs,
requires_blinded_error,
}
} else {
return Err(InboundOnionErr {
@ -313,11 +337,16 @@ where
($msg: expr, $err_code: expr) => {
{
log_info!(logger, "Failed to accept/forward incoming HTLC: {}", $msg);
let (sha256_of_onion, failure_code) = if msg.blinding_point.is_some() {
([0; 32], INVALID_ONION_BLINDING)
} else {
(Sha256::hash(&msg.onion_routing_packet.hop_data).to_byte_array(), $err_code)
};
return Err(HTLCFailureMsg::Malformed(msgs::UpdateFailMalformedHTLC {
channel_id: msg.channel_id,
htlc_id: msg.htlc_id,
sha256_of_onion: Sha256::hash(&msg.onion_routing_packet.hop_data).to_byte_array(),
failure_code: $err_code,
sha256_of_onion,
failure_code,
}));
}
}
@ -327,8 +356,14 @@ where
return_malformed_err!("invalid ephemeral pubkey", 0x8000 | 0x4000 | 6);
}
let blinded_node_id_tweak = msg.blinding_point.map(|bp| {
let blinded_tlvs_ss = node_signer.ecdh(Recipient::Node, &bp, None).unwrap().secret_bytes();
let mut hmac = HmacEngine::<Sha256>::new(b"blinded_node_id");
hmac.input(blinded_tlvs_ss.as_ref());
Scalar::from_be_bytes(Hmac::from_engine(hmac).to_byte_array()).unwrap()
});
let shared_secret = node_signer.ecdh(
Recipient::Node, &msg.onion_routing_packet.public_key.unwrap(), None
Recipient::Node, &msg.onion_routing_packet.public_key.unwrap(), blinded_node_id_tweak.as_ref()
).unwrap().secret_bytes();
if msg.onion_routing_packet.version != 0 {
@ -343,6 +378,10 @@ where
macro_rules! return_err {
($msg: expr, $err_code: expr, $data: expr) => {
{
if msg.blinding_point.is_some() {
return_malformed_err!($msg, INVALID_ONION_BLINDING)
}
log_info!(logger, "Failed to accept/forward incoming HTLC: {}", $msg);
return Err(HTLCFailureMsg::Relay(msgs::UpdateFailHTLC {
channel_id: msg.channel_id,
@ -356,7 +395,7 @@ where
let next_hop = match onion_utils::decode_next_payment_hop(
shared_secret, &msg.onion_routing_packet.hop_data[..], msg.onion_routing_packet.hmac,
msg.payment_hash, node_signer
msg.payment_hash, msg.blinding_point, node_signer
) {
Ok(res) => res,
Err(onion_utils::OnionDecodeErr::Malformed { err_msg, err_code }) => {

View file

@ -940,9 +940,11 @@ pub(crate) enum OnionDecodeErr {
pub(crate) fn decode_next_payment_hop<NS: Deref>(
shared_secret: [u8; 32], hop_data: &[u8], hmac_bytes: [u8; 32], payment_hash: PaymentHash,
node_signer: &NS,
blinding_point: Option<PublicKey>, node_signer: &NS,
) -> Result<Hop, OnionDecodeErr> where NS::Target: NodeSigner {
match decode_next_hop(shared_secret, hop_data, hmac_bytes, Some(payment_hash), node_signer) {
match decode_next_hop(
shared_secret, hop_data, hmac_bytes, Some(payment_hash), (blinding_point, node_signer)
) {
Ok((next_hop_data, None)) => Ok(Hop::Receive(next_hop_data)),
Ok((next_hop_data, Some((next_hop_hmac, FixedSizeOnionPacket(new_packet_bytes))))) => {
Ok(Hop::Forward {

View file

@ -0,0 +1,4 @@
## Backwards Compatibility
* Nodes that upgrade to 0.0.119 and subsequently downgrade after receiving a payment to a blinded
path may lose privacy if one or more of those HTLCs fails.