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 Sep 22, 2023
1 parent 61d896d commit 6841df7
Show file tree
Hide file tree
Showing 3 changed files with 63 additions and 14 deletions.
11 changes: 10 additions & 1 deletion lightning/src/ln/channelmanager.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3348,8 +3348,17 @@ 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 preimage = match route_params.payment_params.payee {
Payee::Clear { node_id, .. } if node_id == self.get_our_node_id() => {
match recipient_onion.payment_secret {
Some(payment_secret) => self.get_payment_preimage(payment_hash, payment_secret).ok(),
None => None,
}
},
_ => None,
};
self.pending_outbound_payments
.send_payment(payment_hash, recipient_onion, payment_id, retry_strategy, route_params,
.send_payment(payment_hash, preimage, recipient_onion, payment_id, retry_strategy, route_params,
&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))
Expand Down
55 changes: 43 additions & 12 deletions lightning/src/ln/outbound_payment.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,11 +14,11 @@ 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, PaymentPurpose};
use crate::ln::{PaymentHash, PaymentPreimage, PaymentSecret};
use crate::ln::channelmanager::{ChannelDetails, EventCompletionAction, HTLCSource, IDEMPOTENCY_TIMEOUT_TICKS, PaymentId};
use crate::ln::onion_utils::{DecodedOnionFailure, HTLCFailReason};
use crate::routing::router::{InFlightHtlcs, Path, PaymentParameters, Route, RouteParameters, Router};
use crate::routing::router::{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 @@ -552,7 +552,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,
&self, payment_hash: PaymentHash, payment_preimage: Option<PaymentPreimage>, recipient_onion: RecipientOnionFields, payment_id: PaymentId,
retry_strategy: Retry, route_params: RouteParameters, router: &R,
first_hops: Vec<ChannelDetails>, compute_inflight_htlcs: IH, entropy_source: &ES,
node_signer: &NS, best_block_height: u32, logger: &L,
Expand All @@ -566,7 +566,7 @@ impl OutboundPayments {
IH: Fn() -> InFlightHtlcs,
SP: Fn(SendAlongPathArgs) -> Result<(), APIError>,
{
self.send_payment_internal(payment_id, payment_hash, recipient_onion, None, retry_strategy,
self.send_payment_internal(payment_id, payment_hash, payment_preimage, recipient_onion, None, retry_strategy,
route_params, router, first_hops, &compute_inflight_htlcs, entropy_source, node_signer,
best_block_height, logger, pending_events, &send_payment_along_path)
}
Expand Down Expand Up @@ -605,7 +605,7 @@ impl OutboundPayments {
let preimage = payment_preimage
.unwrap_or_else(|| PaymentPreimage(entropy_source.get_secure_random_bytes()));
let payment_hash = PaymentHash(Sha256::hash(&preimage.0).into_inner());
self.send_payment_internal(payment_id, payment_hash, recipient_onion, Some(preimage),
self.send_payment_internal(payment_id, payment_hash, None, recipient_onion, Some(preimage),
retry_strategy, route_params, 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 @@ -706,7 +706,7 @@ impl OutboundPayments {
/// [`Event::PaymentPathFailed`]: crate::events::Event::PaymentPathFailed
/// [`Event::PaymentFailed`]: crate::events::Event::PaymentFailed
fn send_payment_internal<R: Deref, NS: Deref, ES: Deref, IH, SP, L: Deref>(
&self, payment_id: PaymentId, payment_hash: PaymentHash, recipient_onion: RecipientOnionFields,
&self, payment_id: PaymentId, payment_hash: PaymentHash, payment_preimage: Option<PaymentPreimage>, 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,
node_signer: &NS, best_block_height: u32, logger: &L,
Expand All @@ -728,8 +728,9 @@ impl OutboundPayments {
}
}

let payer_pubkey = node_signer.get_node_id(Recipient::Node).unwrap();
let route = router.find_route_with_id(
&node_signer.get_node_id(Recipient::Node).unwrap(), &route_params,
&payer_pubkey, &route_params,
Some(&first_hops.iter().collect::<Vec<_>>()), inflight_htlcs(),
payment_hash, payment_id,
).map_err(|_| {
Expand All @@ -747,6 +748,36 @@ impl OutboundPayments {
RetryableSendFailure::DuplicatePayment
})?;

match route_params.payment_params.payee {
Payee::Clear { node_id, .. } if node_id == payer_pubkey => {
let payment_secret = match recipient_onion.payment_secret {
Some(secret) => secret,
None => PaymentSecret([0; 32])
};
let payment_purpose = PaymentPurpose::InvoicePayment {
payment_preimage,
payment_secret,
};

let payment_preimage = payment_preimage
.unwrap_or_else(|| PaymentPreimage(entropy_source.get_secure_random_bytes()));
{
let mut pending_events_lock = pending_events.lock().unwrap();
pending_events_lock.push_back((Event::PaymentSent { payment_id: Some(payment_id), payment_preimage,
payment_hash, fee_paid_msat: None }, None));
pending_events_lock.push_back((Event::PaymentClaimable { receiver_node_id: Some(payer_pubkey), payment_hash,
onion_fields: Some(recipient_onion), amount_msat: route_params.final_value_msat, counterparty_skimmed_fee_msat: 0,
purpose: payment_purpose, via_channel_id: None, via_user_channel_id: None, claim_deadline: None }, None));
}

let mut pending_outbounds_lock = self.pending_outbound_payments.lock().unwrap();
let payment = pending_outbounds_lock.get_mut(&payment_id).unwrap();
payment.mark_fulfilled();
return Ok(());
},
_ => {},
}

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, "Sending payment with id {} and hash {} returned {:?}",
Expand Down Expand Up @@ -1586,7 +1617,7 @@ mod tests {
} else { panic!("Unexpected event"); }
} else {
let err = outbound_payments.send_payment(
PaymentHash([0; 32]), RecipientOnionFields::spontaneous_empty(), PaymentId([0; 32]),
PaymentHash([0; 32]), None, RecipientOnionFields::spontaneous_empty(), PaymentId([0; 32]),
Retry::Attempts(0), expired_route_params, &&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 @@ -1631,7 +1662,7 @@ mod tests {
if let Event::PaymentFailed { .. } = events[0].0 { } else { panic!("Unexpected event"); }
} else {
let err = outbound_payments.send_payment(
PaymentHash([0; 32]), RecipientOnionFields::spontaneous_empty(), PaymentId([0; 32]),
PaymentHash([0; 32]), None, RecipientOnionFields::spontaneous_empty(), PaymentId([0; 32]),
Retry::Attempts(0), route_params, &&router, vec![], || InFlightHtlcs::new(),
&&keys_manager, &&keys_manager, 0, &&logger, &pending_events, |_| Ok(())).unwrap_err();
if let RetryableSendFailure::RouteNotFound = err {
Expand Down Expand Up @@ -1679,7 +1710,7 @@ mod tests {
// PaymentPathFailed event.
let pending_events = Mutex::new(VecDeque::new());
outbound_payments.send_payment(
PaymentHash([0; 32]), RecipientOnionFields::spontaneous_empty(), PaymentId([0; 32]),
PaymentHash([0; 32]), None, RecipientOnionFields::spontaneous_empty(), PaymentId([0; 32]),
Retry::Attempts(0), route_params.clone(), &&router, vec![], || InFlightHtlcs::new(),
&&keys_manager, &&keys_manager, 0, &&logger, &pending_events,
|_| Err(APIError::ChannelUnavailable { err: "test".to_owned() })).unwrap();
Expand All @@ -1697,15 +1728,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]),
PaymentHash([0; 32]), None, RecipientOnionFields::spontaneous_empty(), PaymentId([0; 32]),
Retry::Attempts(0), route_params.clone(), &&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]),
PaymentHash([0; 32]), None, RecipientOnionFields::spontaneous_empty(), PaymentId([1; 32]),
Retry::Attempts(0), route_params.clone(), &&router, vec![], || InFlightHtlcs::new(),
&&keys_manager, &&keys_manager, 0, &&logger, &pending_events,
|_| Err(APIError::APIMisuseError { err: "test".to_owned() })).unwrap();
Expand Down
11 changes: 10 additions & 1 deletion lightning/src/routing/router.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1423,7 +1423,16 @@ where L::Target: Logger {
let our_node_id = NodeId::from_pubkey(&our_node_pubkey);

if payee_node_id_opt.map_or(false, |payee| payee == our_node_id) {
return Err(LightningError{err: "Cannot generate a route to ourselves".to_owned(), action: ErrorAction::IgnoreError});
let dummy_path = Path { hops: vec![RouteHop {
pubkey: our_node_pubkey.clone(),
short_channel_id: 0,
node_features: NodeFeatures::empty(),
channel_features: ChannelFeatures::empty(),
fee_msat: 0,
cltv_expiry_delta: 0,

}], blinded_tail: None };
return Ok(Route { paths: vec![dummy_path], payment_params: Some(payment_params.clone()) });
}

if final_value_msat > MAX_VALUE_MSAT {
Expand Down

0 comments on commit 6841df7

Please sign in to comment.