Skip to content

Commit

Permalink
feat: allow self payment
Browse files Browse the repository at this point in the history
This PR solves issue #2462. If we asked to
pay an invoice that we generated ourselves. We
generate PaymentSent and PaymentClaimable event
and mark the payment as fulfilled in our set
of outbound payments.

This PR is important because we realized users
can easily screw up self payments when they implement
it themselves. See here: https://lists.linuxfoundation.org/pipermail/lightning-dev/2023-June/003983.html
  • Loading branch information
vladimirfomene committed Feb 5, 2024
1 parent 51d9ee3 commit 6de92ed
Show file tree
Hide file tree
Showing 2 changed files with 104 additions and 25 deletions.
49 changes: 43 additions & 6 deletions lightning/src/ln/channelmanager.rs
Original file line number Diff line number Diff line change
Expand Up @@ -674,6 +674,7 @@ struct ClaimablePayment {
purpose: events::PaymentPurpose,
onion_fields: Option<RecipientOnionFields>,
htlcs: Vec<ClaimableHTLC>,
amount_msat: Option<u64>,
}

/// Information about claimable or being-claimed payments
Expand Down Expand Up @@ -3503,11 +3504,39 @@ where
pub fn send_payment(&self, payment_hash: PaymentHash, recipient_onion: RecipientOnionFields, payment_id: PaymentId, route_params: RouteParameters, retry_strategy: Retry) -> Result<(), RetryableSendFailure> {
let best_block_height = self.best_block.read().unwrap().height();
let _persistence_guard = PersistenceNotifierGuard::notify_on_drop(self);
let mut preimage: Option<PaymentPreimage> = None;
let mut payment_secret = PaymentSecret([0; 32]);
let mut is_self_pay = false;
if let Some(secret) = recipient_onion.payment_secret {
payment_secret = secret;
if let Payee::Clear{node_id, .. } = route_params.payment_params.payee {
let is_phantom_payee = match self.node_signer.get_node_id(Recipient::PhantomNode) {
Ok(phantom_node_id) => node_id == phantom_node_id,
Err(_) => false,
};
if node_id == self.get_our_node_id() || is_phantom_payee {
let payment_data = msgs::FinalOnionHopData{ payment_secret, total_msat: route_params.final_value_msat};
preimage = inbound_payment::verify(payment_hash, &payment_data, self.highest_seen_timestamp.load(Ordering::Acquire) as u64, &self.inbound_payment_key, &self.logger).map_err(|_| RetryableSendFailure::RecipientRejected)?.0;
is_self_pay = true;
}
}
}

self.pending_outbound_payments
.send_payment(payment_hash, recipient_onion, payment_id, retry_strategy, route_params,
.send_payment(payment_hash, recipient_onion.clone(), payment_id, retry_strategy, route_params.clone(), preimage,
&self.router, self.list_usable_channels(), || self.compute_inflight_htlcs(),
&self.entropy_source, &self.node_signer, best_block_height, &self.logger,
&self.pending_events, |args| self.send_payment_along_path(args))
&self.pending_events, |args| self.send_payment_along_path(args))?;

if is_self_pay {
let mut claimable_payments = self.claimable_payments.lock().unwrap();
let purpose = events::PaymentPurpose::InvoicePayment { payment_preimage: preimage, payment_secret };
claimable_payments.claimable_payments.insert(payment_hash, ClaimablePayment{ purpose: purpose.clone(), onion_fields: Some(recipient_onion.clone()), htlcs: vec![], amount_msat: Some(route_params.final_value_msat)});
let mut pending_events = self.pending_events.lock().unwrap();
pending_events.push_back((events::Event::PaymentClaimable { receiver_node_id: Some(self.get_our_node_id()), payment_hash, onion_fields: Some(recipient_onion), amount_msat: route_params.final_value_msat, counterparty_skimmed_fee_msat: 0, purpose, via_channel_id: None, via_user_channel_id: None, claim_deadline: None }, None));
}

Ok(())
}

