Skip to content

Commit 735bb99

Browse files
authored
Add HTLC forward counters for client channel monitoring (#17)
Count success/failure for HTLC forwards that touch private (client) channels. Public-to-public forwards are ignored. Counters are plain AtomicU64s so ldk-node doesn't pull in prometheus. The app layer reads snapshots via Node::forward_counters() and maps them to whatever it wants. Each forward is classified by direction (to_client vs from_client) and outcome (success, downstream failure, local failure). A separate counter tracks UnknownNextHop/InvalidForward hits, which point at SCID alias tracking bugs. Channel lookup uses is_announced on both sides. If a channel is gone by the time the event fires, we assume private. Safe default for an LSP.
1 parent f424a65 commit 735bb99

4 files changed

Lines changed: 365 additions & 4 deletions

File tree

src/builder.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1944,6 +1944,7 @@ fn build_with_store_internal(
19441944
node_metrics,
19451945
om_mailbox,
19461946
async_payments_role,
1947+
forward_counters: Arc::new(crate::ForwardCounters::new()),
19471948
})
19481949
}
19491950

src/event.rs

Lines changed: 47 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@ use crate::io::{
4040
EVENT_QUEUE_PERSISTENCE_KEY, EVENT_QUEUE_PERSISTENCE_PRIMARY_NAMESPACE,
4141
EVENT_QUEUE_PERSISTENCE_SECONDARY_NAMESPACE,
4242
};
43+
use crate::forward_metrics::ForwardCounters;
4344
use crate::liquidity::LiquiditySource;
4445
use crate::logger::{log_debug, log_error, log_info, log_trace, LdkLogger, Logger};
4546
use crate::payment::asynchronous::om_mailbox::OnionMessageMailbox;
@@ -505,6 +506,7 @@ where
505506
static_invoice_store: Option<StaticInvoiceStore>,
506507
onion_messenger: Arc<OnionMessenger>,
507508
om_mailbox: Option<Arc<OnionMessageMailbox>>,
509+
forward_counters: Arc<ForwardCounters>,
508510
}
509511

510512
impl<L: Deref + Clone + Sync + Send + 'static> EventHandler<L>
@@ -520,7 +522,7 @@ where
520522
payment_store: Arc<PaymentStore>, peer_store: Arc<PeerStore<L>>,
521523
static_invoice_store: Option<StaticInvoiceStore>, onion_messenger: Arc<OnionMessenger>,
522524
om_mailbox: Option<Arc<OnionMessageMailbox>>, runtime: Arc<Runtime>, logger: L,
523-
config: Arc<Config>,
525+
config: Arc<Config>, forward_counters: Arc<ForwardCounters>,
524526
) -> Self {
525527
Self {
526528
event_queue,
@@ -539,6 +541,7 @@ where
539541
static_invoice_store,
540542
onion_messenger,
541543
om_mailbox,
544+
forward_counters,
542545
}
543546
}
544547

@@ -1125,9 +1128,40 @@ where
11251128
LdkEvent::PaymentPathFailed { .. } => {},
11261129
LdkEvent::ProbeSuccessful { .. } => {},
11271130
LdkEvent::ProbeFailed { .. } => {},
1128-
LdkEvent::HTLCHandlingFailed { failure_type, .. } => {
1131+
LdkEvent::HTLCHandlingFailed {
1132+
prev_channel_id,
1133+
failure_type,
1134+
failure_reason,
1135+
} => {
11291136
if let Some(liquidity_source) = self.liquidity_source.as_ref() {
1130-
liquidity_source.handle_htlc_handling_failed(failure_type).await;
1137+
liquidity_source
1138+
.handle_htlc_handling_failed(failure_type.clone())
1139+
.await;
1140+
}
1141+
1142+
match &failure_type {
1143+
lightning::events::HTLCHandlingFailureType::Forward {
1144+
channel_id: next_channel_id,
1145+
..
1146+
} => {
1147+
let channels = self.channel_manager.list_channels();
1148+
if let Some(dir) = ForwardCounters::classify(
1149+
&channels,
1150+
&prev_channel_id,
1151+
next_channel_id,
1152+
) {
1153+
let is_downstream = matches!(
1154+
failure_reason,
1155+
Some(lightning::events::HTLCHandlingFailureReason::Downstream)
1156+
);
1157+
self.forward_counters.record_failure(dir, is_downstream);
1158+
}
1159+
},
1160+
lightning::events::HTLCHandlingFailureType::UnknownNextHop { .. }
1161+
| lightning::events::HTLCHandlingFailureType::InvalidForward { .. } => {
1162+
self.forward_counters.record_invalid_scid();
1163+
},
1164+
_ => {},
11311165
}
11321166
},
11331167
LdkEvent::SpendableOutputs { outputs, channel_id } => {
@@ -1308,10 +1342,11 @@ where
13081342
claim_from_onchain_tx,
13091343
outbound_amount_forwarded_msat,
13101344
} => {
1345+
let channels = self.channel_manager.list_channels();
1346+
13111347
{
13121348
let read_only_network_graph = self.network_graph.read_only();
13131349
let nodes = read_only_network_graph.nodes();
1314-
let channels = self.channel_manager.list_channels();
13151350

13161351
let node_str = |channel_id: &Option<ChannelId>| {
13171352
channel_id
@@ -1374,6 +1409,14 @@ where
13741409
.await;
13751410
}
13761411

1412+
if let (Some(prev_cid), Some(next_cid)) = (prev_channel_id, next_channel_id) {
1413+
if let Some(dir) =
1414+
ForwardCounters::classify(&channels, &prev_cid, &next_cid)
1415+
{
1416+
self.forward_counters.record_success(dir);
1417+
}
1418+
}
1419+
13771420
let event = Event::PaymentForwarded {
13781421
prev_channel_id: prev_channel_id.expect("prev_channel_id expected for events generated by LDK versions greater than 0.0.107."),
13791422
next_channel_id: next_channel_id.expect("next_channel_id expected for events generated by LDK versions greater than 0.0.107."),

0 commit comments

Comments
 (0)