Skip to content

PoC: integration test for option_htlcs_claim_tx#958

Closed
darosior wants to merge 17 commits into
lightningdevkit:mainfrom
darosior:templatehash_pinning
Closed

PoC: integration test for option_htlcs_claim_tx#958
darosior wants to merge 17 commits into
lightningdevkit:mainfrom
darosior:templatehash_pinning

Conversation

@darosior

@darosior darosior commented Jul 1, 2026

Copy link
Copy Markdown

This uses LDK-node's integration test framework to demonstrate the implementation of option_htlcs_claim_tx (leverage OP_TEMPLATEHASH to close the last open pinning gap in today's lightning channels, see bip448/bolts#2) in rust-lightning (see bip448/rust-lightning#1). This implementation is based on PR 660 to LDK-node, which implements option_zero_fee_commitment.

The integration test expects a TEMPLATEHASH-aware binary. Until the next release is available, you would have to build Bitcoin Inquisition's 29.x branch yourself. Then the integration test from this PR may be run like so:

BITCOIND_EXE=/path/to/inquisition-bitcoind cargo test --test integration_tests_htlcs_claim_tx -- --nocapture

tankyleo and others added 17 commits June 30, 2026 16:03
We only do this for rust files, and leave bat files untouched.

Use git show --ignore-cr-at-eol to check that this commit has no other
edits.
`BroadcasterInterface::broadcast_transactions` requires that any passed
vector containing multiple transactions must be a single child together
with its parents. We will lean on this contract in upcoming commits, so
here we fix a case where we broke this contract.
In an upcoming commit, we will fix `check_sufficient_funds_for_channel`
to check that we have on-chain funds to cover the anchor reserve for an
additional anchor channel in the validation of outbound channel opens.

Before we do this, we stop using this function to check that any
splice-ins leave enough on-chain anchor reserves. This function keeps
an anchor reserve for an additional anchor channel on top of the
existing set of anchor channels, but after splice-ins, our anchor
reserve only needs to cover the existing set of anchor channels.
When we are preparing to open a channel to a peer, we should reserve
onchain funds for an anchor channel when the peer's init features
signals anchor channels as optional, as channel negotiation with such a
peer can result in an anchor channel.

Tests written with codex.
The patch adds support for the `broadcast_package` method added in
electrum protocol v1.6. Upcoming commits will require this patch to pass
CI.
The mempool/electrs docker image used in those tests only supports
submitpackage via the esplora interface, not the electrum interface.
We bump the Bitcoin Core version used in kotlin and python tests to
support ephemeral dust. This is required for 0FC channels.
Do this roundtrip at the same time we make a roundtrip to retrieve the
feerates to keep startup as fast as possible.
Implementations of `BroadcasterInterface` cannot assume any topological
ordering on the transactions received, so here we order the received
transactions before adding them to the broadcast queue. Any consumers of
the queue can now assume all transactions received to be topologically
sorted.

Codex wrote the tests.
These will be useful when we add support for broadcasting packages in an
upcoming commit.
We rely on the `BroadcasterInterface` contract whereby any
multi-transaction vector must be a single child and its parents, and
must be broadcasted together as a package using `submitpackage`.

In a prior commit, we added the guarantee that any packages received
from the broadcast queue are already topologically sorted, and hence
can be passed directly to the `submit_package` Bitcoin Core RPC.
We previously allowed users to unset the anchor channels config while
they still had anchor channels open or unresolved. This allowed our
users to drain their anchor reserves while still having anchor channels
open. This is particularly dangerous for 0FC channels, as these rely
entirely on anchor bumps to force-close the channel.

Here, we require that a user first close and resolve all their existing
anchor channels before unsetting their anchor channels config to disable
the opening of fresh anchor channels.
Point the `lightning*` dependencies at the public `templatehash_antipinning`
rust-lightning branch (upstream main + the `option_htlcs_claim_tx` /
OP_TEMPLATEHASH anti-pinning work) via `[patch]`, and add a `[patch.crates-io]`
entry for the `bitcoin` fork carrying the OP_TEMPLATEHASH opcode and BIP-446
digest. Adapt to the API drift this pulls in:

- The `impl_writeable_tlv_based*` macros were renamed to `impl_ser_tlv_based*`.
- `Bolt11Invoice::recover_payee_pub_key` is now fallible; use the infallible
  `get_payee_pub_key`.
- `OnionMessenger::new` takes an extra "intercept messages to unknown SCIDs"
  bool; pass `false` since our mailbox can only key by node id.
- `OnionMessageIntercepted` now carries a `NextMessageHop` instead of a peer
  node id; mailbox `NodeId` hops and drop `ShortChannelId` ones.
- Handle the new (no-op, delegated to the bump-tx handler)
  `BumpTransactionEvent::HTLCsClaimTxResolution` variant.

This work was done with the assistance of Claude Code (Claude Opus 4.8).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Integrate the experimental `option_htlcs_claim_tx` channel type
(OP_TEMPLATEHASH / BIP-446/448 anti-pinning, building on
`option_zero_fee_commitments`) end-to-end into ldk-node.

- Add `AnchorChannelsConfig::negotiate_htlcs_claim_tx` (default off) and wire
  it into the LDK `ChannelHandshakeConfig`, so channels negotiate the type on
  top of zero-fee commitments when requested.
- On-chain resolution of an offered HTLC via the preimage path broadcasts the
  fixed zero-fee v3 claim transaction plus its CPFP child as a TRUC
  1-parent-1-child package (built, signed and broadcast by the delegated
  `BumpTransactionEventHandler`).
- Add an end-to-end integration test
  (`tests/integration_tests_htlcs_claim_tx.rs`) that force-closes an
  `option_htlcs_claim_tx` channel and resolves an offered HTLC via the
  templated preimage path.

Run the `option_htlcs_claim_tx` end-to-end test against a Bitcoin
Inquisition build (>= v29.2) that activates `OP_TEMPLATEHASH` on regtest
from genesis, instead of stock `bitcoind` with the templated claim mined
out-of-band via `generateblock`.

Add `setup_bitcoind_and_electrsd_with_exe_and_args` so a test can launch
a caller-specified daemon; the test resolves the binary from
`BITCOIND_TEMPLATEHASH_EXE` (defaulting to `../bitcoin-inquisition`) and
skips itself if none is available.

This work was done with the assistance of Claude Code (Claude Opus 4.8).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@darosior

darosior commented Jul 1, 2026

Copy link
Copy Markdown
Author

Wrong upstream, sorry about that!

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants