Skip to content

Commit ff1e41c

Browse files
committed
Add 'send_circular_payment' helper and 'send_spontaneous_payment_with_route' to ChannelManager
Signed-off-by: ABHAY PANDEY <pandeyabhay967@gmail.com>
1 parent c2955a0 commit ff1e41c

2 files changed

Lines changed: 239 additions & 1 deletion

File tree

lightning/src/ln/channelmanager.rs

Lines changed: 115 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -115,7 +115,7 @@ use crate::onion_message::offers::{OffersMessage, OffersMessageHandler};
115115
use crate::routing::gossip::NodeId;
116116
use crate::routing::router::{
117117
BlindedTail, FixedRouter, InFlightHtlcs, Path, Payee, PaymentParameters, Route,
118-
RouteParameters, RouteParametersConfig, Router,
118+
RouteHop, RouteParameters, RouteParametersConfig, Router,
119119
};
120120
use crate::sign::ecdsa::EcdsaChannelSigner;
121121
use crate::sign::{EntropySource, NodeSigner, Recipient, SignerProvider};
@@ -5612,6 +5612,33 @@ impl<
56125612
&self.pending_events, |args| self.send_payment_along_path(args), &logger)
56135613
}
56145614

5615+
/// Sends a spontaneous payment along a given route.
5616+
#[rustfmt::skip]
5617+
pub fn send_spontaneous_payment_with_route(
5618+
&self, mut route: Route, payment_preimage: Option<PaymentPreimage>,
5619+
recipient_onion: RecipientOnionFields, payment_id: PaymentId
5620+
) -> Result<PaymentHash, RetryableSendFailure> {
5621+
let best_block_height = self.best_block.read().unwrap().height;
5622+
let _persistence_guard = PersistenceNotifierGuard::notify_on_drop(self);
5623+
let route_params = route.route_params.clone().unwrap_or_else(|| {
5624+
let (payee_node_id, cltv_delta) = route.paths.first()
5625+
.and_then(|path| path.hops.last().map(|hop| (hop.pubkey, hop.cltv_expiry_delta as u32)))
5626+
.unwrap_or_else(|| (PublicKey::from_slice(&[2; 32]).unwrap(), MIN_FINAL_CLTV_EXPIRY_DELTA as u32));
5627+
let dummy_payment_params = PaymentParameters::from_node_id(payee_node_id, cltv_delta);
5628+
RouteParameters::from_payment_params_and_value(dummy_payment_params, route.get_total_amount())
5629+
});
5630+
if route.route_params.is_none() { route.route_params = Some(route_params.clone()); }
5631+
let router = FixedRouter::new(route);
5632+
let payment_hash = payment_preimage.map(|preimage| preimage.into());
5633+
let logger =
5634+
WithContext::for_payment(&self.logger, None, None, payment_hash, payment_id);
5635+
self.pending_outbound_payments
5636+
.send_spontaneous_payment(payment_preimage, recipient_onion, payment_id, Retry::Attempts(0),
5637+
route_params, &&router, self.list_usable_channels(), || self.compute_inflight_htlcs(),
5638+
&self.entropy_source, &self.node_signer, best_block_height,
5639+
&self.pending_events, |args| self.send_payment_along_path(args), &logger)
5640+
}
5641+
56155642
/// Sends a payment to the route found using the provided [`RouteParameters`], retrying failed
56165643
/// payment paths based on the provided `Retry`.
56175644
///
@@ -6115,6 +6142,93 @@ impl<
61156142
)
61166143
}
61176144

