You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
Watchtower support: expose channel monitor data for external watchtower implementations #813
We've implemented an LDK-to-LND watchtower bridge that lets ldk-node mobile clients use existing LND watchtower infrastructure for channel protection. This is running in production on Android (GrapheneOS) with verified blob pushes to a live LND tower.
We'd like to upstream the ldk-node side of this, which is minimal and opt-in.
Problem
Mobile Lightning nodes are intermittently offline. LND has mature watchtower infrastructure deployed on thousands of home nodes (Umbrel, Start9, RaspiBlitz), but there's no way for ldk-node clients to use it.
What we built
In ldk-node (proposed upstream, ~300 lines):
WatchtowerPersister: wraps MonitorUpdatingPersister, intercepts persist_new_channel and update_persisted_channel callbacks to capture commitment data in real time
ldk-watchtower-client: LND wire protocol (Brontide/Noise_XK with secp256k1), justice blob encryption, embedded Tor via Arti for direct .onion connections
I'm not 100% sure if we want to add support for the LND-proprietary watchtower model (at a minimum there are better ways to do watchtowers - its weird that the LND watchtower model both breaks your privacy and doesn't enforce HTLCs, if its not gonna enforce HTLCs it really should offer privacy!), but we definitely want to improve the watchtower API in LDK so that most of your code can go upstream and you can have a much, much, much simpler patchset to maintain.
As you discovered, using the ChannelMonitor "watchtower" API from lightningdevkit/rust-lightning#2337 requires tracking a bunch of state outside the ChannelMonitor and firuging out how to track (and persist!) that state.
There was a followup attempted at lightningdevkit/rust-lightning#2552 to clean up that API so that that tracking can be done inside the ChannelMonitor and fetching the justice data is a simple call into the updated ChannelMonitor but sadly the original author moved on to other projects before finishing it. Do you have any interest in picking that back up?
Agreed on the LND tower model's privacy trade-offs. The reason I went with it is purely pragmatic: it's what's deployed on most home nodes today, and the target user for this project already has an Umbrel/Start9 running LND. Meeting users where their infrastructure is.
I'll review #2552 and see where it left off. The external state tracking was the most painful part of the implementation, so having that handled inside ChannelMonitor would be a big improvement. I've been through the persist callbacks and the signing flow enough to have opinions on where the abstractions should sit.
I should be upfront that I'm currently unfunded on this project, so my bandwidth is limited. But I'm definitely interested in contributing what I can toward cleaning up the watchtower API. Happy to share what I learned from building the bridge if that's useful context for the design.
Reviewed #2552 and the review thread. The PR takes an external utility approach (JusticeTxTracker alongside the Persist impl), but your feedback suggested moving the state tracking inside ChannelMonitor itself so extracting justice data becomes a single call with no external storage. That aligns with my experience: the external state tracking was the most complex part of my implementation. I'll look at what it would take to store the latest counterparty commitment inside ChannelMonitor and expose a method to extract signed justice txs directly.
Short version: adds ChannelMonitor::get_justice_txs(feerate_per_kw, destination_script) returning signed justice transactions for all revoked counterparty commitments in a single call. No external queue or state tracking needed. The test WatchtowerPersister drops from ~80 lines of queue management to ~10.
A few design questions in the PR description where I'd appreciate input before finalizing.
I'm not 100% sure if we want to add support for the LND-proprietary watchtower model (at a minimum there are better ways to do watchtowers - its weird that the LND watchtower model both breaks your privacy and doesn't enforce HTLCs, if its not gonna enforce HTLCs it really should offer privacy!), but we definitely want to improve the watchtower API in LDK so that most of your code can go upstream and you can have a much, much, much simpler patchset to maintain.
As you discovered, using the ChannelMonitor "watchtower" API from lightningdevkit/rust-lightning#2337 requires tracking a bunch of state outside the ChannelMonitor and firuging out how to track (and persist!) that state.
There was a followup attempted at lightningdevkit/rust-lightning#2552 to clean up that API so that that tracking can be done inside the ChannelMonitor and fetching the justice data is a simple call into the updated ChannelMonitor but sadly the original author moved on to other projects before finishing it. Do you have any interest in picking that back up?
I agree here - thank you for the interest, watchtowers are definitely something we want to support (finally).
However, @FreeOnlineUser if you intend to pick some of this up, please make sure to coordinate with @enigbe who also intends to work on finishing watchtower support for LDK/LDK Node in the coming months.
Thanks @tnull. I put up a draft at lightningdevkit/rust-lightning#4453 as a starting point, but happy to defer to @enigbe on direction here. My watchtower integration already works via an ldk-node fork, so this is more about reducing the downstream patchset than anything urgent. If the approach or API needs to change, no problem. Happy to help however's useful.
Zeus (mobile, embedded ldk-node) is interested in this too. Our users are offline most of the time, so justice tx coverage is the main thing we lack compared to our LND-backed mode.
Right now ChainMonitor in src/types.rs is fixed to MonitorUpdatingPersister, so an app can't wrap the persister without forking. Even before a tower protocol is chosen, would you take a small change that lets the builder accept a Persist wrapper, or some other hook that exposes counterparty commitment data from each monitor update?
I saw rust-lightning#4453 was closed. @enigbe, is there a current branch or design for the LDK-side API that we could test against or help with?
#4453 isn't dead, it moved. rust-lightning PR development went to the project's self-hosted forge in July and the GitHub PRs were auto-closed in the migration (GitHub is a mirror now). It's live at https://git.rust-bitcoin.org/lightningdevkit/rust-lightning/pulls/4453 with review ongoing: the monitor retains revoked counterparty commitments internally and produces signed justice transactions on demand, and ChainMonitor takes a WatchtowerPersist (sync or async) that gets offered the pending set after each revocation, crash-safe across restarts. All review rounds to date are addressed and it's queued for another maintainer pass.
The ldk-node side is then mostly builder plumbing to register a persister; I run a working version of that in my fork today. Once the rust-lightning API settles I plan to bring that up here as a follow-up.
Activity
TheBlueMatt commentedon Mar 1, 2026
Cool!
I'm not 100% sure if we want to add support for the LND-proprietary watchtower model (at a minimum there are better ways to do watchtowers - its weird that the LND watchtower model both breaks your privacy and doesn't enforce HTLCs, if its not gonna enforce HTLCs it really should offer privacy!), but we definitely want to improve the watchtower API in LDK so that most of your code can go upstream and you can have a much, much, much simpler patchset to maintain.
As you discovered, using the
ChannelMonitor"watchtower" API from lightningdevkit/rust-lightning#2337 requires tracking a bunch of state outside theChannelMonitorand firuging out how to track (and persist!) that state.There was a followup attempted at lightningdevkit/rust-lightning#2552 to clean up that API so that that tracking can be done inside the
ChannelMonitorand fetching the justice data is a simple call into the updatedChannelMonitorbut sadly the original author moved on to other projects before finishing it. Do you have any interest in picking that back up?FreeOnlineUser commentedon Mar 1, 2026
Thanks Matt. Appreciate the detailed response.
Agreed on the LND tower model's privacy trade-offs. The reason I went with it is purely pragmatic: it's what's deployed on most home nodes today, and the target user for this project already has an Umbrel/Start9 running LND. Meeting users where their infrastructure is.
I'll review #2552 and see where it left off. The external state tracking was the most painful part of the implementation, so having that handled inside ChannelMonitor would be a big improvement. I've been through the persist callbacks and the signing flow enough to have opinions on where the abstractions should sit.
I should be upfront that I'm currently unfunded on this project, so my bandwidth is limited. But I'm definitely interested in contributing what I can toward cleaning up the watchtower API. Happy to share what I learned from building the bridge if that's useful context for the design.
FreeOnlineUser commentedon Mar 1, 2026
Reviewed #2552 and the review thread. The PR takes an external utility approach (JusticeTxTracker alongside the Persist impl), but your feedback suggested moving the state tracking inside ChannelMonitor itself so extracting justice data becomes a single call with no external storage. That aligns with my experience: the external state tracking was the most complex part of my implementation. I'll look at what it would take to store the latest counterparty commitment inside ChannelMonitor and expose a method to extract signed justice txs directly.
FreeOnlineUser commentedon Mar 1, 2026
Following up on @TheBlueMatt's suggestion to move justice tx state tracking inside
ChannelMonitorrather than using an external utility.I've prototyped this approach against current rust-lightning main. Draft PR here: lightningdevkit/rust-lightning#4453
Short version: adds
ChannelMonitor::get_justice_txs(feerate_per_kw, destination_script)returning signed justice transactions for all revoked counterparty commitments in a single call. No external queue or state tracking needed. The testWatchtowerPersisterdrops from ~80 lines of queue management to ~10.A few design questions in the PR description where I'd appreciate input before finalizing.
tnull commentedon Mar 2, 2026
I agree here - thank you for the interest, watchtowers are definitely something we want to support (finally).
However, @FreeOnlineUser if you intend to pick some of this up, please make sure to coordinate with @enigbe who also intends to work on finishing watchtower support for LDK/LDK Node in the coming months.
FreeOnlineUser commentedon Mar 2, 2026
Thanks @tnull. I put up a draft at lightningdevkit/rust-lightning#4453 as a starting point, but happy to defer to @enigbe on direction here. My watchtower integration already works via an ldk-node fork, so this is more about reducing the downstream patchset than anything urgent. If the approach or API needs to change, no problem. Happy to help however's useful.
Really enjoying building on ldk-node, great work.
Add ChannelMonitor justice tx API for simplified watchtower integration
Add ChannelMonitor justice tx API for simplified watchtower integration
Add ChannelMonitor justice tx API for simplified watchtower integration
Add ChannelMonitor justice tx API for simplified watchtower integration
Add ChannelMonitor justice tx API for simplified watchtower integration
Add ChannelMonitor justice tx API for simplified watchtower integration
Add ChannelMonitor justice tx API for simplified watchtower integration
Add ChannelMonitor justice tx API for simplified watchtower integration
Add ChannelMonitor justice tx API for simplified watchtower integration
1 remaining item
Add ChannelMonitor justice tx API for simplified watchtower integration
Add ChannelMonitor justice tx API for simplified watchtower integration
Add ChannelMonitor justice tx API for simplified watchtower integration
Add ChannelMonitor justice tx API for simplified watchtower integration
Add ChannelMonitor justice tx API for simplified watchtower integration
Add ChannelMonitor justice tx API for simplified watchtower integration
Add ChannelMonitor justice tx API for simplified watchtower integration
Add ChannelMonitor justice tx API for simplified watchtower integration
Add ChannelMonitor justice tx API for simplified watchtower integration
Add ChannelMonitor justice tx API for simplified watchtower integration
Add ChannelMonitor justice tx API for simplified watchtower integration
Add ChannelMonitor justice tx API for simplified watchtower integration
kaloudis commentedon Sep 25, 2026
Zeus (mobile, embedded ldk-node) is interested in this too. Our users are offline most of the time, so justice tx coverage is the main thing we lack compared to our LND-backed mode.
Right now
ChainMonitorinsrc/types.rsis fixed toMonitorUpdatingPersister, so an app can't wrap the persister without forking. Even before a tower protocol is chosen, would you take a small change that lets the builder accept aPersistwrapper, or some other hook that exposes counterparty commitment data from each monitor update?I saw rust-lightning#4453 was closed. @enigbe, is there a current branch or design for the LDK-side API that we could test against or help with?
FreeOnlineUser commentedon Sep 26, 2026
#4453 isn't dead, it moved. rust-lightning PR development went to the project's self-hosted forge in July and the GitHub PRs were auto-closed in the migration (GitHub is a mirror now). It's live at https://git.rust-bitcoin.org/lightningdevkit/rust-lightning/pulls/4453 with review ongoing: the monitor retains revoked counterparty commitments internally and produces signed justice transactions on demand, and
ChainMonitortakes aWatchtowerPersist(sync or async) that gets offered the pending set after each revocation, crash-safe across restarts. All review rounds to date are addressed and it's queued for another maintainer pass.The ldk-node side is then mostly builder plumbing to register a persister; I run a working version of that in my fork today. Once the rust-lightning API settles I plan to bring that up here as a follow-up.
enigbe commentedon Sep 28, 2026
@kaloudis: I see @FreeOnlineUser, who has been driving all the work on this, has already responded.