Skip to content

Commit 955c11c

Browse files
committed
add rough logic for self payment
1 parent 42085b9 commit 955c11c

File tree

3 files changed

+114
-3
lines changed

3 files changed

+114
-3
lines changed

lightning/src/ln/channelmanager.rs

Lines changed: 88 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -123,7 +123,7 @@ use crate::types::string::UntrustedString;
123123
use crate::util::config::{ChannelConfig, ChannelConfigOverrides, ChannelConfigUpdate, UserConfig};
124124
use crate::util::errors::APIError;
125125
use crate::util::logger::{Level, Logger, WithContext};
126-
use crate::util::scid_utils::fake_scid;
126+
use crate::util::scid_utils::{fake_scid, SCID_SELF_PAYMENT};
127127
use crate::util::ser::{
128128
BigSize, FixedLengthReader, LengthReadable, MaybeReadable, Readable, ReadableArgs, VecWriter,
129129
Writeable, Writer,
@@ -4906,6 +4906,11 @@ where
49064906
path, payment_hash, recipient_onion, total_value, cur_height, payment_id, keysend_preimage,
49074907
invoice_request, bolt12_invoice, session_priv_bytes
49084908
} = args;
4909+
4910+
if path.hops.first().unwrap().short_channel_id == SCID_SELF_PAYMENT {
4911+
return self.handle_self_payment(&payment_hash, &recipient_onion, total_value, payment_id, &keysend_preimage);
4912+
}
4913+
49094914
// The top-level caller should hold the total_consistency_lock read lock.
49104915
debug_assert!(self.total_consistency_lock.try_write().is_err());
49114916
let prng_seed = self.entropy_source.get_secure_random_bytes();
@@ -5078,6 +5083,88 @@ where
50785083
|args| self.send_payment_along_path(args),
50795084
)
50805085
}
5086+
5087+
/// Send a payment to ourselves (self-payment).
5088+
///
5089+
/// This method creates a payment route from the node to itself, enabling testing
5090+
/// of payment functionality and providing a way to generate payment events locally.
5091+
///
5092+
/// # Arguments
5093+
/// * `payment_preimage` - The preimage for the payment hash
5094+
/// * `recipient_onion` - The recipient onion fields for the payment
5095+
/// * `payment_id` - A unique identifier for this payment
5096+
///
5097+
/// # Returns
5098+
/// Returns the payment hash on success, or an APIError on failure.
5099+
pub fn send_self_payment(
5100+
&self, payment_preimage: PaymentPreimage, recipient_onion: RecipientOnionFields,
5101+
payment_id: PaymentId, final_value_msat: u64
5102+
) -> Result<PaymentHash, APIError> {
5103+
// Create route parameters for self-payment
5104+
let payment_params = PaymentParameters::from_node_id(
5105+
self.get_our_node_id(),
5106+
MIN_FINAL_CLTV_EXPIRY_DELTA as u32
5107+
);
5108+
let route_params = RouteParameters::from_payment_params_and_value(
5109+
payment_params,
5110+
final_value_msat// Amount in msat
5111+
);
5112+
5113+
// Use existing spontaneous payment infrastructure
5114+
self.send_spontaneous_payment(
5115+
Some(payment_preimage),
5116+
recipient_onion,
5117+
payment_id,
5118+
route_params,
5119+
Retry::Attempts(0)
5120+
).map_err(|e| APIError::APIMisuseError {
5121+
err: format!("Self-payment failed: {:?}", e)
5122+
})
5123+
}
5124+
5125+
fn handle_self_payment(
5126+
&self, payment_hash: &PaymentHash, recipient_onion: &RecipientOnionFields,
5127+
total_value: u64, payment_id: PaymentId, keysend_preimage: &Option<PaymentPreimage>
5128+
) -> Result<(), APIError> {
5129+
// For self-payments, we need to:
5130+
// 1. Validate the payment secret/preimage
5131+
// 2. Generate a PaymentClaimable event
5132+
// 3. The user can then claim_funds() to complete the payment
5133+
5134+
let payment_preimage = if let Some(preimage) = keysend_preimage {
5135+
*preimage
5136+
} else {
5137+
// For non-keysend self-payments, try to find the preimage
5138+
// This would require looking up the payment in inbound_payment_key
5139+
return Err(APIError::APIMisuseError {
5140+
err: "Self-payment without keysend preimage not yet supported".to_owned()
5141+
});
5142+
};
5143+
5144+
// Validate payment hash matches preimage
5145+
if PaymentHash(Sha256::hash(&payment_preimage.0).to_byte_array()) != *payment_hash {
5146+
return Err(APIError::APIMisuseError {
5147+
err: "Payment hash does not match preimage".to_owned()
5148+
});
5149+
}
5150+
5151+
// Create payment claimable event
5152+
let purpose = events::PaymentPurpose::SpontaneousPayment(payment_preimage);
5153+
let payment_claimable = events::Event::PaymentClaimable {
5154+
receiver_node_id: Some(self.get_our_node_id()),
5155+
payment_hash: *payment_hash,
5156+
onion_fields: Some(recipient_onion.clone()),
5157+
amount_msat: total_value,
5158+
counterparty_skimmed_fee_msat: 0,
5159+
purpose,
5160+
via_channel_ids: vec![],
5161+
claim_deadline: None,
5162+
payment_id: Some(payment_id),
5163+
};
5164+
5165+
self.pending_events.lock().unwrap().push_back((payment_claimable, None));
5166+
Ok(())
5167+
}
50815168