6145+
/// Performs a circular rebalancing payment: funds exit our node over `outbound_channel_id`,
6146+
/// traverse the Lightning Network, and re-enter our node through `inbound_channel_id`.
6147+
///
6148+
/// This is a convenient helper for moving liquidity between two of our channels without
6149+
/// requiring a counterparty invoice. It is equivalent to constructing an appropriate circular
6150+
/// [`Route`] and sending a spontaneous (keysend) payment over it.
6151+
///
6152+
/// # How it works
6153+
///
6154+
/// The router finds a path from our node to the `inbound_channel_id`'s counterparty, forced to
6155+
/// start with `outbound_channel_id`. We then manually append a final hop back to ourselves over
6156+
/// the `inbound_channel_id`. The route is sent as a spontaneous payment.
6157+
///
6158+
/// # Limitations
6159+
///
6160+
/// - Only single-path routing (no MPP support) is currently available.
6161+
/// - The payment is not recorded by the `Scorer`.
6162+
///
6163+
/// # Errors
6164+
///
6165+
/// Returns [`RetryableSendFailure::RouteNotFound`] if channel validation fails or no route can be
6166+
/// found. Payment-level errors (e.g. HTLC failures mid-flight) are reported asynchronously
6167+
/// via [`Event::PaymentFailed`].
6168+
///
6169+
/// [`Route`]: crate::routing::router::Route
6170+
/// [`Event::PaymentFailed`]: crate::events::Event::PaymentFailed
6171+
pub fn send_circular_payment(
6172+
&self, outbound_channel_id: ChannelId, inbound_channel_id: ChannelId, amount_msat: u64,
6173+
payment_id: PaymentId,
6174+
) -> Result<PaymentHash, RetryableSendFailure> {
6175+
if outbound_channel_id == inbound_channel_id {
6176+
return Err(RetryableSendFailure::RouteNotFound);
6177+
}
6178+
6179+
let usable_channels = self.list_usable_channels();
6180+
let out_chan = usable_channels
6181+
.iter()
6182+
.find(|c| c.channel_id == outbound_channel_id)
6183+
.ok_or(RetryableSendFailure::RouteNotFound)?;
6184+
6185+
let in_chan = usable_channels
6186+
.iter()
6187+
.find(|c| c.channel_id == inbound_channel_id)
6188+
.ok_or(RetryableSendFailure::RouteNotFound)?;
6189+
6190+
let our_node_id = self.get_our_node_id();
6191+
let forwarding_fee = in_chan.counterparty.forwarding_info.as_ref().map_or(0, |info| {
6192+
info.fee_base_msat as u64
6193+
+ (info.fee_proportional_millionths as u64 * amount_msat) / 1000000
6194+
});
6195+
6196+
let route_params = RouteParameters::from_payment_params_and_value(
6197+
PaymentParameters::from_node_id(
6198+
in_chan.counterparty.node_id,
6199+
MIN_FINAL_CLTV_EXPIRY_DELTA as u32,
6200+
),
6201+
amount_msat + forwarding_fee,
6202+
);
6203+
6204+
let first_hops: [&ChannelDetails; 1] = [out_chan];
6205+
let inflight_htlcs = self.compute_inflight_htlcs();
6206+
let mut route = self.router
6207+
.find_route(&our_node_id, &route_params, Some(&first_hops), inflight_htlcs)
6208+
.map_err(|_| RetryableSendFailure::RouteNotFound)?;
6209+
let inbound_scid = in_chan.get_inbound_payment_scid()
6210+
.ok_or(RetryableSendFailure::RouteNotFound)?;
6211+
let last_hop = RouteHop {
6212+
pubkey: our_node_id,
6213+
node_features: NodeFeatures::empty(),
6214+
short_channel_id: inbound_scid,
6215+
channel_features: ChannelFeatures::empty(),
6216+
fee_msat: amount_msat,
6217+
cltv_expiry_delta: MIN_FINAL_CLTV_EXPIRY_DELTA as u32,
6218+
maybe_announced_channel: in_chan.is_announced,
6219+
};
6220+
for path in route.paths.iter_mut() {
6221+
if let Some(prev_last) = path.hops.last_mut() {
6222+
prev_last.fee_msat = forwarding_fee;
6223+
}
6224+
path.hops.push(last_hop.clone());
6225+
}
6226+
let preimage = PaymentPreimage(self.entropy_source.get_secure_random_bytes());
6227+
let onion = RecipientOnionFields::spontaneous_empty(amount_msat);
6228+
route.route_params = None;
6229+
self.send_spontaneous_payment_with_route(route, Some(preimage), onion, payment_id)
6230+
}
6231+
61186232
/// Send a payment that is probing the given route for liquidity. We calculate the
61196233
/// [`PaymentHash`] of probes based on a static secret and a random [`PaymentId`], which allows
61206234
/// us to easily discern them from real payments.

lightning/src/ln/payment_tests.rs

