use crate::events::{self, PaymentFailureReason};
use crate::ln::{PaymentHash, PaymentPreimage, PaymentSecret};
use crate::ln::channelmanager::{ChannelDetails, EventCompletionAction, HTLCSource, IDEMPOTENCY_TIMEOUT_TICKS, PaymentId};
-use crate::ln::onion_utils::HTLCFailReason;
+use crate::ln::onion_utils::{DecodedOnionFailure, HTLCFailReason};
use crate::routing::router::{InFlightHtlcs, Path, PaymentParameters, Route, RouteParameters, Router};
use crate::util::errors::APIError;
use crate::util::logger::Logger;
payment_secret: Option<PaymentSecret>,
payment_metadata: Option<Vec<u8>>,
keysend_preimage: Option<PaymentPreimage>,
+ custom_tlvs: Vec<(u64, Vec<u8>)>,
pending_amt_msat: u64,
/// Used to track the fee paid. Only present if the payment was serialized on 0.0.103+.
pending_fee_msat: Option<u64>,
pub(super) fn check_merge(&mut self, further_htlc_fields: &mut Self) -> Result<(), ()> {
if self.payment_secret != further_htlc_fields.payment_secret { return Err(()); }
if self.payment_metadata != further_htlc_fields.payment_metadata { return Err(()); }
- // For custom TLVs we should just drop non-matching ones, but not reject the payment.
+
+ let tlvs = &mut self.custom_tlvs;
+ let further_tlvs = &mut further_htlc_fields.custom_tlvs;
+
+ let even_tlvs = tlvs.iter().filter(|(typ, _)| *typ % 2 == 0);
+ let further_even_tlvs = further_tlvs.iter().filter(|(typ, _)| *typ % 2 == 0);
+ if even_tlvs.ne(further_even_tlvs) { return Err(()) }
+
+ tlvs.retain(|tlv| further_tlvs.iter().any(|further_tlv| tlv == further_tlv));
+ further_tlvs.retain(|further_tlv| tlvs.iter().any(|tlv| tlv == further_tlv));
+
Ok(())
}
}
{
#[cfg(feature = "std")] {
if has_expired(&route_params) {
+ log_error!(logger, "Payment with id {} and hash {} had expired before we started paying",
+ payment_id, payment_hash);
return Err(RetryableSendFailure::PaymentExpired)
}
}
&node_signer.get_node_id(Recipient::Node).unwrap(), &route_params,
Some(&first_hops.iter().collect::<Vec<_>>()), inflight_htlcs(),
payment_hash, payment_id,
- ).map_err(|_| RetryableSendFailure::RouteNotFound)?;
+ ).map_err(|_| {
+ log_error!(logger, "Failed to find route for payment with id {} and hash {}",
+ payment_id, payment_hash);
+ RetryableSendFailure::RouteNotFound
+ })?;
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)
- .map_err(|_| RetryableSendFailure::DuplicatePayment)?;
+ .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, keysend_preimage, payment_id, None,
onion_session_privs, node_signer, best_block_height, &send_payment_along_path);
- log_info!(logger, "Result sending payment with id {}: {:?}", log_bytes!(payment_id.0), res);
+ 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, router, first_hops, &inflight_htlcs, entropy_source, node_signer, best_block_height, logger, pending_events, &send_payment_along_path);
}
{
#[cfg(feature = "std")] {
if has_expired(&route_params) {
- log_error!(logger, "Payment params expired on retry, abandoning payment {}", log_bytes!(payment_id.0));
+ log_error!(logger, "Payment params expired on retry, abandoning payment {}", &payment_id);
self.abandon_payment(payment_id, PaymentFailureReason::PaymentExpired, pending_events);
return
}
) {
Ok(route) => route,
Err(e) => {
- log_error!(logger, "Failed to find a route on retry, abandoning payment {}: {:#?}", log_bytes!(payment_id.0), e);
+ log_error!(logger, "Failed to find a route on retry, abandoning payment {}: {:#?}", &payment_id, e);
self.abandon_payment(payment_id, PaymentFailureReason::RouteNotFound, pending_events);
return
}
hash_map::Entry::Occupied(mut payment) => {
let res = match payment.get() {
PendingOutboundPayment::Retryable {
- total_msat, keysend_preimage, payment_secret, payment_metadata, pending_amt_msat, ..
+ total_msat, keysend_preimage, payment_secret, payment_metadata,
+ custom_tlvs, pending_amt_msat, ..
} => {
let retry_amt_msat = route.get_total_amount();
if retry_amt_msat + *pending_amt_msat > *total_msat * (100 + RETRY_OVERFLOW_PERCENTAGE) / 100 {
(*total_msat, RecipientOnionFields {
payment_secret: *payment_secret,
payment_metadata: payment_metadata.clone(),
- custom_tlvs: Vec::new(),
+ custom_tlvs: custom_tlvs.clone(),
}, *keysend_preimage)
},
PendingOutboundPayment::Legacy { .. } => {
},
};
if !payment.get().is_retryable_now() {
- log_error!(logger, "Retries exhausted for payment id {}", log_bytes!(payment_id.0));
+ log_error!(logger, "Retries exhausted for payment id {}", &payment_id);
abandon_with_entry!(payment, PaymentFailureReason::RetriesExhausted);
return
}
res
},
hash_map::Entry::Vacant(_) => {
- log_error!(logger, "Payment with ID {} not found", log_bytes!(payment_id.0));
+ log_error!(logger, "Payment with ID {} not found", &payment_id);
return
}
}
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);
- log_info!(logger, "Result retrying payment id {}: {:?}", log_bytes!(payment_id.0), res);
+ 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);
}
payment_secret: recipient_onion.payment_secret,
payment_metadata: recipient_onion.payment_metadata,
keysend_preimage,
+ custom_tlvs: recipient_onion.custom_tlvs,
starting_block_height: best_block_height,
total_msat: route.get_total_amount(),
});
pub(super) fn claim_htlc<L: Deref>(
&self, payment_id: PaymentId, payment_preimage: PaymentPreimage, session_priv: SecretKey,
- path: Path, from_onchain: bool,
+ path: Path, from_onchain: bool, ev_completion_action: EventCompletionAction,
pending_events: &Mutex<VecDeque<(events::Event, Option<EventCompletionAction>)>>,
logger: &L,
) where L::Target: Logger {
if let hash_map::Entry::Occupied(mut payment) = outbounds.entry(payment_id) {
if !payment.get().is_fulfilled() {
let payment_hash = PaymentHash(Sha256::hash(&payment_preimage.0).into_inner());
+ log_info!(logger, "Payment with id {} and hash {} sent!", payment_id, payment_hash);
let fee_paid_msat = payment.get().get_pending_fee_msat();
pending_events.push_back((events::Event::PaymentSent {
payment_id: Some(payment_id),
payment_preimage,
payment_hash,
fee_paid_msat,
- }, None));
+ }, Some(ev_completion_action.clone())));
payment.get_mut().mark_fulfilled();
}
payment_id,
payment_hash,
path,
- }, None));
+ }, Some(ev_completion_action)));
}
}
} else {
- log_trace!(logger, "Received duplicative fulfill for HTLC with payment_preimage {}", log_bytes!(payment_preimage.0));
+ log_trace!(logger, "Received duplicative fulfill for HTLC with payment_preimage {}", &payment_preimage);
}
}
pending_events: &Mutex<VecDeque<(events::Event, Option<EventCompletionAction>)>>, logger: &L,
) -> bool where L::Target: Logger {
#[cfg(test)]
- let (network_update, short_channel_id, payment_retryable, onion_error_code, onion_error_data) = onion_error.decode_onion_failure(secp_ctx, logger, &source);
+ let DecodedOnionFailure {
+ network_update, short_channel_id, payment_retryable, onion_error_code, onion_error_data
+ } = onion_error.decode_onion_failure(secp_ctx, logger, &source);
#[cfg(not(test))]
- let (network_update, short_channel_id, payment_retryable, _, _) = onion_error.decode_onion_failure(secp_ctx, logger, &source);
+ let DecodedOnionFailure { network_update, short_channel_id, payment_retryable } =
+ onion_error.decode_onion_failure(secp_ctx, logger, &source);
let payment_is_probe = payment_is_probe(payment_hash, &payment_id, probing_cookie_secret);
let mut session_priv_bytes = [0; 32];
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 {}", log_bytes!(payment_hash.0));
+ 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", log_bytes!(payment_hash.0));
+ 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();
}
is_retryable_now
} else {
- log_trace!(logger, "Received duplicative fail for HTLC with payment_hash {}", log_bytes!(payment_hash.0));
+ log_trace!(logger, "Received duplicative fail for HTLC with payment_hash {}", &payment_hash);
return false
};
core::mem::drop(outbounds);
- log_trace!(logger, "Failing outbound payment HTLC with payment_hash {}", log_bytes!(payment_hash.0));
+ log_trace!(logger, "Failing outbound payment HTLC with payment_hash {}", &payment_hash);
let path_failure = {
if payment_is_probe {
(6, total_msat, required),
(7, payment_metadata, option),
(8, pending_amt_msat, required),
+ (9, custom_tlvs, optional_vec),
(10, starting_block_height, required),
(not_written, retry_strategy, (static_value, None)),
(not_written, attempts, (static_value, PaymentAttempts::new())),