mirror of
https://github.com/lightningdevkit/rust-lightning.git
synced 2025-01-19 05:43:55 +01:00
Merge pull request #3207 from valentinewallace/2024-07-invreq-in-onion
Include invoice requests in async payment onions
This commit is contained in:
commit
a130bd69de
@ -121,8 +121,8 @@ impl OffersMessageHandler for TestOffersMessageHandler {
|
||||
struct TestAsyncPaymentsMessageHandler {}
|
||||
|
||||
impl AsyncPaymentsMessageHandler for TestAsyncPaymentsMessageHandler {
|
||||
fn held_htlc_available(
|
||||
&self, message: HeldHtlcAvailable, responder: Option<Responder>,
|
||||
fn handle_held_htlc_available(
|
||||
&self, _message: HeldHtlcAvailable, responder: Option<Responder>,
|
||||
) -> Option<(ReleaseHeldHtlc, ResponseInstruction)> {
|
||||
let responder = match responder {
|
||||
Some(resp) => resp,
|
||||
@ -130,7 +130,7 @@ impl AsyncPaymentsMessageHandler for TestAsyncPaymentsMessageHandler {
|
||||
};
|
||||
Some((ReleaseHeldHtlc {}, responder.respond()))
|
||||
}
|
||||
fn release_held_htlc(&self, _message: ReleaseHeldHtlc, _context: AsyncPaymentsContext) {}
|
||||
fn handle_release_held_htlc(&self, _message: ReleaseHeldHtlc, _context: AsyncPaymentsContext) {}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
|
@ -560,11 +560,15 @@ pub enum PaymentFailureReason {
|
||||
/// [`PaymentParameters::expiry_time`]: crate::routing::router::PaymentParameters::expiry_time
|
||||
/// [`InvoiceRequestExpired`]: Self::InvoiceRequestExpired
|
||||
PaymentExpired,
|
||||
/// We failed to find a route while retrying the payment.
|
||||
/// We failed to find a route while sending or retrying the payment.
|
||||
///
|
||||
/// Note that this generally indicates that we've exhausted the available set of possible
|
||||
/// routes - we tried the payment over a few routes but were not able to find any further
|
||||
/// candidate routes beyond those.
|
||||
///
|
||||
/// Also used for [`BlindedPathCreationFailed`] when downgrading to versions prior to 0.0.124.
|
||||
///
|
||||
/// [`BlindedPathCreationFailed`]: Self::BlindedPathCreationFailed
|
||||
RouteNotFound,
|
||||
/// This error should generally never happen. This likely means that there is a problem with
|
||||
/// your router.
|
||||
@ -577,6 +581,12 @@ pub enum PaymentFailureReason {
|
||||
///
|
||||
/// [`InvoiceRequest`]: crate::offers::invoice_request::InvoiceRequest
|
||||
InvoiceRequestRejected,
|
||||
/// Failed to create a blinded path back to ourselves.
|
||||
/// We attempted to initiate payment to a static invoice but failed to create a reply path for our
|
||||
/// [`HeldHtlcAvailable`] message.
|
||||
///
|
||||
/// [`HeldHtlcAvailable`]: crate::onion_message::async_payments::HeldHtlcAvailable
|
||||
BlindedPathCreationFailed,
|
||||
}
|
||||
|
||||
impl_writeable_tlv_based_enum_upgradable!(PaymentFailureReason,
|
||||
@ -587,6 +597,7 @@ impl_writeable_tlv_based_enum_upgradable!(PaymentFailureReason,
|
||||
(4, RetriesExhausted) => {},
|
||||
(5, InvoiceRequestRejected) => {},
|
||||
(6, PaymentExpired) => {},
|
||||
(7, BlindedPathCreationFailed) => {},
|
||||
(8, RouteNotFound) => {},
|
||||
(10, UnexpectedError) => {},
|
||||
);
|
||||
@ -1651,6 +1662,8 @@ impl Writeable for Event {
|
||||
&Some(PaymentFailureReason::RetriesExhausted),
|
||||
Some(PaymentFailureReason::InvoiceRequestRejected) =>
|
||||
&Some(PaymentFailureReason::RecipientRejected),
|
||||
Some(PaymentFailureReason::BlindedPathCreationFailed) =>
|
||||
&Some(PaymentFailureReason::RouteNotFound)
|
||||
};
|
||||
write_tlv_fields!(writer, {
|
||||
(0, payment_id, required),
|
||||
|
@ -318,7 +318,7 @@ fn do_forward_checks_failure(check: ForwardCheckFail, intro_fails: bool) {
|
||||
let onion_keys = onion_utils::construct_onion_keys(&Secp256k1::new(), &route.paths[0], &session_priv).unwrap();
|
||||
let cur_height = nodes[0].best_block_info().1;
|
||||
let (mut onion_payloads, ..) = onion_utils::build_onion_payloads(
|
||||
&route.paths[0], amt_msat, &recipient_onion_fields, cur_height, &None).unwrap();
|
||||
&route.paths[0], amt_msat, &recipient_onion_fields, cur_height, &None, None).unwrap();
|
||||
// Remove the receive payload so the blinded forward payload is encoded as a final payload
|
||||
// (i.e. next_hop_hmac == [0; 32])
|
||||
onion_payloads.pop();
|
||||
@ -897,7 +897,7 @@ fn do_multi_hop_receiver_fail(check: ReceiveCheckFail) {
|
||||
let cur_height = nodes[0].best_block_info().1;
|
||||
let recipient_onion_fields = RecipientOnionFields::spontaneous_empty();
|
||||
let (mut onion_payloads, ..) = onion_utils::build_onion_payloads(
|
||||
&route.paths[0], amt_msat, &recipient_onion_fields, cur_height, &None).unwrap();
|
||||
&route.paths[0], amt_msat, &recipient_onion_fields, cur_height, &None, None).unwrap();
|
||||
|
||||
let update_add = &mut payment_event_1_2.msgs[0];
|
||||
onion_payloads.last_mut().map(|p| {
|
||||
@ -1447,7 +1447,7 @@ fn route_blinding_spec_test_vector() {
|
||||
}),
|
||||
};
|
||||
let cur_height = 747_000;
|
||||
let (bob_onion, _, _) = onion_utils::create_payment_onion(&secp_ctx, &path, &session_priv, amt_msat, &RecipientOnionFields::spontaneous_empty(), cur_height, &PaymentHash([0; 32]), &None, [0; 32]).unwrap();
|
||||
let (bob_onion, _, _) = onion_utils::create_payment_onion(&secp_ctx, &path, &session_priv, amt_msat, &RecipientOnionFields::spontaneous_empty(), cur_height, &PaymentHash([0; 32]), &None, None, [0; 32]).unwrap();
|
||||
|
||||
struct TestEcdhSigner {
|
||||
node_secret: SecretKey,
|
||||
|
@ -4306,14 +4306,14 @@ where
|
||||
let _lck = self.total_consistency_lock.read().unwrap();
|
||||
self.send_payment_along_path(SendAlongPathArgs {
|
||||
path, payment_hash, recipient_onion: &recipient_onion, total_value,
|
||||
cur_height, payment_id, keysend_preimage, session_priv_bytes
|
||||
cur_height, payment_id, keysend_preimage, invoice_request: None, session_priv_bytes
|
||||
})
|
||||
}
|
||||
|
||||
fn send_payment_along_path(&self, args: SendAlongPathArgs) -> Result<(), APIError> {
|
||||
let SendAlongPathArgs {
|
||||
path, payment_hash, recipient_onion, total_value, cur_height, payment_id, keysend_preimage,
|
||||
session_priv_bytes
|
||||
invoice_request, session_priv_bytes
|
||||
} = args;
|
||||
// The top-level caller should hold the total_consistency_lock read lock.
|
||||
debug_assert!(self.total_consistency_lock.try_write().is_err());
|
||||
@ -4322,7 +4322,7 @@ where
|
||||
|
||||
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, prng_seed
|
||||
payment_hash, keysend_preimage, invoice_request, prng_seed
|
||||
).map_err(|e| {
|
||||
let logger = WithContext::from(&self.logger, Some(path.hops.first().unwrap().pubkey), None, Some(*payment_hash));
|
||||
log_error!(logger, "Failed to build an onion for path for payment hash {}", payment_hash);
|
||||
@ -4590,7 +4590,7 @@ where
|
||||
) {
|
||||
Ok(paths) => paths,
|
||||
Err(()) => {
|
||||
self.abandon_payment_with_reason(payment_id, PaymentFailureReason::RouteNotFound);
|
||||
self.abandon_payment_with_reason(payment_id, PaymentFailureReason::BlindedPathCreationFailed);
|
||||
res = Err(Bolt12PaymentError::BlindedPathCreationFailed);
|
||||
return NotifyOption::DoPersist
|
||||
}
|
||||
@ -11474,13 +11474,13 @@ where
|
||||
MR::Target: MessageRouter,
|
||||
L::Target: Logger,
|
||||
{
|
||||
fn held_htlc_available(
|
||||
fn handle_held_htlc_available(
|
||||
&self, _message: HeldHtlcAvailable, _responder: Option<Responder>
|
||||
) -> Option<(ReleaseHeldHtlc, ResponseInstruction)> {
|
||||
None
|
||||
}
|
||||
|
||||
fn release_held_htlc(&self, _message: ReleaseHeldHtlc, _context: AsyncPaymentsContext) {
|
||||
fn handle_release_held_htlc(&self, _message: ReleaseHeldHtlc, _context: AsyncPaymentsContext) {
|
||||
#[cfg(async_payments)] {
|
||||
let AsyncPaymentsContext::OutboundPayment { payment_id, hmac, nonce } = _context;
|
||||
if payment_id.verify_for_async_payment(hmac, nonce, &self.inbound_payment_key).is_err() { return }
|
||||
|
@ -1438,7 +1438,7 @@ fn test_fee_spike_violation_fails_htlc() {
|
||||
let onion_keys = onion_utils::construct_onion_keys(&secp_ctx, &route.paths[0], &session_priv).unwrap();
|
||||
let recipient_onion_fields = RecipientOnionFields::secret_only(payment_secret);
|
||||
let (onion_payloads, htlc_msat, htlc_cltv) = onion_utils::build_onion_payloads(&route.paths[0],
|
||||
3460001, &recipient_onion_fields, cur_height, &None).unwrap();
|
||||
3460001, &recipient_onion_fields, cur_height, &None, None).unwrap();
|
||||
let onion_packet = onion_utils::construct_onion_packet(onion_payloads, onion_keys, [0; 32], &payment_hash).unwrap();
|
||||
let msg = msgs::UpdateAddHTLC {
|
||||
channel_id: chan.2,
|
||||
@ -1637,7 +1637,7 @@ fn test_chan_reserve_violation_inbound_htlc_outbound_channel() {
|
||||
let onion_keys = onion_utils::construct_onion_keys(&secp_ctx, &route.paths[0], &session_priv).unwrap();
|
||||
let recipient_onion_fields = RecipientOnionFields::secret_only(payment_secret);
|
||||
let (onion_payloads, htlc_msat, htlc_cltv) = onion_utils::build_onion_payloads(&route.paths[0],
|
||||
700_000, &recipient_onion_fields, cur_height, &None).unwrap();
|
||||
700_000, &recipient_onion_fields, cur_height, &None, None).unwrap();
|
||||
let onion_packet = onion_utils::construct_onion_packet(onion_payloads, onion_keys, [0; 32], &payment_hash).unwrap();
|
||||
let msg = msgs::UpdateAddHTLC {
|
||||
channel_id: chan.2,
|
||||
@ -1817,7 +1817,7 @@ fn test_chan_reserve_violation_inbound_htlc_inbound_chan() {
|
||||
let onion_keys = onion_utils::construct_onion_keys(&secp_ctx, &route_2.paths[0], &session_priv).unwrap();
|
||||
let recipient_onion_fields = RecipientOnionFields::spontaneous_empty();
|
||||
let (onion_payloads, htlc_msat, htlc_cltv) = onion_utils::build_onion_payloads(
|
||||
&route_2.paths[0], recv_value_2, &recipient_onion_fields, cur_height, &None).unwrap();
|
||||
&route_2.paths[0], recv_value_2, &recipient_onion_fields, cur_height, &None, None).unwrap();
|
||||
let onion_packet = onion_utils::construct_onion_packet(onion_payloads, onion_keys, [0; 32], &our_payment_hash_1).unwrap();
|
||||
let msg = msgs::UpdateAddHTLC {
|
||||
channel_id: chan.2,
|
||||
@ -3556,7 +3556,7 @@ fn fail_backward_pending_htlc_upon_channel_failure() {
|
||||
let current_height = nodes[1].node.best_block.read().unwrap().height + 1;
|
||||
let recipient_onion_fields = RecipientOnionFields::secret_only(payment_secret);
|
||||
let (onion_payloads, _amount_msat, cltv_expiry) = onion_utils::build_onion_payloads(
|
||||
&route.paths[0], 50_000, &recipient_onion_fields, current_height, &None).unwrap();
|
||||
&route.paths[0], 50_000, &recipient_onion_fields, current_height, &None, None).unwrap();
|
||||
let onion_keys = onion_utils::construct_onion_keys(&secp_ctx, &route.paths[0], &session_priv).unwrap();
|
||||
let onion_routing_packet = onion_utils::construct_onion_packet(onion_payloads, onion_keys, [0; 32], &payment_hash).unwrap();
|
||||
|
||||
@ -6551,7 +6551,7 @@ fn test_update_add_htlc_bolt2_receiver_check_max_htlc_limit() {
|
||||
let onion_keys = onion_utils::construct_onion_keys(&Secp256k1::signing_only(), &route.paths[0], &session_priv).unwrap();
|
||||
let recipient_onion_fields = RecipientOnionFields::secret_only(our_payment_secret);
|
||||
let (onion_payloads, _htlc_msat, htlc_cltv) = onion_utils::build_onion_payloads(
|
||||
&route.paths[0], send_amt, &recipient_onion_fields, cur_height, &None).unwrap();
|
||||
&route.paths[0], send_amt, &recipient_onion_fields, cur_height, &None, None).unwrap();
|
||||
let onion_packet = onion_utils::construct_onion_packet(onion_payloads, onion_keys, [0; 32], &our_payment_hash).unwrap();
|
||||
|
||||
let mut msg = msgs::UpdateAddHTLC {
|
||||
@ -8294,7 +8294,7 @@ fn test_onion_value_mpp_set_calculation() {
|
||||
let mut onion_keys = onion_utils::construct_onion_keys(&Secp256k1::new(), &route.paths[0], &session_priv).unwrap();
|
||||
let recipient_onion_fields = RecipientOnionFields::secret_only(our_payment_secret);
|
||||
let (mut onion_payloads, _, _) = onion_utils::build_onion_payloads(&route.paths[0], 100_000,
|
||||
&recipient_onion_fields, height + 1, &None).unwrap();
|
||||
&recipient_onion_fields, height + 1, &None, None).unwrap();
|
||||
// Edit amt_to_forward to simulate the sender having set
|
||||
// the final amount and the routing node taking less fee
|
||||
if let msgs::OutboundOnionPayload::Receive {
|
||||
|
@ -110,7 +110,7 @@ fn large_payment_metadata() {
|
||||
let secp_ctx = Secp256k1::signing_only();
|
||||
route_0_1.paths[0].hops[0].fee_msat = MIN_FINAL_VALUE_ESTIMATE_WITH_OVERPAY;
|
||||
route_0_1.paths[0].hops[0].cltv_expiry_delta = DEFAULT_MAX_TOTAL_CLTV_EXPIRY_DELTA;
|
||||
let err = onion_utils::create_payment_onion(&secp_ctx, &route_0_1.paths[0], &test_utils::privkey(42), MIN_FINAL_VALUE_ESTIMATE_WITH_OVERPAY, &recipient_onion_too_large_md, nodes[0].best_block_info().1 + DEFAULT_MAX_TOTAL_CLTV_EXPIRY_DELTA, &payment_hash, &None, [0; 32]).unwrap_err();
|
||||
let err = onion_utils::create_payment_onion(&secp_ctx, &route_0_1.paths[0], &test_utils::privkey(42), MIN_FINAL_VALUE_ESTIMATE_WITH_OVERPAY, &recipient_onion_too_large_md, nodes[0].best_block_info().1 + DEFAULT_MAX_TOTAL_CLTV_EXPIRY_DELTA, &payment_hash, &None, None, [0; 32]).unwrap_err();
|
||||
match err {
|
||||
APIError::InvalidRoute { err } => {
|
||||
assert_eq!(err, "Route size too large considering onion data");
|
||||
@ -184,6 +184,7 @@ fn one_hop_blinded_path_with_custom_tlv() {
|
||||
encrypted_tlvs: &blinded_path.blinded_hops()[0].encrypted_payload,
|
||||
intro_node_blinding_point: Some(blinded_path.blinding_point()),
|
||||
keysend_preimage: None,
|
||||
invoice_request: None,
|
||||
custom_tlvs: &Vec::new()
|
||||
}.serialized_length();
|
||||
let max_custom_tlv_len = 1300
|
||||
@ -275,7 +276,7 @@ fn blinded_path_with_custom_tlv() {
|
||||
let reserved_packet_bytes_without_custom_tlv: usize = onion_utils::build_onion_payloads(
|
||||
&route.paths[0], MIN_FINAL_VALUE_ESTIMATE_WITH_OVERPAY,
|
||||
&RecipientOnionFields::spontaneous_empty(),
|
||||
nodes[0].best_block_info().1 + DEFAULT_MAX_TOTAL_CLTV_EXPIRY_DELTA, &None
|
||||
nodes[0].best_block_info().1 + DEFAULT_MAX_TOTAL_CLTV_EXPIRY_DELTA, &None, None
|
||||
)
|
||||
.unwrap()
|
||||
.0
|
||||
@ -317,7 +318,7 @@ fn blinded_path_with_custom_tlv() {
|
||||
let secp_ctx = Secp256k1::signing_only();
|
||||
route.paths[0].hops[0].fee_msat = MIN_FINAL_VALUE_ESTIMATE_WITH_OVERPAY;
|
||||
route.paths[0].hops[0].cltv_expiry_delta = DEFAULT_MAX_TOTAL_CLTV_EXPIRY_DELTA;
|
||||
let err = onion_utils::create_payment_onion(&secp_ctx, &route.paths[0], &test_utils::privkey(42), MIN_FINAL_VALUE_ESTIMATE_WITH_OVERPAY, &recipient_onion_too_large_custom_tlv, nodes[0].best_block_info().1 + DEFAULT_MAX_TOTAL_CLTV_EXPIRY_DELTA, &payment_hash, &None, [0; 32]).unwrap_err();
|
||||
let err = onion_utils::create_payment_onion(&secp_ctx, &route.paths[0], &test_utils::privkey(42), MIN_FINAL_VALUE_ESTIMATE_WITH_OVERPAY, &recipient_onion_too_large_custom_tlv, nodes[0].best_block_info().1 + DEFAULT_MAX_TOTAL_CLTV_EXPIRY_DELTA, &payment_hash, &None, None, [0; 32]).unwrap_err();
|
||||
match err {
|
||||
APIError::InvalidRoute { err } => {
|
||||
assert_eq!(err, "Route size too large considering onion data");
|
||||
|
@ -1747,6 +1747,7 @@ pub struct FinalOnionHopData {
|
||||
mod fuzzy_internal_msgs {
|
||||
use bitcoin::secp256k1::PublicKey;
|
||||
use crate::blinded_path::payment::{BlindedPaymentPath, PaymentConstraints, PaymentContext, PaymentRelay};
|
||||
use crate::offers::invoice_request::InvoiceRequest;
|
||||
use crate::types::payment::{PaymentPreimage, PaymentSecret};
|
||||
use crate::types::features::{BlindedHopFeatures, Bolt12InvoiceFeatures};
|
||||
use super::{FinalOnionHopData, TrampolineOnionPacket};
|
||||
@ -1827,6 +1828,7 @@ mod fuzzy_internal_msgs {
|
||||
intro_node_blinding_point: Option<PublicKey>, // Set if the introduction node of the blinded path is the final node
|
||||
keysend_preimage: Option<PaymentPreimage>,
|
||||
custom_tlvs: &'a Vec<(u64, Vec<u8>)>,
|
||||
invoice_request: Option<&'a InvoiceRequest>,
|
||||
}
|
||||
}
|
||||
|
||||
@ -2760,13 +2762,17 @@ impl<'a> Writeable for OutboundOnionPayload<'a> {
|
||||
},
|
||||
Self::BlindedReceive {
|
||||
sender_intended_htlc_amt_msat, total_msat, cltv_expiry_height, encrypted_tlvs,
|
||||
intro_node_blinding_point, keysend_preimage, ref custom_tlvs,
|
||||
intro_node_blinding_point, keysend_preimage, ref invoice_request, ref custom_tlvs,
|
||||
} => {
|
||||
// We need to update [`ln::outbound_payment::RecipientOnionFields::with_custom_tlvs`]
|
||||
// to reject any reserved types in the experimental range if new ones are ever
|
||||
// standardized.
|
||||
let invoice_request_tlv = invoice_request.map(|invreq| (77_777, invreq.encode())); // TODO: update TLV type once the async payments spec is merged
|
||||
let keysend_tlv = keysend_preimage.map(|preimage| (5482373484, preimage.encode()));
|
||||
let mut custom_tlvs: Vec<&(u64, Vec<u8>)> = custom_tlvs.iter().chain(keysend_tlv.iter()).collect();
|
||||
let mut custom_tlvs: Vec<&(u64, Vec<u8>)> = custom_tlvs.iter()
|
||||
.chain(invoice_request_tlv.iter())
|
||||
.chain(keysend_tlv.iter())
|
||||
.collect();
|
||||
custom_tlvs.sort_unstable_by_key(|(typ, _)| *typ);
|
||||
_encode_varint_length_prefixed_tlv!(w, {
|
||||
(2, HighZeroBytesDroppedBigSize(*sender_intended_htlc_amt_msat), required),
|
||||
|
@ -541,7 +541,7 @@ mod tests {
|
||||
let path = Path { hops, blinded_tail: None, };
|
||||
let onion_keys = super::onion_utils::construct_onion_keys(&secp_ctx, &path, &session_priv).unwrap();
|
||||
let (onion_payloads, ..) = super::onion_utils::build_onion_payloads(
|
||||
&path, total_amt_msat, &recipient_onion, cur_height + 1, &Some(keysend_preimage)
|
||||
&path, total_amt_msat, &recipient_onion, cur_height + 1, &Some(keysend_preimage), None
|
||||
).unwrap();
|
||||
|
||||
assert!(super::onion_utils::construct_onion_packet(
|
||||
@ -569,7 +569,7 @@ mod tests {
|
||||
|
||||
let (onion, amount_msat, cltv_expiry) = create_payment_onion(
|
||||
&secp_ctx, &path, &session_priv, total_amt_msat, &recipient_onion,
|
||||
cur_height, &payment_hash, &Some(preimage), prng_seed
|
||||
cur_height, &payment_hash, &Some(preimage), None, prng_seed
|
||||
).unwrap();
|
||||
|
||||
let msg = make_update_add_msg(amount_msat, cltv_expiry, payment_hash, onion);
|
||||
|
@ -356,7 +356,7 @@ fn test_onion_failure() {
|
||||
let onion_keys = onion_utils::construct_onion_keys(&Secp256k1::new(), &route.paths[0], &session_priv).unwrap();
|
||||
let recipient_onion_fields = RecipientOnionFields::spontaneous_empty();
|
||||
let (mut onion_payloads, _htlc_msat, _htlc_cltv) = onion_utils::build_onion_payloads(
|
||||
&route.paths[0], 40000, &recipient_onion_fields, cur_height, &None).unwrap();
|
||||
&route.paths[0], 40000, &recipient_onion_fields, cur_height, &None, None).unwrap();
|
||||
let mut new_payloads = Vec::new();
|
||||
for payload in onion_payloads.drain(..) {
|
||||
new_payloads.push(BogusOnionHopData::new(payload));
|
||||
@ -375,7 +375,7 @@ fn test_onion_failure() {
|
||||
let onion_keys = onion_utils::construct_onion_keys(&Secp256k1::new(), &route.paths[0], &session_priv).unwrap();
|
||||
let recipient_onion_fields = RecipientOnionFields::spontaneous_empty();
|
||||
let (mut onion_payloads, _htlc_msat, _htlc_cltv) = onion_utils::build_onion_payloads(
|
||||
&route.paths[0], 40000, &recipient_onion_fields, cur_height, &None).unwrap();
|
||||
&route.paths[0], 40000, &recipient_onion_fields, cur_height, &None, None).unwrap();
|
||||
let mut new_payloads = Vec::new();
|
||||
for payload in onion_payloads.drain(..) {
|
||||
new_payloads.push(BogusOnionHopData::new(payload));
|
||||
@ -627,7 +627,7 @@ fn test_onion_failure() {
|
||||
let onion_keys = onion_utils::construct_onion_keys(&Secp256k1::new(), &route.paths[0], &session_priv).unwrap();
|
||||
let recipient_onion_fields = RecipientOnionFields::spontaneous_empty();
|
||||
let (onion_payloads, _, htlc_cltv) = onion_utils::build_onion_payloads(
|
||||
&route.paths[0], 40000, &recipient_onion_fields, height, &None).unwrap();
|
||||
&route.paths[0], 40000, &recipient_onion_fields, height, &None, None).unwrap();
|
||||
let onion_packet = onion_utils::construct_onion_packet(onion_payloads, onion_keys, [0; 32], &payment_hash).unwrap();
|
||||
msg.cltv_expiry = htlc_cltv;
|
||||
msg.onion_routing_packet = onion_packet;
|
||||
@ -964,7 +964,7 @@ fn test_always_create_tlv_format_onion_payloads() {
|
||||
let cur_height = nodes[0].best_block_info().1 + 1;
|
||||
let recipient_onion_fields = RecipientOnionFields::spontaneous_empty();
|
||||
let (onion_payloads, _htlc_msat, _htlc_cltv) = onion_utils::build_onion_payloads(
|
||||
&route.paths[0], 40000, &recipient_onion_fields, cur_height, &None).unwrap();
|
||||
&route.paths[0], 40000, &recipient_onion_fields, cur_height, &None, None).unwrap();
|
||||
|
||||
match onion_payloads[0] {
|
||||
msgs::OutboundOnionPayload::Forward {..} => {},
|
||||
@ -1221,7 +1221,7 @@ fn test_phantom_invalid_onion_payload() {
|
||||
let recipient_onion_fields = RecipientOnionFields::secret_only(payment_secret);
|
||||
let (mut onion_payloads, _, _) = onion_utils::build_onion_payloads(
|
||||
&route.paths[0], msgs::MAX_VALUE_MSAT + 1,
|
||||
&recipient_onion_fields, height + 1, &None).unwrap();
|
||||
&recipient_onion_fields, height + 1, &None, None).unwrap();
|
||||
// We only want to construct the onion packet for the last hop, not the entire route, so
|
||||
// remove the first hop's payload and its keys.
|
||||
onion_keys.remove(0);
|
||||
|
@ -13,6 +13,7 @@ use crate::crypto::streams::ChaChaReader;
|
||||
use crate::ln::channel::TOTAL_BITCOIN_SUPPLY_SATOSHIS;
|
||||
use crate::ln::channelmanager::{HTLCSource, RecipientOnionFields};
|
||||
use crate::ln::msgs;
|
||||
use crate::offers::invoice_request::InvoiceRequest;
|
||||
use crate::routing::gossip::NetworkUpdate;
|
||||
use crate::routing::router::{Path, RouteHop, RouteParameters};
|
||||
use crate::sign::NodeSigner;
|
||||
@ -179,6 +180,7 @@ pub(super) fn construct_onion_keys<T: secp256k1::Signing>(
|
||||
pub(super) fn build_onion_payloads<'a>(
|
||||
path: &'a Path, total_msat: u64, recipient_onion: &'a RecipientOnionFields,
|
||||
starting_htlc_offset: u32, keysend_preimage: &Option<PaymentPreimage>,
|
||||
invoice_request: Option<&'a InvoiceRequest>,
|
||||
) -> Result<(Vec<msgs::OutboundOnionPayload<'a>>, u64, u32), APIError> {
|
||||
let mut res: Vec<msgs::OutboundOnionPayload> = Vec::with_capacity(
|
||||
path.hops.len() + path.blinded_tail.as_ref().map_or(0, |t| t.hops.len()),
|
||||
@ -197,6 +199,7 @@ pub(super) fn build_onion_payloads<'a>(
|
||||
recipient_onion,
|
||||
starting_htlc_offset,
|
||||
keysend_preimage,
|
||||
invoice_request,
|
||||
|action, payload| match action {
|
||||
PayloadCallbackAction::PushBack => res.push(payload),
|
||||
PayloadCallbackAction::PushFront => res.insert(0, payload),
|
||||
@ -218,7 +221,8 @@ enum PayloadCallbackAction {
|
||||
fn build_onion_payloads_callback<'a, H, B, F>(
|
||||
hops: H, mut blinded_tail: Option<BlindedTailHopIter<'a, B>>, total_msat: u64,
|
||||
recipient_onion: &'a RecipientOnionFields, starting_htlc_offset: u32,
|
||||
keysend_preimage: &Option<PaymentPreimage>, mut callback: F,
|
||||
keysend_preimage: &Option<PaymentPreimage>, invoice_request: Option<&'a InvoiceRequest>,
|
||||
mut callback: F,
|
||||
) -> Result<(u64, u32), APIError>
|
||||
where
|
||||
H: DoubleEndedIterator<Item = &'a RouteHop>,
|
||||
@ -262,6 +266,7 @@ where
|
||||
encrypted_tlvs: &blinded_hop.encrypted_payload,
|
||||
intro_node_blinding_point: blinding_point.take(),
|
||||
keysend_preimage: *keysend_preimage,
|
||||
invoice_request,
|
||||
custom_tlvs: &recipient_onion.custom_tlvs,
|
||||
},
|
||||
);
|
||||
@ -315,7 +320,8 @@ pub(crate) const MIN_FINAL_VALUE_ESTIMATE_WITH_OVERPAY: u64 = 100_000_000;
|
||||
|
||||
pub(crate) fn set_max_path_length(
|
||||
route_params: &mut RouteParameters, recipient_onion: &RecipientOnionFields,
|
||||
keysend_preimage: Option<PaymentPreimage>, best_block_height: u32,
|
||||
keysend_preimage: Option<PaymentPreimage>, invoice_request: Option<&InvoiceRequest>,
|
||||
best_block_height: u32,
|
||||
) -> Result<(), ()> {
|
||||
const PAYLOAD_HMAC_LEN: usize = 32;
|
||||
let unblinded_intermed_payload_len = msgs::OutboundOnionPayload::Forward {
|
||||
@ -362,6 +368,7 @@ pub(crate) fn set_max_path_length(
|
||||
&recipient_onion,
|
||||
best_block_height,
|
||||
&keysend_preimage,
|
||||
invoice_request,
|
||||
|_, payload| {
|
||||
num_reserved_bytes = num_reserved_bytes
|
||||
.saturating_add(payload.serialized_length())
|
||||
@ -1157,7 +1164,8 @@ where
|
||||
pub fn create_payment_onion<T: secp256k1::Signing>(
|
||||
secp_ctx: &Secp256k1<T>, path: &Path, session_priv: &SecretKey, total_msat: u64,
|
||||
recipient_onion: &RecipientOnionFields, cur_block_height: u32, payment_hash: &PaymentHash,
|
||||
keysend_preimage: &Option<PaymentPreimage>, prng_seed: [u8; 32],
|
||||
keysend_preimage: &Option<PaymentPreimage>, invoice_request: Option<&InvoiceRequest>,
|
||||
prng_seed: [u8; 32],
|
||||
) -> Result<(msgs::OnionPacket, u64, u32), APIError> {
|
||||
let onion_keys = construct_onion_keys(&secp_ctx, &path, &session_priv).map_err(|_| {
|
||||
APIError::InvalidRoute { err: "Pubkey along hop was maliciously selected".to_owned() }
|
||||
@ -1168,6 +1176,7 @@ pub fn create_payment_onion<T: secp256k1::Signing>(
|
||||
recipient_onion,
|
||||
cur_block_height,
|
||||
keysend_preimage,
|
||||
invoice_request,
|
||||
)?;
|
||||
let onion_packet = construct_onion_packet(onion_payloads, onion_keys, prng_seed, payment_hash)
|
||||
.map_err(|_| APIError::InvalidRoute {
|
||||
|
@ -83,6 +83,7 @@ pub(crate) enum PendingOutboundPayment {
|
||||
keysend_preimage: PaymentPreimage,
|
||||
retry_strategy: Retry,
|
||||
route_params: RouteParameters,
|
||||
invoice_request: InvoiceRequest,
|
||||
},
|
||||
Retryable {
|
||||
retry_strategy: Option<Retry>,
|
||||
@ -93,6 +94,7 @@ pub(crate) enum PendingOutboundPayment {
|
||||
payment_secret: Option<PaymentSecret>,
|
||||
payment_metadata: Option<Vec<u8>>,
|
||||
keysend_preimage: Option<PaymentPreimage>,
|
||||
invoice_request: Option<InvoiceRequest>,
|
||||
custom_tlvs: Vec<(u64, Vec<u8>)>,
|
||||
pending_amt_msat: u64,
|
||||
/// Used to track the fee paid. Present iff the payment was serialized on 0.0.103+.
|
||||
@ -641,6 +643,7 @@ impl RecipientOnionFields {
|
||||
for (typ, _) in custom_tlvs.iter() {
|
||||
if *typ < 1 << 16 { return Err(()); }
|
||||
if *typ == 5482373484 { return Err(()); } // keysend
|
||||
if *typ == 77_777 { return Err(()); } // invoice requests for async payments
|
||||
match prev_type {
|
||||
Some(prev) if prev >= *typ => return Err(()),
|
||||
_ => {},
|
||||
@ -715,6 +718,7 @@ pub(super) struct SendAlongPathArgs<'a> {
|
||||
pub cur_height: u32,
|
||||
pub payment_id: PaymentId,
|
||||
pub keysend_preimage: &'a Option<PaymentPreimage>,
|
||||
pub invoice_request: Option<&'a InvoiceRequest>,
|
||||
pub session_priv_bytes: [u8; 32],
|
||||
}
|
||||
|
||||
@ -768,7 +772,7 @@ impl OutboundPayments {
|
||||
F: Fn(SendAlongPathArgs) -> Result<(), APIError>
|
||||
{
|
||||
let onion_session_privs = self.add_new_pending_payment(payment_hash, recipient_onion.clone(), payment_id, None, route, None, None, entropy_source, best_block_height)?;
|
||||
self.pay_route_internal(route, payment_hash, &recipient_onion, None, payment_id, None,
|
||||
self.pay_route_internal(route, payment_hash, &recipient_onion, None, None, payment_id, None,
|
||||
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 })
|
||||
}
|
||||
@ -813,7 +817,7 @@ impl OutboundPayments {
|
||||
let onion_session_privs = self.add_new_pending_payment(payment_hash, recipient_onion.clone(),
|
||||
payment_id, Some(preimage), &route, None, None, entropy_source, best_block_height)?;
|
||||
|
||||
match self.pay_route_internal(route, payment_hash, &recipient_onion, Some(preimage),
|
||||
match self.pay_route_internal(route, payment_hash, &recipient_onion, Some(preimage), None,
|
||||
payment_id, None, onion_session_privs, node_signer, best_block_height, &send_payment_along_path
|
||||
) {
|
||||
Ok(()) => Ok(payment_hash),
|
||||
@ -878,7 +882,7 @@ impl OutboundPayments {
|
||||
route_params.max_total_routing_fee_msat = Some(max_fee_msat);
|
||||
}
|
||||
self.send_payment_for_bolt12_invoice_internal(
|
||||
payment_id, payment_hash, None, route_params, retry_strategy, router, first_hops,
|
||||
payment_id, payment_hash, None, None, route_params, retry_strategy, router, first_hops,
|
||||
inflight_htlcs, entropy_source, node_signer, node_id_lookup, secp_ctx, best_block_height,
|
||||
logger, pending_events, send_payment_along_path
|
||||
)
|
||||
@ -888,10 +892,10 @@ impl OutboundPayments {
|
||||
R: Deref, ES: Deref, NS: Deref, NL: Deref, IH, SP, L: Deref
|
||||
>(
|
||||
&self, payment_id: PaymentId, payment_hash: PaymentHash,
|
||||
keysend_preimage: Option<PaymentPreimage>, mut route_params: RouteParameters,
|
||||
retry_strategy: Retry, router: &R, first_hops: Vec<ChannelDetails>, inflight_htlcs: IH,
|
||||
entropy_source: &ES, node_signer: &NS, node_id_lookup: &NL,
|
||||
secp_ctx: &Secp256k1<secp256k1::All>, best_block_height: u32, logger: &L,
|
||||
keysend_preimage: Option<PaymentPreimage>, invoice_request: Option<&InvoiceRequest>,
|
||||
mut route_params: RouteParameters, retry_strategy: Retry, router: &R,
|
||||
first_hops: Vec<ChannelDetails>, inflight_htlcs: IH, entropy_source: &ES, node_signer: &NS,
|
||||
node_id_lookup: &NL, secp_ctx: &Secp256k1<secp256k1::All>, best_block_height: u32, logger: &L,
|
||||
pending_events: &Mutex<VecDeque<(events::Event, Option<EventCompletionAction>)>>,
|
||||
send_payment_along_path: SP,
|
||||
) -> Result<(), Bolt12PaymentError>
|
||||
@ -928,8 +932,9 @@ impl OutboundPayments {
|
||||
custom_tlvs: vec![],
|
||||
};
|
||||
let route = match self.find_initial_route(
|
||||
payment_id, payment_hash, &recipient_onion, None, &mut route_params, router,
|
||||
&first_hops, &inflight_htlcs, node_signer, best_block_height, logger,
|
||||
payment_id, payment_hash, &recipient_onion, keysend_preimage, invoice_request,
|
||||
&mut route_params, router, &first_hops, &inflight_htlcs, node_signer, best_block_height,
|
||||
logger,
|
||||
) {
|
||||
Ok(route) => route,
|
||||
Err(e) => {
|
||||
@ -945,25 +950,38 @@ impl OutboundPayments {
|
||||
};
|
||||
|
||||
let payment_params = Some(route_params.payment_params.clone());
|
||||
let (retryable_payment, onion_session_privs) = self.create_pending_payment(
|
||||
payment_hash, recipient_onion.clone(), keysend_preimage, &route, Some(retry_strategy),
|
||||
payment_params, entropy_source, best_block_height
|
||||
);
|
||||
match self.pending_outbound_payments.lock().unwrap().entry(payment_id) {
|
||||
let mut outbounds = self.pending_outbound_payments.lock().unwrap();
|
||||
let onion_session_privs = match outbounds.entry(payment_id) {
|
||||
hash_map::Entry::Occupied(entry) => match entry.get() {
|
||||
PendingOutboundPayment::InvoiceReceived { .. }
|
||||
| PendingOutboundPayment::StaticInvoiceReceived { .. } => {
|
||||
PendingOutboundPayment::InvoiceReceived { .. } => {
|
||||
let (retryable_payment, onion_session_privs) = Self::create_pending_payment(
|
||||
payment_hash, recipient_onion.clone(), keysend_preimage, None, &route,
|
||||
Some(retry_strategy), payment_params, entropy_source, best_block_height
|
||||
);
|
||||
*entry.into_mut() = retryable_payment;
|
||||
onion_session_privs
|
||||
},
|
||||
PendingOutboundPayment::StaticInvoiceReceived { .. } => {
|
||||
let invreq = if let PendingOutboundPayment::StaticInvoiceReceived { invoice_request, .. } = entry.remove() {
|
||||
invoice_request
|
||||
} else { unreachable!() };
|
||||
let (retryable_payment, onion_session_privs) = Self::create_pending_payment(
|
||||
payment_hash, recipient_onion.clone(), keysend_preimage, Some(invreq), &route,
|
||||
Some(retry_strategy), payment_params, entropy_source, best_block_height
|
||||
);
|
||||
outbounds.insert(payment_id, retryable_payment);
|
||||
onion_session_privs
|
||||
},
|
||||
_ => return Err(Bolt12PaymentError::DuplicateInvoice),
|
||||
},
|
||||
hash_map::Entry::Vacant(_) => return Err(Bolt12PaymentError::UnexpectedInvoice),
|
||||
}
|
||||
};
|
||||
core::mem::drop(outbounds);
|
||||
|
||||
let result = self.pay_route_internal(
|
||||
&route, payment_hash, &recipient_onion, keysend_preimage, payment_id,
|
||||
Some(route_params.final_value_msat), onion_session_privs, node_signer,
|
||||
best_block_height, &send_payment_along_path
|
||||
&route, payment_hash, &recipient_onion, keysend_preimage, invoice_request, payment_id,
|
||||
Some(route_params.final_value_msat), onion_session_privs, node_signer, best_block_height,
|
||||
&send_payment_along_path
|
||||
);
|
||||
log_info!(
|
||||
logger, "Sending payment with id {} and hash {} returned {:?}", payment_id,
|
||||
@ -1002,7 +1020,7 @@ impl OutboundPayments {
|
||||
}
|
||||
|
||||
match self.pending_outbound_payments.lock().unwrap().entry(payment_id) {
|
||||
hash_map::Entry::Occupied(mut entry) => match entry.get() {
|
||||
hash_map::Entry::Occupied(mut entry) => match entry.get_mut() {
|
||||
PendingOutboundPayment::AwaitingInvoice {
|
||||
retry_strategy, retryable_invoice_request, max_total_routing_fee_msat, ..
|
||||
} => {
|
||||
@ -1035,7 +1053,7 @@ impl OutboundPayments {
|
||||
|
||||
if let Err(()) = onion_utils::set_max_path_length(
|
||||
&mut route_params, &RecipientOnionFields::spontaneous_empty(), Some(keysend_preimage),
|
||||
best_block_height
|
||||
Some(invreq), best_block_height
|
||||
) {
|
||||
abandon_with_entry!(entry, PaymentFailureReason::RouteNotFound);
|
||||
return Err(Bolt12PaymentError::SendingFailed(RetryableSendFailure::OnionPacketSizeExceeded))
|
||||
@ -1046,6 +1064,11 @@ impl OutboundPayments {
|
||||
keysend_preimage,
|
||||
retry_strategy: *retry_strategy,
|
||||
route_params,
|
||||
invoice_request:
|
||||
retryable_invoice_request
|
||||
.take()
|
||||
.ok_or(Bolt12PaymentError::UnexpectedInvoice)?
|
||||
.invoice_request,
|
||||
};
|
||||
return Ok(())
|
||||
},
|
||||
@ -1074,13 +1097,14 @@ impl OutboundPayments {
|
||||
IH: Fn() -> InFlightHtlcs,
|
||||
SP: Fn(SendAlongPathArgs) -> Result<(), APIError>,
|
||||
{
|
||||
let (payment_hash, keysend_preimage, route_params, retry_strategy) =
|
||||
let (payment_hash, keysend_preimage, route_params, retry_strategy, invoice_request) =
|
||||
match self.pending_outbound_payments.lock().unwrap().entry(payment_id) {
|
||||
hash_map::Entry::Occupied(entry) => match entry.get() {
|
||||
PendingOutboundPayment::StaticInvoiceReceived {
|
||||
payment_hash, route_params, retry_strategy, keysend_preimage, ..
|
||||
payment_hash, route_params, retry_strategy, keysend_preimage, invoice_request, ..
|
||||
} => {
|
||||
(*payment_hash, *keysend_preimage, route_params.clone(), *retry_strategy)
|
||||
(*payment_hash, *keysend_preimage, route_params.clone(), *retry_strategy,
|
||||
invoice_request.clone())
|
||||
},
|
||||
_ => return Err(Bolt12PaymentError::DuplicateInvoice),
|
||||
},
|
||||
@ -1088,9 +1112,9 @@ impl OutboundPayments {
|
||||
};
|
||||
|
||||
self.send_payment_for_bolt12_invoice_internal(
|
||||
payment_id, payment_hash, Some(keysend_preimage), route_params, retry_strategy, router,
|
||||
first_hops, inflight_htlcs, entropy_source, node_signer, node_id_lookup, secp_ctx,
|
||||
best_block_height, logger, pending_events, send_payment_along_path
|
||||
payment_id, payment_hash, Some(keysend_preimage), Some(&invoice_request), route_params,
|
||||
retry_strategy, router, first_hops, inflight_htlcs, entropy_source, node_signer,
|
||||
node_id_lookup, secp_ctx, best_block_height, logger, pending_events, send_payment_along_path
|
||||
)
|
||||
}
|
||||
|
||||
@ -1159,8 +1183,8 @@ impl OutboundPayments {
|
||||
}
|
||||
|
||||
fn find_initial_route<R: Deref, NS: Deref, IH, L: Deref>(
|
||||
&self, payment_id: PaymentId, payment_hash: PaymentHash,
|
||||
recipient_onion: &RecipientOnionFields, keysend_preimage: Option<PaymentPreimage>,
|
||||
&self, payment_id: PaymentId, payment_hash: PaymentHash, recipient_onion: &RecipientOnionFields,
|
||||
keysend_preimage: Option<PaymentPreimage>, invoice_request: Option<&InvoiceRequest>,
|
||||
route_params: &mut RouteParameters, router: &R, first_hops: &Vec<ChannelDetails>,
|
||||
inflight_htlcs: &IH, node_signer: &NS, best_block_height: u32, logger: &L,
|
||||
) -> Result<Route, RetryableSendFailure>
|
||||
@ -1179,7 +1203,7 @@ impl OutboundPayments {
|
||||
}
|
||||
|
||||
onion_utils::set_max_path_length(
|
||||
route_params, recipient_onion, keysend_preimage, best_block_height
|
||||
route_params, recipient_onion, keysend_preimage, invoice_request, best_block_height
|
||||
)
|
||||
.map_err(|()| {
|
||||
log_error!(logger, "Can't construct an onion packet without exceeding 1300-byte onion \
|
||||
@ -1227,7 +1251,7 @@ impl OutboundPayments {
|
||||
SP: Fn(SendAlongPathArgs) -> Result<(), APIError>,
|
||||
{
|
||||
let route = self.find_initial_route(
|
||||
payment_id, payment_hash, &recipient_onion, keysend_preimage, &mut route_params, router,
|
||||
payment_id, payment_hash, &recipient_onion, keysend_preimage, None, &mut route_params, router,
|
||||
&first_hops, &inflight_htlcs, node_signer, best_block_height, logger,
|
||||
)?;
|
||||
|
||||
@ -1241,7 +1265,7 @@ impl OutboundPayments {
|
||||
})?;
|
||||
|
||||
let res = self.pay_route_internal(&route, payment_hash, &recipient_onion,
|
||||
keysend_preimage, payment_id, None, onion_session_privs, node_signer,
|
||||
keysend_preimage, 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);
|
||||
@ -1315,14 +1339,14 @@ impl OutboundPayments {
|
||||
}
|
||||
}
|
||||
}
|
||||
let (total_msat, recipient_onion, keysend_preimage, onion_session_privs) = {
|
||||
let (total_msat, recipient_onion, keysend_preimage, onion_session_privs, invoice_request) = {
|
||||
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, ..
|
||||
custom_tlvs, pending_amt_msat, invoice_request, ..
|
||||
} => {
|
||||
const RETRY_OVERFLOW_PERCENTAGE: u64 = 10;
|
||||
let retry_amt_msat = route.get_total_amount();
|
||||
@ -1345,6 +1369,7 @@ impl OutboundPayments {
|
||||
custom_tlvs: custom_tlvs.clone(),
|
||||
};
|
||||
let keysend_preimage = *keysend_preimage;
|
||||
let invoice_request = invoice_request.clone();
|
||||
|
||||
let mut onion_session_privs = Vec::with_capacity(route.paths.len());
|
||||
for _ in 0..route.paths.len() {
|
||||
@ -1357,7 +1382,7 @@ impl OutboundPayments {
|
||||
|
||||
payment.get_mut().increment_attempts();
|
||||
|
||||
(total_msat, recipient_onion, keysend_preimage, onion_session_privs)
|
||||
(total_msat, recipient_onion, keysend_preimage, onion_session_privs, invoice_request)
|
||||
},
|
||||
PendingOutboundPayment::Legacy { .. } => {
|
||||
log_error!(logger, "Unable to retry payments that were initially sent on LDK versions prior to 0.0.102");
|
||||
@ -1395,8 +1420,8 @@ impl OutboundPayments {
|
||||
}
|
||||
};
|
||||
let res = self.pay_route_internal(&route, payment_hash, &recipient_onion, keysend_preimage,
|
||||
payment_id, Some(total_msat), onion_session_privs, node_signer, best_block_height,
|
||||
&send_payment_along_path);
|
||||
invoice_request.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 {
|
||||
self.handle_pay_route_err(e, payment_id, payment_hash, route, route_params, router, first_hops, inflight_htlcs, entropy_source, node_signer, best_block_height, logger, pending_events, send_payment_along_path);
|
||||
@ -1507,7 +1532,8 @@ impl OutboundPayments {
|
||||
|
||||
let recipient_onion_fields = RecipientOnionFields::spontaneous_empty();
|
||||
match self.pay_route_internal(&route, payment_hash, &recipient_onion_fields,
|
||||
None, payment_id, None, onion_session_privs, node_signer, best_block_height, &send_payment_along_path
|
||||
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) => {
|
||||
@ -1546,8 +1572,8 @@ impl OutboundPayments {
|
||||
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, route, retry_strategy,
|
||||
let (payment, onion_session_privs) = Self::create_pending_payment(
|
||||
payment_hash, recipient_onion, keysend_preimage, None, route, retry_strategy,
|
||||
payment_params, entropy_source, best_block_height
|
||||
);
|
||||
entry.insert(payment);
|
||||
@ -1557,9 +1583,10 @@ impl OutboundPayments {
|
||||
}
|
||||
|
||||
fn create_pending_payment<ES: Deref>(
|
||||
&self, payment_hash: PaymentHash, recipient_onion: RecipientOnionFields,
|
||||
keysend_preimage: Option<PaymentPreimage>, route: &Route, retry_strategy: Option<Retry>,
|
||||
payment_params: Option<PaymentParameters>, entropy_source: &ES, best_block_height: u32
|
||||
payment_hash: PaymentHash, recipient_onion: RecipientOnionFields,
|
||||
keysend_preimage: Option<PaymentPreimage>, invoice_request: Option<InvoiceRequest>,
|
||||
route: &Route, retry_strategy: Option<Retry>, payment_params: Option<PaymentParameters>,
|
||||
entropy_source: &ES, best_block_height: u32
|
||||
) -> (PendingOutboundPayment, Vec<[u8; 32]>)
|
||||
where
|
||||
ES::Target: EntropySource,
|
||||
@ -1580,6 +1607,7 @@ impl OutboundPayments {
|
||||
payment_secret: recipient_onion.payment_secret,
|
||||
payment_metadata: recipient_onion.payment_metadata,
|
||||
keysend_preimage,
|
||||
invoice_request,
|
||||
custom_tlvs: recipient_onion.custom_tlvs,
|
||||
starting_block_height: best_block_height,
|
||||
total_msat: route.get_total_amount(),
|
||||
@ -1619,9 +1647,9 @@ impl OutboundPayments {
|
||||
|
||||
fn pay_route_internal<NS: Deref, F>(
|
||||
&self, route: &Route, payment_hash: PaymentHash, recipient_onion: &RecipientOnionFields,
|
||||
keysend_preimage: Option<PaymentPreimage>, payment_id: PaymentId, recv_value_msat: Option<u64>,
|
||||
onion_session_privs: Vec<[u8; 32]>, node_signer: &NS, best_block_height: u32,
|
||||
send_payment_along_path: &F
|
||||
keysend_preimage: Option<PaymentPreimage>, invoice_request: Option<&InvoiceRequest>,
|
||||
payment_id: PaymentId, recv_value_msat: Option<u64>, onion_session_privs: Vec<[u8; 32]>,
|
||||
node_signer: &NS, best_block_height: u32, send_payment_along_path: &F
|
||||
) -> Result<(), PaymentSendFailure>
|
||||
where
|
||||
NS::Target: NodeSigner,
|
||||
@ -1674,7 +1702,7 @@ impl OutboundPayments {
|
||||
for (path, session_priv_bytes) in route.paths.iter().zip(onion_session_privs.into_iter()) {
|
||||
let mut 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,
|
||||
cur_height, payment_id, keysend_preimage: &keysend_preimage, invoice_request,
|
||||
session_priv_bytes
|
||||
});
|
||||
match path_res {
|
||||
@ -1759,7 +1787,7 @@ impl OutboundPayments {
|
||||
F: Fn(SendAlongPathArgs) -> Result<(), APIError>,
|
||||
{
|
||||
self.pay_route_internal(route, payment_hash, &recipient_onion,
|
||||
keysend_preimage, payment_id, recv_value_msat, onion_session_privs,
|
||||
keysend_preimage, 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 })
|
||||
}
|
||||
@ -2141,6 +2169,7 @@ impl OutboundPayments {
|
||||
payment_secret: None, // only used for retries, and we'll never retry on startup
|
||||
payment_metadata: None, // only used for retries, and we'll never retry on startup
|
||||
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
|
||||
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),
|
||||
@ -2227,6 +2256,7 @@ impl_writeable_tlv_based_enum_upgradable!(PendingOutboundPayment,
|
||||
(9, custom_tlvs, optional_vec),
|
||||
(10, starting_block_height, required),
|
||||
(11, remaining_max_total_routing_fee_msat, option),
|
||||
(13, invoice_request, option),
|
||||
(not_written, retry_strategy, (static_value, None)),
|
||||
(not_written, attempts, (static_value, PaymentAttempts::new())),
|
||||
},
|
||||
@ -2246,13 +2276,14 @@ impl_writeable_tlv_based_enum_upgradable!(PendingOutboundPayment,
|
||||
(2, retry_strategy, required),
|
||||
(4, max_total_routing_fee_msat, option),
|
||||
},
|
||||
// Added in 0.0.125. Prior versions will drop these outbounds on downgrade, which is safe because
|
||||
// no HTLCs are in-flight.
|
||||
// Added in 0.1. Prior versions will drop these outbounds on downgrade, which is safe because no
|
||||
// HTLCs are in-flight.
|
||||
(9, StaticInvoiceReceived) => {
|
||||
(0, payment_hash, required),
|
||||
(2, keysend_preimage, required),
|
||||
(4, retry_strategy, required),
|
||||
(6, route_params, required),
|
||||
(8, invoice_request, required),
|
||||
},
|
||||
);
|
||||
|
||||
@ -2267,15 +2298,19 @@ mod tests {
|
||||
use crate::events::{Event, PathFailure, PaymentFailureReason};
|
||||
use crate::types::payment::{PaymentHash, PaymentPreimage};
|
||||
use crate::ln::channelmanager::{PaymentId, RecipientOnionFields};
|
||||
use crate::ln::inbound_payment::ExpandedKey;
|
||||
use crate::types::features::{Bolt12InvoiceFeatures, ChannelFeatures, NodeFeatures};
|
||||
use crate::ln::msgs::{ErrorAction, LightningError};
|
||||
use crate::ln::outbound_payment::{Bolt12PaymentError, OutboundPayments, PendingOutboundPayment, Retry, RetryableSendFailure, StaleExpiration};
|
||||
#[cfg(feature = "std")]
|
||||
use crate::offers::invoice::DEFAULT_RELATIVE_EXPIRY;
|
||||
use crate::offers::invoice_request::InvoiceRequest;
|
||||
use crate::offers::nonce::Nonce;
|
||||
use crate::offers::offer::OfferBuilder;
|
||||
use crate::offers::test_utils::*;
|
||||
use crate::routing::gossip::NetworkGraph;
|
||||
use crate::routing::router::{InFlightHtlcs, Path, PaymentParameters, Route, RouteHop, RouteParameters};
|
||||
use crate::sign::KeyMaterial;
|
||||
use crate::sync::{Arc, Mutex, RwLock};
|
||||
use crate::util::errors::APIError;
|
||||
use crate::util::hash_tables::new_hash_map;
|
||||
@ -2820,6 +2855,22 @@ mod tests {
|
||||
assert!(pending_events.lock().unwrap().is_empty());
|
||||
}
|
||||
|
||||
fn dummy_invoice_request() -> InvoiceRequest {
|
||||
let expanded_key = ExpandedKey::new(&KeyMaterial([42; 32]));
|
||||
let entropy = FixedEntropy {};
|
||||
let nonce = Nonce::from_entropy_source(&entropy);
|
||||
let secp_ctx = Secp256k1::new();
|
||||
let payment_id = PaymentId([1; 32]);
|
||||
|
||||
OfferBuilder::new(recipient_pubkey())
|
||||
.amount_msats(1000)
|
||||
.build().unwrap()
|
||||
.request_invoice_deriving_signing_pubkey(&expanded_key, nonce, &secp_ctx, payment_id)
|
||||
.unwrap()
|
||||
.build_and_sign()
|
||||
.unwrap()
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn time_out_unreleased_async_payments() {
|
||||
let pending_events = Mutex::new(VecDeque::new());
|
||||
@ -2841,6 +2892,7 @@ mod tests {
|
||||
keysend_preimage: PaymentPreimage([0; 32]),
|
||||
retry_strategy: Retry::Attempts(0),
|
||||
route_params,
|
||||
invoice_request: dummy_invoice_request(),
|
||||
};
|
||||
outbounds.insert(payment_id, outbound);
|
||||
core::mem::drop(outbounds);
|
||||
@ -2887,6 +2939,7 @@ mod tests {
|
||||
keysend_preimage: PaymentPreimage([0; 32]),
|
||||
retry_strategy: Retry::Attempts(0),
|
||||
route_params,
|
||||
invoice_request: dummy_invoice_request(),
|
||||
};
|
||||
outbounds.insert(payment_id, outbound);
|
||||
core::mem::drop(outbounds);
|
||||
|
@ -4271,7 +4271,7 @@ fn peel_payment_onion_custom_tlvs() {
|
||||
|
||||
let (onion_routing_packet, first_hop_msat, cltv_expiry) = onion_utils::create_payment_onion(
|
||||
&secp_ctx, &route.paths[0], &session_priv, amt_msat, &recipient_onion,
|
||||
nodes[0].best_block_info().1, &payment_hash, &Some(keysend_preimage), prng_seed
|
||||
nodes[0].best_block_info().1, &payment_hash, &Some(keysend_preimage), None, prng_seed
|
||||
).unwrap();
|
||||
|
||||
let update_add = msgs::UpdateAddHTLC {
|
||||
|
@ -148,12 +148,12 @@ impl OffersMessageHandler for IgnoringMessageHandler {
|
||||
}
|
||||
}
|
||||
impl AsyncPaymentsMessageHandler for IgnoringMessageHandler {
|
||||
fn held_htlc_available(
|
||||
fn handle_held_htlc_available(
|
||||
&self, _message: HeldHtlcAvailable, _responder: Option<Responder>,
|
||||
) -> Option<(ReleaseHeldHtlc, ResponseInstruction)> {
|
||||
None
|
||||
}
|
||||
fn release_held_htlc(&self, _message: ReleaseHeldHtlc, _context: AsyncPaymentsContext) {}
|
||||
fn handle_release_held_htlc(&self, _message: ReleaseHeldHtlc, _context: AsyncPaymentsContext) {}
|
||||
}
|
||||
impl DNSResolverMessageHandler for IgnoringMessageHandler {
|
||||
fn handle_dnssec_query(
|
||||
|
@ -27,13 +27,13 @@ const RELEASE_HELD_HTLC_TLV_TYPE: u64 = 74;
|
||||
pub trait AsyncPaymentsMessageHandler {
|
||||
/// Handle a [`HeldHtlcAvailable`] message. A [`ReleaseHeldHtlc`] should be returned to release
|
||||
/// the held funds.
|
||||
fn held_htlc_available(
|
||||
fn handle_held_htlc_available(
|
||||
&self, message: HeldHtlcAvailable, responder: Option<Responder>,
|
||||
) -> Option<(ReleaseHeldHtlc, ResponseInstruction)>;
|
||||
|
||||
/// Handle a [`ReleaseHeldHtlc`] message. If authentication of the message succeeds, an HTLC
|
||||
/// should be released to the corresponding payee.
|
||||
fn release_held_htlc(&self, message: ReleaseHeldHtlc, context: AsyncPaymentsContext);
|
||||
fn handle_release_held_htlc(&self, message: ReleaseHeldHtlc, context: AsyncPaymentsContext);
|
||||
|
||||
/// Release any [`AsyncPaymentsMessage`]s that need to be sent.
|
||||
///
|
||||
|
@ -84,12 +84,12 @@ impl OffersMessageHandler for TestOffersMessageHandler {
|
||||
struct TestAsyncPaymentsMessageHandler {}
|
||||
|
||||
impl AsyncPaymentsMessageHandler for TestAsyncPaymentsMessageHandler {
|
||||
fn held_htlc_available(
|
||||
fn handle_held_htlc_available(
|
||||
&self, _message: HeldHtlcAvailable, _responder: Option<Responder>,
|
||||
) -> Option<(ReleaseHeldHtlc, ResponseInstruction)> {
|
||||
None
|
||||
}
|
||||
fn release_held_htlc(&self, _message: ReleaseHeldHtlc, _context: AsyncPaymentsContext) {}
|
||||
fn handle_release_held_htlc(&self, _message: ReleaseHeldHtlc, _context: AsyncPaymentsContext) {}
|
||||
}
|
||||
|
||||
struct TestDNSResolverMessageHandler {}
|
||||
|
@ -1623,7 +1623,7 @@ where
|
||||
},
|
||||
#[cfg(async_payments)]
|
||||
ParsedOnionMessageContents::AsyncPayments(AsyncPaymentsMessage::HeldHtlcAvailable(msg)) => {
|
||||
let response_instructions = self.async_payments_handler.held_htlc_available(
|
||||
let response_instructions = self.async_payments_handler.handle_held_htlc_available(
|
||||
msg, responder
|
||||
);
|
||||
if let Some((msg, instructions)) = response_instructions {
|
||||
@ -1640,7 +1640,7 @@ where
|
||||
},
|
||||
None => return,
|
||||
};
|
||||
self.async_payments_handler.release_held_htlc(msg, context);
|
||||
self.async_payments_handler.handle_release_held_htlc(msg, context);
|
||||
},
|
||||
ParsedOnionMessageContents::DNSResolver(DNSResolverMessage::DNSSECQuery(msg)) => {
|
||||
let response_instructions = self.dns_resolver_handler.handle_dnssec_query(msg, responder);
|
||||
|
@ -613,8 +613,9 @@ impl RouteParameters {
|
||||
&mut self, recipient_onion: &RecipientOnionFields, is_keysend: bool, best_block_height: u32
|
||||
) -> Result<(), ()> {
|
||||
let keysend_preimage_opt = is_keysend.then(|| PaymentPreimage([42; 32]));
|
||||
// TODO: no way to account for the invoice request here yet
|
||||
onion_utils::set_max_path_length(
|
||||
self, recipient_onion, keysend_preimage_opt, best_block_height
|
||||
self, recipient_onion, keysend_preimage_opt, None, best_block_height
|
||||
)
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user