Skip to content
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
105 changes: 100 additions & 5 deletions lightning/src/ln/outbound_payment.rs
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ use crate::routing::router::{
RouteParametersConfig, Router,
};
use crate::sign::{EntropySource, NodeSigner, Recipient};
use crate::types::features::Bolt12InvoiceFeatures;
use crate::types::features::{Bolt12InvoiceFeatures, ChannelFeatures, NodeFeatures};
use crate::types::payment::{PaymentHash, PaymentPreimage, PaymentSecret};
use crate::util::errors::APIError;
use crate::util::logger::Logger;
Expand Down Expand Up @@ -1484,10 +1484,51 @@ where
IH: Fn() -> InFlightHtlcs,
SP: Fn(SendAlongPathArgs) -> Result<(), APIError>,
{
let route = self.find_initial_route(
payment_id, payment_hash, &recipient_onion, keysend_preimage, None, &mut route_params, router,
&first_hops, &inflight_htlcs, node_signer, best_block_height,
)?;
#[cfg(feature = "std")] {
if has_expired(&route_params) {
log_error!(self.logger, "Payment with id {} and hash {} had expired before we started paying",
payment_id, payment_hash);
return Err(RetryableSendFailure::PaymentExpired)
}
}
//If the router returns "Cannot generate a route to ourselves" and we have a preimage,
// treat this as a self-payment and fulfill it locally.
onion_utils::set_max_path_length(
&mut route_params, &recipient_onion, keysend_preimage, None, best_block_height
).map_err(|()| {
log_error!(self.logger, "Can't construct an onion packet without exceeding 1300-byte onion \n\
hop_data length for payment with id {} and hash {}", payment_id, payment_hash);
RetryableSendFailure::OnionPacketSizeExceeded
})?;

let (route, is_self_payment) = match router.find_route_with_id(
&node_signer.get_node_id(Recipient::Node).unwrap(), &route_params,
Some(&first_hops.iter().collect::<Vec<_>>()), inflight_htlcs(),
payment_hash, payment_id,
) {
Ok(r) => (r, false),
Err(e) => {
if e == "Cannot generate a route to ourselves" && keysend_preimage.is_some() {
use crate::routing::router::{Route, Path, RouteHop};
let payee_pk = node_signer.get_node_id(Recipient::Node).unwrap();
let hop = RouteHop {
pubkey: payee_pk,
node_features: NodeFeatures::empty(),
short_channel_id: 0,
channel_features: ChannelFeatures::empty(),
fee_msat: 0,
cltv_expiry_delta: 0,
maybe_announced_channel: false,
};
let path = Path { hops: vec![hop], blinded_tail: None };
(Route { paths: vec![path], route_params: Some(route_params.clone()) }, true)
} else {
log_error!(self.logger, "Failed to find route for payment with id {} and hash {}",
payment_id, payment_hash);
return Err(RetryableSendFailure::RouteNotFound)
}
}
};

let onion_session_privs = self.add_new_pending_payment(payment_hash,
recipient_onion.clone(), payment_id, keysend_preimage, &route, Some(retry_strategy),
Expand All @@ -1498,6 +1539,18 @@ where
RetryableSendFailure::DuplicatePayment
})?;

// If self-payment with preimage, claim HTLCs locally immediately.
if is_self_payment {
if let Some(preimage) = keysend_preimage {
let mut ev_completion_action = None;
for (path, session_priv_bytes) in route.paths.iter().zip(onion_session_privs.iter()) {
let sk = SecretKey::from_slice(&session_priv_bytes[..]).expect("session priv has correct length");
self.claim_htlc(payment_id, preimage, None, sk, path.clone(), false, &mut ev_completion_action, pending_events);
}
return Ok(());
}
}

let res = self.pay_route_internal(&route, payment_hash, &recipient_onion,
keysend_preimage, None, None, payment_id, None, &onion_session_privs, false, node_signer,
best_block_height, &send_payment_along_path);
Expand Down Expand Up @@ -2829,12 +2882,15 @@ mod tests {
InFlightHtlcs, Path, PaymentParameters, Route, RouteHop, RouteParameters,
RouteParametersConfig,
};
use crate::sign::{NodeSigner, Recipient};
use crate::sync::{Arc, Mutex, RwLock};
use crate::types::features::{Bolt12InvoiceFeatures, ChannelFeatures, NodeFeatures};
use crate::types::payment::{PaymentHash, PaymentPreimage};
use crate::util::errors::APIError;
use crate::util::hash_tables::new_hash_map;
use crate::util::test_utils;
use bitcoin::hashes::sha256::Hash as Sha256;
use bitcoin::hashes::Hash;

use alloc::collections::VecDeque;

Expand Down Expand Up @@ -3503,4 +3559,43 @@ mod tests {
reason: Some(PaymentFailureReason::UserAbandoned),
}, None));
}

#[test]
#[rustfmt::skip]
fn self_spontaneous_payment_fulfills() {
let pending_events = Mutex::new(VecDeque::new());
let logger = test_utils::TestLogger::new();
let outbound_payments = OutboundPayments::new(new_hash_map(), &logger);
let network_graph = Arc::new(NetworkGraph::new(Network::Testnet, &logger));
let scorer = RwLock::new(test_utils::TestScorer::new());
let router = test_utils::TestRouter::new(network_graph, &logger, &scorer);
let keys_manager = test_utils::TestKeysInterface::new(&[0; 32], Network::Testnet);

let our_pk = keys_manager.get_node_id(Recipient::Node).unwrap();
let payment_params = PaymentParameters::from_node_id(our_pk, 0);
let mut route_params = RouteParameters::from_payment_params_and_value(payment_params.clone(), 1000);

let preimage = PaymentPreimage([1; 32]);
let recipient_onion = RecipientOnionFields::spontaneous_empty();

// Adjust `route_params` like the send path before routing
crate::ln::onion_utils::set_max_path_length(&mut route_params, &recipient_onion, Some(preimage), None, 0).unwrap();

router.expect_find_route(route_params.clone(), Err("Cannot generate a route to ourselves"));

let res = outbound_payments.send_spontaneous_payment(Some(preimage), RecipientOnionFields::spontaneous_empty(),
PaymentId([0; 32]), Retry::Attempts(0), route_params.clone(), &&router, vec![], || InFlightHtlcs::new(),
&&keys_manager, &&keys_manager, 0, &pending_events, |_| Ok(()));
assert!(res.is_ok());

let mut events = pending_events.lock().unwrap();

assert!(!events.is_empty());

if let Event::PaymentSent { payment_preimage: p, payment_hash, .. } = events.pop_front().unwrap().0 {
assert_eq!(p.0, [1; 32]);
let expected_hash = PaymentHash(Sha256::hash(&p.0).to_byte_array());
assert_eq!(payment_hash, expected_hash);
} else { panic!("Expected PaymentSent event"); }
}
}
Loading