sweep: add durable descriptor-backed sweeps - #11067
Draft
sputn1ck wants to merge 2 commits into
Draft
Conversation
sputn1ck
force-pushed
the
kon/descriptor-sweep-service
branch
from
August 13, 2026 12:51
cfb72f3 to
31e341c
Compare
sputn1ck
force-pushed
the
kon/descriptor-sweep-service
branch
from
August 13, 2026 13:47
cb4dbdf to
180c0be
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Change Description
This PR adds a durable WalletKit service that watches for outputs described by
fixed native P2WSH or P2TR descriptors. Once one Miniscript branch can be
satisfied, the service gives the concrete output to lnd's existing
UtxoSweeper.This separates contract discovery from contract satisfaction. An application
such as Loop can register an HTLC before it is funded, add a preimage after the
output confirms, or let a timelocked recovery branch mature. The existing
sweeper still batches compatible inputs, applies each input's fee budget, and
rebuilds the witness for every replacement transaction.
The lifecycle is:
RegisterSweepDescriptorcanonicalizes the descriptor, validates its publickeys and lnd key bindings, derives the exact script, and persists the
registration.
wrong-value matches and rejects ambiguous exact matches.
AddSweepDescriptorDatacan add an SHA256 preimage later. Block epochs alsomake height-based CLTV and block-based CSV branches available when they
mature.
that plan, its transaction constraints, its fee deadline, and, for P2TR,
the selected Taproot leaf and control block.
input.Inputobtains fresh signatures and rebuilds itswitness for every RBF candidate.
UtxoSweeperthen handles batching, feebumping, publication, and confirmation.
P2TR script paths
P2TR registrations use script paths only. The Taproot key path is never offered
to the planner, so an external internal key may remain unbound while lnd signs
the keys used by one or more leaves.
The descriptor plan supplies the selected leaf script, leaf hash, Merkle root,
and control block. The service persists the selected leaf identity so a restart
cannot silently switch to another leaf with the same locktime constraints. It
also preserves the full parity of locator-derived keys for remote signers,
creates a fresh Schnorr signature with
SigHashDefaultfor each RBF candidate,and sends remote signers the real control block after verifying that it commits
the selected leaf to the watched output.
PendingSweepsreports these inputs as the new additiveDESCRIPTOR_TR = 44witness type. Native P2WSH descriptor inputs retainDESCRIPTOR_WSH = 43.RPC and CLI surface
This PR adds:
RegisterSweepDescriptorandlncli wallet registersweepdescriptor;AddSweepDescriptorDataandlncli wallet addsweepdescriptordata; andListSweepDescriptorsandlncli wallet listsweepdescriptors.A registration includes the expected output value, chain birthday, required
confirmations, fee budget, deadline policy, and explicit mappings from
descriptor keys to lnd key locators. The list RPC reports the registration's
state, matched outpoint, sweep transaction, policy, and terminal error.
Persistence and recovery
The service stores registrations, preimages, scan cursors, discovered outputs,
frozen plans, deadlines, and lifecycle state in a versioned
kvdbbucket. Itrestores unfinished work after a restart and waits until the chain notifier and
sweeper are ready before resuming registrations.
Database transitions are store-first and copy-on-write. Transient notifier,
block-source, key-ring, database, and sweeper errors use bounded, quit-aware
retries; deterministic descriptor and satisfaction errors remain terminal.
Current scope
The first adapter deliberately accepts a narrow subset:
wsh(...)descriptors;tr(...)descriptors with Miniscript script paths;that lnd must sign;
It does not use Taproot key-path spending or accept ranged or multipath
descriptors, extended or private keys, time-based locks, registration
cancellation, or pruning.
min_confsdefines source finality; after an input isoffered to the current sweeper, this service does not roll it back across a
deeper funding reorganization.
Stacked btcd dependency
This is a stacked draft on the btcd Miniscript and descriptor work in
btcd #2592 and
btcd #2568, plus the descriptor
extensions in
sputn1ck/btcd@877b61a3.The extensions add late preimage lookup, potential-timelock discovery,
selected-path transaction constraints, frozen satisfaction replay, callback
isolation, and value-based metadata for the selected Taproot spend path.
The temporary
go.modreplacement makes this draft reproducible without alocal workspace. It must be removed and the dependency repointed after the
btcd changes land upstream; this PR should not merge before then.
Steps to Test
Run the focused package, race, vet, and integration checks without a local Go
workspace replacement:
The integration test runs four cases: P2WSH and P2TR, each through both sides
of this toy HTLC policy:
The success cases register before funding, add the preimage after confirmation,
and observe a sweep without the future CLTV. The timeout cases supply no
preimage, mine to the CLTV height, and observe an automatic sweep with the exact
transaction locktime. The P2TR cases additionally verify the revealed leaf,
control-block depth, output commitment, exact pending outpoint, and
DESCRIPTOR_TRclassification.All commands above pass locally with
GOWORK=off. The full custom linter,release-note check, REST annotation check, generated-RPC build, and btcd
descriptor package tests also pass.
Pull Request Checklist
Testing
Code Style and Documentation
lnclicommands have matching RPC tags in the proto comments.