#[cfg(test)]
Expand Down Expand Up @@ -4547,7 +4576,7 @@ where
.or_insert_with(|| {
committed_to_claimable = true;
ClaimablePayment {
purpose: $purpose.clone(), htlcs: Vec::new(), onion_fields: None,
purpose: $purpose.clone(), htlcs: Vec::new(), onion_fields: None, amount_msat: None,
}
});
if $purpose != claimable_payment.purpose {
Expand Down Expand Up @@ -5390,6 +5419,14 @@ where
let mut claimable_payments = self.claimable_payments.lock().unwrap();
if let Some(payment) = claimable_payments.claimable_payments.remove(&payment_hash) {
let mut receiver_node_id = self.our_network_pubkey;
if let events::PaymentPurpose::InvoicePayment { payment_secret, .. } = payment.purpose {
if let Ok(_) = self.get_payment_preimage(payment_hash, payment_secret) {
let mut pending_events_lock = self.pending_events.lock().unwrap();
pending_events_lock.push_back((Event::PaymentClaimed { receiver_node_id: Some(receiver_node_id), payment_hash,
amount_msat: payment.amount_msat.unwrap(), purpose: payment.purpose, htlcs: vec![], sender_intended_total_msat: None }, None));
return;
}
}
for htlc in payment.htlcs.iter() {
if htlc.prev_hop.phantom_shared_secret.is_some() {
let phantom_pubkey = self.node_signer.get_node_id(Recipient::PhantomNode)
Expand Down Expand Up @@ -10865,14 +10902,14 @@ where
purposes.into_iter().zip(onion_fields.into_iter().zip(claimable_htlcs_list.into_iter()))
{
let existing_payment = claimable_payments.insert(payment_hash, ClaimablePayment {
purpose, htlcs, onion_fields: onion,
purpose, htlcs, onion_fields: onion, amount_msat: None,
});
if existing_payment.is_some() { return Err(DecodeError::InvalidValue); }
}
} else {
for (purpose, (payment_hash, htlcs)) in purposes.into_iter().zip(claimable_htlcs_list.into_iter()) {
let existing_payment = claimable_payments.insert(payment_hash, ClaimablePayment {
purpose, htlcs, onion_fields: None,
purpose, htlcs, onion_fields: None, amount_msat: None,
});
if existing_payment.is_some() { return Err(DecodeError::InvalidValue); }
}
Expand Down Expand Up @@ -10906,7 +10943,7 @@ where
events::PaymentPurpose::SpontaneousPayment(*payment_preimage),
};
claimable_payments.insert(payment_hash, ClaimablePayment {
purpose, htlcs, onion_fields: None,
purpose, htlcs, onion_fields: None, amount_msat: None,
});
}
}
Expand Down
80 changes: 61 additions & 19 deletions lightning/src/ln/outbound_payment.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,12 +14,12 @@ use bitcoin::hashes::sha256::Hash as Sha256;
use bitcoin::secp256k1::{self, Secp256k1, SecretKey};

use crate::sign::{EntropySource, NodeSigner, Recipient};
use crate::events::{self, PaymentFailureReason};
use crate::events::{self, PaymentFailureReason, Event};
use crate::ln::{PaymentHash, PaymentPreimage, PaymentSecret};
use crate::ln::channelmanager::{ChannelDetails, EventCompletionAction, HTLCSource, PaymentId};
use crate::ln::onion_utils::{DecodedOnionFailure, HTLCFailReason};
use crate::offers::invoice::Bolt12Invoice;
use crate::routing::router::{BlindedTail, InFlightHtlcs, Path, PaymentParameters, Route, RouteParameters, Router};
use crate::routing::router::{BlindedTail, InFlightHtlcs, Path, PaymentParameters, Route, RouteParameters, Router, Payee};
use crate::util::errors::APIError;
use crate::util::logger::Logger;
use crate::util::time::Time;
Expand Down Expand Up @@ -421,6 +421,8 @@ pub enum RetryableSendFailure {
/// [`Event::PaymentSent`]: crate::events::Event::PaymentSent
/// [`Event::PaymentFailed`]: crate::events::Event::PaymentFailed
DuplicatePayment,
/// The intended recipient rejected our payment.
RecipientRejected,
}

/// If a payment fails to send with [`ChannelManager::send_payment_with_route`], it can be in one
Expand Down Expand Up @@ -682,7 +684,7 @@ impl OutboundPayments {

pub(super) fn send_payment<R: Deref, ES: Deref, NS: Deref, IH, SP, L: Deref>(
&self, payment_hash: PaymentHash, recipient_onion: RecipientOnionFields, payment_id: PaymentId,
retry_strategy: Retry, route_params: RouteParameters, router: &R,
retry_strategy: Retry, route_params: RouteParameters, payment_preimage: Option<PaymentPreimage>, router: &R,
first_hops: Vec<ChannelDetails>, compute_inflight_htlcs: IH, entropy_source: &ES,
node_signer: &NS, best_block_height: u32, logger: &L,
pending_events: &Mutex<VecDeque<(events::Event, Option<EventCompletionAction>)>>, send_payment_along_path: SP,
Expand All @@ -696,7 +698,7 @@ impl OutboundPayments {
SP: Fn(SendAlongPathArgs) -> Result<(), APIError>,
{
self.send_payment_internal(payment_id, payment_hash, recipient_onion, None, retry_strategy,
route_params, router, first_hops, &compute_inflight_htlcs, entropy_source, node_signer,
route_params, payment_preimage, router, first_hops, &compute_inflight_htlcs, entropy_source, node_signer,
best_block_height, logger, pending_events, &send_payment_along_path)
}

Expand Down Expand Up @@ -735,7 +737,7 @@ impl OutboundPayments {
.unwrap_or_else(|| PaymentPreimage(entropy_source.get_secure_random_bytes()));
let payment_hash = PaymentHash(Sha256::hash(&preimage.0).to_byte_array());
self.send_payment_internal(payment_id, payment_hash, recipient_onion, Some(preimage),
retry_strategy, route_params, router, first_hops, inflight_htlcs, entropy_source,
retry_strategy, route_params, Some(preimage), router, first_hops, inflight_htlcs, entropy_source,
node_signer, best_block_height, logger, pending_events, send_payment_along_path)
.map(|()| payment_hash)
}
Expand Down Expand Up @@ -887,7 +889,7 @@ impl OutboundPayments {
fn send_payment_internal<R: Deref, NS: Deref, ES: Deref, IH, SP, L: Deref>(
&self, payment_id: PaymentId, payment_hash: PaymentHash, recipient_onion: RecipientOnionFields,
keysend_preimage: Option<PaymentPreimage>, retry_strategy: Retry, route_params: RouteParameters,
router: &R, first_hops: Vec<ChannelDetails>, inflight_htlcs: IH, entropy_source: &ES,
payment_preimage: Option<PaymentPreimage>, router: &R, first_hops: Vec<ChannelDetails>, inflight_htlcs: IH, entropy_source: &ES,
node_signer: &NS, best_block_height: u32, logger: &L,
pending_events: &Mutex<VecDeque<(events::Event, Option<EventCompletionAction>)>>, send_payment_along_path: SP,
) -> Result<(), RetryableSendFailure>
Expand All @@ -907,16 +909,56 @@ impl OutboundPayments {
}
}

let mut route = router.find_route_with_id(
&node_signer.get_node_id(Recipient::Node).unwrap(), &route_params,
let payer = node_signer.get_node_id(Recipient::Node).unwrap();
let route = match router.find_route_with_id(
&payer,&route_params,
Some(&first_hops.iter().collect::<Vec<_>>()), inflight_htlcs(),
payment_hash, payment_id,
).map_err(|_| {
log_error!(logger, "Failed to find route for payment with id {} and hash {}",
payment_id, payment_hash);
RetryableSendFailure::RouteNotFound
})?;

) {
Ok(res) => Some(res),
Err(_) => {
// The following code handles self payments.
if let Payee::Clear{node_id, .. } = route_params.payment_params.payee {
let is_phantom_payee = match node_signer.get_node_id(Recipient::PhantomNode) {
Ok(phantom_node_id) => node_id == phantom_node_id,
Err(_) => false,
};
if node_id == payer || is_phantom_payee {
let dummy_route = Route {
paths: vec![Path {
hops: vec![],
blinded_tail: None,
}],
route_params: Some(route_params.clone()),
};

// We add a new pending payment only to mark it as fulfilled immediately, to protect against
// duplicate payments.
let _ = self.add_new_pending_payment(payment_hash,
recipient_onion.clone(), payment_id, keysend_preimage, &dummy_route, Some(retry_strategy),
Some(route_params.payment_params.clone()), entropy_source, best_block_height)
.map_err(|_| {
log_error!(logger, "Payment with id {} is already pending. New payment had payment hash {}",
payment_id, payment_hash);
RetryableSendFailure::DuplicatePayment
})?;
let mut pending_outbounds_lock = self.pending_outbound_payments.lock().unwrap();
let payment = pending_outbounds_lock.get_mut(&payment_id).unwrap();
payment.mark_fulfilled();
let mut pending_events_lock = pending_events.lock().unwrap();
pending_events_lock.push_back((Event::PaymentSent { payment_id: Some(payment_id), payment_preimage: payment_preimage.unwrap(),
payment_hash, fee_paid_msat: None }, None));
return Ok(());
}
}
None
}
};
if route.is_none() {
log_error!(logger, "Failed to find route for payment with id {} and hash {}", payment_id, payment_hash);
return Err(RetryableSendFailure::RouteNotFound);
}
let mut route = route.unwrap();
if route.route_params.as_ref() != Some(&route_params) {
debug_assert!(false,
"Routers are expected to return a Route which includes the requested RouteParameters");
Expand Down Expand Up @@ -1916,7 +1958,7 @@ mod tests {
} else {
let err = outbound_payments.send_payment(
PaymentHash([0; 32]), RecipientOnionFields::spontaneous_empty(), PaymentId([0; 32]),
Retry::Attempts(0), expired_route_params, &&router, vec![], || InFlightHtlcs::new(),
Retry::Attempts(0), expired_route_params, None, &&router, vec![], || InFlightHtlcs::new(),
&&keys_manager, &&keys_manager, 0, &&logger, &pending_events, |_| Ok(())).unwrap_err();
if let RetryableSendFailure::PaymentExpired = err { } else { panic!("Unexpected error"); }
}
Expand Down Expand Up @@ -1958,7 +2000,7 @@ mod tests {
} else {
let err = outbound_payments.send_payment(
PaymentHash([0; 32]), RecipientOnionFields::spontaneous_empty(), PaymentId([0; 32]),
Retry::Attempts(0), route_params, &&router, vec![], || InFlightHtlcs::new(),
Retry::Attempts(0), route_params, None, &&router, vec![], || InFlightHtlcs::new(),
&&keys_manager, &&keys_manager, 0, &&logger, &pending_events, |_| Ok(())).unwrap_err();
if let RetryableSendFailure::RouteNotFound = err {
} else { panic!("Unexpected error"); }
Expand Down Expand Up @@ -2006,7 +2048,7 @@ mod tests {
let pending_events = Mutex::new(VecDeque::new());
outbound_payments.send_payment(
PaymentHash([0; 32]), RecipientOnionFields::spontaneous_empty(), PaymentId([0; 32]),
Retry::Attempts(0), route_params.clone(), &&router, vec![], || InFlightHtlcs::new(),
Retry::Attempts(0), route_params.clone(), None, &&router, vec![], || InFlightHtlcs::new(),
&&keys_manager, &&keys_manager, 0, &&logger, &pending_events,
|_| Err(APIError::ChannelUnavailable { err: "test".to_owned() })).unwrap();
let mut events = pending_events.lock().unwrap();
Expand All @@ -2024,15 +2066,15 @@ mod tests {
// Ensure that a MonitorUpdateInProgress "error" will not result in a PaymentPathFailed event.
outbound_payments.send_payment(
PaymentHash([0; 32]), RecipientOnionFields::spontaneous_empty(), PaymentId([0; 32]),
Retry::Attempts(0), route_params.clone(), &&router, vec![], || InFlightHtlcs::new(),
Retry::Attempts(0), route_params.clone(), None, &&router, vec![], || InFlightHtlcs::new(),
&&keys_manager, &&keys_manager, 0, &&logger, &pending_events,
|_| Err(APIError::MonitorUpdateInProgress)).unwrap();
assert_eq!(pending_events.lock().unwrap().len(), 0);

// Ensure that any other error will result in a PaymentPathFailed event but no blamed scid.
outbound_payments.send_payment(
PaymentHash([0; 32]), RecipientOnionFields::spontaneous_empty(), PaymentId([1; 32]),
Retry::Attempts(0), route_params.clone(), &&router, vec![], || InFlightHtlcs::new(),
Retry::Attempts(0), route_params.clone(), None, &&router, vec![], || InFlightHtlcs::new(),
&&keys_manager, &&keys_manager, 0, &&logger, &pending_events,
|_| Err(APIError::APIMisuseError { err: "test".to_owned() })).unwrap();
let events = pending_events.lock().unwrap();
Expand Down

0 comments on commit 6de92ed

Please sign in to comment.