50825169
#[cfg(any(test, feature = "_externalize_tests"))]
50835170
pub(super) fn test_send_payment_internal(

lightning/src/routing/router.rs

Lines changed: 21 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@ use crate::types::features::{
3737
use crate::types::payment::{PaymentHash, PaymentPreimage};
3838
use crate::util::logger::Logger;
3939
use crate::util::ser::{Readable, ReadableArgs, Writeable, Writer};
40-
40+
use crate::util::scid_utils::SCID_SELF_PAYMENT;
4141
use crate::io;
4242
use crate::prelude::*;
4343
use alloc::collections::BinaryHeap;
@@ -2453,7 +2453,7 @@ where L::Target: Logger {
24532453
let our_node_id = NodeId::from_pubkey(&our_node_pubkey);
24542454

24552455
if payee_node_id_opt.map_or(false, |payee| payee == our_node_id) {
2456-
return Err("Cannot generate a route to ourselves");
2456+
return create_self_payment_route(our_node_pubkey, route_params);
24572457
}
24582458
if our_node_id == maybe_dummy_payee_node_id {
24592459
return Err("Invalid origin node id provided, use a different one");
@@ -3727,6 +3727,25 @@ where L::Target: Logger {
37273727
Ok(route)
37283728
}
37293729

3730+
fn create_self_payment_route(our_node_pubkey: &PublicKey, route_params: &RouteParameters) -> Result<Route, &'static str> {
3731+
let path = Path {
3732+
hops: vec![RouteHop {
3733+
pubkey: our_node_pubkey.clone(),
3734+
short_channel_id: SCID_SELF_PAYMENT, // Dummy short_channel_id specifying self payment
3735+
fee_msat: 0, // Zero fees
3736+
cltv_expiry_delta: MIN_FINAL_CLTV_EXPIRY_DELTA.into(),
3737+
node_features: NodeFeatures::empty(),
3738+
channel_features: ChannelFeatures::empty(),
3739+
maybe_announced_channel: false,
3740+
}],
3741+
blinded_tail: None,
3742+
};
3743+
Ok(Route {
3744+
paths: vec![path],
3745+
route_params: Some(route_params.clone()),
3746+
})
3747+
}
3748+
37303749
// When an adversarial intermediary node observes a payment, it may be able to infer its
37313750
// destination, if the remaining CLTV expiry delta exactly matches a feasible path in the network
37323751
// graph. In order to improve privacy, this method obfuscates the CLTV expiry deltas along the

lightning/src/util/scid_utils.rs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,11 @@ pub const MAX_SCID_TX_INDEX: u64 = 0x00ffffff;
2121
/// value is based on the 2-bytes available for the vout index.
2222
pub const MAX_SCID_VOUT_INDEX: u64 = 0xffff;
2323

24+
// Self payment scid is 0 to avoid confusion with real channels
25+
/// A special short channel ID (0) used to indicate a self-payment.
26+
/// When routing a payment to ourselves, the first hop will have this SCID.
27+
pub const SCID_SELF_PAYMENT: u64 = 0;
28+
2429
/// A `short_channel_id` construction error
2530
#[derive(Debug, PartialEq, Eq)]
2631
pub enum ShortChannelIdError {

0 commit comments

Comments
 (0)