Lines changed: 124 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5946,3 +5946,127 @@ fn bolt11_multi_node_mpp_with_retry() {
59465946
panic!("{payment_sent_b:?}");
59475947
}
59485948
}
5949+
5950+
#[test]
5951+
fn test_circular_payment_rebalance() {
5952+
let chanmon_cfgs = create_chanmon_cfgs(3);
5953+
let node_cfgs = create_node_cfgs(3, &chanmon_cfgs);
5954+
let node_chanmgrs = create_node_chanmgrs(3, &node_cfgs, &[None, None, None]);
5955+
let nodes = create_network(3, &node_cfgs, &node_chanmgrs);
5956+
5957+
let node_a_id = nodes[0].node.get_our_node_id();
5958+
let node_b_id = nodes[1].node.get_our_node_id();
5959+
let node_c_id = nodes[2].node.get_our_node_id();
5960+
5961+
let chan_1 = create_announced_chan_between_nodes(&nodes, 0, 1);
5962+
let _chan_2 = create_announced_chan_between_nodes(&nodes, 1, 2);
5963+
let chan_3 = create_announced_chan_between_nodes(&nodes, 2, 0);
5964+
5965+
let out_chan_id = chan_1.2;
5966+
let in_chan_id = chan_3.2;
5967+
5968+
let amount_msat = 10_000;
5969+
5970+
// Test 1: Same channel for both in/out
5971+
let same_chan_err = nodes[0].node.send_circular_payment(
5972+
out_chan_id,
5973+
out_chan_id,
5974+
amount_msat,
5975+
PaymentId([1; 32]),
5976+
);
5977+
assert_eq!(same_chan_err.unwrap_err(), RetryableSendFailure::RouteNotFound);
5978+
5979+
// Test 2: Channel not found
5980+
let fake_chan_id = ChannelId([99; 32]);
5981+
let missing_chan_err = nodes[0].node.send_circular_payment(
5982+
fake_chan_id,
5983+
in_chan_id,
5984+
amount_msat,
5985+
PaymentId([2; 32]),
5986+
);
5987+
assert_eq!(missing_chan_err.unwrap_err(), RetryableSendFailure::RouteNotFound);
5988+
5989+
let missing_chan_err2 = nodes[0].node.send_circular_payment(
5990+
out_chan_id,
5991+
fake_chan_id,
5992+
amount_msat,
5993+
PaymentId([3; 32]),
5994+
);
5995+
assert_eq!(missing_chan_err2.unwrap_err(), RetryableSendFailure::RouteNotFound);
5996+
5997+
// Test 3: Happy path
5998+
let payment_id = PaymentId([42; 32]);
5999+
let _hash = nodes[0]
6000+
.node
6001+
.send_circular_payment(out_chan_id, in_chan_id, amount_msat, payment_id)
6002+
.unwrap();
6003+
check_added_monitors(&nodes[0], 1);
6004+
6005+
// Route should be 0 -> 1 -> 2 -> 0.
6006+
let mut events = nodes[0].node.get_and_clear_pending_msg_events();
6007+
assert_eq!(events.len(), 1);
6008+
6009+
// Forward 0 -> 1
6010+
let node_1_msgs = remove_first_msg_event_to_node(&node_b_id, &mut events);
6011+
let send_event_1 = SendEvent::from_event(node_1_msgs);
6012+
nodes[1].node.handle_update_add_htlc(node_a_id, &send_event_1.msgs[0]);
6013+
do_commitment_signed_dance(&nodes[1], &nodes[0], &send_event_1.commitment_msg, false, true);
6014+
6015+
// Forward 1 -> 2
6016+
expect_and_process_pending_htlcs(&nodes[1], false);
6017+
check_added_monitors(&nodes[1], 1);
6018+
let mut events_1 = nodes[1].node.get_and_clear_pending_msg_events();
6019+
let node_2_msgs = remove_first_msg_event_to_node(&node_c_id, &mut events_1);
6020+
let send_event_2 = SendEvent::from_event(node_2_msgs);
6021+
nodes[2].node.handle_update_add_htlc(node_b_id, &send_event_2.msgs[0]);
6022+
do_commitment_signed_dance(&nodes[2], &nodes[1], &send_event_2.commitment_msg, false, true);
6023+
6024+
// Forward 2 -> 0
6025+
expect_and_process_pending_htlcs(&nodes[2], false);
6026+
check_added_monitors(&nodes[2], 1);
6027+
let mut events_2 = nodes[2].node.get_and_clear_pending_msg_events();
6028+
let node_0_msgs = remove_first_msg_event_to_node(&node_a_id, &mut events_2);
6029+
let send_event_3 = SendEvent::from_event(node_0_msgs);
6030+
nodes[0].node.handle_update_add_htlc(node_c_id, &send_event_3.msgs[0]);
6031+
do_commitment_signed_dance(&nodes[0], &nodes[2], &send_event_3.commitment_msg, false, true);
6032+
6033+
// Now node 0 should process it and claim it.
6034+
expect_and_process_pending_htlcs(&nodes[0], false);
6035+
let claim_events = nodes[0].node.get_and_clear_pending_events();
6036+
assert_eq!(claim_events.len(), 1);
6037+
let preimage = if let Event::PaymentClaimable {
6038+
purpose: PaymentPurpose::SpontaneousPayment(preimage),
6039+
..
6040+
} = claim_events[0]
6041+
{
6042+
preimage
6043+
} else {
6044+
panic!("Expected PaymentClaimable SpontaneousPayment");
6045+
};
6046+
6047+
let route: &[&[&Node]] = &[&[&nodes[1], &nodes[2], &nodes[0]]];
6048+
claim_payment_along_route(ClaimAlongRouteArgs::new(&nodes[0], route, preimage));
6049+
}
6050+
6051+
#[test]
6052+
fn test_circular_payment_no_route() {
6053+
let chanmon_cfgs = create_chanmon_cfgs(3);
6054+
let node_cfgs = create_node_cfgs(3, &chanmon_cfgs);
6055+
let node_chanmgrs = create_node_chanmgrs(3, &node_cfgs, &[None, None, None]);
6056+
let nodes = create_network(3, &node_cfgs, &node_chanmgrs);
6057+
6058+
let chan_1 = create_announced_chan_between_nodes(&nodes, 0, 1);
6059+
let chan_2 = create_announced_chan_between_nodes(&nodes, 0, 2);
6060+
6061+
let out_chan_id = chan_1.2;
6062+
let in_chan_id = chan_2.2;
6063+
6064+
let amount_msat = 10_000;
6065+
let no_route_err = nodes[0].node.send_circular_payment(
6066+
out_chan_id,
6067+
in_chan_id,
6068+
amount_msat,
6069+
PaymentId([5; 32]),
6070+
);
6071+
assert_eq!(no_route_err.unwrap_err(), RetryableSendFailure::RouteNotFound);
6072+
}

0 commit comments

Comments
 (0)