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 Oct 28, 2023
1 parent d2242f6 commit fe79793
Show file tree
Hide file tree
Showing 3 changed files with 68 additions and 21 deletions.
14 changes: 7 additions & 7 deletions lightning/src/ln/channelmanager.rs
Original file line number Diff line number Diff line change
Expand Up @@ -410,7 +410,7 @@ pub enum FailureCode {
}

impl Into<u16> for FailureCode {
fn into(self) -> u16 {
fn into(self) -> u16 {
match self {
FailureCode::TemporaryNodeFailure => 0x2000 | 2,
FailureCode::RequiredNodeFeatureMissing => 0x4000 | 0x2000 | 3,
Expand Down Expand Up @@ -3546,7 +3546,7 @@ where
let _persistence_guard = PersistenceNotifierGuard::notify_on_drop(self);
self.pending_outbound_payments
.send_payment_with_route(route, payment_hash, recipient_onion, payment_id,
&self.entropy_source, &self.node_signer, best_block_height,
&self.entropy_source, &self.node_signer, best_block_height, &self.pending_events,
|args| self.send_payment_along_path(args))
}

Expand All @@ -3556,7 +3556,7 @@ where
let best_block_height = self.best_block.read().unwrap().height();
let _persistence_guard = PersistenceNotifierGuard::notify_on_drop(self);
self.pending_outbound_payments
.send_payment(payment_hash, recipient_onion, payment_id, retry_strategy, route_params,
.send_payment(payment_hash,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 All @@ -3568,7 +3568,7 @@ where
let _persistence_guard = PersistenceNotifierGuard::notify_on_drop(self);
self.pending_outbound_payments.test_send_payment_internal(route, payment_hash, recipient_onion,
keysend_preimage, payment_id, recv_value_msat, onion_session_privs, &self.node_signer,
best_block_height, |args| self.send_payment_along_path(args))
best_block_height, &self.pending_events, |args| self.send_payment_along_path(args))
}

#[cfg(test)]
Expand Down Expand Up @@ -3644,7 +3644,7 @@ where
let _persistence_guard = PersistenceNotifierGuard::notify_on_drop(self);
self.pending_outbound_payments.send_spontaneous_payment_with_route(
route, payment_preimage, recipient_onion, payment_id, &self.entropy_source,
&self.node_signer, best_block_height, |args| self.send_payment_along_path(args))
&self.node_signer, best_block_height, &self.pending_events, |args| self.send_payment_along_path(args))
}

/// Similar to [`ChannelManager::send_spontaneous_payment`], but will automatically find a route
Expand All @@ -3671,7 +3671,7 @@ where
let _persistence_guard = PersistenceNotifierGuard::notify_on_drop(self);
self.pending_outbound_payments.send_probe(path, self.probing_cookie_secret,
&self.entropy_source, &self.node_signer, best_block_height,
|args| self.send_payment_along_path(args))
&self.pending_events, |args| self.send_payment_along_path(args))
}

/// Returns whether a payment with the given [`PaymentHash`] and [`PaymentId`] is, in fact, a
Expand Down Expand Up @@ -6098,7 +6098,7 @@ where

