Skip to content

Commit db9e7b7

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 db9e7b7

2 files changed

Lines changed: 276 additions & 10 deletions

File tree

lightning/src/ln/channelmanager.rs

Lines changed: 152 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -5582,6 +5582,29 @@ impl<
55825582
}
55835583
}
55845584

5585+
fn route_params_for_fixed_route(route: &mut Route) -> RouteParameters {
5586+
let params = route.route_params.clone().unwrap_or_else(|| {
5587+
let (payee_node_id, cltv_delta) = route
5588+
.paths
5589+
.first()
5590+
.and_then(|path| {
5591+
path.hops.last().map(|hop| (hop.pubkey, hop.cltv_expiry_delta as u32))
5592+
})
5593+
.unwrap_or_else(|| {
5594+
(PublicKey::from_slice(&[2; 33]).unwrap(), MIN_FINAL_CLTV_EXPIRY_DELTA as u32)
5595+
});
5596+
let dummy_payment_params = PaymentParameters::from_node_id(payee_node_id, cltv_delta);
5597+
RouteParameters::from_payment_params_and_value(
5598+
dummy_payment_params,
5599+
route.get_total_amount(),
5600+
)
5601+
});
5602+
if route.route_params.is_none() {
5603+
route.route_params = Some(params.clone());
5604+
}
5605+
params
5606+
}
5607+
55855608
/// Sends a payment along a given route. See [`Self::send_payment`] for more info.
55865609
///
55875610
/// LDK will not automatically retry this payment, though it may be manually re-sent after an
@@ -5593,25 +5616,51 @@ impl<
55935616
) -> Result<(), RetryableSendFailure> {
55945617
let best_block_height = self.best_block.read().unwrap().height;
55955618
let _persistence_guard = PersistenceNotifierGuard::notify_on_drop(self);
5596-
let route_params = route.route_params.clone().unwrap_or_else(|| {
5597-
// Create a dummy route params since they're a required parameter but unused in this case
5598-
let (payee_node_id, cltv_delta) = route.paths.first()
5599-
.and_then(|path| path.hops.last().map(|hop| (hop.pubkey, hop.cltv_expiry_delta as u32)))
5600-
.unwrap_or_else(|| (PublicKey::from_slice(&[2; 32]).unwrap(), MIN_FINAL_CLTV_EXPIRY_DELTA as u32));
5601-
let dummy_payment_params = PaymentParameters::from_node_id(payee_node_id, cltv_delta);
5602-
RouteParameters::from_payment_params_and_value(dummy_payment_params, route.get_total_amount())
5603-
});
5604-
if route.route_params.is_none() { route.route_params = Some(route_params.clone()); }
5619+
let route_params = Self::route_params_for_fixed_route(&mut route);
56055620
let router = FixedRouter::new(route);
56065621
let logger =
56075622
WithContext::for_payment(&self.logger, None, None, Some(payment_hash), payment_id);
56085623
self.pending_outbound_payments
56095624
.send_payment(payment_hash, recipient_onion, payment_id, Retry::Attempts(0),
5610-
route_params, &&router, self.list_usable_channels(), || self.compute_inflight_htlcs(),
5625+
route_params, &router, self.list_usable_channels(), || self.compute_inflight_htlcs(),
56115626
&self.entropy_source, &self.node_signer, best_block_height,
56125627
&self.pending_events, |args| self.send_payment_along_path(args), &logger)
56135628
}
56145629

5630+
/// Sends a spontaneous payment along a given route. See
5631+
/// [`Self::send_spontaneous_payment`] for more info.
5632+
///
5633+
/// LDK will not automatically retry this payment, though it may be manually
5634+
/// re-sent after an
5635+
/// [`Event::PaymentFailed`] is generated.
5636+
pub fn send_spontaneous_payment_with_route(
5637+
&self, mut route: Route, payment_preimage: Option<PaymentPreimage>,
5638+
recipient_onion: RecipientOnionFields, payment_id: PaymentId,
5639+
) -> Result<PaymentHash, RetryableSendFailure> {
5640+
let best_block_height = self.best_block.read().unwrap().height;
5641+
let _persistence_guard = PersistenceNotifierGuard::notify_on_drop(self);
5642+
let route_params = Self::route_params_for_fixed_route(&mut route);
5643+
let router = FixedRouter::new(route);
5644+
let payment_hash = payment_preimage.map(|preimage| preimage.into());
5645+
let logger = WithContext::for_payment(&self.logger, None, None, payment_hash, payment_id);
5646+
self.pending_outbound_payments.send_spontaneous_payment(
5647+
payment_preimage,
5648+
recipient_onion,
5649+
payment_id,
5650+
Retry::Attempts(0),
5651+
route_params,
5652+
&router,
5653+
self.list_usable_channels(),
5654+
|| self.compute_inflight_htlcs(),
5655+
&self.entropy_source,
5656+
&self.node_signer,
5657+
best_block_height,
5658+
&self.pending_events,
5659+
|args| self.send_payment_along_path(args),
5660+
&logger,
5661+
)
5662+
}
5663+
56155664
/// Sends a payment to the route found using the provided [`RouteParameters`], retrying failed
56165665
/// payment paths based on the provided `Retry`.
56175666
///
@@ -6115,6 +6164,99 @@ impl<
61156164
)
61166165
}
61176166

6167+
/// Performs a circular rebalancing payment: funds exit our node over `outbound_channel_id`,
6168+
/// traverse the Lightning Network, and re-enter our node through `inbound_channel_id`.
6169+
///
6170+
/// This is a convenient helper for moving liquidity between two of our channels without
6171+
/// requiring a counterparty invoice. It is equivalent to constructing an appropriate circular
6172+
/// [`Route`] and sending a spontaneous (keysend) payment over it.
6173+
///
6174+
/// # How it works
6175+
///
6176+
/// The router finds a path from our node to the `inbound_channel_id`'s counterparty, forced to
6177+
/// start with `outbound_channel_id`. We then manually append a final hop back to ourselves over
6178+
/// the `inbound_channel_id`. The route is sent as a spontaneous payment.
6179+
///
6180+
/// # Limitations
6181+
///
6182+
/// - Only single-path routing (no MPP support) is currently available.
6183+
/// - The payment is not recorded by the `Scorer`.
6184+
///
6185+
/// # Errors
6186+
///
6187+
/// Returns [`RetryableSendFailure::RouteNotFound`] if channel validation fails or no route can be
6188+
/// found. Payment-level errors (e.g. HTLC failures mid-flight) are reported asynchronously
6189+
/// via [`Event::PaymentFailed`].
6190+
///
6191+
/// [`Route`]: crate::routing::router::Route
6192+
/// [`Event::PaymentFailed`]: crate::events::Event::PaymentFailed
6193+
pub fn send_circular_payment(
6194+
&self, outbound_channel_id: ChannelId, inbound_channel_id: ChannelId, amount_msat: u64,
6195+
payment_id: PaymentId,
6196+
) -> Result<PaymentHash, RetryableSendFailure> {
6197+
if outbound_channel_id == inbound_channel_id {
6198+
return Err(RetryableSendFailure::RouteNotFound);
6199+
}
6200+
6201+
let usable_channels = self.list_usable_channels();
6202+
let out_chan = usable_channels
6203+
.iter()
6204+
.find(|c| c.channel_id == outbound_channel_id)
6205+
.ok_or(RetryableSendFailure::RouteNotFound)?;
6206+
6207+
let in_chan = usable_channels
6208+
.iter()
6209+
.find(|c| c.channel_id == inbound_channel_id)
6210+
.ok_or(RetryableSendFailure::RouteNotFound)?;
6211+
6212+
let our_node_id = self.get_our_node_id();
6213+
let forwarding_info = in_chan
6214+
.counterparty
6215+
.forwarding_info
6216+
.as_ref()
6217+
.ok_or(RetryableSendFailure::RouteNotFound)?;
6218+
let dummy_payee = PublicKey::from_slice(&[2; 33]).unwrap();
6219+
let route_hint = crate::routing::router::RouteHint(vec![
6220+
crate::routing::router::RouteHintHop {
6221+
src_node_id: in_chan.counterparty.node_id,
6222+
short_channel_id: in_chan
6223+
.get_inbound_payment_scid()
6224+
.ok_or(RetryableSendFailure::RouteNotFound)?,
6225+
fees: lightning_types::routing::RoutingFees {
6226+
base_msat: forwarding_info.fee_base_msat,
6227+
proportional_millionths: forwarding_info.fee_proportional_millionths,
6228+
},
6229+
cltv_expiry_delta: forwarding_info.cltv_expiry_delta,
6230+
htlc_minimum_msat: None,
6231+
htlc_maximum_msat: None,
6232+
},
6233+
]);
6234+
6235+
let route_params = RouteParameters::from_payment_params_and_value(
6236+
PaymentParameters::from_node_id(dummy_payee, MIN_FINAL_CLTV_EXPIRY_DELTA as u32)
6237+
.with_route_hints(vec![route_hint])
6238+
.map_err(|_| RetryableSendFailure::RouteNotFound)?,
6239+
amount_msat,
6240+
);
6241+
6242+
let first_hops: [&ChannelDetails; 1] = [out_chan];
6243+
let inflight_htlcs = self.compute_inflight_htlcs();
6244+
let mut route = self
6245+
.router
6246+
.find_route(&our_node_id, &route_params, Some(&first_hops), inflight_htlcs)
6247+
.map_err(|_| RetryableSendFailure::RouteNotFound)?;
6248+
6249+
for path in route.paths.iter_mut() {
6250+
if let Some(last) = path.hops.last_mut() {
6251+
last.pubkey = our_node_id;
6252+
}
6253+
}
6254+
6255+
let preimage = PaymentPreimage(self.entropy_source.get_secure_random_bytes());
6256+
let onion = RecipientOnionFields::spontaneous_empty(amount_msat);
6257+
self.send_spontaneous_payment_with_route(route, Some(preimage), onion, payment_id)
6258+
}
6259+
61186260
/// Send a payment that is probing the given route for liquidity. We calculate the
61196261
/// [`PaymentHash`] of probes based on a static secret and a random [`PaymentId`], which allows
61206262
/// 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)