Skip to content

Config: expose negotiate_anchor_zero_fee_commitments so integrators can opt into 0FC channels #987

Closed as not planned

Description

@hash-money

default_user_config sets only negotiate_anchors_zero_fee_htlc_tx (src/config.rs#L421 at current main); channel_handshake_config.negotiate_anchor_zero_fee_commitments stays at LDK's default false, and no Config/ChannelConfig surface exposes it — so two ldk-node peers can't open a zero-fee-commitment channel even experimentally, despite the pinned LDK carrying the full 0FC machinery (feerate-0 commitment, keyless P2A anchor, TRUC CPFP child).

Why we need it (not hypothetically)

We integrate ldk-node via orange-sdk in a sweep-everything mobile wallet (Phoenix model: on-chain balance ~0 at steady state). Verified on-device (Mutinynet, 2026-07-11): with the LSP offline, a force-close cannot confirm —

  1. the legacy-anchor commitment is built at the 253 sat/kW floor and never relays alone;
  2. no CPFP child can be built: with the LSP in trusted_peers_no_reserve the bump event is skipped by design, and de-trusting re-introduces the per-channel reserve — the exact thing a sweep-everything wallet can't hold (and without a reserve, coin selection finds no confirmed UTXO and aborts);
  3. nothing external can rescue it: the legacy anchor is keyed, and the anyone-can-spend branch needs 16 confirmations of the very tx that can't confirm.

0FC + P2A resolves this class without a reserve: keyless anchor, bring-your-own-fees child, pure L1 recovery even with the counterparty dead. That makes the handshake flag the first gate to a real fix for us, not a nice-to-have — the only current workaround is a fork.

Ask

A config surface that sets negotiate_anchor_zero_fee_commitments (opt-in, default off — wherever it fits your config layering). If the actual gate is that 0FC needs more than the handshake flag (e.g. the reserve/test assumptions in #875), we'd genuinely like to know what the real blocker list is.

We control both channel ends (our LSP is ldk-node too) and run staging on Mutinynet, which already relays TRUC + ephemeral anchors — so we can validate end-to-end and publish before/after results as soon as a knob exists.

Related: the trusted-peer half of this failure is filed as lightningdevkit/orange-sdk#95; the rescue-tool export surface is a separate ask (one thread per topic).

Activity

  1. hash-money commented on Jul 16, 2026

    @hash-money
    Author

    Closing for now — we're first researching a client-side alternative that needs no changes here: a recovery tool that works from the user's own encrypted state backup (seed + channel state, held as a cloud/file backup) to complete the hostile/non-cooperative exit out of band. If that path proves unworkable we may ask to reopen this with our findings. Thanks for the consideration.

  2. tankyleo commented on Jul 17, 2026

    @tankyleo
    Contributor

    @hash-money We just merged zero-fee commitments in LDK-node, see #660

  3. hash-money commented on Jul 17, 2026

    @hash-money
    Author

    Thanks for the ping @tankyleo — great to see this land, and enable_zero_fee_commitments on AnchorChannelsConfig is exactly the surface we asked for. Taking up the standing offer from the issue body: we'll validate end-to-end on Mutinynet (we control both channel ends; TRUC + ephemeral anchor relay is available there) against the exact scenario that motivated this — counterparty offline, force-close, feerate-0 commitment + keyless P2A fee child confirming with no pre-funded reserve. Will post before/after results here.

  4. hash-money commented on Jul 17, 2026

    @hash-money
    Author

    Validated #660 end-to-end on Mutinynet — thanks again for the ping, @tankyleo. Two ldk-node main (056447c) nodes, esplora chain source, enable_zero_fee_commitments = true.

    0FC negotiates and force-closes confirm as a package. A funded party force-closing with the counterparty offline: commitment 8fceb534…aac9 (fee 0, weight 648, P2A anchor + to_local) + child 6d5ee6cd…a448 (anchor + one wallet UTXO, 404 sat) via submitpackage → "success", block 3269710.

    The result that matters for a sweep-everything wallet: a zero-on-chain user can recover from an unresponsive-LSP force-close using only a backup + any fee UTXO. We modelled the real recovery: a 0FC channel where the LSP pushed the user 40k and the user holds 0 on-chain; snapshot the user's channel state (seed + monitor), kill the LSP permanently, then restore that state onto a fresh wallet and force-close with the LSP still down. The restored wallet broadcast the fee-0 commitment and CPFP'd the keyless P2A anchor with a 25k user-provided UTXO as a package (submitpackage → "success"); commitment 2c055014 (+ child 786e074f, 692 sat) confirmed block 3270210, with the user's 40k landing in to_local. Reproduced on a second independent channel (commitment b2895207, block 3270239). This is exactly the unilateral-exit path that was previously blocked — 0FC's keyless anchor is what makes it work without a per-channel reserve or any keyed-anchor tooling.

    One note for reproducers (no ask): a zero-on-chain node rejects an inbound 0FC channel on the anchor reserve (Available: 0/25000sats) and silently falls back to option_static_remotekey; trusted_peers_no_reserve is what lets it negotiate 0FC. Relatedly, a node that has its counterparty in trusted_peers_no_reserve doesn't broadcast its own force-close while that peer is offline (the BumpTransactionEvent::ChannelClose handler returns early for trusted peers — event.rs); we see the same for legacy anchors and 0FC. That's consistent with what opting into no-reserve means, and the recovery path above routes around it entirely (the restoring wallet doesn't trust the dead peer), so no change requested — just flagging in case the 0FC interaction is worth a doc note.

    Net: #660 delivers what we needed — pending the knob surfacing through orange-sdk, the unilateral-exit gap for our wallet class is closed. Thanks to everyone who worked it.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions