Skip to content

Commit abca32a

Browse files
committed
feat: add ProbeHandle and probe success/failure events
1 parent afcebdf commit abca32a

8 files changed

Lines changed: 304 additions & 60 deletions

File tree

.gitignore

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,9 @@ swift.swiftdoc
2929
*.a
3030
*.d
3131

32+
# macOS Finder metadata (icon positions, view options)
33+
.DS_Store
34+
3235
# IDE and local files
3336
.idea
3437
.build

bindings/ldk_node.udl

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -235,9 +235,9 @@ interface Bolt11Payment {
235235
[Throws=NodeError]
236236
PaymentId send_using_amount([ByRef]Bolt11Invoice invoice, u64 amount_msat, RouteParametersConfig? route_parameters);
237237
[Throws=NodeError]
238-
void send_probes([ByRef]Bolt11Invoice invoice, RouteParametersConfig? route_parameters);
238+
sequence<ProbeHandle> send_probes([ByRef]Bolt11Invoice invoice, RouteParametersConfig? route_parameters);
239239
[Throws=NodeError]
240-
void send_probes_using_amount([ByRef]Bolt11Invoice invoice, u64 amount_msat, RouteParametersConfig? route_parameters);
240+
sequence<ProbeHandle> send_probes_using_amount([ByRef]Bolt11Invoice invoice, u64 amount_msat, RouteParametersConfig? route_parameters);
241241
[Throws=NodeError]
242242
void claim_for_hash(PaymentHash payment_hash, u64 claimable_amount_msat, PaymentPreimage preimage);
243243
[Throws=NodeError]
@@ -295,7 +295,7 @@ interface SpontaneousPayment {
295295
[Throws=NodeError]
296296
PaymentId send_with_preimage_and_custom_tlvs(u64 amount_msat, PublicKey node_id, sequence<CustomTlvRecord> custom_tlvs, PaymentPreimage preimage, RouteParametersConfig? route_parameters);
297297
[Throws=NodeError]
298-
void send_probes(u64 amount_msat, PublicKey node_id);
298+
sequence<ProbeHandle> send_probes(u64 amount_msat, PublicKey node_id);
299299
};
300300

301301
interface OnchainPayment {
@@ -518,6 +518,8 @@ interface Event {
518518
PaymentClaimable(PaymentId payment_id, PaymentHash payment_hash, u64 claimable_amount_msat, u32? claim_deadline, sequence<CustomTlvRecord> custom_records);
519519
PaymentForwarded(ChannelId prev_channel_id, ChannelId next_channel_id, UserChannelId?
520520
prev_user_channel_id, UserChannelId? next_user_channel_id, PublicKey? prev_node_id, PublicKey? next_node_id, u64? total_fee_earned_msat, u64? skimmed_fee_msat, boolean claim_from_onchain_tx, u64? outbound_amount_forwarded_msat);
521+
ProbeSuccessful(PaymentId payment_id, PaymentHash payment_hash);
522+
ProbeFailed(PaymentId payment_id, PaymentHash payment_hash, u64? short_channel_id);
521523
ChannelPending(ChannelId channel_id, UserChannelId user_channel_id, ChannelId former_temporary_channel_id, PublicKey counterparty_node_id, OutPoint funding_txo);
522524
ChannelReady(ChannelId channel_id, UserChannelId user_channel_id, PublicKey? counterparty_node_id, OutPoint? funding_txo);
523525
ChannelClosed(ChannelId channel_id, UserChannelId user_channel_id, PublicKey? counterparty_node_id, ClosureReason? reason);
@@ -621,6 +623,11 @@ dictionary RouteParametersConfig {
621623
u8 max_channel_saturation_power_of_half;
622624
};
623625

626+
dictionary ProbeHandle {
627+
PaymentHash payment_hash;
628+
PaymentId payment_id;
629+
};
630+
624631
dictionary CustomTlvRecord {
625632
u64 type_num;
626633
sequence<u8> value;

bindings/swift/Sources/LDKNode/LDKNode.swift

Lines changed: 144 additions & 41 deletions
Large diffs are not rendered by default.

src/event.rs

Lines changed: 81 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -418,6 +418,22 @@ pub enum Event {
418418
/// Custom TLV records attached to the payment
419419
custom_records: Vec<CustomTlvRecord>,
420420
},
421+
/// A sent payment probe was successful.
422+
ProbeSuccessful {
423+
/// A local identifier used to track the probe.
424+
payment_id: PaymentId,
425+
/// The hash of the probe payment.
426+
payment_hash: PaymentHash,
427+
},
428+
/// A sent payment probe has failed.
429+
ProbeFailed {
430+
/// A local identifier used to track the probe.
431+
payment_id: PaymentId,
432+
/// The hash of the probe payment.
433+
payment_hash: PaymentHash,
434+
/// The channel responsible for the failed probe, if known.
435+
short_channel_id: Option<u64>,
436+
},
421437
/// A channel has been created and is pending confirmation on-chain.
422438
ChannelPending {
423439
/// The `channel_id` of the channel.
@@ -944,6 +960,15 @@ impl_writeable_tlv_based_enum!(Event,
944960
(6, new_total_onchain_balance_sats, required),
945961
(8, old_total_lightning_balance_sats, required),
946962
(10, new_total_lightning_balance_sats, required),
963+
},
964+
(18, ProbeSuccessful) => {
965+
(0, payment_hash, required),
966+
(2, payment_id, required),
967+
},
968+
(19, ProbeFailed) => {
969+
(0, payment_hash, required),
970+
(1, short_channel_id, option),
971+
(3, payment_id, required),
947972
}
948973
);
949974

@@ -1779,8 +1804,26 @@ where
17791804

17801805
LdkEvent::PaymentPathSuccessful { .. } => {},
17811806
LdkEvent::PaymentPathFailed { .. } => {},
1782-
LdkEvent::ProbeSuccessful { .. } => {},
1783-
LdkEvent::ProbeFailed { .. } => {},
1807+
LdkEvent::ProbeSuccessful { payment_id, payment_hash, .. } => {
1808+
let event = Event::ProbeSuccessful { payment_id, payment_hash };
1809+
match self.event_queue.add_event(event).await {
1810+
Ok(_) => {},
1811+
Err(e) => {
1812+
log_error!(self.logger, "Failed to push probe event: {}", e);
1813+
return Err(ReplayEvent());
1814+
},
1815+
}
1816+
},
1817+
LdkEvent::ProbeFailed { payment_id, payment_hash, short_channel_id, .. } => {
1818+
let event = Event::ProbeFailed { payment_id, payment_hash, short_channel_id };
1819+
match self.event_queue.add_event(event).await {
1820+
Ok(_) => {},
1821+
Err(e) => {
1822+
log_error!(self.logger, "Failed to push probe event: {}", e);
1823+
return Err(ReplayEvent());
1824+
},
1825+
}
1826+
},
17841827
LdkEvent::HTLCHandlingFailed { failure_type, .. } => {
17851828
if let Some(liquidity_source) = self.liquidity_source.as_ref() {
17861829
liquidity_source.handle_htlc_handling_failed(failure_type).await;
@@ -2573,4 +2616,40 @@ mod tests {
25732616
}
25742617
assert_eq!(event_queue.next_event(), None);
25752618
}
2619+
2620+
#[tokio::test]
2621+
async fn probe_events_persistence_roundtrip() {
2622+
let store: Arc<DynStore> = Arc::new(InMemoryStore::new());
2623+
let logger = Arc::new(TestLogger::new());
2624+
let event_queue = Arc::new(EventQueue::new(Arc::clone(&store), Arc::clone(&logger)));
2625+
2626+
assert_eq!(event_queue.next_event(), None);
2627+
2628+
let payment_hash = PaymentHash([7u8; 32]);
2629+
let payment_id = PaymentId(payment_hash.0);
2630+
let expected_event =
2631+
Event::ProbeFailed { payment_id, payment_hash, short_channel_id: Some(42) };
2632+
2633+
event_queue.add_event(expected_event.clone()).await.unwrap();
2634+
2635+
// Check we get the expected event and that it is returned until handled.
2636+
assert_eq!(event_queue.next_event_async().await, expected_event);
2637+
assert_eq!(event_queue.next_event(), Some(expected_event.clone()));
2638+
2639+
// Check we can read back what we persisted.
2640+
let persisted_bytes = KVStore::read(
2641+
&*store,
2642+
EVENT_QUEUE_PERSISTENCE_PRIMARY_NAMESPACE,
2643+
EVENT_QUEUE_PERSISTENCE_SECONDARY_NAMESPACE,
2644+
EVENT_QUEUE_PERSISTENCE_KEY,
2645+
)
2646+
.await
2647+
.unwrap();
2648+
let deser_event_queue =
2649+
EventQueue::read(&mut &persisted_bytes[..], (Arc::clone(&store), logger)).unwrap();
2650+
assert_eq!(deser_event_queue.next_event_async().await, expected_event);
2651+
2652+
event_queue.event_handled().await.unwrap();
2653+
assert_eq!(event_queue.next_event(), None);
2654+
}
25762655
}

src/lib.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -96,6 +96,7 @@ pub mod logger;
9696
mod message_handler;
9797
pub mod payment;
9898
mod peer_store;
99+
mod probe_handle;
99100
mod runtime;
100101
mod scoring;
101102
mod tx_broadcaster;
@@ -161,6 +162,7 @@ use payment::{
161162
UnifiedQrPayment,
162163
};
163164
use peer_store::{PeerInfo, PeerStore};
165+
pub use probe_handle::ProbeHandle;
164166
use rand::Rng;
165167
use runtime::Runtime;
166168
use types::{

src/payment/bolt11.rs

Lines changed: 22 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@ use crate::payment::store::{
3838
use crate::peer_store::{PeerInfo, PeerStore};
3939
use crate::runtime::Runtime;
4040
use crate::types::{ChannelManager, PaymentStore, Router};
41+
use crate::ProbeHandle;
4142
#[cfg(not(feature = "uniffi"))]
4243
type Bolt11Invoice = LdkBolt11Invoice;
4344
#[cfg(feature = "uniffi")]
@@ -834,9 +835,13 @@ impl Bolt11Payment {
834835
///
835836
/// If `route_parameters` are provided they will override the default as well as the
836837
/// node-wide parameters configured via [`Config::route_parameters`] on a per-field basis.
838+
///
839+
/// Returns one [`ProbeHandle`] per probe path. Use [`ProbeHandle::payment_id`] (and/or
840+
/// [`ProbeHandle::payment_hash`]) to match [`crate::Event::ProbeSuccessful`] /
841+
/// [`crate::Event::ProbeFailed`]. These values are **not** the invoice payment hash.
837842
pub fn send_probes(
838843
&self, invoice: &Bolt11Invoice, route_parameters: Option<RouteParametersConfig>,
839-
) -> Result<(), Error> {
844+
) -> Result<Vec<ProbeHandle>, Error> {
840845
if !*self.is_running.read().unwrap() {
841846
return Err(Error::NotRunning);
842847
}
@@ -870,12 +875,16 @@ impl Bolt11Payment {
870875

871876
self.channel_manager
872877
.send_preflight_probes(route_params, liquidity_limit_multiplier)
878+
.map(|pairs| {
879+
pairs
880+
.into_iter()
881+
.map(|(payment_hash, payment_id)| ProbeHandle { payment_hash, payment_id })
882+
.collect()
883+
})
873884
.map_err(|e| {
874885
log_error!(self.logger, "Failed to send payment probes: {:?}", e);
875886
Error::ProbeSendingFailed
876-
})?;
877-
878-
Ok(())
887+
})
879888
}
880889

881890
/// Sends payment probes over all paths of a route that would be used to pay the given
@@ -887,11 +896,11 @@ impl Bolt11Payment {
887896
/// If `route_parameters` are provided they will override the default as well as the
888897
/// node-wide parameters configured via [`Config::route_parameters`] on a per-field basis.
889898
///
890-
/// See [`Self::send_probes`] for more information.
899+
/// See [`Self::send_probes`] for return value semantics and correlation with probe events.
891900
pub fn send_probes_using_amount(
892901
&self, invoice: &Bolt11Invoice, amount_msat: u64,
893902
route_parameters: Option<RouteParametersConfig>,
894-
) -> Result<(), Error> {
903+
) -> Result<Vec<ProbeHandle>, Error> {
895904
if !*self.is_running.read().unwrap() {
896905
return Err(Error::NotRunning);
897906
}
@@ -932,12 +941,16 @@ impl Bolt11Payment {
932941

933942
self.channel_manager
934943
.send_preflight_probes(route_params, liquidity_limit_multiplier)
944+
.map(|pairs| {
945+
pairs
946+
.into_iter()
947+
.map(|(payment_hash, payment_id)| ProbeHandle { payment_hash, payment_id })
948+
.collect()
949+
})
935950
.map_err(|e| {
936951
log_error!(self.logger, "Failed to send payment probes: {:?}", e);
937952
Error::ProbeSendingFailed
938-
})?;
939-
940-
Ok(())
953+
})
941954
}
942955

943956
/// Estimates the routing fees for a given invoice.

src/payment/spontaneous.rs

Lines changed: 13 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ use crate::error::Error;
2020
use crate::logger::{log_error, log_info, LdkLogger, Logger};
2121
use crate::payment::store::{PaymentDetails, PaymentDirection, PaymentKind, PaymentStatus};
2222
use crate::types::{ChannelManager, CustomTlvRecord, KeysManager, PaymentStore};
23+
use crate::ProbeHandle;
2324

2425
// The default `final_cltv_expiry_delta` we apply when not set.
2526
const LDK_DEFAULT_FINAL_CLTV_EXPIRY_DELTA: u32 = 144;
@@ -191,10 +192,13 @@ impl SpontaneousPayment {
191192
/// Sends payment probes over all paths of a route that would be used to pay the given
192193
/// amount to the given `node_id`.
193194
///
194-
/// See [`Bolt11Payment::send_probes`] for more information.
195+
/// See [`Bolt11Payment::send_probes`] for semantics and how returned [`ProbeHandle`] values
196+
/// correlate with [`crate::Event::ProbeSuccessful`] / [`crate::Event::ProbeFailed`].
195197
///
196198
/// [`Bolt11Payment::send_probes`]: crate::payment::Bolt11Payment
197-
pub fn send_probes(&self, amount_msat: u64, node_id: PublicKey) -> Result<(), Error> {
199+
pub fn send_probes(
200+
&self, amount_msat: u64, node_id: PublicKey,
201+
) -> Result<Vec<ProbeHandle>, Error> {
198202
if !*self.is_running.read().unwrap() {
199203
return Err(Error::NotRunning);
200204
}
@@ -208,11 +212,15 @@ impl SpontaneousPayment {
208212
LDK_DEFAULT_FINAL_CLTV_EXPIRY_DELTA,
209213
liquidity_limit_multiplier,
210214
)
215+
.map(|pairs| {
216+
pairs
217+
.into_iter()
218+
.map(|(payment_hash, payment_id)| ProbeHandle { payment_hash, payment_id })
219+
.collect()
220+
})
211221
.map_err(|e| {
212222
log_error!(self.logger, "Failed to send payment probes: {:?}", e);
213223
Error::ProbeSendingFailed
214-
})?;
215-
216-
Ok(())
224+
})
217225
}
218226
}

src/probe_handle.rs

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
// This file is Copyright its original authors, visible in version control history.
2+
//
3+
// This file is licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
4+
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license <LICENSE-MIT or
5+
// http://opensource.org/licenses/MIT>, at your option. You may not use this file except in
6+
// accordance with one or both of these licenses.
7+
8+
//! Handle returned when sending pre-flight probes.
9+
//!
10+
//! UniFFI uses `dictionary ProbeHandle` in `bindings/ldk_node.udl`; this module defines the Rust
11+
//! struct with the same fields. With `feature = "uniffi"`, `lib.rs` must `pub use` this type
12+
//! **before** `uniffi::include_scaffolding!` so the generated `FfiConverter` impl applies here (same
13+
//! pattern as [`crate::SpendableUtxo`], [`crate::PeerDetails`], etc.).
14+
15+
use lightning::ln::channelmanager::PaymentId;
16+
use lightning_types::payment::PaymentHash;
17+
18+
/// Identifies one outbound probe; match against [`crate::Event::ProbeSuccessful`] /
19+
/// [`crate::Event::ProbeFailed`] using [`Self::payment_id`] and/or [`Self::payment_hash`].
20+
///
21+
/// The [`PaymentHash`] is **not** the BOLT11 invoice payment hash; LDK generates a synthetic hash
22+
/// per probe.
23+
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
24+
pub struct ProbeHandle {
25+
/// Synthetic probe payment hash (not the BOLT11 invoice payment hash).
26+
pub payment_hash: PaymentHash,
27+
/// Local id for this probe; matches probe [`crate::Event`] variants.
28+
pub payment_id: PaymentId,
29+
}

0 commit comments

Comments
 (0)