lnd/sweep/interface.go
Jared Tobin a9e3e9ae17
sweep: account for aux extra budget when filtering inputs
The BudgetAggregator filters out inputs whose budget cannot cover the
min relay fee or their requested starting fee rate. For inputs that
carry a resolution blob (custom channel outputs), the aux sweeper
contributes a sizable extra budget to any input set they join, but the
filter only considered the input's own budget, which for asset outputs
is tiny (their value is carried off-chain).

The filter is mostly harmless with default parameters, but the
starting fee rate of an input is ratcheted whenever a sweep attempt
fails, including failures that have nothing to do with fees: e.g. when
a concurrent sweep transaction spends the wallet UTXO that was backing
this input's set (the sweeper currently doesn't lease selected wallet
UTXOs, so concurrent input sets can pick the same one). One such
collision is enough to push the required starting fee above a small
asset input's own budget, after which the input is filtered out of
every future input set and the sweep is silently stranded forever.

Account for the aux extra budget in the filter, mirroring how the
budget input set itself accounts for it when deciding whether wallet
inputs are needed. Inputs without a resolution blob (the only kind
that exists without an aux sweeper) are unaffected.
2026-07-09 08:52:50 -02:30

105 lines
4.4 KiB
Go

package sweep
import (
"github.com/btcsuite/btcd/btcutil/v2"
"github.com/btcsuite/btcd/chainhash/v2"
"github.com/btcsuite/btcd/wire/v2"
"github.com/lightningnetwork/lnd/fn/v2"
"github.com/lightningnetwork/lnd/input"
"github.com/lightningnetwork/lnd/keychain"
"github.com/lightningnetwork/lnd/lnwallet"
)
// Wallet contains all wallet related functionality required by sweeper.
type Wallet interface {
// PublishTransaction performs cursory validation (dust checks, etc) and
// broadcasts the passed transaction to the Bitcoin network.
PublishTransaction(tx *wire.MsgTx, label string) error
// ListUnspentWitnessFromDefaultAccount returns all unspent outputs
// which are version 0 witness programs from the default wallet account.
// The 'minConfs' and 'maxConfs' parameters indicate the minimum
// and maximum number of confirmations an output needs in order to be
// returned by this method.
ListUnspentWitnessFromDefaultAccount(minConfs, maxConfs int32) (
[]*lnwallet.Utxo, error)
// WithCoinSelectLock will execute the passed function closure in a
// synchronized manner preventing any coin selection operations from
// proceeding while the closure is executing. This can be seen as the
// ability to execute a function closure under an exclusive coin
// selection lock.
WithCoinSelectLock(f func() error) error
// RemoveDescendants removes any wallet transactions that spends
// outputs created by the specified transaction.
RemoveDescendants(*wire.MsgTx) error
// FetchTx returns the transaction that corresponds to the transaction
// hash passed in. If the transaction can't be found then a nil
// transaction pointer is returned.
FetchTx(chainhash.Hash) (*wire.MsgTx, error)
// CancelRebroadcast is used to inform the rebroadcaster sub-system
// that it no longer needs to try to rebroadcast a transaction. This is
// used to ensure that invalid transactions (inputs spent) aren't
// retried in the background.
CancelRebroadcast(tx chainhash.Hash)
// CheckMempoolAcceptance checks whether a transaction follows mempool
// policies and returns an error if it cannot be accepted into the
// mempool.
CheckMempoolAcceptance(tx *wire.MsgTx) error
// GetTransactionDetails returns a detailed description of a tx given
// its transaction hash.
GetTransactionDetails(txHash *chainhash.Hash) (
*lnwallet.TransactionDetail, error)
// BackEnd returns a name for the wallet's backing chain service,
// which could be e.g. btcd, bitcoind, neutrino, or another consensus
// service.
BackEnd() string
}
// SweepOutput is an output used to sweep funds from a channel output.
type SweepOutput struct { //nolint:revive
wire.TxOut
// IsExtra indicates whether this output is an extra output that was
// added by a party other than the sweeper.
IsExtra bool
// InternalKey is the taproot internal key of the extra output. This is
// None, if this isn't a taproot output.
InternalKey fn.Option[keychain.KeyDescriptor]
}
// AuxSweeper is used to enable a 3rd party to further shape the sweeping
// transaction by adding a set of extra outputs to the sweeping transaction.
type AuxSweeper interface {
// DeriveSweepAddr takes a set of inputs, and the change address we'd
// use to sweep them, and maybe results an extra sweep output that we
// should add to the sweeping transaction.
DeriveSweepAddr(inputs []input.Input,
change lnwallet.AddrWithKey) fn.Result[SweepOutput]
// ExtraBudgetForInputs is used to determine the extra budget that
// should be allocated to sweep the given set of inputs. This can be
// used to add extra funds to the sweep transaction, for example to
// cover fees for additional outputs of custom channels.
//
// The returned amount must be non-negative, and the contribution
// must be additive across inputs: the result for a slice of inputs
// must equal the sum of the per-input results, so that callers may
// query the contribution of a single input by passing a singleton
// slice. The budget aggregator relies on this when pre-filtering
// inputs by their own budget plus their individual aux contribution.
ExtraBudgetForInputs(inputs []input.Input) fn.Result[btcutil.Amount]
// NotifyBroadcast is used to notify external callers of the broadcast
// of a sweep transaction, generated by the passed BumpRequest.
NotifyBroadcast(req *BumpRequest, tx *wire.MsgTx,
totalFees btcutil.Amount,
outpointToTxIndex map[wire.OutPoint]int) error
}