let per_peer_state = self.per_peer_state.read().unwrap();
let peer_state_mutex = per_peer_state.get(counterparty_node_id)
.ok_or_else(|| {
.ok_or_else(|| {
debug_assert!(false);
MsgHandleErrInternal::send_err_msg_no_close(format!("Can't find a peer matching the passed counterparty node_id {}", counterparty_node_id), msg.temporary_channel_id.clone())
})?;
Expand Down
63 changes: 50 additions & 13 deletions lightning/src/ln/outbound_payment.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ 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, PaymentId};
use crate::ln::onion_utils::{DecodedOnionFailure, HTLCFailReason};
Expand Down Expand Up @@ -698,7 +698,7 @@ impl OutboundPayments {
pub(super) fn send_payment_with_route<ES: Deref, NS: Deref, F>(
&self, route: &Route, payment_hash: PaymentHash, recipient_onion: RecipientOnionFields,
payment_id: PaymentId, entropy_source: &ES, node_signer: &NS, best_block_height: u32,
send_payment_along_path: F
pending_events: &Mutex<VecDeque<(events::Event, Option<EventCompletionAction>)>>, send_payment_along_path: F
) -> Result<(), PaymentSendFailure>
where
ES::Target: EntropySource,
Expand All @@ -707,7 +707,7 @@ impl OutboundPayments {
{
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,
onion_session_privs, node_signer, best_block_height, &send_payment_along_path)
onion_session_privs, node_signer, best_block_height, pending_events, &send_payment_along_path)
.map_err(|e| { self.remove_outbound_if_all_failed(payment_id, &e); e })
}

Expand Down Expand Up @@ -738,7 +738,8 @@ impl OutboundPayments {
pub(super) fn send_spontaneous_payment_with_route<ES: Deref, NS: Deref, F>(
&self, route: &Route, payment_preimage: Option<PaymentPreimage>,
recipient_onion: RecipientOnionFields, payment_id: PaymentId, entropy_source: &ES,
node_signer: &NS, best_block_height: u32, send_payment_along_path: F
node_signer: &NS, best_block_height: u32, pending_events: &Mutex<VecDeque<(events::Event, Option<EventCompletionAction>)>>,
send_payment_along_path: F
) -> Result<PaymentHash, PaymentSendFailure>
where
ES::Target: EntropySource,
Expand All @@ -752,7 +753,7 @@ impl OutboundPayments {
payment_id, Some(preimage), &route, None, None, entropy_source, best_block_height)?;

match self.pay_route_internal(route, payment_hash, recipient_onion, Some(preimage),
payment_id, None, onion_session_privs, node_signer, best_block_height, &send_payment_along_path
payment_id, None, onion_session_privs, node_signer, best_block_height, pending_events, &send_payment_along_path
) {
Ok(()) => Ok(payment_hash),
Err(e) => {
Expand Down Expand Up @@ -903,7 +904,7 @@ impl OutboundPayments {
}

let mut route = router.find_route_with_id(
&node_signer.get_node_id(Recipient::Node).unwrap(), &route_params,
&node_signer.get_node_id(Recipient::Node).unwrap(),&route_params,
Some(&first_hops.iter().collect::<Vec<_>>()), inflight_htlcs(),
payment_hash, payment_id,
).map_err(|_| {
Expand All @@ -928,7 +929,7 @@ impl OutboundPayments {
})?;

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);
onion_session_privs, node_signer, best_block_height, pending_events, &send_payment_along_path);
log_info!(logger, "Sending payment with id {} and hash {} returned {:?}",
payment_id, payment_hash, res);
if let Err(e) = res {
Expand Down Expand Up @@ -1087,7 +1088,7 @@ 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);
pending_events, &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);
Expand Down Expand Up @@ -1173,7 +1174,7 @@ impl OutboundPayments {

pub(super) fn send_probe<ES: Deref, NS: Deref, F>(
&self, path: Path, probing_cookie_secret: [u8; 32], entropy_source: &ES, node_signer: &NS,
best_block_height: u32, send_payment_along_path: F
best_block_height: u32, pending_events: &Mutex<VecDeque<(events::Event, Option<EventCompletionAction>)>>, send_payment_along_path: F
) -> Result<(PaymentHash, PaymentId), PaymentSendFailure>
where
ES::Target: EntropySource,
Expand All @@ -1197,7 +1198,7 @@ impl OutboundPayments {
entropy_source, best_block_height)?;

match self.pay_route_internal(&route, payment_hash, RecipientOnionFields::spontaneous_empty(),
None, payment_id, None, onion_session_privs, node_signer, best_block_height, &send_payment_along_path
None, payment_id, None, onion_session_privs, node_signer, best_block_height, pending_events, &send_payment_along_path
) {
Ok(()) => Ok((payment_hash, payment_id)),
Err(e) => {
Expand Down Expand Up @@ -1307,7 +1308,7 @@ impl OutboundPayments {
&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
pending_events: &Mutex<VecDeque<(events::Event, Option<EventCompletionAction>)>>, send_payment_along_path: &F
) -> Result<(), PaymentSendFailure>
where
NS::Target: NodeSigner,
Expand All @@ -1321,6 +1322,42 @@ impl OutboundPayments {
{
return Err(PaymentSendFailure::ParameterError(APIError::APIMisuseError{err: "Payment secret is required for multi-path payments".to_owned()}));
}

if let Some(path) = route.paths.get(0) {
// handle self payment if path doesn't have a blinded tail.
if path.blinded_tail.is_none() {
let last_hop = path.hops.last().unwrap();
if node_signer.get_node_id(Recipient::Node).unwrap() == last_hop.pubkey {
let payment_secret = match recipient_onion.payment_secret {
Some(secret) => secret,
None => PaymentSecret([0; 32])
};

let payment_preimage = PaymentPreimage([0; 32]);
let payment_purpose = PaymentPurpose::InvoicePayment {
payment_preimage: Some(payment_preimage),
payment_secret,
};

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_hash, fee_paid_msat: None }, None));
let amt_to_receive = match &route.route_params {
Some(route_params) => route_params.final_value_msat,
None => if recv_value_msat.is_some() { recv_value_msat.unwrap() } else { 0 },
};
pending_events_lock.push_back((Event::PaymentClaimable { receiver_node_id: Some(last_hop.pubkey), payment_hash,
onion_fields: Some(recipient_onion), amount_msat: amt_to_receive, counterparty_skimmed_fee_msat: 0,
purpose: payment_purpose, via_channel_id: None, via_user_channel_id: None, claim_deadline: None }, None));
return Ok(());
}
}
}

let mut total_value = 0;
let our_node_id = node_signer.get_node_id(Recipient::Node).unwrap(); // TODO no unwrap
let mut path_errs = Vec::with_capacity(route.paths.len());
Expand Down Expand Up @@ -1430,15 +1467,15 @@ impl OutboundPayments {
&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
pending_events: &Mutex<VecDeque<(events::Event, Option<EventCompletionAction>)>>, send_payment_along_path: F
) -> Result<(), PaymentSendFailure>
where
NS::Target: NodeSigner,
F: Fn(SendAlongPathArgs) -> Result<(), APIError>,
{
self.pay_route_internal(route, payment_hash, recipient_onion, keysend_preimage, payment_id,
recv_value_msat, onion_session_privs, node_signer, best_block_height,
&send_payment_along_path)
pending_events, &send_payment_along_path)
.map_err(|e| { self.remove_outbound_if_all_failed(payment_id, &e); e })
}

Expand Down
12 changes: 11 additions & 1 deletion lightning/src/routing/router.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1496,7 +1496,17 @@ 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,
maybe_announced_channel: false,

}], blinded_tail: None };
return Ok(Route { paths: vec![dummy_path], route_params: Some(route_params.clone()) });
}
if our_node_id == maybe_dummy_payee_node_id {
return Err(LightningError{err: "Invalid origin node id provided, use a different one".to_owned(), action: ErrorAction::IgnoreError});
Expand Down

0 comments on commit fe79793

Please sign in to comment.