Update ldk-node dependency & expose bolt12 proofs - #258
Conversation
|
👋 Thanks for assigning @tankyleo as a reviewer! |
c41e3d8 to
a0b2517
Compare
|
Rebased and updated ldk-node to new commit with the pagination changes. Now using the paginated payments instead of the ldk-server version |
tnull
left a comment
There was a problem hiding this comment.
Looks good, but we should probably also include the new channel type in list_channels now.
a0b2517 to
1a3344b
Compare
9346952 to
6076c50
Compare
| | NodeError::GossipUpdateTimeout | ||
| | NodeError::LiquiditySourceUnavailable | ||
| | NodeError::LiquidityRequestFailed | ||
| | NodeError::PayerProofCreationFailed |
There was a problem hiding this comment.
Codex wants to classify this to an InvalidRequestError rather than an InternalServerError. It's not perfect, but seems InvalidRequestError is more likely if we hit PayerProofCreationFailed
There was a problem hiding this comment.
Yeah not perfect mapping, but fixed
6076c50 to
29b7174
Compare
tankyleo
left a comment
There was a problem hiding this comment.
I asked Codex to cast a broader net, here's what it found, feel free to dismiss aggressively, but all seemed worth taking a look to me.
|
Also confirmed this patch now keeps my fans quiet on mainnet |
d498240 to
5a1a162
Compare
| &event_sender); | ||
|
|
||
| if let Some(metrics) = &metrics { | ||
| metrics.update_payments_count(true); |
There was a problem hiding this comment.
Codex found this can race with update_all_pollable_metrics and suggests to remove fn update_payments_count entirely and have update_all_pollable_metrics be the sole writer.
I then asked about the other metrics here's what it had to say:
_ Yes, but not all in the same way.
- Payment counters had the strongest race: update_all_pollable_metrics() performed an absolute store(), while update_payments_count() performed fetch_add() on the same value. That could permanently double-count or lose an
update until reconciliation.
- Balance gauges can still race: both the polling task and payment event handlers call update_all_balances(). Because both write absolute snapshots, an older, slower call could overwrite a newer snapshot. This causes
temporary staleness, not double-counting, and the next refresh corrects it.
- Channel metrics avoid this particular race: total_channels_count is event-driven, while public/private channel counts are poll-driven. They do not concurrently update the same atomic value.
- Peer count is polling-only.
For complete consistency, balance refreshes should also be serialized or assigned to a single updater. Prometheus gauges usually tolerate brief staleness, whereas payment counters decreasing or double-counting is more
operationally problematic.
| self.total_pending_payments_count.store(pending_payments_count, Ordering::Relaxed); | ||
|
|
||
| let channels_count = node.list_channels().len() as i64; | ||
| self.total_channels_count.store(channels_count, Ordering::Relaxed); |
There was a problem hiding this comment.
Codex suggests we initialize other metrics in addition to the total channels count:
Initialize poll-only metrics at startup
Seed peer and channel visibility gauges before the first delayed metrics poll.\n\nAI assistance: OpenAI Codex was used for this change.
diff --git a/ldk-server/src/util/metrics.rs b/ldk-server/src/util/metrics.rs
index 1c92b87..893b1b9 100644
--- a/ldk-server/src/util/metrics.rs
+++ b/ldk-server/src/util/metrics.rs
@@ -133,9 +133,20 @@ impl Metrics {
Err(e) => error!("Failed to initialize payment metrics: {e}"),
}
- let channels_count = node.list_channels().len() as i64;
- self.total_channels_count.store(channels_count, Ordering::Relaxed);
+ let all_channels = node.list_channels();
+ self.total_channels_count.store(all_channels.len() as i64, Ordering::Relaxed);
+
+ let public_channels_count =
+ all_channels.iter().filter(|channel_details| channel_details.is_announced).count()
+ as i64;
+ self.total_public_channels_count.store(public_channels_count, Ordering::Relaxed);
+ let private_channels_count =
+ all_channels.iter().filter(|channel_details| !channel_details.is_announced).count()
+ as i64;
+ self.total_private_channels_count.store(private_channels_count, Ordering::Relaxed);
+
+ self.update_peer_count(node);
self.update_all_balances(node);
}There was a problem hiding this comment.
fixed, collect all before a delayed poll
| Ok(None) => error!("Unable to find payment with payment ID: {payment_id}"), | ||
| Err(e) => error!("Failed to retrieve payment with payment ID {payment_id}: {e}"), |
There was a problem hiding this comment.
Grok 4.6 found this: should we call event_node.event_handled() in these branches too ?
[bug] ldk-server/src/main.rs:728 _ send_payment_event skips event_handled() when payment-store lookup fails, which can stall the event queue
Then codex:
[P1] Resolve payment events when payment lookup fails _
With an active subscriber, Ok(None) and Err(_) return without calling event_handled().
Since LDK Node repeatedly returns the queue-head event until acknowledged,
a missing or unreadable payment record blocks every later event and creates a tight retry loop.
Emit a partial event with payment: None, or otherwise explicitly acknowledge/drop the event;
use controlled backoff if retrieval errors should be retried.
There was a problem hiding this comment.
this is the suggested patch:
diff --git a/ldk-server/src/main.rs b/ldk-server/src/main.rs
index c5fd800..e1fab26 100644
--- a/ldk-server/src/main.rs
+++ b/ldk-server/src/main.rs
@@ -706,27 +706,23 @@ fn send_payment_event(
) {
if event_sender.receiver_count() == 0 {
debug!("No event subscribers connected, skipping payment event");
- if let Err(e) = event_node.event_handled() {
- error!("Failed to mark event as handled: {e}");
+ } else {
+ match event_node.payment(payment_id) {
+ Ok(Some(payment_details)) => {
+ let payment = payment_to_proto(payment_details);
+
+ let event = payment_to_event(payment);
+ if let Err(e) = event_sender.send(EventEnvelope { event: Some(event) }) {
+ debug!("No event subscribers connected, skipping event: {e}");
+ }
+ },
+ Ok(None) => error!("Unable to find payment with payment ID: {payment_id}"),
+ Err(e) => error!("Failed to retrieve payment with payment ID {payment_id}: {e}"),
}
- return;
}
- match event_node.payment(payment_id) {
- Ok(Some(payment_details)) => {
- let payment = payment_to_proto(payment_details);
-
- let event = payment_to_event(payment);
- if let Err(e) = event_sender.send(EventEnvelope { event: Some(event) }) {
- debug!("No event subscribers connected, skipping event: {e}");
- }
-
- if let Err(e) = event_node.event_handled() {
- error!("Failed to mark event as handled: {e}");
- }
- },
- Ok(None) => error!("Unable to find payment with payment ID: {payment_id}"),
- Err(e) => error!("Failed to retrieve payment with payment ID {payment_id}: {e}"),
+ if let Err(e) = event_node.event_handled() {
+ error!("Failed to mark event as handled: {e}");
}
}|
Also fixed the proto definition to be a single string for the |
ffdb37f to
fba0e98
Compare
tankyleo
left a comment
There was a problem hiding this comment.
One more comment, found with a quick "look for ways to simplify the code" with codex.
fba0e98 to
9e77526
Compare
tankyleo
left a comment
There was a problem hiding this comment.
Good to squash thanks, also filed this issue in ldk-node to get clarification on this claimable_amount_msat parameter
| use ldk_server_grpc::types::HtlcLocator; | ||
| use prost::Message; | ||
|
|
||
| // Encoded with the original schema: channel_id = 1, user_channel_id = 2, node_id = 3. |
There was a problem hiding this comment.
Keep telling my AIs that we can aggressively break compat here since we haven't released yet no ?
There was a problem hiding this comment.
oh woops, missed that, will remove, maybe should put in the claude.md for now, but we are so close to release so..
9e77526 to
738caff
Compare
|
squashed and removed the backwards compat thing |
tankyleo
left a comment
There was a problem hiding this comment.
Worth breaking this PR into a few more commits I think ? No need for any changes to the contents. The comment below is from codex:
> Could you split this PR into more focused commits? 5ceee90 does considerably more than update the ldk-node dependency: it also changes the hold-invoice RPCs from hash to ID, changes payment-event schemas, renames a public
> payment field, exposes new channel/HTLC fields, and rewrites payment metrics. Likewise, 738caff combines the payment-storage migration with pagination API changes, event-acknowledgement behavior, new PaymentClaimable
> data, and unrelated CLI/metrics refactors.
>
> These are independently reviewable changes_several affecting public API or wire compatibility_and each should have a commit message explaining its intent. At minimum, please separate the dependency/API adaptation, newly
> exposed fields, payment storage and pagination migration, event-delivery behavior, and cleanup refactors. The protobuf tag compatibility fix should be kept with the isolated payment-event schema commit.
Update the pinned node API and replace hash-based claim and fail RPCs with payment-ID requests across the server, clients, CLI, and MCP. Keep manual handling enabled for unknown BOLT 11 payments. Adapt fallible payment reads, paginated metric reads, event inputs, and mnemonic generation to the new dependency. Keep event schemas and the existing metric writers unchanged here. AI assistance: OpenAI Codex was used for this change.
Rename Payment.id to Payment.payment_id for consistent naming across payment requests and responses. This changes generated client fields and JSON output; protobuf field number 1 remains unchanged. AI assistance: OpenAI Codex was used for this change.
738caff to
5554746
Compare
Add local payment IDs to payment events and expose the claimable amount for hold-invoice consumers. Explain the existing lower-bound claim check without changing its optional request parameter. Number event fields in declaration order, with payment_id first. Backward compatibility is not required yet. AI assistance: OpenAI Codex was used for this change.
Return negotiated channel-type features from ListChannels and carry incoming and outgoing HTLC amounts in forwarding records. Keep HTLC amounts optional because the node may not know them. AI assistance: OpenAI Codex was used for this change.
Add payer-proof creation to the gRPC, CLI, and MCP interfaces. Include the preimage and invoice in successful-payment events because stateless proof creation requires both values. Document the proof lifecycle and reject invalid proof inputs as request errors. AI assistance: OpenAI Codex was used for this change.
Refresh payment counters from paginated node snapshots and remove concurrent event increments. Initialize all metrics before polling and delay the first poll to avoid repeating the startup scan. AI assistance: OpenAI Codex was used for this change.
Read payment history directly from LDK Node and stop duplicating payment records in the server database. Retain the server store for forwarded-payment history. Replace structured page tokens with opaque strings across both listing APIs and adapt CLI pagination to pass them through. Existing clients must update for the token schema change. AI assistance: OpenAI Codex was used for this change.
Acknowledge payment events when no subscriber is connected or required payment details cannot be read, so the node event queue can continue. Skip payment lookups when no subscriber is connected. Document live delivery limits, state reconciliation, unrecoverable payer-proof inputs, and automatic failure of unclaimed hold payments at their claim deadline. AI assistance: OpenAI Codex was used for this change.
Move payment values into event constructors to avoid deep clones and convert event-only fields only when the constructor is called. Share channel visibility counts between initialization and polling, deriving private counts from the same snapshot. AI assistance: OpenAI Codex was used for this change.
5554746 to
54a6192
Compare
Update ldk-node and adapt payment events to its current API. Expose payment IDs in events and retain manual handling for unknown BOLT 11 payments.
Add BOLT 12 payer-proof creation to the gRPC, CLI, and MCP interfaces. Include the preimage and invoice in successful-payment events for stateless proof creation.
Use ldk-node pagination for payment history. Remove duplicate payment records from the ldk-server SQLite store, which now contains only forwarded-payment history.