diff --git a/lightning/src/chain/channelmonitor.rs b/lightning/src/chain/channelmonitor.rs index ddb1e31f645..6707ce3452c 100644 --- a/lightning/src/chain/channelmonitor.rs +++ b/lightning/src/chain/channelmonitor.rs @@ -2626,6 +2626,7 @@ impl ChannelMonitorImpl { let outbound_payment = match source { None => panic!("Outbound HTLCs should have a source"), Some(&HTLCSource::PreviousHopData(_)) => false, + Some(&HTLCSource::TrampolineForward { .. }) => false, Some(&HTLCSource::OutboundRoute { .. }) => true, }; return Some(Balance::MaybeTimeoutClaimableHTLC { @@ -2835,6 +2836,7 @@ impl ChannelMonitor { let outbound_payment = match source { None => panic!("Outbound HTLCs should have a source"), Some(HTLCSource::PreviousHopData(_)) => false, + Some(HTLCSource::TrampolineForward { .. }) => false, Some(HTLCSource::OutboundRoute { .. }) => true, }; if outbound_payment { @@ -3494,7 +3496,8 @@ impl ChannelMonitorImpl { } })); } - self.counterparty_fulfilled_htlcs.insert(*claimed_htlc_id, *claimed_preimage); + let claimed = claimed_htlc_id.clone(); + self.counterparty_fulfilled_htlcs.insert(claimed, *claimed_preimage); } Ok(()) diff --git a/lightning/src/events/mod.rs b/lightning/src/events/mod.rs index 2b712073568..dc86a7fdade 100644 --- a/lightning/src/events/mod.rs +++ b/lightning/src/events/mod.rs @@ -539,6 +539,14 @@ pub enum HTLCHandlingFailureType { /// Short channel id we are requesting to forward an HTLC to. requested_forward_scid: u64, }, + /// We couldn't forward to the next Trampoline node. That may happen if we cannot find a route, + /// or if the route we found didn't work out. + FailedTrampolineForward { + /// The node ID of the next Trampoline hop we tried forwarding to. + requested_next_node_id: PublicKey, + /// The channel we tried forwarding over, if we have settled to one. + forward_scid: Option, + }, /// We couldn't decode the incoming onion to obtain the forwarding details. InvalidOnion, /// Failure scenario where an HTLC may have been forwarded to be intended for us, @@ -572,6 +580,10 @@ impl_writeable_tlv_based_enum_upgradable!(HTLCHandlingFailureType, (4, Receive) => { (0, payment_hash, required), }, + (5, FailedTrampolineForward) => { + (0, requested_next_node_id, required), + (2, forward_scid, option), + } ); /// The reason for HTLC failures in [`Event::HTLCHandlingFailed`]. diff --git a/lightning/src/ln/blinded_payment_tests.rs b/lightning/src/ln/blinded_payment_tests.rs index d72c816cba9..3429655366d 100644 --- a/lightning/src/ln/blinded_payment_tests.rs +++ b/lightning/src/ln/blinded_payment_tests.rs @@ -9,6 +9,7 @@ // You may not use this file except in accordance with one or both of these // licenses. +use std::cmp::PartialEq; use bitcoin::hashes::hex::FromHex; use bitcoin::hashes::sha256::Hash as Sha256; use bitcoin::hashes::Hash; @@ -2086,7 +2087,7 @@ fn do_test_trampoline_single_hop_receive(success: bool) { pubkey: carol_node_id, node_features: Features::empty(), fee_msat: amt_msat, - cltv_expiry_delta: 24, + cltv_expiry_delta: 104, }, ], hops: carol_blinded_hops, @@ -2206,8 +2207,7 @@ fn test_trampoline_single_hop_receive() { do_test_trampoline_single_hop_receive(false); } -#[test] -fn test_trampoline_unblinded_receive() { +fn do_test_trampoline_unblinded_receive(underpay: bool) { // Simulate a payment of A (0) -> B (1) -> C(Trampoline) (2) const TOTAL_NODE_COUNT: usize = 3; @@ -2277,7 +2277,7 @@ fn test_trampoline_unblinded_receive() { node_features: NodeFeatures::empty(), short_channel_id: bob_carol_scid, channel_features: ChannelFeatures::empty(), - fee_msat: 0, + fee_msat: 0, // no routing fees because it's the final hop cltv_expiry_delta: 48, maybe_announced_channel: false, } @@ -2288,8 +2288,8 @@ fn test_trampoline_unblinded_receive() { TrampolineHop { pubkey: carol_node_id, node_features: Features::empty(), - fee_msat: amt_msat, - cltv_expiry_delta: 24, + fee_msat: 0, + cltv_expiry_delta: 72, }, ], hops: carol_blinded_hops, @@ -2301,6 +2301,8 @@ fn test_trampoline_unblinded_receive() { route_params: None, }; + let payment_id = PaymentId(payment_hash.0); + nodes[0].node.send_payment_with_route(route.clone(), payment_hash, RecipientOnionFields::spontaneous_empty(), PaymentId(payment_hash.0)).unwrap(); let replacement_onion = { @@ -2311,17 +2313,18 @@ fn test_trampoline_unblinded_receive() { let recipient_onion_fields = RecipientOnionFields::spontaneous_empty(); let blinded_tail = route.paths[0].blinded_tail.clone().unwrap(); - let (mut trampoline_payloads, outer_total_msat, outer_starting_htlc_offset) = onion_utils::build_trampoline_onion_payloads(&blinded_tail, amt_msat, &recipient_onion_fields, 32, &None).unwrap(); + let (mut trampoline_payloads, outer_total_msat, outer_starting_htlc_offset) = onion_utils::build_trampoline_onion_payloads(&blinded_tail, amt_msat, &recipient_onion_fields, 32, &None).unwrap(); // pop the last dummy hop trampoline_payloads.pop(); + let replacement_payload_amount = if underpay { amt_msat * 2 } else { amt_msat }; trampoline_payloads.push(msgs::OutboundTrampolinePayload::Receive { payment_data: Some(msgs::FinalOnionHopData { payment_secret, - total_msat: amt_msat, + total_msat: replacement_payload_amount, }), - sender_intended_htlc_amt_msat: amt_msat, + sender_intended_htlc_amt_msat: replacement_payload_amount, cltv_expiry_height: 104, }); @@ -2334,10 +2337,20 @@ fn test_trampoline_unblinded_receive() { None, ).unwrap(); - // Use a different session key to construct the replacement onion packet. Note that the sender isn't aware of - // this and won't be able to decode the fulfill hold times. - let outer_session_priv = secret_from_hex("e52c20461ed7acd46c4e7b591a37610519179482887bd73bf3b94617f8f03677"); + // Get the original inner session private key that the ChannelManager generated so we can + // re-use it for the outer session private key. This way HMAC validation in attributable + // errors do not makes the test fail. + let mut orig_inner_priv_bytes = [0u8; 32]; + nodes[0].node.test_modify_pending_payment(&payment_id, |pmt| { + if let crate::ln::outbound_payment::PendingOutboundPayment::Retryable { session_privs, .. } = pmt { + orig_inner_priv_bytes = *session_privs.iter().next().unwrap(); + } + }); + let inner_session_priv = SecretKey::from_slice(&orig_inner_priv_bytes).unwrap(); + // Derive the outer session private key from the inner one. + let outer_session_priv_hash = Sha256::hash(&inner_session_priv.secret_bytes()); + let outer_session_priv = SecretKey::from_slice(&outer_session_priv_hash.to_byte_array()).unwrap(); let (outer_payloads, _, _) = onion_utils::build_onion_payloads(&route.paths[0], outer_total_msat, &recipient_onion_fields, outer_starting_htlc_offset, &None, None, Some(trampoline_packet)).unwrap(); let outer_onion_keys = onion_utils::construct_onion_keys(&secp_ctx, &route.clone().paths[0], &outer_session_priv); let outer_packet = onion_utils::construct_onion_packet( @@ -2367,11 +2380,51 @@ fn test_trampoline_unblinded_receive() { }); let route: &[&Node] = &[&nodes[1], &nodes[2]]; - let args = PassAlongPathArgs::new(&nodes[0], route, amt_msat, payment_hash, first_message_event) - .with_payment_secret(payment_secret); + let args = PassAlongPathArgs::new(&nodes[0], route, amt_msat, payment_hash, first_message_event); + + let args = if underpay { + args.with_payment_preimage(payment_preimage) + .without_claimable_event() + .expect_failure(HTLCHandlingFailureType::Receive { payment_hash }) + } else { + args.with_payment_secret(payment_secret) + }; + do_pass_along_path(args); - claim_payment(&nodes[0], &[&nodes[1], &nodes[2]], payment_preimage); + if underpay { + { + let unblinded_node_updates = get_htlc_update_msgs!(nodes[2], nodes[1].node.get_our_node_id()); + nodes[1].node.handle_update_fail_htlc( + nodes[2].node.get_our_node_id(), &unblinded_node_updates.update_fail_htlcs[0] + ); + do_commitment_signed_dance(&nodes[1], &nodes[2], &unblinded_node_updates.commitment_signed, true, false); + } + { + let unblinded_node_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(), &unblinded_node_updates.update_fail_htlcs[0] + ); + do_commitment_signed_dance(&nodes[0], &nodes[1], &unblinded_node_updates.commitment_signed, false, false); + } + { + let payment_failed_conditions = PaymentFailedConditions::new() + .expected_htlc_error_data(LocalHTLCFailureReason::FinalIncorrectHTLCAmount, &[0, 0, 0, 0, 0, 0, 3, 232]); + expect_payment_failed_conditions(&nodes[0], payment_hash, false, payment_failed_conditions); + } + } else { + claim_payment(&nodes[0], &[&nodes[1], &nodes[2]], payment_preimage); + } +} + +#[test] +fn test_trampoline_unblinded_receive_underpay() { + do_test_trampoline_unblinded_receive(true); +} + +#[test] +fn test_trampoline_unblinded_receive_normal() { + do_test_trampoline_unblinded_receive(false); } #[test] @@ -2486,9 +2539,703 @@ fn test_trampoline_forward_rejection() { do_commitment_signed_dance(&nodes[0], &nodes[1], &unblinded_node_updates.commitment_signed, false, false); } { - // Expect UnknownNextPeer error while we are unable to route forwarding Trampoline payments. let payment_failed_conditions = PaymentFailedConditions::new() - .expected_htlc_error_data(LocalHTLCFailureReason::UnknownNextPeer, &[0; 0]); + .expected_htlc_error_data(LocalHTLCFailureReason::FinalIncorrectHTLCAmount, &[0, 0, 0, 0, 0, 0, 3, 232]); expect_payment_failed_conditions(&nodes[0], payment_hash, false, payment_failed_conditions); } } + +#[derive(PartialEq)] +enum TrampolineForwardFailureScenario { + NoRoute, + InvalidRecipientOnion, + InvalidInterTrampolineOnion, +} + +fn do_test_unblinded_trampoline_forward(failure_scenario: Option) { + // Simulate a payment of A (0) -> B (1) -> C(Trampoline) (2) -> D(Trampoline(receive)) (3) + // trampoline hops C -> T0 (4) -> D + // make it fail at B, then at C's outer onion, then at C's inner onion + const TOTAL_NODE_COUNT: usize = 5; + let secp_ctx = Secp256k1::new(); + + let chanmon_cfgs = create_chanmon_cfgs(TOTAL_NODE_COUNT); + let node_cfgs = create_node_cfgs(TOTAL_NODE_COUNT, &chanmon_cfgs); + let node_chanmgrs = create_node_chanmgrs(TOTAL_NODE_COUNT, &node_cfgs, &vec![None; TOTAL_NODE_COUNT]); + let mut nodes = create_network(TOTAL_NODE_COUNT, &node_cfgs, &node_chanmgrs); + + let (_, _, chan_id_alice_bob, _) = create_announced_chan_between_nodes_with_value(&nodes, 0, 1, 1_000_000, 0); + let (_, _, chan_id_bob_carol, _) = create_announced_chan_between_nodes_with_value(&nodes, 1, 2, 1_000_000, 0); + if failure_scenario != Some(TrampolineForwardFailureScenario::NoRoute) { + let (_, _, _, _) = create_announced_chan_between_nodes_with_value(&nodes, 2, 4, 1_000_000, 0); + } + let (_, _, _, _) = create_announced_chan_between_nodes_with_value(&nodes, 4, 3, 1_000_000, 0); + + for i in 0..TOTAL_NODE_COUNT { // connect all nodes' blocks + connect_blocks(&nodes[i], (TOTAL_NODE_COUNT as u32) * CHAN_CONFIRM_DEPTH + 1 - nodes[i].best_block_info().1); + } + + let alice_node_id = nodes[0].node().get_our_node_id(); + let bob_node_id = nodes[1].node().get_our_node_id(); + let carol_node_id = nodes[2].node().get_our_node_id(); + let dave_node_id = nodes[3].node().get_our_node_id(); + + let alice_bob_scid = nodes[0].node().list_channels().iter().find(|c| c.channel_id == chan_id_alice_bob).unwrap().short_channel_id.unwrap(); + let bob_carol_scid = nodes[1].node().list_channels().iter().find(|c| c.channel_id == chan_id_bob_carol).unwrap().short_channel_id.unwrap(); + + let amt_msat = 1000; + let (payment_preimage, payment_hash, payment_secret) = get_payment_preimage_hash(&nodes[3], Some(amt_msat), None); + + let route = Route { + paths: vec![Path { + hops: vec![ + // Bob + RouteHop { + pubkey: bob_node_id, + node_features: NodeFeatures::empty(), + short_channel_id: alice_bob_scid, + channel_features: ChannelFeatures::empty(), + fee_msat: 1000, // forwarding fee to Carol + cltv_expiry_delta: 48, + maybe_announced_channel: false, + }, + + // Carol + RouteHop { + pubkey: carol_node_id, + node_features: NodeFeatures::empty(), + short_channel_id: bob_carol_scid, + channel_features: ChannelFeatures::empty(), + fee_msat: 2000, // fee for the usage of the entire blinded path, including Trampoline + cltv_expiry_delta: 48, + maybe_announced_channel: false, + } + ], + blinded_tail: Some(BlindedTail { + trampoline_hops: vec![ + // Carol + TrampolineHop { + pubkey: carol_node_id, + node_features: Features::empty(), + fee_msat: amt_msat, + cltv_expiry_delta: 176, // let her cook + }, + + // Dave (recipient) + TrampolineHop { + pubkey: dave_node_id, + node_features: Features::empty(), + fee_msat: 0, // no need to charge a fee as the recipient + cltv_expiry_delta: 24, + }, + ], + hops: vec![ + // Dave's blinded node id + BlindedHop { + blinded_node_id: pubkey_from_hex("0295d40514096a8be54859e7dfe947b376eaafea8afe5cb4eb2c13ff857ed0b4be"), + encrypted_payload: bytes_from_hex("0ccf3c8a58deaa603f657ee2a5ed9d604eb5c8ca1e5f801989afa8f3ea6d789bbdde2c7e7a1ef9ca8c38d2c54760febad8446d3f273ddb537569ef56613846ccd3aba78a"), + } + ], + blinding_point: alice_node_id, + excess_final_cltv_expiry_delta: 39, + final_value_msat: amt_msat, + }) + }], + route_params: None, + }; + + let payment_id = PaymentId(payment_hash.0); + nodes[0].node.send_payment_with_route(route.clone(), payment_hash, RecipientOnionFields::spontaneous_empty(), PaymentId(payment_hash.0)).unwrap(); + + let mut replacement_onion = None; + + // We don't check in failure scenario as the substitution breaks HMAC Legacy verification. + if failure_scenario.is_none() { + replacement_onion = { + // create a substitute onion where the last Trampoline hop is an unblinded receive, which we + // (deliberately) do not support out of the box, therefore necessitating this workaround + let trampoline_secret_key = secret_from_hex("0134928f7b7ca6769080d70f16be84c812c741f545b49a34db47ce338a205799"); + let prng_seed = secret_from_hex("fe02b4b9054302a3ddf4e1e9f7c411d644aebbd295218ab009dca94435f775a9"); + let recipient_onion_fields = RecipientOnionFields::spontaneous_empty(); + + let blinded_tail = route.paths[0].blinded_tail.clone().unwrap(); + let (mut trampoline_payloads, outer_total_msat, outer_starting_htlc_offset) = onion_utils::build_trampoline_onion_payloads(&blinded_tail, amt_msat, &recipient_onion_fields, 32, &None).unwrap(); + + // pop the last dummy hop + trampoline_payloads.pop(); + + trampoline_payloads.push(msgs::OutboundTrampolinePayload::Receive { + payment_data: Some(msgs::FinalOnionHopData { + payment_secret, + total_msat: amt_msat, + }), + sender_intended_htlc_amt_msat: amt_msat, + cltv_expiry_height: 96, + }); + + let trampoline_onion_keys = onion_utils::construct_trampoline_onion_keys(&secp_ctx, &route.paths[0].blinded_tail.as_ref().unwrap(), &trampoline_secret_key); + let trampoline_packet = onion_utils::construct_trampoline_onion_packet( + trampoline_payloads, + trampoline_onion_keys, + prng_seed.secret_bytes(), + &payment_hash, + None, + ).unwrap(); + + // Get the original inner session private key that the ChannelManager generated so we can + // re-use it for the outer session private key. This way HMAC validation in attributable + // errors do not makes the test fail. + let mut orig_inner_priv_bytes = [0u8; 32]; + nodes[0].node.test_modify_pending_payment(&payment_id, |pmt| { + if let crate::ln::outbound_payment::PendingOutboundPayment::Retryable { session_privs, .. } = pmt { + orig_inner_priv_bytes = *session_privs.iter().next().unwrap(); + } + }); + let inner_session_priv = SecretKey::from_slice(&orig_inner_priv_bytes).unwrap(); + + // Derive the outer session private key from the inner one. + let outer_session_priv_hash = Sha256::hash(&inner_session_priv.secret_bytes()); + let outer_session_priv = SecretKey::from_slice(&outer_session_priv_hash.to_byte_array()).unwrap(); + + let (outer_payloads, _, _) = onion_utils::build_onion_payloads(&route.paths[0], outer_total_msat, &recipient_onion_fields, outer_starting_htlc_offset, &None, None, Some(trampoline_packet)).unwrap(); let outer_onion_keys = onion_utils::construct_onion_keys(&secp_ctx, &route.clone().paths[0], &outer_session_priv); + let outer_packet = onion_utils::construct_onion_packet( + outer_payloads, + outer_onion_keys, + prng_seed.secret_bytes(), + &payment_hash, + ).unwrap(); + + Some(outer_packet) + }; + } + + check_added_monitors!(&nodes[0], 1); + + let mut events = nodes[0].node.get_and_clear_pending_msg_events(); + assert_eq!(events.len(), 1); + let mut first_message_event = remove_first_msg_event_to_node(&nodes[1].node.get_our_node_id(), &mut events); + + if let Some(replacement_onion) = replacement_onion { + { + let mut update_message_alice_bob = match first_message_event { + MessageSendEvent::UpdateHTLCs { ref mut updates, .. } => { + assert_eq!(updates.update_add_htlcs.len(), 1); + updates.update_add_htlcs.get_mut(0) + } + _ => panic!() + }; + update_message_alice_bob.map(|msg| { + msg.onion_routing_packet = replacement_onion.clone(); + }); + } + } + + match failure_scenario { + None => { + let route: &[&Node] = &[&nodes[1], &nodes[2], &nodes[4], &nodes[3]]; + let args = PassAlongPathArgs::new(&nodes[0], route, amt_msat, payment_hash, first_message_event) + .with_payment_secret(payment_secret); + do_pass_along_path(args); + claim_payment(&nodes[0], &[&nodes[1], &nodes[2], &nodes[4], &nodes[3]], payment_preimage); + } + Some(TrampolineForwardFailureScenario::NoRoute) => { + let route = &[&nodes[1], &nodes[2]]; + let args = PassAlongPathArgs::new(&nodes[0], route, amt_msat, payment_hash, first_message_event) + .with_payment_preimage(payment_preimage) + .without_claimable_event() + .expect_failure(HTLCHandlingFailureType::FailedTrampolineForward { requested_next_node_id: dave_node_id, forward_scid: None }); + do_pass_along_path(args); + + { + let unblinded_node_updates = get_htlc_update_msgs!(nodes[2], nodes[1].node.get_our_node_id()); + nodes[1].node.handle_update_fail_htlc( + nodes[2].node.get_our_node_id(), &unblinded_node_updates.update_fail_htlcs[0], + ); + do_commitment_signed_dance(&nodes[1], &nodes[2], &unblinded_node_updates.commitment_signed, true, false); + } + { + let unblinded_node_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(), &unblinded_node_updates.update_fail_htlcs[0], + ); + do_commitment_signed_dance(&nodes[0], &nodes[1], &unblinded_node_updates.commitment_signed, false, false); + } + { + // We use Unkonwn Failure code instead of TemporaryTrampolineFailure because errors uses u16 bolt04 codes. + let payment_failed_conditions = PaymentFailedConditions::new() + .expected_htlc_error_data(LocalHTLCFailureReason::UnknownFailureCode { code: 16409 }, &[0; 0]); + expect_payment_failed_conditions(&nodes[0], payment_hash, false, payment_failed_conditions); + } + } + Some(TrampolineForwardFailureScenario::InvalidInterTrampolineOnion) => { + let route_alice_carol: &[&Node] = &[&nodes[1], &nodes[2]]; + pass_along_path(&nodes[0], route_alice_carol, amt_msat, payment_hash.clone(), + None, first_message_event.clone(), false, Some(payment_preimage)); + + check_added_monitors!(&nodes[2], 1); + let mut events = nodes[2].node.get_and_clear_pending_msg_events(); + assert_eq!(events.len(), 1); + + let mut update_message_carol_t0 = remove_first_msg_event_to_node(&nodes[4].node.get_our_node_id(), &mut events); + { + let mut update_message = match update_message_carol_t0 { + MessageSendEvent::UpdateHTLCs { ref mut updates, .. } => { + assert_eq!(updates.update_add_htlcs.len(), 1); + updates.update_add_htlcs.get_mut(0) + } + _ => panic!() + }; + update_message.map(|msg| { + // corrupt the onion packet + msg.onion_routing_packet.hmac = [1; 32]; + }); + } + + let route_carol_t0: &[&Node] = &[&nodes[4]]; + let args = PassAlongPathArgs::new(&nodes[2], route_carol_t0, amt_msat, payment_hash, update_message_carol_t0.clone()).expect_failure(HTLCHandlingFailureType::InvalidOnion); + do_pass_along_path(args); + + { + let downstream_id = 4; + let upstream_id = 2; + let unblinded_node_updates = get_htlc_update_msgs!(nodes[downstream_id], nodes[upstream_id].node.get_our_node_id()); + nodes[upstream_id].node.handle_update_fail_malformed_htlc( + nodes[downstream_id].node.get_our_node_id(), &unblinded_node_updates.update_fail_malformed_htlcs[0] + ); + do_commitment_signed_dance(&nodes[upstream_id], &nodes[downstream_id], &unblinded_node_updates.commitment_signed, true, false); + } + { + let downstream_id = 2; + let upstream_id = 1; + let unblinded_node_updates = get_htlc_update_msgs!(nodes[downstream_id], nodes[upstream_id].node.get_our_node_id()); + nodes[upstream_id].node.handle_update_fail_htlc( + nodes[downstream_id].node.get_our_node_id(), &unblinded_node_updates.update_fail_htlcs[0] + ); + do_commitment_signed_dance(&nodes[upstream_id], &nodes[downstream_id], &unblinded_node_updates.commitment_signed, true, false); + } + { + let downstream_id = 1; + let upstream_id = 0; + let unblinded_node_updates = get_htlc_update_msgs!(nodes[downstream_id], nodes[upstream_id].node.get_our_node_id()); + nodes[upstream_id].node.handle_update_fail_htlc( + nodes[downstream_id].node.get_our_node_id(), &unblinded_node_updates.update_fail_htlcs[0] + ); + do_commitment_signed_dance(&nodes[upstream_id], &nodes[downstream_id], &unblinded_node_updates.commitment_signed, false, false); + } + { + // We use Unkonwn Failure code instead of TemporaryTrampolineFailure because errors uses u16 bolt04 codes. + let payment_failed_conditions = PaymentFailedConditions::new() + .expected_htlc_error_data(LocalHTLCFailureReason::UnknownFailureCode { code: 16409 }, &[0; 0]); + expect_payment_failed_conditions(&nodes[0], payment_hash, false, payment_failed_conditions); + } + } + Some(TrampolineForwardFailureScenario::InvalidRecipientOnion) => { + let route_alice_t0: &[&Node] = &[&nodes[1], &nodes[2], &nodes[4]]; + pass_along_path(&nodes[0], route_alice_t0, amt_msat, payment_hash.clone(), + None, first_message_event.clone(), false, Some(payment_preimage)); + + check_added_monitors!(&nodes[4], 1); + let mut events = nodes[4].node.get_and_clear_pending_msg_events(); + assert_eq!(events.len(), 1); + + let mut update_message_t0_dave = remove_first_msg_event_to_node(&nodes[3].node.get_our_node_id(), &mut events); + { + let mut update_message = match update_message_t0_dave { + MessageSendEvent::UpdateHTLCs { ref mut updates, .. } => { + assert_eq!(updates.update_add_htlcs.len(), 1); + updates.update_add_htlcs.get_mut(0) + } + _ => panic!() + }; + update_message.map(|msg| { + // corrupt the onion packet + msg.onion_routing_packet.hmac = [1; 32]; + }); + } + + let route_to_dave: &[&Node] = &[&nodes[3]]; + let args = PassAlongPathArgs::new(&nodes[4], route_to_dave, amt_msat, payment_hash, update_message_t0_dave.clone()).expect_failure(HTLCHandlingFailureType::InvalidOnion); + do_pass_along_path(args); + + { + let downstream_id = 3; + let upstream_id = 4; + let unblinded_node_updates = get_htlc_update_msgs!(nodes[downstream_id], nodes[upstream_id].node.get_our_node_id()); + nodes[upstream_id].node.handle_update_fail_malformed_htlc( + nodes[downstream_id].node.get_our_node_id(), &unblinded_node_updates.update_fail_malformed_htlcs[0] + ); + do_commitment_signed_dance(&nodes[upstream_id], &nodes[downstream_id], &unblinded_node_updates.commitment_signed, true, false); + } + + { + let downstream_id = 4; + let upstream_id = 2; + let unblinded_node_updates = get_htlc_update_msgs!(nodes[downstream_id], nodes[upstream_id].node.get_our_node_id()); + nodes[upstream_id].node.handle_update_fail_htlc( + nodes[downstream_id].node.get_our_node_id(), &unblinded_node_updates.update_fail_htlcs[0] + ); + do_commitment_signed_dance(&nodes[upstream_id], &nodes[downstream_id], &unblinded_node_updates.commitment_signed, true, false); + } + { + let downstream_id = 2; + let upstream_id = 1; + let unblinded_node_updates = get_htlc_update_msgs!(nodes[downstream_id], nodes[upstream_id].node.get_our_node_id()); + nodes[upstream_id].node.handle_update_fail_htlc( + nodes[downstream_id].node.get_our_node_id(), &unblinded_node_updates.update_fail_htlcs[0] + ); + do_commitment_signed_dance(&nodes[upstream_id], &nodes[downstream_id], &unblinded_node_updates.commitment_signed, true, false); + } + { + let downstream_id = 1; + let upstream_id = 0; + let unblinded_node_updates = get_htlc_update_msgs!(nodes[downstream_id], nodes[upstream_id].node.get_our_node_id()); + nodes[upstream_id].node.handle_update_fail_htlc( + nodes[downstream_id].node.get_our_node_id(), &unblinded_node_updates.update_fail_htlcs[0] + ); + do_commitment_signed_dance(&nodes[upstream_id], &nodes[downstream_id], &unblinded_node_updates.commitment_signed, false, false); + } + { + // We use Unkonwn Failure code instead of TemporaryTrampolineFailure because errors uses u16 bolt04 codes. + let payment_failed_conditions = PaymentFailedConditions::new() + .expected_htlc_error_data(LocalHTLCFailureReason::UnknownFailureCode { code: 16409 }, &[0; 0]); + expect_payment_failed_conditions(&nodes[0], payment_hash, false, payment_failed_conditions); + } + } + } +} + +#[test] +fn test_unblinded_trampoline_forward_ok() { + do_test_unblinded_trampoline_forward(None); +} + +#[test] +fn test_unblinded_trampoline_forward_no_route() { + do_test_unblinded_trampoline_forward(Some(TrampolineForwardFailureScenario::NoRoute)); +} + +#[test] +fn test_unblinded_trampoline_forward_invalid_inter_trampoline(){ + do_test_unblinded_trampoline_forward(Some(TrampolineForwardFailureScenario::InvalidInterTrampolineOnion)); +} + +#[test] +fn test_unblinded_trampoline_forward_invalid_recipient(){ + do_test_unblinded_trampoline_forward(Some(TrampolineForwardFailureScenario::InvalidRecipientOnion)); +} + +fn do_test_blinded_trampoline_forward(failure_scenario: Option) { + // Simulate a payment of A (0) -> B (1) -> C(Trampoline (blinded forward)) (2) -> D(Trampoline(blinded receive)) (3) + // trampoline hops C -> T0 (4) -> D + // make it fail at B, then at C's outer onion, then at C's inner onion + const TOTAL_NODE_COUNT: usize = 5; + let secp_ctx = Secp256k1::new(); + + let chanmon_cfgs = create_chanmon_cfgs(TOTAL_NODE_COUNT); + let node_cfgs = create_node_cfgs(TOTAL_NODE_COUNT, &chanmon_cfgs); + let node_chanmgrs = create_node_chanmgrs(TOTAL_NODE_COUNT, &node_cfgs, &vec![None; TOTAL_NODE_COUNT]); + let mut nodes = create_network(TOTAL_NODE_COUNT, &node_cfgs, &node_chanmgrs); + + let (_, _, chan_id_alice_bob, _) = create_announced_chan_between_nodes_with_value(&nodes, 0, 1, 1_000_000, 0); + let (_, _, chan_id_bob_carol, _) = create_announced_chan_between_nodes_with_value(&nodes, 1, 2, 1_000_000, 0); + if failure_scenario != Some(TrampolineForwardFailureScenario::NoRoute) { + let (_, _, _, _) = create_announced_chan_between_nodes_with_value(&nodes, 2, 4, 1_000_000, 0); + } + let (_, _, _, _) = create_announced_chan_between_nodes_with_value(&nodes, 4, 3, 1_000_000, 0); + + for i in 0..TOTAL_NODE_COUNT { // connect all nodes' blocks + connect_blocks(&nodes[i], (TOTAL_NODE_COUNT as u32) * CHAN_CONFIRM_DEPTH + 1 - nodes[i].best_block_info().1); + } + + let bob_node_id = nodes[1].node().get_our_node_id(); + let carol_node_id = nodes[2].node().get_our_node_id(); + let dave_node_id = nodes[3].node().get_our_node_id(); + + let alice_bob_scid = nodes[0].node().list_channels().iter().find(|c| c.channel_id == chan_id_alice_bob).unwrap().short_channel_id.unwrap(); + let bob_carol_scid = nodes[1].node().list_channels().iter().find(|c| c.channel_id == chan_id_bob_carol).unwrap().short_channel_id.unwrap(); + + let amt_msat = 1000; + let (payment_preimage, payment_hash, payment_secret) = get_payment_preimage_hash(&nodes[3], Some(amt_msat), None); + + let alice_carol_trampoline_shared_secret = secret_from_hex("a0f4b8d7b6c2d0ffdfaf718f76e9decaef4d9fb38a8c4addb95c4007cc3eee03"); + let carol_blinding_point = PublicKey::from_secret_key(&secp_ctx, &alice_carol_trampoline_shared_secret); + let payee_tlvs = UnauthenticatedReceiveTlvs { + payment_secret, + payment_constraints: PaymentConstraints { + max_cltv_expiry: u32::max_value(), + htlc_minimum_msat: amt_msat, + }, + payment_context: PaymentContext::Bolt12Refund(Bolt12RefundContext {}), + }; + + let nonce = Nonce([42u8; 16]); + let expanded_key = nodes[3].keys_manager.get_inbound_payment_key(); + let payee_tlvs = payee_tlvs.authenticate(nonce, &expanded_key); + let dave_unblinded_tlvs = payee_tlvs.encode(); + + // Blinded path is Carol -> Dave + let path = [((dave_node_id, None), WithoutLength(&dave_unblinded_tlvs))]; + let blinded_hops = blinded_path::utils::construct_blinded_hops( + &secp_ctx, path.into_iter(), &alice_carol_trampoline_shared_secret, + ).unwrap(); + + let route = Route { + paths: vec![Path { + hops: vec![ + // Bob + RouteHop { + pubkey: bob_node_id, + node_features: NodeFeatures::empty(), + short_channel_id: alice_bob_scid, + channel_features: ChannelFeatures::empty(), + fee_msat: 1000, // forwarding fee to Carol + cltv_expiry_delta: 48, + maybe_announced_channel: false, + }, + + // Carol + RouteHop { + pubkey: carol_node_id, + node_features: NodeFeatures::empty(), + short_channel_id: bob_carol_scid, + channel_features: ChannelFeatures::empty(), + fee_msat: 2000, // fee for the usage of the entire blinded path, including Trampoline + cltv_expiry_delta: 48, + maybe_announced_channel: false, + } + ], + blinded_tail: Some(BlindedTail { + trampoline_hops: vec![ + // Carol + TrampolineHop { + pubkey: carol_node_id, + node_features: Features::empty(), + fee_msat: amt_msat, + cltv_expiry_delta: 176, // let her cook + }, + + // Dave (recipient) + TrampolineHop { + pubkey: dave_node_id, + node_features: Features::empty(), + fee_msat: 0, // no need to charge a fee as the recipient + cltv_expiry_delta: 24, + }, + ], + hops: blinded_hops, + blinding_point: carol_blinding_point, + excess_final_cltv_expiry_delta: 39, + final_value_msat: amt_msat, + }) + }], + route_params: None, + }; + + nodes[0].node.send_payment_with_route(route.clone(), payment_hash, RecipientOnionFields::spontaneous_empty(), PaymentId(payment_hash.0)).unwrap(); + check_added_monitors!(&nodes[0], 1); + + match failure_scenario { + None => { + pass_along_route(&nodes[0], &[&[&nodes[1], &nodes[2], &nodes[4], &nodes[3]]], amt_msat, payment_hash, payment_secret); + claim_payment(&nodes[0], &[&nodes[1], &nodes[2], &nodes[4], &nodes[3]], payment_preimage); + }, + Some(TrampolineForwardFailureScenario::NoRoute) => { + let mut events = nodes[0].node.get_and_clear_pending_msg_events(); + assert_eq!(events.len(), 1); + let first_message_event = remove_first_msg_event_to_node(&nodes[1].node.get_our_node_id(), &mut events); + + let route = &[&nodes[1], &nodes[2]]; + let args = PassAlongPathArgs::new(&nodes[0], route, amt_msat, payment_hash, first_message_event) + .with_payment_preimage(payment_preimage) + .without_claimable_event() + .expect_failure(HTLCHandlingFailureType::FailedTrampolineForward { requested_next_node_id: dave_node_id, forward_scid: None }); + do_pass_along_path(args); + + { + let downstream_id = 2; + let upstream_id = 1; + let unblinded_node_updates = get_htlc_update_msgs!(nodes[downstream_id], nodes[upstream_id].node.get_our_node_id()); + nodes[upstream_id].node.handle_update_fail_htlc( + nodes[downstream_id].node.get_our_node_id(), &unblinded_node_updates.update_fail_htlcs[0] + ); + do_commitment_signed_dance(&nodes[upstream_id], &nodes[downstream_id], &unblinded_node_updates.commitment_signed, true, false); + } + { + let downstream_id = 1; + let upstream_id = 0; + let unblinded_node_updates = get_htlc_update_msgs!(nodes[downstream_id], nodes[upstream_id].node.get_our_node_id()); + nodes[upstream_id].node.handle_update_fail_htlc( + nodes[downstream_id].node.get_our_node_id(), &unblinded_node_updates.update_fail_htlcs[0] + ); + do_commitment_signed_dance(&nodes[upstream_id], &nodes[downstream_id], &unblinded_node_updates.commitment_signed, false, false); + } + { + let payment_failed_conditions = PaymentFailedConditions::new() + .expected_htlc_error_data(LocalHTLCFailureReason::UnknownFailureCode { code: 16409 }, &[0; 0]); + expect_payment_failed_conditions(&nodes[0], payment_hash, false, payment_failed_conditions); + } + } + Some(TrampolineForwardFailureScenario::InvalidInterTrampolineOnion) => { + let mut events = nodes[0].node.get_and_clear_pending_msg_events(); + assert_eq!(events.len(), 1); + let first_message_event = remove_first_msg_event_to_node(&nodes[1].node.get_our_node_id(), &mut events); + + let route_alice_carol: &[&Node] = &[&nodes[1], &nodes[2]]; + pass_along_path(&nodes[0], route_alice_carol, amt_msat, payment_hash.clone(), + None, first_message_event.clone(), false, Some(payment_preimage)); + + check_added_monitors!(&nodes[2], 1); + let mut events = nodes[2].node.get_and_clear_pending_msg_events(); + assert_eq!(events.len(), 1); + + let mut update_message_carol_t0 = remove_first_msg_event_to_node(&nodes[4].node.get_our_node_id(), &mut events); + { + let mut update_message = match update_message_carol_t0 { + MessageSendEvent::UpdateHTLCs { ref mut updates, .. } => { + assert_eq!(updates.update_add_htlcs.len(), 1); + updates.update_add_htlcs.get_mut(0) + } + _ => panic!() + }; + update_message.map(|msg| { + // corrupt the onion packet + msg.onion_routing_packet.hmac = [1; 32]; + }); + } + + let route_carol_t0: &[&Node] = &[&nodes[4]]; + let args = PassAlongPathArgs::new(&nodes[2], route_carol_t0, amt_msat, payment_hash, update_message_carol_t0.clone()).expect_failure(HTLCHandlingFailureType::InvalidOnion); + do_pass_along_path(args); + + { + let downstream_id = 4; + let upstream_id = 2; + let unblinded_node_updates = get_htlc_update_msgs!(nodes[downstream_id], nodes[upstream_id].node.get_our_node_id()); + nodes[upstream_id].node.handle_update_fail_malformed_htlc( + nodes[downstream_id].node.get_our_node_id(), &unblinded_node_updates.update_fail_malformed_htlcs[0] + ); + do_commitment_signed_dance(&nodes[upstream_id], &nodes[downstream_id], &unblinded_node_updates.commitment_signed, true, false); + } + { + let downstream_id = 2; + let upstream_id = 1; + let unblinded_node_updates = get_htlc_update_msgs!(nodes[downstream_id], nodes[upstream_id].node.get_our_node_id()); + nodes[upstream_id].node.handle_update_fail_htlc( + nodes[downstream_id].node.get_our_node_id(), &unblinded_node_updates.update_fail_htlcs[0] + ); + do_commitment_signed_dance(&nodes[upstream_id], &nodes[downstream_id], &unblinded_node_updates.commitment_signed, true, false); + } + { + let downstream_id = 1; + let upstream_id = 0; + let unblinded_node_updates = get_htlc_update_msgs!(nodes[downstream_id], nodes[upstream_id].node.get_our_node_id()); + nodes[upstream_id].node.handle_update_fail_htlc( + nodes[downstream_id].node.get_our_node_id(), &unblinded_node_updates.update_fail_htlcs[0] + ); + do_commitment_signed_dance(&nodes[upstream_id], &nodes[downstream_id], &unblinded_node_updates.commitment_signed, false, false); + } + { + let payment_failed_conditions = PaymentFailedConditions::new() + .expected_htlc_error_data(LocalHTLCFailureReason::UnknownFailureCode { code: 16409 }, &[0; 0]); + expect_payment_failed_conditions(&nodes[0], payment_hash, false, payment_failed_conditions); + } + } + Some(TrampolineForwardFailureScenario::InvalidRecipientOnion) => { + let mut events = nodes[0].node.get_and_clear_pending_msg_events(); + assert_eq!(events.len(), 1); + let first_message_event = remove_first_msg_event_to_node(&nodes[1].node.get_our_node_id(), &mut events); + + let route_alice_t0: &[&Node] = &[&nodes[1], &nodes[2], &nodes[4]]; + pass_along_path(&nodes[0], route_alice_t0, amt_msat, payment_hash.clone(), + None, first_message_event.clone(), false, Some(payment_preimage)); + + check_added_monitors!(&nodes[4], 1); + let mut events = nodes[4].node.get_and_clear_pending_msg_events(); + assert_eq!(events.len(), 1); + + let mut update_message_t0_dave = remove_first_msg_event_to_node(&nodes[3].node.get_our_node_id(), &mut events); + { + let mut update_message = match update_message_t0_dave { + MessageSendEvent::UpdateHTLCs { ref mut updates, .. } => { + assert_eq!(updates.update_add_htlcs.len(), 1); + updates.update_add_htlcs.get_mut(0) + } + _ => panic!() + }; + update_message.map(|msg| { + // corrupt the onion packet + msg.onion_routing_packet.hmac = [1; 32]; + }); + } + + let route_to_dave: &[&Node] = &[&nodes[3]]; + let args = PassAlongPathArgs::new(&nodes[4], route_to_dave, amt_msat, payment_hash, update_message_t0_dave.clone()).expect_failure(HTLCHandlingFailureType::InvalidOnion); + do_pass_along_path(args); + + { + let downstream_id = 3; + let upstream_id = 4; + let unblinded_node_updates = get_htlc_update_msgs!(nodes[downstream_id], nodes[upstream_id].node.get_our_node_id()); + nodes[upstream_id].node.handle_update_fail_malformed_htlc( + nodes[downstream_id].node.get_our_node_id(), &unblinded_node_updates.update_fail_malformed_htlcs[0] + ); + do_commitment_signed_dance(&nodes[upstream_id], &nodes[downstream_id], &unblinded_node_updates.commitment_signed, true, false); + } + { + let downstream_id = 4; + let upstream_id = 2; + let unblinded_node_updates = get_htlc_update_msgs!(nodes[downstream_id], nodes[upstream_id].node.get_our_node_id()); + nodes[upstream_id].node.handle_update_fail_htlc( + nodes[downstream_id].node.get_our_node_id(), &unblinded_node_updates.update_fail_htlcs[0] + ); + do_commitment_signed_dance(&nodes[upstream_id], &nodes[downstream_id], &unblinded_node_updates.commitment_signed, true, false); + } + { + let downstream_id = 2; + let upstream_id = 1; + let unblinded_node_updates = get_htlc_update_msgs!(nodes[downstream_id], nodes[upstream_id].node.get_our_node_id()); + nodes[upstream_id].node.handle_update_fail_htlc( + nodes[downstream_id].node.get_our_node_id(), &unblinded_node_updates.update_fail_htlcs[0] + ); + do_commitment_signed_dance(&nodes[upstream_id], &nodes[downstream_id], &unblinded_node_updates.commitment_signed, true, false); + } + { + let downstream_id = 1; + let upstream_id = 0; + let unblinded_node_updates = get_htlc_update_msgs!(nodes[downstream_id], nodes[upstream_id].node.get_our_node_id()); + nodes[upstream_id].node.handle_update_fail_htlc( + nodes[downstream_id].node.get_our_node_id(), &unblinded_node_updates.update_fail_htlcs[0] + ); + do_commitment_signed_dance(&nodes[upstream_id], &nodes[downstream_id], &unblinded_node_updates.commitment_signed, false, false); + } + { + let payment_failed_conditions = PaymentFailedConditions::new() + .expected_htlc_error_data(LocalHTLCFailureReason::UnknownFailureCode { code: 16409 }, &[0; 0]); + + expect_payment_failed_conditions(&nodes[0], payment_hash, false, payment_failed_conditions); + } + } + } + + +} + +#[test] +fn test_blinded_trampoline_forward() { + do_test_blinded_trampoline_forward(None); +} + +#[test] +fn test_blinded_trampoline_forward_no_route() { + do_test_blinded_trampoline_forward(Some(TrampolineForwardFailureScenario::NoRoute)); +} + +#[test] +fn test_blinded_trampoline_forward_invalid_inter_trampoline() { + do_test_blinded_trampoline_forward(Some(TrampolineForwardFailureScenario::InvalidInterTrampolineOnion)); +} + +#[test] +fn test_blinded_trampoline_forward_invalid_recipient() { + do_test_blinded_trampoline_forward(Some(TrampolineForwardFailureScenario::InvalidRecipientOnion)); +} diff --git a/lightning/src/ln/channelmanager.rs b/lightning/src/ln/channelmanager.rs index 2f52cb9e843..e395c48abb8 100644 --- a/lightning/src/ln/channelmanager.rs +++ b/lightning/src/ln/channelmanager.rs @@ -85,8 +85,8 @@ use crate::ln::our_peer_storage::EncryptedOurPeerStorage; #[cfg(test)] use crate::ln::outbound_payment; use crate::ln::outbound_payment::{ - OutboundPayments, PendingOutboundPayment, RetryableInvoiceRequest, SendAlongPathArgs, - StaleExpiration, + NextTrampolineHopInfo, OutboundPayments, PendingOutboundPayment, RetryableInvoiceRequest, + SendAlongPathArgs, StaleExpiration, TrampolineForwardInfo, }; use crate::ln::types::ChannelId; use crate::offers::async_receive_offer_cache::AsyncReceiveOfferCache; @@ -112,11 +112,11 @@ use crate::onion_message::messenger::{ use crate::onion_message::offers::{OffersMessage, OffersMessageHandler}; use crate::routing::router::{ BlindedTail, FixedRouter, InFlightHtlcs, Path, Payee, PaymentParameters, Route, - RouteParameters, RouteParametersConfig, Router, + RouteParameters, RouteParametersConfig, Router, DEFAULT_MAX_PATH_COUNT, + MAX_PATH_LENGTH_ESTIMATE, }; use crate::sign::ecdsa::EcdsaChannelSigner; use crate::sign::{EntropySource, NodeSigner, Recipient, SignerProvider}; -#[cfg(any(feature = "_test_utils", test))] use crate::types::features::Bolt11InvoiceFeatures; use crate::types::features::{ Bolt12InvoiceFeatures, ChannelFeatures, ChannelTypeFeatures, InitFeatures, NodeFeatures, @@ -639,10 +639,17 @@ impl Readable for InterceptId { } #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] +pub(crate) struct PreviousHopId { + short_channel_id: u64, + htlc_id: u64, +} + +#[derive(Clone, Debug, PartialEq, Eq, Hash)] /// Uniquely describes an HTLC by its source. Just the guaranteed-unique subset of [`HTLCSource`]. pub(crate) enum SentHTLCId { PreviousHopData { short_channel_id: u64, htlc_id: u64 }, OutboundRoute { session_priv: [u8; SECRET_KEY_SIZE] }, + TrampolineForward { session_priv: [u8; SECRET_KEY_SIZE], previous_hop_ids: Vec }, } impl SentHTLCId { pub(crate) fn from_source(source: &HTLCSource) -> Self { @@ -654,9 +661,25 @@ impl SentHTLCId { HTLCSource::OutboundRoute { session_priv, .. } => { Self::OutboundRoute { session_priv: session_priv.secret_bytes() } }, + HTLCSource::TrampolineForward { session_priv, previous_hop_data, .. } => { + Self::TrampolineForward { + session_priv: session_priv.secret_bytes(), + previous_hop_ids: previous_hop_data + .iter() + .map(|id| PreviousHopId { + short_channel_id: id.short_channel_id, + htlc_id: id.htlc_id, + }) + .collect(), + } + }, } } } +impl_writeable_tlv_based!(PreviousHopId, { + (0, short_channel_id, required), + (2, htlc_id, required), +}); impl_writeable_tlv_based_enum!(SentHTLCId, (0, PreviousHopData) => { (0, short_channel_id, required), @@ -665,6 +688,10 @@ impl_writeable_tlv_based_enum!(SentHTLCId, (2, OutboundRoute) => { (0, session_priv, required), }, + (4, TrampolineForward) => { + (0, session_priv, required), + (2, previous_hop_ids, required_vec), + } ); // (src_channel_id, src_counterparty_node_id, src_funding_outpoint, src_chan_id, src_user_chan_id) @@ -674,6 +701,8 @@ type PerSourcePendingForward = type FailedHTLCForward = (HTLCSource, PaymentHash, HTLCFailReason, HTLCHandlingFailureType); mod fuzzy_channelmanager { + use crate::routing::router::RouteHop; + use super::*; /// Tracks the inbound corresponding to an outbound HTLC @@ -681,6 +710,18 @@ mod fuzzy_channelmanager { #[derive(Clone, Debug, PartialEq, Eq)] pub enum HTLCSource { PreviousHopData(HTLCPreviousHopData), + TrampolineForward { + /// We might be forwarding an incoming payment that was received over MPP, and therefore + /// need to store the vector of corresponding `HTLCPreviousHopId` values. + previous_hop_data: Vec, + incoming_trampoline_shared_secret: [u8; 32], + hops: Vec, + /// In order to decode inter-Trampoline errors, we need to store the session_priv key + /// given we're effectively creating new outbound routes. + session_priv: SecretKey, + /// We might need to retry payments, therefore we need a way to track it. + payment_id: PaymentId, + }, OutboundRoute { path: Path, session_priv: SecretKey, @@ -743,6 +784,20 @@ impl core::hash::Hash for HTLCSource { first_hop_htlc_msat.hash(hasher); bolt12_invoice.hash(hasher); }, + HTLCSource::TrampolineForward { + previous_hop_data, + incoming_trampoline_shared_secret, + hops, + session_priv, + payment_id, + } => { + 2u8.hash(hasher); + previous_hop_data.hash(hasher); + incoming_trampoline_shared_secret.hash(hasher); + hops.hash(hasher); + session_priv[..].hash(hasher); + payment_id.hash(hasher); + }, } } } @@ -4811,6 +4866,7 @@ where keysend_preimage, invoice_request: None, bolt12_invoice: None, + trampoline_forward_info: None, session_priv_bytes, }) } @@ -4826,6 +4882,7 @@ where keysend_preimage, invoice_request, bolt12_invoice, + trampoline_forward_info, session_priv_bytes, } = args; // The top-level caller should hold the total_consistency_lock read lock. @@ -4833,22 +4890,43 @@ where let prng_seed = self.entropy_source.get_secure_random_bytes(); let session_priv = SecretKey::from_slice(&session_priv_bytes[..]).expect("RNG is busted"); - let (onion_packet, htlc_msat, htlc_cltv) = onion_utils::create_payment_onion( - &self.secp_ctx, - &path, - &session_priv, - total_value, - recipient_onion, - cur_height, - payment_hash, - keysend_preimage, - invoice_request, - prng_seed, - ) - .map_err(|e| { + let onion_result = if let Some(trampoline_forward_info) = trampoline_forward_info { + // TODO: ensure inter-Trampoline payment secret is always available for Trampoline forwards + onion_utils::create_trampoline_forward_onion( + &self.secp_ctx, + &path, + &session_priv, + total_value, + recipient_onion.payment_secret.unwrap(), + cur_height, + payment_hash, + keysend_preimage, + &trampoline_forward_info.next_hop_info, + prng_seed, + ) + } else { + onion_utils::create_payment_onion( + &self.secp_ctx, + &path, + &session_priv, + total_value, + recipient_onion, + cur_height, + payment_hash, + keysend_preimage, + invoice_request, + prng_seed, + ) + }; + + let (onion_packet, htlc_msat, htlc_cltv) = onion_result.map_err(|e| { let first_hop_key = Some(path.hops.first().unwrap().pubkey); let logger = WithContext::from(&self.logger, first_hop_key, None, Some(*payment_hash)); - log_error!(logger, "Failed to build an onion for path for payment hash {payment_hash}"); + log_error!( + logger, + "Failed to build an onion for path for payment hash {}", + payment_hash + ); e })?; @@ -4902,12 +4980,25 @@ where &chan.context, Some(*payment_hash), ); - let htlc_source = HTLCSource::OutboundRoute { - path: path.clone(), - session_priv: session_priv.clone(), - first_hop_htlc_msat: htlc_msat, - payment_id, - bolt12_invoice: bolt12_invoice.cloned(), + + let htlc_source = match trampoline_forward_info { + None => HTLCSource::OutboundRoute { + path: path.clone(), + session_priv: session_priv.clone(), + first_hop_htlc_msat: htlc_msat, + payment_id, + bolt12_invoice: bolt12_invoice.cloned(), + }, + Some(trampoline_forward_info) => HTLCSource::TrampolineForward { + previous_hop_data: trampoline_forward_info + .previous_hop_data + .clone(), + incoming_trampoline_shared_secret: trampoline_forward_info + .incoming_trampoline_shared_secret, + session_priv: session_priv.clone(), + hops: path.clone().hops, + payment_id, + }, }; let send_res = chan.send_htlc_and_commit( htlc_msat, @@ -5091,7 +5182,7 @@ where ) } - #[cfg(all(test, async_payments))] + #[cfg(all(test))] pub(crate) fn test_modify_pending_payment(&self, payment_id: &PaymentId, mut callback: Fn) where Fn: FnMut(&mut PendingOutboundPayment), @@ -6270,20 +6361,29 @@ where // Now process the HTLC on the outgoing channel if it's a forward. if let Some(next_packet_details) = next_packet_details_opt.as_ref() { - if let Err(reason) = - self.can_forward_htlc(&update_add_htlc, next_packet_details) - { - let htlc_fail = self.htlc_failure_from_update_add_err( - &update_add_htlc, - &incoming_counterparty_node_id, - reason, - is_intro_node_blinded_forward, - &shared_secret, - ); - let failure_type = - get_htlc_failure_type(outgoing_scid_opt, update_add_htlc.payment_hash); - htlc_fails.push((htlc_fail, failure_type, reason.into())); - continue; + match next_packet_details.outgoing_connector { + HopConnector::ShortChannelId(_) => { + if let Err(reason) = + self.can_forward_htlc(&update_add_htlc, next_packet_details) + { + let htlc_fail = self.htlc_failure_from_update_add_err( + &update_add_htlc, + &incoming_counterparty_node_id, + reason, + is_intro_node_blinded_forward, + &shared_secret, + ); + let failure_type = get_htlc_failure_type( + outgoing_scid_opt, + update_add_htlc.payment_hash, + ); + htlc_fails.push((htlc_fail, failure_type, reason.into())); + continue; + } + }, + HopConnector::Trampoline(_) => { + // we don't know the next scid yet, so there is nothing to check + }, } } @@ -6935,6 +7035,204 @@ where ) { 'next_forwardable_htlc: for forward_info in pending_forwards.drain(..) { match forward_info { + HTLCForwardInfo::AddHTLC(PendingAddHTLCInfo { + prev_short_channel_id, + prev_htlc_id, + prev_channel_id, + prev_funding_outpoint, + prev_user_channel_id, + prev_counterparty_node_id, + forward_info: + PendingHTLCInfo { + incoming_shared_secret: incoming_outer_shared_secret, + payment_hash, + incoming_amt_msat, + outgoing_amt_msat, + outgoing_cltv_value, + routing: + PendingHTLCRouting::TrampolineForward { + incoming_shared_secret: incoming_trampoline_shared_secret, + node_id: next_node_id, + ref onion_packet, + blinded, + incoming_cltv_expiry, + .. + }, + .. + }, + }) => { + let htlc_source = HTLCSource::TrampolineForward { + // dummy value + session_priv: SecretKey::from_slice( + &self.entropy_source.get_secure_random_bytes(), + ) + .unwrap(), + previous_hop_data: vec![HTLCPreviousHopData { + short_channel_id: prev_short_channel_id, + user_channel_id: Some(prev_user_channel_id), + counterparty_node_id: prev_counterparty_node_id, + channel_id: prev_channel_id, + outpoint: prev_funding_outpoint, + htlc_id: prev_htlc_id, + incoming_packet_shared_secret: incoming_outer_shared_secret, + // Phantom payments are only PendingHTLCRouting::Receive. + phantom_shared_secret: None, + blinded_failure: blinded.map(|b| b.failure), + cltv_expiry: Some(incoming_cltv_expiry), + }], + incoming_trampoline_shared_secret, + hops: Vec::new(), + payment_id: PaymentId(payment_hash.0), + }; + + let mut push_trampoline_forwarding_failure = + |msg: String, + htlc_source: HTLCSource, + forward_scid: Option, + reason: LocalHTLCFailureReason, + err_data: Vec| { + let logger = WithContext::from( + &self.logger, + Some(next_node_id), + Some(prev_channel_id), + Some(payment_hash), + ); + log_info!( + logger, + "Failed to forward incoming Trampoline HTLC: {}", + msg + ); + + failed_forwards.push(( + htlc_source, + payment_hash, + HTLCFailReason::reason(reason, err_data), + HTLCHandlingFailureType::FailedTrampolineForward { + requested_next_node_id: next_node_id, + forward_scid, + }, + )); + }; + + let next_blinding_point = blinded.and_then(|b| { + b.next_blinding_override.or_else(|| { + let encrypted_tlvs_ss = self + .node_signer + .ecdh(Recipient::Node, &b.inbound_blinding_point, None) + .unwrap() + .secret_bytes(); + onion_utils::next_hop_pubkey( + &self.secp_ctx, + b.inbound_blinding_point, + &encrypted_tlvs_ss, + ) + .ok() + }) + }); + + let incoming_amount = match incoming_amt_msat { + Some(amount) => amount, + None => { + push_trampoline_forwarding_failure(format!("Missing incoming amount to calculate routing parameters to next Trampoline hop {next_node_id}"), htlc_source, None, LocalHTLCFailureReason::TemporaryTrampolineFailure, Vec::new()); + continue; + }, + }; + + let proportional_fee = self + .default_configuration + .channel_config + .forwarding_fee_proportional_millionths as u64 + * outgoing_amt_msat / 1_000_000; + let forwarding_fee = proportional_fee + + self.default_configuration.channel_config.forwarding_fee_base_msat as u64; + let cltv_expiry_delta = incoming_cltv_expiry - outgoing_cltv_value; + + let max_total_routing_fee_msat = match incoming_amount + .checked_sub(forwarding_fee + outgoing_amt_msat) + { + Some(amount) => amount, + None => { + push_trampoline_forwarding_failure(format!("Insufficient fee to forward to the next Trampoline hop {next_node_id}"), htlc_source, None, LocalHTLCFailureReason::TrampolineFeeOrExpiryInsufficient, Vec::new()); + continue; + }, + }; + + // assume any Trampoline node supports MPP + let mut recipient_features = Bolt11InvoiceFeatures::empty(); + recipient_features.set_basic_mpp_optional(); + + let route_parameters = RouteParameters { + payment_params: PaymentParameters { + payee: Payee::Clear { + node_id: next_node_id, + route_hints: vec![], + features: Some(recipient_features), + final_cltv_expiry_delta: 4, + }, + expiry_time: None, + max_total_cltv_expiry_delta: cltv_expiry_delta, + max_path_count: DEFAULT_MAX_PATH_COUNT, + max_path_length: MAX_PATH_LENGTH_ESTIMATE / 2, + max_channel_saturation_power_of_half: 2, + previously_failed_channels: vec![], + previously_failed_blinded_path_idxs: vec![], + }, + final_value_msat: outgoing_amt_msat, + max_total_routing_fee_msat: Some(max_total_routing_fee_msat), + }; + + #[cfg(not(any(test, feature = "_test_utils")))] + let retry_strategy = Retry::Attempts(3); + #[cfg(any(test, feature = "_test_utils"))] + let retry_strategy = Retry::Attempts(0); + + let result = + self.pending_outbound_payments.send_payment_for_trampoline_forward( + PaymentId(payment_hash.0), + payment_hash, + TrampolineForwardInfo { + next_hop_info: NextTrampolineHopInfo { + onion_packet: onion_packet.clone(), + blinding_point: next_blinding_point, + }, + previous_hop_data: vec![HTLCPreviousHopData { + short_channel_id: prev_short_channel_id, + user_channel_id: Some(prev_user_channel_id), + counterparty_node_id: prev_counterparty_node_id, + channel_id: prev_channel_id, + outpoint: prev_funding_outpoint, + htlc_id: prev_htlc_id, + incoming_packet_shared_secret: incoming_outer_shared_secret, + // Phantom payments are only PendingHTLCRouting::Receive. + phantom_shared_secret: None, + blinded_failure: blinded.map(|b| b.failure), + cltv_expiry: Some(incoming_cltv_expiry), + }], + incoming_trampoline_shared_secret, + }, + retry_strategy, + route_parameters.clone(), + &self.router, + self.list_usable_channels(), + || self.compute_inflight_htlcs(), + &self.entropy_source, + &self.node_signer, + self.current_best_block().height, + &self.logger, + &self.pending_events, + |args| self.send_payment_along_path(args), + ); + + if let Err(retryable_send_failure) = result { + push_trampoline_forwarding_failure( + format!("Trampoline send failure {:?}", retryable_send_failure), + htlc_source, + None, + LocalHTLCFailureReason::TemporaryTrampolineFailure, + Vec::new(), + ); + }; + }, HTLCForwardInfo::AddHTLC(PendingAddHTLCInfo { prev_short_channel_id, prev_htlc_id, @@ -7868,55 +8166,142 @@ where &payment_hash, onion_error ); - let failure = match blinded_failure { - Some(BlindedFailure::FromIntroductionNode) => { - let blinded_onion_error = HTLCFailReason::reason( - LocalHTLCFailureReason::InvalidOnionBlinding, - vec![0; 32], + let htlc_failure_reason = self.get_htlc_failure_from_blinded_failure_forward( + blinded_failure, + onion_error, + incoming_packet_shared_secret, + phantom_shared_secret, + htlc_id, + ); + self.fail_htlc_backwards_from_forward( + &onion_error, + failure_type, + short_channel_id, + channel_id, + htlc_failure_reason, + ); + }, + HTLCSource::TrampolineForward { + ref hops, + ref session_priv, + payment_id, + previous_hop_data, + incoming_trampoline_shared_secret, + .. + } => { + let path = Path { hops: hops.clone(), blinded_tail: None }; + let should_fail_backwards = self.pending_outbound_payments.trampoline_htlc_failed( + source, + payment_hash, + onion_error, + &path, + session_priv, + payment_id, + &self.secp_ctx, + &self.pending_events, + &self.logger, + ); + + if should_fail_backwards { + let incoming_trampoline_shared_secret = + Some(*incoming_trampoline_shared_secret); + + // For each possible mpp hop we fail the htlc backwards. + for current_hop_data in previous_hop_data { + let incoming_packet_shared_secret = + ¤t_hop_data.incoming_packet_shared_secret; + let channel_id = ¤t_hop_data.channel_id; + let short_channel_id = ¤t_hop_data.short_channel_id; + let htlc_id = ¤t_hop_data.htlc_id; + let blinded_failure = ¤t_hop_data.blinded_failure; + + log_trace!( + WithContext::from(&self.logger, None, Some(*channel_id), Some(*payment_hash)), + "Failing {}HTLC with payment_hash {} backwards from us following Trampoline forwarding failure: {:?}", + if blinded_failure.is_some() { "blinded " } else { "" }, &payment_hash, onion_error ); - let err_packet = blinded_onion_error.get_encrypted_failure_packet( - incoming_packet_shared_secret, - phantom_shared_secret, + let onion_error = HTLCFailReason::reason( + LocalHTLCFailureReason::TemporaryTrampolineFailure, + Vec::new(), ); - HTLCForwardInfo::FailHTLC { htlc_id: *htlc_id, err_packet } - }, - Some(BlindedFailure::FromBlindedNode) => HTLCForwardInfo::FailMalformedHTLC { - htlc_id: *htlc_id, - failure_code: LocalHTLCFailureReason::InvalidOnionBlinding.failure_code(), - sha256_of_onion: [0; 32], - }, - None => { - let err_packet = onion_error.get_encrypted_failure_packet( - incoming_packet_shared_secret, - phantom_shared_secret, + let htlc_failure_reason = self + .get_htlc_failure_from_blinded_failure_forward( + blinded_failure, + &onion_error, + incoming_packet_shared_secret, + &incoming_trampoline_shared_secret, + htlc_id, + ); + self.fail_htlc_backwards_from_forward( + &onion_error, + failure_type.clone(), + short_channel_id, + channel_id, + htlc_failure_reason, ); - HTLCForwardInfo::FailHTLC { htlc_id: *htlc_id, err_packet } - }, - }; - - let mut forward_htlcs = self.forward_htlcs.lock().unwrap(); - match forward_htlcs.entry(*short_channel_id) { - hash_map::Entry::Occupied(mut entry) => { - entry.get_mut().push(failure); - }, - hash_map::Entry::Vacant(entry) => { - entry.insert(vec![failure]); - }, + } } - mem::drop(forward_htlcs); - let mut pending_events = self.pending_events.lock().unwrap(); - pending_events.push_back(( - events::Event::HTLCHandlingFailed { - prev_channel_id: *channel_id, - failure_type, - failure_reason: Some(onion_error.into()), - }, - None, - )); }, } } + fn get_htlc_failure_from_blinded_failure_forward( + &self, blinded_failure: &Option, onion_error: &HTLCFailReason, + incoming_packet_shared_secret: &[u8; 32], secondary_shared_secret: &Option<[u8; 32]>, + htlc_id: &u64, + ) -> HTLCForwardInfo { + match blinded_failure { + Some(BlindedFailure::FromIntroductionNode) => { + let blinded_onion_error = HTLCFailReason::reason( + LocalHTLCFailureReason::InvalidOnionBlinding, + vec![0; 32], + ); + let err_packet = blinded_onion_error.get_encrypted_failure_packet( + incoming_packet_shared_secret, + secondary_shared_secret, + ); + HTLCForwardInfo::FailHTLC { htlc_id: *htlc_id, err_packet } + }, + Some(BlindedFailure::FromBlindedNode) => HTLCForwardInfo::FailMalformedHTLC { + htlc_id: *htlc_id, + failure_code: LocalHTLCFailureReason::InvalidOnionBlinding.failure_code(), + sha256_of_onion: [0; 32], + }, + None => { + let err_packet = onion_error.get_encrypted_failure_packet( + incoming_packet_shared_secret, + secondary_shared_secret, + ); + HTLCForwardInfo::FailHTLC { htlc_id: *htlc_id, err_packet } + }, + } + } + + fn fail_htlc_backwards_from_forward( + &self, onion_error: &HTLCFailReason, failure_type: HTLCHandlingFailureType, + short_channel_id: &u64, channel_id: &ChannelId, failure: HTLCForwardInfo, + ) { + let mut forward_htlcs = self.forward_htlcs.lock().unwrap(); + match forward_htlcs.entry(*short_channel_id) { + hash_map::Entry::Occupied(mut entry) => { + entry.get_mut().push(failure); + }, + hash_map::Entry::Vacant(entry) => { + entry.insert(vec![failure]); + }, + } + mem::drop(forward_htlcs); + let mut pending_events = self.pending_events.lock().unwrap(); + pending_events.push_back(( + events::Event::HTLCHandlingFailed { + prev_channel_id: *channel_id, + failure_type, + failure_reason: Some(onion_error.into()), + }, + None, + )); + } + /// Provides a payment preimage in response to [`Event::PaymentClaimable`], generating any /// [`MessageSendEvent`]s needed to claim the payment. /// @@ -8492,138 +8877,183 @@ This indicates a bug inside LDK. Please report this error at https://github.com/ ); }, HTLCSource::PreviousHopData(hop_data) => { - let prev_channel_id = hop_data.channel_id; - let prev_user_channel_id = hop_data.user_channel_id; - let prev_node_id = hop_data.counterparty_node_id; - let completed_blocker = - RAAMonitorUpdateBlockingAction::from_prev_hop_data(&hop_data); - - // Obtain hold time, if available. - let hold_time = hold_time_since(send_timestamp).unwrap_or(0); - - // If attribution data was received from downstream, we shift it and get it ready for adding our hold - // time. Note that fulfilled HTLCs take a fast path to the incoming side. We don't need to wait for RAA - // to record the hold time like we do for failed HTLCs. - let attribution_data = process_fulfill_attribution_data( - attribution_data, - &hop_data.incoming_packet_shared_secret, - hold_time, + self.claim_funds_from_hop_internal( + payment_preimage, + forwarded_htlc_value_msat, + skimmed_fee_msat, + from_onchain, + startup_replay, + next_channel_counterparty_node_id, + next_channel_outpoint, + next_channel_id, + next_user_channel_id, + hop_data, + &attribution_data, + &send_timestamp, ); + }, + HTLCSource::TrampolineForward { previous_hop_data, .. } => { + for current_previous_hop_data in previous_hop_data { + self.claim_funds_from_hop_internal( + payment_preimage, + forwarded_htlc_value_msat, + skimmed_fee_msat, + from_onchain, + startup_replay, + next_channel_counterparty_node_id, + next_channel_outpoint, + next_channel_id, + next_user_channel_id, + current_previous_hop_data, + &attribution_data, + &send_timestamp, + ); + } + }, + } + } - #[cfg(test)] - let claiming_chan_funding_outpoint = hop_data.outpoint; - self.claim_funds_from_hop( - hop_data, - payment_preimage, - None, - Some(attribution_data), - |htlc_claim_value_msat, definitely_duplicate| { - let chan_to_release = Some(EventUnblockedChannel { - counterparty_node_id: next_channel_counterparty_node_id, - funding_txo: next_channel_outpoint, - channel_id: next_channel_id, - blocking_action: completed_blocker, - }); + fn claim_funds_from_hop_internal( + &self, payment_preimage: PaymentPreimage, forwarded_htlc_value_msat: Option, + skimmed_fee_msat: Option, from_onchain: bool, startup_replay: bool, + next_channel_counterparty_node_id: PublicKey, next_channel_outpoint: OutPoint, + next_channel_id: ChannelId, next_user_channel_id: Option, + hop_data: HTLCPreviousHopData, attribution_data: &Option, + send_timestamp: &Option, + ) { + let prev_channel_id = hop_data.channel_id; + let prev_user_channel_id = hop_data.user_channel_id; + let prev_node_id = hop_data.counterparty_node_id; + let completed_blocker = RAAMonitorUpdateBlockingAction::from_prev_hop_data(&hop_data); + // Obtain hold time, if available. + let hold_time = hold_time_since(*send_timestamp).unwrap_or(0); + + // If attribution data was received from downstream, we shift it and get it ready for adding our hold + // time. Note that fulfilled HTLCs take a fast path to the incoming side. We don't need to wait for RAA + // to record the hold time like we do for failed HTLCs. + let attribution_data = process_fulfill_attribution_data( + attribution_data.clone(), + &hop_data.incoming_packet_shared_secret, + hold_time, + ); - if definitely_duplicate && startup_replay { - // On startup we may get redundant claims which are related to - // monitor updates still in flight. In that case, we shouldn't - // immediately free, but instead let that monitor update complete - // in the background. - #[cfg(test)] - { - let per_peer_state = self.per_peer_state.deadlocking_read(); - // The channel we'd unblock should already be closed, or... - let channel_closed = per_peer_state - .get(&next_channel_counterparty_node_id) - .map(|lck| lck.deadlocking_lock()) - .map(|peer| !peer.channel_by_id.contains_key(&next_channel_id)) - .unwrap_or(true); - let background_events = - self.pending_background_events.lock().unwrap(); - // there should be a `BackgroundEvent` pending... - let matching_bg_event = - background_events.iter().any(|ev| { - match ev { - // to apply a monitor update that blocked the claiming channel, - BackgroundEvent::MonitorUpdateRegeneratedOnStartup { - funding_txo, update, .. - } => { - if *funding_txo == claiming_chan_funding_outpoint { - assert!(update.updates.iter().any(|upd| - if let ChannelMonitorUpdateStep::PaymentPreimage { - payment_preimage: update_preimage, .. - } = upd { - payment_preimage == *update_preimage - } else { false } - ), "{:?}", update); - true - } else { false } - }, - // or the monitor update has completed and will unblock - // immediately once we get going. - BackgroundEvent::MonitorUpdatesComplete { - channel_id, .. - } => - *channel_id == prev_channel_id, + #[cfg(test)] + let claiming_chan_funding_outpoint = hop_data.outpoint; + self.claim_funds_from_hop( + hop_data, + payment_preimage, + None, + Some(attribution_data), + |htlc_claim_value_msat, definitely_duplicate| { + let chan_to_release = Some(EventUnblockedChannel { + counterparty_node_id: next_channel_counterparty_node_id, + funding_txo: next_channel_outpoint, + channel_id: next_channel_id, + blocking_action: completed_blocker, + }); + + if definitely_duplicate && startup_replay { + // On startup we may get redundant claims which are related to + // monitor updates still in flight. In that case, we shouldn't + // immediately free, but instead let that monitor update complete + // in the background. + #[cfg(test)] + { + let per_peer_state = self.per_peer_state.deadlocking_read(); + // The channel we'd unblock should already be closed, or... + let channel_closed = per_peer_state + .get(&next_channel_counterparty_node_id) + .map(|lck| lck.deadlocking_lock()) + .map(|peer| !peer.channel_by_id.contains_key(&next_channel_id)) + .unwrap_or(true); + let background_events = self.pending_background_events.lock().unwrap(); + // there should be a `BackgroundEvent` pending... + let matching_bg_event = + background_events.iter().any(|ev| { + match ev { + // to apply a monitor update that blocked the claiming channel, + BackgroundEvent::MonitorUpdateRegeneratedOnStartup { + funding_txo, + update, + .. + } => { + if *funding_txo == claiming_chan_funding_outpoint { + assert!( + update.updates.iter().any(|upd| { + if let ChannelMonitorUpdateStep::PaymentPreimage { + payment_preimage: update_preimage, .. + } = upd { + payment_preimage == *update_preimage + } else { false } + }), + "{:?}", + update + ); + true + } else { + false } - }); - assert!( - channel_closed || matching_bg_event, - "{:?}", - *background_events - ); - } - (None, None) - } else if definitely_duplicate { - if let Some(other_chan) = chan_to_release { - (Some(MonitorUpdateCompletionAction::FreeOtherChannelImmediately { - downstream_counterparty_node_id: other_chan.counterparty_node_id, - downstream_funding_outpoint: other_chan.funding_txo, - downstream_channel_id: other_chan.channel_id, - blocking_action: other_chan.blocking_action, - }), None) + }, + // or the monitor update has completed and will unblock + // immediately once we get going. + BackgroundEvent::MonitorUpdatesComplete { + channel_id, .. + } => *channel_id == prev_channel_id, + } + }); + assert!(channel_closed || matching_bg_event, "{:?}", *background_events); + } + (None, None) + } else if definitely_duplicate { + if let Some(other_chan) = chan_to_release { + ( + Some(MonitorUpdateCompletionAction::FreeOtherChannelImmediately { + downstream_counterparty_node_id: other_chan.counterparty_node_id, + downstream_funding_outpoint: other_chan.funding_txo, + downstream_channel_id: other_chan.channel_id, + blocking_action: other_chan.blocking_action, + }), + None, + ) + } else { + (None, None) + } + } else { + let total_fee_earned_msat = + if let Some(forwarded_htlc_value) = forwarded_htlc_value_msat { + if let Some(claimed_htlc_value) = htlc_claim_value_msat { + Some(claimed_htlc_value - forwarded_htlc_value) } else { - (None, None) + None } } else { - let total_fee_earned_msat = - if let Some(forwarded_htlc_value) = forwarded_htlc_value_msat { - if let Some(claimed_htlc_value) = htlc_claim_value_msat { - Some(claimed_htlc_value - forwarded_htlc_value) - } else { - None - } - } else { - None - }; - debug_assert!( - skimmed_fee_msat <= total_fee_earned_msat, - "skimmed_fee_msat must always be included in total_fee_earned_msat" - ); - ( - Some(MonitorUpdateCompletionAction::EmitEventAndFreeOtherChannel { - event: events::Event::PaymentForwarded { - prev_channel_id: Some(prev_channel_id), - next_channel_id: Some(next_channel_id), - prev_user_channel_id, - next_user_channel_id, - prev_node_id, - next_node_id: Some(next_channel_counterparty_node_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, - }), - None, - ) - } - }, - ); + None + }; + debug_assert!( + skimmed_fee_msat <= total_fee_earned_msat, + "skimmed_fee_msat must always be included in total_fee_earned_msat" + ); + ( + Some(MonitorUpdateCompletionAction::EmitEventAndFreeOtherChannel { + event: events::Event::PaymentForwarded { + prev_channel_id: Some(prev_channel_id), + next_channel_id: Some(next_channel_id), + prev_user_channel_id, + next_user_channel_id, + prev_node_id, + next_node_id: Some(next_channel_counterparty_node_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, + }), + None, + ) + } }, - } + ); } /// Gets the node_id held by this ChannelManager @@ -10222,14 +10652,14 @@ This indicates a bug inside LDK. Please report this error at https://github.com/ match peer_state.channel_by_id.entry(msg.channel_id) { hash_map::Entry::Occupied(mut chan_entry) => { if (msg.failure_code & 0x8000) == 0 { - let chan_err = ChannelError::close("Got update_fail_malformed_htlc with BADONION not set".to_owned()); - try_channel_entry!(self, peer_state, Err(chan_err), chan_entry); + let chan_err = ChannelError::close("Got update_fail_malformed_htlc with BADONION not set".to_owned()); + try_channel_entry!(self, peer_state, Err(chan_err), chan_entry); } if let Some(chan) = chan_entry.get_mut().as_funded_mut() { - try_channel_entry!(self, peer_state, chan.update_fail_malformed_htlc(&msg, HTLCFailReason::reason(msg.failure_code.into(), msg.sha256_of_onion.to_vec())), chan_entry); + try_channel_entry!(self, peer_state, chan.update_fail_malformed_htlc(&msg, HTLCFailReason::reason(msg.failure_code.into(), msg.sha256_of_onion.to_vec())), chan_entry); } else { - return try_channel_entry!(self, peer_state, Err(ChannelError::close( - "Got an update_fail_malformed_htlc message for an unfunded channel!".into())), chan_entry); + return try_channel_entry!(self, peer_state, Err(ChannelError::close( + "Got an update_fail_malformed_htlc message for an unfunded channel!".into())), chan_entry); } Ok(()) }, @@ -13793,6 +14223,7 @@ where Err(_) => NotifyOption::SkipPersistHandleEvents, Ok(()) => NotifyOption::SkipPersistNoEvents, }; + let _ = handle_error!(self, res, counterparty_node_id); persist }); @@ -14882,6 +15313,27 @@ impl Readable for HTLCSource { }) } 1 => Ok(HTLCSource::PreviousHopData(Readable::read(reader)?)), + 2 => { + let mut previous_hop_data = Vec::new(); + let mut incoming_trampoline_shared_secret: crate::util::ser::RequiredWrapper<[u8; 32]> = crate::util::ser::RequiredWrapper(None); + let mut session_priv: crate::util::ser::RequiredWrapper = crate::util::ser::RequiredWrapper(None); + let mut hops = Vec::new(); + let mut payment_id: crate::util::ser::RequiredWrapper = crate::util::ser::RequiredWrapper(None); + read_tlv_fields!(reader, { + (0, previous_hop_data, required_vec), + (2, incoming_trampoline_shared_secret, required), + (4, session_priv, required), + (6, hops, required_vec), + (8, payment_id, required), + }); + Ok(HTLCSource::TrampolineForward { + previous_hop_data, + incoming_trampoline_shared_secret: incoming_trampoline_shared_secret.0.unwrap(), + hops, + session_priv: session_priv.0.unwrap(), + payment_id: payment_id.0.unwrap(), + }) + }, _ => Err(DecodeError::UnknownRequiredFeature), } } @@ -14914,6 +15366,24 @@ impl Writeable for HTLCSource { 1u8.write(writer)?; field.write(writer)?; }, + HTLCSource::TrampolineForward { + previous_hop_data: previous_hop_data_ref, + ref incoming_trampoline_shared_secret, + ref session_priv, + hops: hops_ref, + payment_id, + } => { + 2u8.write(writer)?; + let previous_hop_data = previous_hop_data_ref.clone(); + let hops = hops_ref.clone(); + write_tlv_fields!(writer, { + (0, previous_hop_data, required_vec), + (2, incoming_trampoline_shared_secret, required), + (4, session_priv, required), + (6, hops, required_vec), + (8, payment_id, required), + }); + }, } Ok(()) } @@ -16357,6 +16827,58 @@ where } else { true } }); }, + HTLCSource::TrampolineForward { previous_hop_data, .. } => { + for current_previous_hop_data in previous_hop_data { + let pending_forward_matches_htlc = + |info: &PendingAddHTLCInfo| { + info.prev_funding_outpoint + == current_previous_hop_data.outpoint && info + .prev_htlc_id + == current_previous_hop_data.htlc_id + }; + // The ChannelMonitor is now responsible for this HTLC's + // failure/success and will let us know what its outcome is. If we + // still have an entry for this HTLC in `forward_htlcs` or + // `pending_intercepted_htlcs`, we were apparently not persisted after + // the monitor was when forwarding the payment. + decode_update_add_htlcs.retain(|scid, update_add_htlcs| { + update_add_htlcs.retain(|update_add_htlc| { + let matches = *scid == current_previous_hop_data.short_channel_id && + update_add_htlc.htlc_id == current_previous_hop_data.htlc_id; + if matches { + log_info!(logger, "Removing pending to-decode HTLC with hash {} as it was forwarded to the closed channel {}", + &htlc.payment_hash, &monitor.channel_id()); + } + !matches + }); + !update_add_htlcs.is_empty() + }); + forward_htlcs.retain(|_, forwards| { + forwards.retain(|forward| { + if let HTLCForwardInfo::AddHTLC(htlc_info) = forward { + if pending_forward_matches_htlc(&htlc_info) { + log_info!(logger, "Removing pending to-forward HTLC with hash {} as it was forwarded to the closed channel {}", + &htlc.payment_hash, &monitor.channel_id()); + false + } else { true } + } else { true } + }); + !forwards.is_empty() + }); + pending_intercepted_htlcs.as_mut().unwrap().retain(|intercepted_id, htlc_info| { + if pending_forward_matches_htlc(&htlc_info) { + log_info!(logger, "Removing pending intercepted HTLC with hash {} as it was forwarded to the closed channel {}", + &htlc.payment_hash, &monitor.channel_id()); + pending_events_read.retain(|(event, _)| { + if let Event::HTLCIntercepted { intercept_id: ev_id, .. } = event { + intercepted_id != ev_id + } else { true } + }); + false + } else { true } + }); + } + }, HTLCSource::OutboundRoute { payment_id, session_priv, diff --git a/lightning/src/ln/msgs.rs b/lightning/src/ln/msgs.rs index e0219a5523f..5a317d8b2e1 100644 --- a/lightning/src/ln/msgs.rs +++ b/lightning/src/ln/msgs.rs @@ -2308,7 +2308,6 @@ mod fuzzy_internal_msgs { /// This is used for Trampoline hops that are not the blinded path intro hop. /// We would only ever construct this variant when we are a Trampoline node forwarding a /// payment along a blinded path. - #[allow(unused)] BlindedTrampolineEntrypoint { amt_to_forward: u64, outgoing_cltv_value: u32, diff --git a/lightning/src/ln/onion_payment.rs b/lightning/src/ln/onion_payment.rs index 79952faca9a..0f34885a0c2 100644 --- a/lightning/src/ln/onion_payment.rs +++ b/lightning/src/ln/onion_payment.rs @@ -22,6 +22,7 @@ use crate::sign::{NodeSigner, Recipient}; use crate::types::features::BlindedHopFeatures; use crate::types::payment::PaymentHash; use crate::util::logger::Logger; +use crate::util::ser::Writeable; #[allow(unused_imports)] use crate::prelude::*; @@ -74,6 +75,24 @@ fn check_blinded_forward( Ok((amt_to_forward, outgoing_cltv_value)) } +fn check_trampoline_onion_constraints( + outer_hop_data: &msgs::InboundTrampolineEntrypointPayload, trampoline_cltv_value: u32, + trampoline_amount: u64, +) -> Result<(), LocalHTLCFailureReason> { + if outer_hop_data.outgoing_cltv_value < trampoline_cltv_value { + return Err(LocalHTLCFailureReason::FinalIncorrectCLTVExpiry); + } + let outgoing_amount = outer_hop_data + .multipath_trampoline_data + .as_ref() + .map_or(outer_hop_data.amt_to_forward, |mtd| mtd.total_msat); + if outgoing_amount < trampoline_amount { + return Err(LocalHTLCFailureReason::FinalIncorrectHTLCAmount); + } + + Ok(()) +} + enum RoutingInfo { Direct { short_channel_id: u64, @@ -135,7 +154,25 @@ pub(super) fn create_fwd_pending_htlc_info( reason: LocalHTLCFailureReason::InvalidOnionPayload, err_data: Vec::new(), }), - onion_utils::Hop::TrampolineForward { next_trampoline_hop_data, next_trampoline_hop_hmac, new_trampoline_packet_bytes, trampoline_shared_secret, .. } => { + onion_utils::Hop::TrampolineForward { ref outer_hop_data, next_trampoline_hop_data, next_trampoline_hop_hmac, new_trampoline_packet_bytes, trampoline_shared_secret, .. } => { + check_trampoline_onion_constraints(outer_hop_data, next_trampoline_hop_data.outgoing_cltv_value, next_trampoline_hop_data.amt_to_forward).map_err(|reason| { + let mut err_data = Vec::new(); + match reason { + LocalHTLCFailureReason::FinalIncorrectCLTVExpiry => { + outer_hop_data.outgoing_cltv_value.write(&mut err_data).unwrap(); + } + LocalHTLCFailureReason::FinalIncorrectHTLCAmount => { + outer_hop_data.amt_to_forward.write(&mut err_data).unwrap(); + } + _ => unreachable!() + } + // The Trampoline onion's amt and CLTV values cannot exceed the outer onion's + InboundHTLCErr { + reason, + err_data, + msg: "Underflow calculating outbound amount or CLTV value for Trampoline forward", + } + })?; ( RoutingInfo::Trampoline { next_trampoline: next_trampoline_hop_data.next_trampoline, @@ -150,7 +187,7 @@ pub(super) fn create_fwd_pending_htlc_info( None ) }, - onion_utils::Hop::TrampolineBlindedForward { outer_hop_data, next_trampoline_hop_data, next_trampoline_hop_hmac, new_trampoline_packet_bytes, trampoline_shared_secret, .. } => { + onion_utils::Hop::TrampolineBlindedForward { ref outer_hop_data, next_trampoline_hop_data, next_trampoline_hop_hmac, new_trampoline_packet_bytes, trampoline_shared_secret, .. } => { let (amt_to_forward, outgoing_cltv_value) = check_blinded_forward( msg.amount_msat, msg.cltv_expiry, &next_trampoline_hop_data.payment_relay, &next_trampoline_hop_data.payment_constraints, &next_trampoline_hop_data.features ).map_err(|()| { @@ -162,6 +199,15 @@ pub(super) fn create_fwd_pending_htlc_info( err_data: vec![0; 32], } })?; + check_trampoline_onion_constraints(outer_hop_data, outgoing_cltv_value, amt_to_forward).map_err(|_| { + // The Trampoline onion's amt and CLTV values cannot exceed the outer onion's, but + // we're inside a blinded path + InboundHTLCErr { + reason: LocalHTLCFailureReason::InvalidOnionBlinding, + err_data: vec![0; 32], + msg: "Underflow calculating outbound amount or CLTV value for Trampoline forward", + } + })?; ( RoutingInfo::Trampoline { next_trampoline: next_trampoline_hop_data.next_trampoline, @@ -281,14 +327,35 @@ pub(super) fn create_recv_pending_htlc_info( intro_node_blinding_point.is_none(), true, invoice_request) } onion_utils::Hop::TrampolineReceive { + ref outer_hop_data, trampoline_hop_data: msgs::InboundOnionReceivePayload { payment_data, keysend_preimage, custom_tlvs, sender_intended_htlc_amt_msat, cltv_expiry_height, payment_metadata, .. }, .. - } => + } => { + check_trampoline_onion_constraints(outer_hop_data, cltv_expiry_height, sender_intended_htlc_amt_msat).map_err(|reason| { + let mut err_data = Vec::new(); + match reason { + LocalHTLCFailureReason::FinalIncorrectCLTVExpiry => { + outer_hop_data.outgoing_cltv_value.write(&mut err_data).unwrap(); + } + LocalHTLCFailureReason::FinalIncorrectHTLCAmount => { + outer_hop_data.amt_to_forward.write(&mut err_data).unwrap(); + } + _ => unreachable!() + } + // The Trampoline onion's amt and CLTV values cannot exceed the outer onion's + InboundHTLCErr { + reason, + err_data, + msg: "Underflow calculating skimmable amount or CLTV value for Trampoline receive", + } + })?; (payment_data, keysend_preimage, custom_tlvs, sender_intended_htlc_amt_msat, - cltv_expiry_height, payment_metadata, None, false, keysend_preimage.is_none(), None), + cltv_expiry_height, payment_metadata, None, false, keysend_preimage.is_none(), None) + } onion_utils::Hop::TrampolineBlindedReceive { + ref outer_hop_data, trampoline_hop_data: msgs::InboundOnionBlindedReceivePayload { sender_intended_htlc_amt_msat, total_msat, cltv_expiry_height, payment_secret, intro_node_blinding_point, payment_constraints, payment_context, keysend_preimage, @@ -306,6 +373,15 @@ pub(super) fn create_recv_pending_htlc_info( } })?; let payment_data = msgs::FinalOnionHopData { payment_secret, total_msat }; + check_trampoline_onion_constraints(outer_hop_data, cltv_expiry_height, sender_intended_htlc_amt_msat).map_err(|_| { + // The Trampoline onion's amt and CLTV values cannot exceed the outer onion's, but + // we're inside a blinded path + InboundHTLCErr { + reason: LocalHTLCFailureReason::InvalidOnionBlinding, + err_data: vec![0; 32], + msg: "Underflow calculating skimmable amount or CLTV value for Trampoline receive", + } + })?; (Some(payment_data), keysend_preimage, custom_tlvs, sender_intended_htlc_amt_msat, cltv_expiry_height, None, Some(payment_context), intro_node_blinding_point.is_none(), true, invoice_request) @@ -602,6 +678,25 @@ where outgoing_cltv_value, }) } + onion_utils::Hop::TrampolineBlindedForward { next_trampoline_hop_data: msgs::InboundTrampolineBlindedForwardPayload { next_trampoline, ref payment_relay, ref payment_constraints, ref features, .. }, outer_shared_secret, trampoline_shared_secret, incoming_trampoline_public_key, .. } => { + let (amt_to_forward, outgoing_cltv_value) = match check_blinded_forward( + msg.amount_msat, msg.cltv_expiry, &payment_relay, &payment_constraints, &features + ) { + Ok((amt, cltv)) => (amt, cltv), + Err(()) => { + return encode_relay_error("Underflow calculating outbound amount or cltv value for blinded forward", + LocalHTLCFailureReason::InvalidOnionBlinding, outer_shared_secret.secret_bytes(), Some(trampoline_shared_secret.secret_bytes()), &[0; 32]); + } + }; + let next_trampoline_packet_pubkey = onion_utils::next_hop_pubkey(secp_ctx, + incoming_trampoline_public_key, &trampoline_shared_secret.secret_bytes()); + Some(NextPacketDetails { + next_packet_pubkey: next_trampoline_packet_pubkey, + outgoing_connector: HopConnector::Trampoline(next_trampoline), + outgoing_amt_msat: amt_to_forward, + outgoing_cltv_value, + }) + } _ => None }; diff --git a/lightning/src/ln/onion_utils.rs b/lightning/src/ln/onion_utils.rs index d45860b0e26..a9ccbd016d5 100644 --- a/lightning/src/ln/onion_utils.rs +++ b/lightning/src/ln/onion_utils.rs @@ -7,7 +7,7 @@ // You may not use this file except in accordance with one or both of these // licenses. -use super::msgs::OnionErrorPacket; +use super::msgs::{FinalOnionHopData, OnionErrorPacket}; use crate::blinded_path::BlindedHop; use crate::crypto::chacha20::ChaCha20; use crate::crypto::streams::ChaChaReader; @@ -15,12 +15,13 @@ use crate::events::HTLCHandlingFailureReason; use crate::ln::channel::TOTAL_BITCOIN_SUPPLY_SATOSHIS; use crate::ln::channelmanager::{HTLCSource, RecipientOnionFields}; use crate::ln::msgs; +use crate::ln::outbound_payment::NextTrampolineHopInfo; use crate::offers::invoice_request::InvoiceRequest; use crate::routing::gossip::NetworkUpdate; use crate::routing::router::{BlindedTail, Path, RouteHop, RouteParameters, TrampolineHop}; use crate::sign::{NodeSigner, Recipient}; use crate::types::features::{ChannelFeatures, NodeFeatures}; -use crate::types::payment::{PaymentHash, PaymentPreimage}; +use crate::types::payment::{PaymentHash, PaymentPreimage, PaymentSecret}; use crate::util::errors::APIError; use crate::util::logger::Logger; use crate::util::ser::{ @@ -204,6 +205,7 @@ trait OnionPayload<'a, 'b> { fn new_trampoline_entry( total_msat: u64, amt_to_forward: u64, outgoing_cltv_value: u32, recipient_onion: &'a RecipientOnionFields, packet: msgs::TrampolineOnionPacket, + blinding_point: Option, ) -> Result; } impl<'a, 'b> OnionPayload<'a, 'b> for msgs::OutboundOnionPayload<'a> { @@ -253,17 +255,31 @@ impl<'a, 'b> OnionPayload<'a, 'b> for msgs::OutboundOnionPayload<'a> { fn new_trampoline_entry( total_msat: u64, amt_to_forward: u64, outgoing_cltv_value: u32, recipient_onion: &'a RecipientOnionFields, packet: msgs::TrampolineOnionPacket, + blinding_point: Option, ) -> Result { - Ok(Self::TrampolineEntrypoint { - amt_to_forward, - outgoing_cltv_value, - multipath_trampoline_data: recipient_onion - .payment_secret - .map(|payment_secret| msgs::FinalOnionHopData { payment_secret, total_msat }), - trampoline_packet: packet, - }) + if let Some(blinding_point) = blinding_point { + Ok(Self::BlindedTrampolineEntrypoint { + amt_to_forward, + outgoing_cltv_value, + multipath_trampoline_data: recipient_onion + .payment_secret + .map(|payment_secret| msgs::FinalOnionHopData { payment_secret, total_msat }), + trampoline_packet: packet, + current_path_key: blinding_point, + }) + } else { + Ok(Self::TrampolineEntrypoint { + amt_to_forward, + outgoing_cltv_value, + multipath_trampoline_data: recipient_onion + .payment_secret + .map(|payment_secret| msgs::FinalOnionHopData { payment_secret, total_msat }), + trampoline_packet: packet, + }) + } } } + impl<'a, 'b> OnionPayload<'a, 'b> for msgs::OutboundTrampolinePayload<'a> { type PathHopForId = &'b TrampolineHop; type ReceiveType = msgs::OutboundTrampolinePayload<'a>; @@ -305,6 +321,7 @@ impl<'a, 'b> OnionPayload<'a, 'b> for msgs::OutboundTrampolinePayload<'a> { fn new_trampoline_entry( _total_msat: u64, _amt_to_forward: u64, _outgoing_cltv_value: u32, _recipient_onion: &'a RecipientOnionFields, _packet: msgs::TrampolineOnionPacket, + _blinding_point: Option, ) -> Result { Err(APIError::InvalidRoute { err: "Trampoline onions cannot contain Trampoline entrypoints!".to_string(), @@ -572,6 +589,7 @@ where cur_cltv, &recipient_onion, trampoline_packet, + None, )?, ); }, @@ -992,8 +1010,13 @@ pub fn process_onion_failure( where L::Target: Logger, { + let mut trampoline_forward_path_option = None; let (path, primary_session_priv) = match htlc_source { HTLCSource::OutboundRoute { ref path, ref session_priv, .. } => (path, session_priv), + HTLCSource::TrampolineForward { ref hops, ref session_priv, .. } => { + trampoline_forward_path_option.replace(Path { hops: hops.clone(), blinded_tail: None }); + (trampoline_forward_path_option.as_ref().unwrap(), session_priv) + }, _ => unreachable!(), }; @@ -1002,6 +1025,7 @@ where let session_priv_hash = Sha256::hash(&primary_session_priv.secret_bytes()).to_byte_array(); let outer_session_priv = SecretKey::from_slice(&session_priv_hash[..]).expect("You broke SHA-256!"); + process_onion_failure_inner( secp_ctx, logger, @@ -1678,6 +1702,13 @@ pub enum LocalHTLCFailureReason { HTLCMaximum, /// The HTLC was failed because our remote peer is offline. PeerOffline, + /// We have been unable to forward a payment to the next Trampoline node but may be able to + /// do it later. + TemporaryTrampolineFailure, + /// The amount or CLTV expiry were insufficient to route the payment to the next Trampoline. + TrampolineFeeOrExpiryInsufficient, + /// The specified next Trampoline node cannot be reached from our node. + UnkonwnNextTrampoline, } impl LocalHTLCFailureReason { @@ -1718,6 +1749,9 @@ impl LocalHTLCFailureReason { Self::InvalidOnionPayload | Self::InvalidTrampolinePayload => PERM | 22, Self::MPPTimeout => 23, Self::InvalidOnionBlinding => BADONION | PERM | 24, + Self::TemporaryTrampolineFailure => PERM | 25, + Self::TrampolineFeeOrExpiryInsufficient => PERM | 26, + Self::UnkonwnNextTrampoline => PERM | 27, Self::UnknownFailureCode { code } => *code, } } @@ -1852,6 +1886,9 @@ impl_writeable_tlv_based_enum!(LocalHTLCFailureReason, (79, HTLCMinimum) => {}, (81, HTLCMaximum) => {}, (83, PeerOffline) => {}, + (85, TemporaryTrampolineFailure) => {}, + (87, TrampolineFeeOrExpiryInsufficient) => {}, + (89, UnkonwnNextTrampoline) => {}, ); impl From<&HTLCFailReason> for HTLCHandlingFailureReason { @@ -2018,6 +2055,11 @@ impl HTLCFailReason { debug_assert!(false, "Unknown failure code: {}", code) } }, + LocalHTLCFailureReason::TemporaryTrampolineFailure => debug_assert!(data.is_empty()), + LocalHTLCFailureReason::TrampolineFeeOrExpiryInsufficient => { + debug_assert!(data.is_empty()) + }, + LocalHTLCFailureReason::UnkonwnNextTrampoline => debug_assert!(data.is_empty()), } Self(HTLCFailReasonRepr::Reason { data, failure_reason }) @@ -2101,8 +2143,8 @@ impl HTLCFailReason { // failures here, but that would be insufficient as find_route // generally ignores its view of our own channels as we provide them via // ChannelDetails. - if let &HTLCSource::OutboundRoute { ref path, .. } = htlc_source { - DecodedOnionFailure { + match htlc_source { + HTLCSource::OutboundRoute { ref path, .. } => DecodedOnionFailure { network_update: None, payment_failed_permanently: false, short_channel_id: Some(path.hops[0].short_channel_id), @@ -2114,9 +2156,21 @@ impl HTLCFailReason { onion_error_data: Some(data.clone()), #[cfg(test)] attribution_failed_channel: None, - } - } else { - unreachable!(); + }, + HTLCSource::TrampolineForward { ref hops, .. } => DecodedOnionFailure { + network_update: None, + payment_failed_permanently: false, + short_channel_id: hops.first().map(|h| h.short_channel_id), + failed_within_blinded_path: false, + hold_times: Vec::new(), + #[cfg(any(test, feature = "_test_utils"))] + onion_error_code: Some(*failure_reason), + #[cfg(any(test, feature = "_test_utils"))] + onion_error_data: Some(data.clone()), + #[cfg(test)] + attribution_failed_channel: None, + }, + _ => unreachable!(), } }, } @@ -2513,6 +2567,65 @@ pub fn create_payment_onion( ) } +pub(crate) fn create_trampoline_forward_onion( + secp_ctx: &Secp256k1, path: &Path, session_priv: &SecretKey, total_msat: u64, + payment_secret: PaymentSecret, cur_block_height: u32, payment_hash: &PaymentHash, + keysend_preimage: &Option, trampoline_forward_info: &NextTrampolineHopInfo, + prng_seed: [u8; 32], +) -> Result<(msgs::OnionPacket, u64, u32), APIError> { + let recipient_onion = RecipientOnionFields::spontaneous_empty(); + let (mut onion_payloads, htlc_msat, htlc_cltv) = build_onion_payloads( + &path, + path.final_value_msat(), + // we don't need a real recipient onion + &recipient_onion, + cur_block_height, + keysend_preimage, + None, + // TODO: find idiomatic way of that being considered without post-processing in this method + Some(trampoline_forward_info.onion_packet.clone()), + )?; + + let multipath_trampoline_data = Some(FinalOnionHopData { payment_secret, total_msat }); + if let Some(last_payload) = onion_payloads.last_mut() { + match last_payload { + msgs::OutboundOnionPayload::Receive { + sender_intended_htlc_amt_msat, + cltv_expiry_height, + .. + } => { + *last_payload = match trampoline_forward_info.blinding_point { + None => msgs::OutboundOnionPayload::TrampolineEntrypoint { + amt_to_forward: *sender_intended_htlc_amt_msat, + outgoing_cltv_value: *cltv_expiry_height, + multipath_trampoline_data, + trampoline_packet: trampoline_forward_info.onion_packet.clone(), + }, + Some(blinding_point) => { + msgs::OutboundOnionPayload::BlindedTrampolineEntrypoint { + amt_to_forward: *sender_intended_htlc_amt_msat, + outgoing_cltv_value: *cltv_expiry_height, + multipath_trampoline_data, + trampoline_packet: trampoline_forward_info.onion_packet.clone(), + current_path_key: blinding_point, + } + }, + }; + }, + _ => { + unreachable!("Last element must always initially be of type Receive."); + }, + } + } + + let onion_keys = construct_onion_keys(&secp_ctx, &path, session_priv); + let onion_packet = construct_onion_packet(onion_payloads, onion_keys, prng_seed, payment_hash) + .map_err(|_| APIError::InvalidRoute { + err: "Route size too large considering onion data".to_owned(), + })?; + Ok((onion_packet, htlc_msat, htlc_cltv)) +} + /// Build a payment onion, returning the first hop msat and cltv values as well. /// `cur_block_height` should be set to the best known block height + 1. pub(crate) fn create_payment_onion_internal( diff --git a/lightning/src/ln/outbound_payment.rs b/lightning/src/ln/outbound_payment.rs index ffc3ee4ae19..fa2349ca9ea 100644 --- a/lightning/src/ln/outbound_payment.rs +++ b/lightning/src/ln/outbound_payment.rs @@ -11,13 +11,16 @@ use bitcoin::hashes::sha256::Hash as Sha256; use bitcoin::hashes::Hash; -use bitcoin::secp256k1::{self, Secp256k1, SecretKey}; +use bitcoin::secp256k1::{self, PublicKey, Secp256k1, SecretKey}; use lightning_invoice::Bolt11Invoice; use crate::blinded_path::{IntroductionNode, NodeIdLookUp}; use crate::events::{self, PaidBolt12Invoice, PaymentFailureReason}; use crate::ln::channel_state::ChannelDetails; -use crate::ln::channelmanager::{EventCompletionAction, HTLCSource, PaymentId}; +use crate::ln::channelmanager::{ + EventCompletionAction, HTLCPreviousHopData, HTLCSource, PaymentId, +}; +use crate::ln::msgs::TrampolineOnionPacket; use crate::ln::onion_utils; use crate::ln::onion_utils::{DecodedOnionFailure, HTLCFailReason}; use crate::offers::invoice::Bolt12Invoice; @@ -126,6 +129,9 @@ pub(crate) enum PendingOutboundPayment { // Storing the BOLT 12 invoice here to allow Proof of Payment after // the payment is made. bolt12_invoice: Option, + // Storing forward information for trampoline payments in order to build next hop info + // or build error or claims to the origin. + trampoline_forward_info: Option, custom_tlvs: Vec<(u64, Vec)>, pending_amt_msat: u64, /// Used to track the fee paid. Present iff the payment was serialized on 0.0.103+. @@ -161,6 +167,36 @@ pub(crate) enum PendingOutboundPayment { }, } +#[derive(Clone)] +pub(crate) struct NextTrampolineHopInfo { + /// The Trampoline packet to include for the next Trampoline hop + pub(crate) onion_packet: TrampolineOnionPacket, + /// If blinded, the current_path_key to set at the next Trampoline hop + pub(crate) blinding_point: Option, +} + +impl_writeable_tlv_based!(NextTrampolineHopInfo, { + (0, onion_packet, required), + (1, blinding_point, option), +}); + +#[derive(Clone)] +pub(crate) struct TrampolineForwardInfo { + /// Information necessary to construct the onion packet for the next Trampoline hop + pub(crate) next_hop_info: NextTrampolineHopInfo, + /// Upstream hop data to correctly propagate claims and errors back to the origin. If the + /// inbound payment was split up via MPP, the vector will contain an entry for each component. + pub(crate) previous_hop_data: Vec, + /// The shared secret from the incoming trampoline onion, needed for error encryption + pub(crate) incoming_trampoline_shared_secret: [u8; 32], +} + +impl_writeable_tlv_based!(TrampolineForwardInfo, { + (0, next_hop_info, required), + (1, previous_hop_data, required_vec), + (2, incoming_trampoline_shared_secret, required), +}); + #[derive(Clone)] pub(crate) struct RetryableInvoiceRequest { pub(crate) invoice_request: InvoiceRequest, @@ -832,6 +868,7 @@ pub(super) struct SendAlongPathArgs<'a> { pub keysend_preimage: &'a Option, pub invoice_request: Option<&'a InvoiceRequest>, pub bolt12_invoice: Option<&'a PaidBolt12Invoice>, + pub trampoline_forward_info: Option<&'a TrampolineForwardInfo>, pub session_priv_bytes: [u8; 32], } @@ -1069,7 +1106,7 @@ impl OutboundPayments { hash_map::Entry::Occupied(entry) => match entry.get() { PendingOutboundPayment::InvoiceReceived { .. } => { let (retryable_payment, onion_session_privs) = Self::create_pending_payment( - payment_hash, recipient_onion.clone(), keysend_preimage, None, Some(bolt12_invoice.clone()), &route, + payment_hash, recipient_onion.clone(), keysend_preimage, None, Some(bolt12_invoice.clone()), None, &route, Some(retry_strategy), payment_params, entropy_source, best_block_height, ); *entry.into_mut() = retryable_payment; @@ -1080,8 +1117,8 @@ impl OutboundPayments { invoice_request } else { unreachable!() }; let (retryable_payment, onion_session_privs) = Self::create_pending_payment( - payment_hash, recipient_onion.clone(), keysend_preimage, Some(invreq), Some(bolt12_invoice.clone()), &route, - Some(retry_strategy), payment_params, entropy_source, best_block_height + payment_hash, recipient_onion.clone(), keysend_preimage, Some(invreq), Some(bolt12_invoice.clone()), + None, &route, Some(retry_strategy), payment_params, entropy_source, best_block_height ); outbounds.insert(payment_id, retryable_payment); onion_session_privs @@ -1093,8 +1130,8 @@ impl OutboundPayments { core::mem::drop(outbounds); let result = self.pay_route_internal( - &route, payment_hash, &recipient_onion, keysend_preimage, invoice_request, Some(&bolt12_invoice), payment_id, - Some(route_params.final_value_msat), &onion_session_privs, node_signer, best_block_height, + &route, payment_hash, &recipient_onion, keysend_preimage, invoice_request, Some(&bolt12_invoice), None, + payment_id, Some(route_params.final_value_msat), &onion_session_privs, node_signer, best_block_height, &send_payment_along_path ); log_info!( @@ -1429,7 +1466,7 @@ impl OutboundPayments { let onion_session_privs = self.add_new_pending_payment(payment_hash, recipient_onion.clone(), payment_id, keysend_preimage, &route, Some(retry_strategy), - Some(route_params.payment_params.clone()), entropy_source, best_block_height, None) + Some(route_params.payment_params.clone()), entropy_source, best_block_height, None, None) .map_err(|_| { log_error!(logger, "Payment with id {} is already pending. New payment had payment hash {}", payment_id, payment_hash); @@ -1437,8 +1474,8 @@ impl OutboundPayments { })?; let res = self.pay_route_internal(&route, payment_hash, &recipient_onion, - keysend_preimage, None, None, payment_id, None, &onion_session_privs, node_signer, - best_block_height, &send_payment_along_path); + keysend_preimage, None, None, None, payment_id, + None, &onion_session_privs, node_signer, best_block_height, &send_payment_along_path); log_info!(logger, "Sending payment with id {} and hash {} returned {:?}", payment_id, payment_hash, res); if let Err(e) = res { @@ -1451,11 +1488,127 @@ impl OutboundPayments { Ok(()) } + /// Errors immediately on [`RetryableSendFailure`] error conditions. Otherwise, further errors may + /// be surfaced asynchronously via [`Event::PaymentPathFailed`] and [`Event::PaymentFailed`]. + /// + /// [`Event::PaymentPathFailed`]: crate::events::Event::PaymentPathFailed + /// [`Event::PaymentFailed`]: crate::events::Event::PaymentFailed + pub(super) fn send_payment_for_trampoline_forward< + R: Deref, + NS: Deref, + ES: Deref, + IH, + SP, + L: Deref, + >( + &self, payment_id: PaymentId, payment_hash: PaymentHash, + trampoline_forward_info: TrampolineForwardInfo, retry_strategy: Retry, + mut route_params: RouteParameters, router: &R, first_hops: Vec, + inflight_htlcs: IH, entropy_source: &ES, node_signer: &NS, best_block_height: u32, + logger: &L, + pending_events: &Mutex)>>, + send_payment_along_path: SP, + ) -> Result<(), RetryableSendFailure> + where + R::Target: Router, + ES::Target: EntropySource, + NS::Target: NodeSigner, + L::Target: Logger, + IH: Fn() -> InFlightHtlcs, + SP: Fn(SendAlongPathArgs) -> Result<(), APIError>, + { + let inter_trampoline_payment_secret = + PaymentSecret(entropy_source.get_secure_random_bytes()); + let recipient_onion = RecipientOnionFields::secret_only(inter_trampoline_payment_secret); + + let route = self.find_initial_route( + payment_id, + payment_hash, + &recipient_onion, + None, + None, + &mut route_params, + router, + &first_hops, + &inflight_htlcs, + node_signer, + best_block_height, + logger, + )?; + + let onion_session_privs = self + .add_new_pending_payment( + payment_hash, + recipient_onion.clone(), + payment_id, + None, + &route, + Some(retry_strategy), + Some(route_params.payment_params.clone()), + entropy_source, + best_block_height, + None, + Some(trampoline_forward_info.clone()), + ) + .map_err(|_| { + log_error!( + logger, + "Payment with id {} is already pending. New payment had payment hash {}", + payment_id, + payment_hash + ); + RetryableSendFailure::DuplicatePayment + })?; + + let res = self.pay_route_internal( + &route, + payment_hash, + &recipient_onion, + None, + None, + None, + Some(&trampoline_forward_info), + payment_id, + None, + &onion_session_privs, + node_signer, + best_block_height, + &send_payment_along_path, + ); + log_info!( + logger, + "Sending payment with id {} and hash {} returned {:?}", + payment_id, + payment_hash, + res + ); + if let Err(e) = res { + self.handle_pay_route_err( + e, + payment_id, + payment_hash, + route, + route_params, + onion_session_privs, + router, + first_hops, + &inflight_htlcs, + entropy_source, + node_signer, + best_block_height, + logger, + pending_events, + &send_payment_along_path, + ); + } + Ok(()) + } + #[rustfmt::skip] fn find_route_and_send_payment( &self, payment_hash: PaymentHash, payment_id: PaymentId, route_params: RouteParameters, - router: &R, first_hops: Vec, inflight_htlcs: &IH, entropy_source: &ES, - node_signer: &NS, best_block_height: u32, logger: &L, + router: &R, first_hops: Vec, inflight_htlcs: &IH, + entropy_source: &ES, node_signer: &NS, best_block_height: u32, logger: &L, pending_events: &Mutex)>>, send_payment_along_path: &SP, ) where @@ -1516,14 +1669,14 @@ impl OutboundPayments { } } } - let (total_msat, recipient_onion, keysend_preimage, onion_session_privs, invoice_request, bolt12_invoice) = { + let (total_msat, recipient_onion, keysend_preimage, onion_session_privs, invoice_request, bolt12_invoice, trampoline_forward_info) = { let mut outbounds = self.pending_outbound_payments.lock().unwrap(); match outbounds.entry(payment_id) { hash_map::Entry::Occupied(mut payment) => { match payment.get() { PendingOutboundPayment::Retryable { total_msat, keysend_preimage, payment_secret, payment_metadata, - custom_tlvs, pending_amt_msat, invoice_request, .. + custom_tlvs, pending_amt_msat, invoice_request, trampoline_forward_info, .. } => { const RETRY_OVERFLOW_PERCENTAGE: u64 = 10; let retry_amt_msat = route.get_total_amount(); @@ -1540,6 +1693,7 @@ impl OutboundPayments { } let total_msat = *total_msat; + let trampoline_forward_info = trampoline_forward_info.clone(); let recipient_onion = RecipientOnionFields { payment_secret: *payment_secret, payment_metadata: payment_metadata.clone(), @@ -1560,7 +1714,7 @@ impl OutboundPayments { payment.get_mut().increment_attempts(); let bolt12_invoice = payment.get().bolt12_invoice(); - (total_msat, recipient_onion, keysend_preimage, onion_session_privs, invoice_request, bolt12_invoice.cloned()) + (total_msat, recipient_onion, keysend_preimage, onion_session_privs, invoice_request, bolt12_invoice.cloned(), trampoline_forward_info) }, PendingOutboundPayment::Legacy { .. } => { log_error!(logger, "Unable to retry payments that were initially sent on LDK versions prior to 0.0.102"); @@ -1600,7 +1754,7 @@ impl OutboundPayments { } }; let res = self.pay_route_internal(&route, payment_hash, &recipient_onion, keysend_preimage, - invoice_request.as_ref(), bolt12_invoice.as_ref(), payment_id, Some(total_msat), &onion_session_privs, node_signer, + invoice_request.as_ref(), bolt12_invoice.as_ref(), trampoline_forward_info.as_ref(), payment_id, Some(total_msat), &onion_session_privs, node_signer, best_block_height, &send_payment_along_path); log_info!(logger, "Result retrying payment id {}: {:?}", &payment_id, res); if let Err(e) = res { @@ -1757,7 +1911,7 @@ impl OutboundPayments { let route = Route { paths: vec![path], route_params: None }; let onion_session_privs = self.add_new_pending_payment(payment_hash, RecipientOnionFields::secret_only(payment_secret), payment_id, None, &route, None, None, - entropy_source, best_block_height, None + entropy_source, best_block_height, None, None ).map_err(|e| { debug_assert!(matches!(e, PaymentSendFailure::DuplicatePayment)); ProbeSendFailure::DuplicateProbe @@ -1765,8 +1919,8 @@ impl OutboundPayments { let recipient_onion_fields = RecipientOnionFields::spontaneous_empty(); match self.pay_route_internal(&route, payment_hash, &recipient_onion_fields, - None, None, None, payment_id, None, &onion_session_privs, node_signer, best_block_height, - &send_payment_along_path + None, None, None, None, + payment_id, None, &onion_session_privs, node_signer, best_block_height, &send_payment_along_path ) { Ok(()) => Ok((payment_hash, payment_id)), Err(e) => { @@ -1813,7 +1967,7 @@ impl OutboundPayments { &self, payment_hash: PaymentHash, recipient_onion: RecipientOnionFields, payment_id: PaymentId, route: &Route, retry_strategy: Option, entropy_source: &ES, best_block_height: u32 ) -> Result, PaymentSendFailure> where ES::Target: EntropySource { - self.add_new_pending_payment(payment_hash, recipient_onion, payment_id, None, route, retry_strategy, None, entropy_source, best_block_height, None) + self.add_new_pending_payment(payment_hash, recipient_onion, payment_id, None, route, retry_strategy, None, entropy_source, best_block_height, None, None) } #[rustfmt::skip] @@ -1821,15 +1975,15 @@ impl OutboundPayments { &self, payment_hash: PaymentHash, recipient_onion: RecipientOnionFields, payment_id: PaymentId, keysend_preimage: Option, route: &Route, retry_strategy: Option, payment_params: Option, entropy_source: &ES, best_block_height: u32, - bolt12_invoice: Option + bolt12_invoice: Option, trampoline_forward_info: Option ) -> Result, PaymentSendFailure> where ES::Target: EntropySource { let mut pending_outbounds = self.pending_outbound_payments.lock().unwrap(); match pending_outbounds.entry(payment_id) { hash_map::Entry::Occupied(_) => Err(PaymentSendFailure::DuplicatePayment), hash_map::Entry::Vacant(entry) => { let (payment, onion_session_privs) = Self::create_pending_payment( - payment_hash, recipient_onion, keysend_preimage, None, bolt12_invoice, route, retry_strategy, - payment_params, entropy_source, best_block_height + payment_hash, recipient_onion, keysend_preimage, None, bolt12_invoice, trampoline_forward_info, + route, retry_strategy, payment_params, entropy_source, best_block_height ); entry.insert(payment); Ok(onion_session_privs) @@ -1841,7 +1995,8 @@ impl OutboundPayments { fn create_pending_payment( payment_hash: PaymentHash, recipient_onion: RecipientOnionFields, keysend_preimage: Option, invoice_request: Option, - bolt12_invoice: Option, route: &Route, retry_strategy: Option, + bolt12_invoice: Option, trampoline_forward_info: Option, + route: &Route, retry_strategy: Option, payment_params: Option, entropy_source: &ES, best_block_height: u32 ) -> (PendingOutboundPayment, Vec<[u8; 32]>) where @@ -1865,6 +2020,7 @@ impl OutboundPayments { keysend_preimage, invoice_request, bolt12_invoice, + trampoline_forward_info, custom_tlvs: recipient_onion.custom_tlvs, starting_block_height: best_block_height, total_msat: route.get_total_amount(), @@ -2013,7 +2169,7 @@ impl OutboundPayments { fn pay_route_internal( &self, route: &Route, payment_hash: PaymentHash, recipient_onion: &RecipientOnionFields, keysend_preimage: Option, invoice_request: Option<&InvoiceRequest>, bolt12_invoice: Option<&PaidBolt12Invoice>, - payment_id: PaymentId, recv_value_msat: Option, onion_session_privs: &Vec<[u8; 32]>, + trampoline_forward_info: Option<&TrampolineForwardInfo>, payment_id: PaymentId, recv_value_msat: Option, onion_session_privs: &Vec<[u8; 32]>, node_signer: &NS, best_block_height: u32, send_payment_along_path: &F ) -> Result<(), PaymentSendFailure> where @@ -2068,7 +2224,7 @@ impl OutboundPayments { let path_res = send_payment_along_path(SendAlongPathArgs { path: &path, payment_hash: &payment_hash, recipient_onion, total_value, cur_height, payment_id, keysend_preimage: &keysend_preimage, invoice_request, - bolt12_invoice, + bolt12_invoice, trampoline_forward_info, session_priv_bytes: *session_priv_bytes }); results.push(path_res); @@ -2136,7 +2292,7 @@ impl OutboundPayments { F: Fn(SendAlongPathArgs) -> Result<(), APIError>, { self.pay_route_internal(route, payment_hash, &recipient_onion, - keysend_preimage, None, None, payment_id, recv_value_msat, &onion_session_privs, + keysend_preimage, None, None, None, payment_id, recv_value_msat, &onion_session_privs, node_signer, best_block_height, &send_payment_along_path) .map_err(|e| { self.remove_outbound_if_all_failed(payment_id, &e); e }) } @@ -2323,6 +2479,123 @@ impl OutboundPayments { }); } + // Returns a bool indicating whether if we should fail the HTLC backwards. + pub(super) fn trampoline_htlc_failed( + &self, source: &HTLCSource, payment_hash: &PaymentHash, onion_error: &HTLCFailReason, + path: &Path, session_priv: &SecretKey, payment_id: &PaymentId, + secp_ctx: &Secp256k1, + _pending_events: &Mutex)>>, + logger: &L, + ) -> bool + where + L::Target: Logger, + { + #[cfg(any(test, feature = "_test_utils"))] + let DecodedOnionFailure { + short_channel_id, + payment_failed_permanently, + failed_within_blinded_path, + .. + } = onion_error.decode_onion_failure(secp_ctx, logger, &source); + + #[cfg(not(any(test, feature = "_test_utils")))] + let DecodedOnionFailure { + short_channel_id, + payment_failed_permanently, + failed_within_blinded_path, + .. + } = onion_error.decode_onion_failure(secp_ctx, logger, &source); + + let mut session_priv_bytes = [0; 32]; + session_priv_bytes.copy_from_slice(&session_priv[..]); + let mut outbounds = self.pending_outbound_payments.lock().unwrap(); + + // If any payments already need retry, there's no need to generate a redundant + // `PendingHTLCsForwardable`. + let already_awaiting_retry = outbounds.iter().any(|(_, pmt)| { + let mut awaiting_retry = false; + if pmt.is_auto_retryable_now() { + if let PendingOutboundPayment::Retryable { pending_amt_msat, total_msat, .. } = pmt + { + if pending_amt_msat < total_msat { + awaiting_retry = true; + } + } + } + awaiting_retry + }); + + let mut pending_retry_ev = false; + let attempts_remaining = + if let hash_map::Entry::Occupied(mut payment) = outbounds.entry(*payment_id) { + if !payment.get_mut().remove(&session_priv_bytes, Some(&path)) { + log_trace!( + logger, + "Received duplicative fail for HTLC with payment_hash {}", + &payment_hash + ); + return false; + } + if payment.get().is_fulfilled() { + log_trace!( + logger, + "Received failure of HTLC with payment_hash {} after payment completion", + &payment_hash + ); + return false; + } + let mut is_retryable_now = payment.get().is_auto_retryable_now(); + if let Some(scid) = short_channel_id { + // TODO: If we decided to blame ourselves (or one of our channels) in + // process_onion_failure we should close that channel as it implies our + // next-hop is needlessly blaming us! + payment.get_mut().insert_previously_failed_scid(scid); + } + if failed_within_blinded_path { + debug_assert!(short_channel_id.is_none()); + if let Some(bt) = &path.blinded_tail { + payment.get_mut().insert_previously_failed_blinded_path(&bt); + } else { + debug_assert!(false); + } + } + + if !is_retryable_now || payment_failed_permanently { + let reason = if payment_failed_permanently { + PaymentFailureReason::RecipientRejected + } else { + PaymentFailureReason::RetriesExhausted + }; + payment.get_mut().mark_abandoned(reason); + is_retryable_now = false; + } + if payment.get().remaining_parts() == 0 { + if let PendingOutboundPayment::Abandoned { .. } = payment.get() { + payment.remove(); + return true; + } + } + is_retryable_now + } else { + log_trace!( + logger, + "Received fail for HTLC with payment_hash {} not found.", + &payment_hash + ); + return true; + }; + core::mem::drop(outbounds); + log_trace!(logger, "Failing Trampoline forward HTLC with payment_hash {}", &payment_hash); + + // If we miss abandoning the payment above, we *must* generate an event here or else the + // payment will sit in our outbounds forever. + if attempts_remaining && !already_awaiting_retry { + pending_retry_ev = true; + }; + + !pending_retry_ev + } + pub(super) fn fail_htlc( &self, source: &HTLCSource, payment_hash: &PaymentHash, onion_error: &HTLCFailReason, path: &Path, session_priv: &SecretKey, payment_id: &PaymentId, @@ -2576,6 +2849,7 @@ impl OutboundPayments { keysend_preimage: None, // only used for retries, and we'll never retry on startup invoice_request: None, // only used for retries, and we'll never retry on startup bolt12_invoice: None, // only used for retries, and we'll never retry on startup! + trampoline_forward_info: None, // only used for retries, and we'll never retry on startup custom_tlvs: Vec::new(), // only used for retries, and we'll never retry on startup pending_amt_msat: path_amt, pending_fee_msat: Some(path_fee), @@ -2664,6 +2938,7 @@ impl_writeable_tlv_based_enum_upgradable!(PendingOutboundPayment, (11, remaining_max_total_routing_fee_msat, option), (13, invoice_request, option), (15, bolt12_invoice, option), + (17, trampoline_forward_info, option), (not_written, retry_strategy, (static_value, None)), (not_written, attempts, (static_value, PaymentAttempts::new())), }, @@ -2827,7 +3102,7 @@ mod tests { outbound_payments.add_new_pending_payment(PaymentHash([0; 32]), RecipientOnionFields::spontaneous_empty(), PaymentId([0; 32]), None, &Route { paths: vec![], route_params: None }, Some(Retry::Attempts(1)), Some(expired_route_params.payment_params.clone()), - &&keys_manager, 0, None).unwrap(); + &&keys_manager, 0, None, None).unwrap(); outbound_payments.find_route_and_send_payment( PaymentHash([0; 32]), PaymentId([0; 32]), expired_route_params, &&router, vec![], &|| InFlightHtlcs::new(), &&keys_manager, &&keys_manager, 0, &&logger, &pending_events, @@ -2871,7 +3146,7 @@ mod tests { outbound_payments.add_new_pending_payment(PaymentHash([0; 32]), RecipientOnionFields::spontaneous_empty(), PaymentId([0; 32]), None, &Route { paths: vec![], route_params: None }, Some(Retry::Attempts(1)), Some(route_params.payment_params.clone()), - &&keys_manager, 0, None).unwrap(); + &&keys_manager, 0, None, None).unwrap(); outbound_payments.find_route_and_send_payment( PaymentHash([0; 32]), PaymentId([0; 32]), route_params, &&router, vec![], &|| InFlightHtlcs::new(), &&keys_manager, &&keys_manager, 0, &&logger, &pending_events, diff --git a/lightning/src/util/ser.rs b/lightning/src/util/ser.rs index ac2b529f0bd..ba75e852f62 100644 --- a/lightning/src/util/ser.rs +++ b/lightning/src/util/ser.rs @@ -1077,6 +1077,7 @@ impl Readable for Vec { impl_for_vec!(ecdsa::Signature); impl_for_vec!(crate::chain::channelmonitor::ChannelMonitorUpdate); +impl_for_vec!(crate::ln::channelmanager::HTLCPreviousHopData); impl_for_vec!(crate::ln::channelmanager::MonitorUpdateCompletionAction); impl_for_vec!(crate::ln::channelmanager::PaymentClaimDetails); impl_for_vec!(crate::ln::msgs::SocketAddress); @@ -1086,6 +1087,7 @@ impl_for_vec!(NegotiatedTxInput); impl_for_vec!(InteractiveTxOutput); impl_writeable_for_vec!(&crate::routing::router::BlindedTail); impl_readable_for_vec!(crate::routing::router::BlindedTail); +impl_for_vec!(crate::routing::router::RouteHop); impl_for_vec!(crate::routing::router::TrampolineHop); impl_for_vec_with_element_length_prefix!(crate::ln::msgs::UpdateAddHTLC); impl_writeable_for_vec_with_element_length_prefix!(&crate::ln::msgs::UpdateAddHTLC);