2018-12-06 11:37:54 +01:00
|
|
|
package sweep
|
|
|
|
|
|
|
|
import (
|
2022-07-15 22:25:31 +02:00
|
|
|
"errors"
|
2018-12-06 11:37:54 +01:00
|
|
|
"fmt"
|
2018-12-07 08:36:58 +01:00
|
|
|
"sort"
|
2019-10-23 13:00:25 +02:00
|
|
|
"strings"
|
2018-12-07 08:36:58 +01:00
|
|
|
|
2018-12-06 11:37:54 +01:00
|
|
|
"github.com/btcsuite/btcd/blockchain"
|
2022-02-23 14:48:00 +01:00
|
|
|
"github.com/btcsuite/btcd/btcutil"
|
2022-03-18 18:37:44 +01:00
|
|
|
"github.com/btcsuite/btcd/txscript"
|
2018-12-06 11:37:54 +01:00
|
|
|
"github.com/btcsuite/btcd/wire"
|
2019-01-16 15:47:43 +01:00
|
|
|
"github.com/lightningnetwork/lnd/input"
|
2021-09-23 21:40:37 +02:00
|
|
|
"github.com/lightningnetwork/lnd/lnwallet"
|
2019-10-31 03:43:05 +01:00
|
|
|
"github.com/lightningnetwork/lnd/lnwallet/chainfee"
|
2018-12-06 11:37:54 +01:00
|
|
|
)
|
|
|
|
|
2018-12-07 08:36:58 +01:00
|
|
|
var (
|
|
|
|
// DefaultMaxInputsPerTx specifies the default maximum number of inputs
|
|
|
|
// allowed in a single sweep tx. If more need to be swept, multiple txes
|
|
|
|
// are created and published.
|
2024-03-19 22:56:56 +01:00
|
|
|
DefaultMaxInputsPerTx = uint32(100)
|
2023-10-25 08:13:23 +02:00
|
|
|
|
|
|
|
// ErrLocktimeConflict is returned when inputs with different
|
|
|
|
// transaction nLockTime values are included in the same transaction.
|
|
|
|
//
|
|
|
|
// NOTE: due the SINGLE|ANYONECANPAY sighash flag, which is used in the
|
|
|
|
// second level success/timeout txns, only the txns sharing the same
|
|
|
|
// nLockTime can exist in the same tx.
|
|
|
|
ErrLocktimeConflict = errors.New("incompatible locktime")
|
2018-12-07 08:36:58 +01:00
|
|
|
)
|
|
|
|
|
2021-01-14 11:49:27 +01:00
|
|
|
// createSweepTx builds a signed tx spending the inputs to the given outputs,
|
|
|
|
// sending any leftover change to the change script.
|
|
|
|
func createSweepTx(inputs []input.Input, outputs []*wire.TxOut,
|
|
|
|
changePkScript []byte, currentBlockHeight uint32,
|
2023-10-30 13:43:33 +01:00
|
|
|
feeRate, maxFeeRate chainfee.SatPerKWeight,
|
2023-10-25 08:13:23 +02:00
|
|
|
signer input.Signer) (*wire.MsgTx, btcutil.Amount, error) {
|
2021-01-14 11:49:27 +01:00
|
|
|
|
2022-07-15 22:25:31 +02:00
|
|
|
inputs, estimator, err := getWeightEstimate(
|
2024-06-04 08:08:37 +02:00
|
|
|
inputs, outputs, feeRate, maxFeeRate, [][]byte{changePkScript},
|
2022-03-18 18:37:46 +01:00
|
|
|
)
|
2022-07-15 22:25:31 +02:00
|
|
|
if err != nil {
|
2023-10-25 08:13:23 +02:00
|
|
|
return nil, 0, err
|
2022-07-15 22:25:31 +02:00
|
|
|
}
|
|
|
|
|
2024-03-20 23:31:08 +01:00
|
|
|
txFee := estimator.feeWithParent()
|
2018-12-06 11:37:54 +01:00
|
|
|
|
2020-12-07 09:34:13 +01:00
|
|
|
var (
|
|
|
|
// Create the sweep transaction that we will be building. We
|
|
|
|
// use version 2 as it is required for CSV.
|
|
|
|
sweepTx = wire.NewMsgTx(2)
|
2020-11-06 19:35:01 +01:00
|
|
|
|
2020-12-07 09:34:13 +01:00
|
|
|
// Track whether any of the inputs require a certain locktime.
|
|
|
|
locktime = int32(-1)
|
2020-11-06 19:35:01 +01:00
|
|
|
|
2020-12-07 09:34:13 +01:00
|
|
|
// We keep track of total input amount, and required output
|
|
|
|
// amount to use for calculating the change amount below.
|
2020-11-16 12:52:22 +01:00
|
|
|
totalInput btcutil.Amount
|
|
|
|
requiredOutput btcutil.Amount
|
2020-12-07 09:34:13 +01:00
|
|
|
|
|
|
|
// We'll add the inputs as we go so we know the final ordering
|
|
|
|
// of inputs to sign.
|
|
|
|
idxs []input.Input
|
2020-11-16 12:52:22 +01:00
|
|
|
)
|
2020-12-07 09:34:13 +01:00
|
|
|
|
|
|
|
// We start by adding all inputs that commit to an output. We do this
|
|
|
|
// since the input and output index must stay the same for the
|
|
|
|
// signatures to be valid.
|
2018-12-06 11:37:54 +01:00
|
|
|
for _, o := range inputs {
|
2020-11-16 12:52:22 +01:00
|
|
|
if o.RequiredTxOut() == nil {
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
|
2020-12-07 09:34:13 +01:00
|
|
|
idxs = append(idxs, o)
|
2020-11-06 19:35:01 +01:00
|
|
|
sweepTx.AddTxIn(&wire.TxIn{
|
2024-03-27 10:07:48 +01:00
|
|
|
PreviousOutPoint: o.OutPoint(),
|
2020-11-06 19:35:01 +01:00
|
|
|
Sequence: o.BlocksToMaturity(),
|
|
|
|
})
|
2020-11-16 12:52:22 +01:00
|
|
|
sweepTx.AddTxOut(o.RequiredTxOut())
|
2020-11-06 19:35:01 +01:00
|
|
|
|
|
|
|
if lt, ok := o.RequiredLockTime(); ok {
|
|
|
|
// If another input commits to a different locktime,
|
2023-12-30 12:46:35 +01:00
|
|
|
// they cannot be combined in the same transaction.
|
2020-11-06 19:35:01 +01:00
|
|
|
if locktime != -1 && locktime != int32(lt) {
|
2023-10-25 08:13:23 +02:00
|
|
|
return nil, 0, ErrLocktimeConflict
|
2020-11-06 19:35:01 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
locktime = int32(lt)
|
|
|
|
}
|
|
|
|
|
2020-11-16 12:52:22 +01:00
|
|
|
totalInput += btcutil.Amount(o.SignDesc().Output.Value)
|
|
|
|
requiredOutput += btcutil.Amount(o.RequiredTxOut().Value)
|
2018-12-06 11:37:54 +01:00
|
|
|
}
|
|
|
|
|
2020-11-16 12:52:22 +01:00
|
|
|
// Sum up the value contained in the remaining inputs, and add them to
|
|
|
|
// the sweep transaction.
|
|
|
|
for _, o := range inputs {
|
|
|
|
if o.RequiredTxOut() != nil {
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
|
2020-12-07 09:34:13 +01:00
|
|
|
idxs = append(idxs, o)
|
2020-11-16 12:52:22 +01:00
|
|
|
sweepTx.AddTxIn(&wire.TxIn{
|
2024-03-27 10:07:48 +01:00
|
|
|
PreviousOutPoint: o.OutPoint(),
|
2020-11-16 12:52:22 +01:00
|
|
|
Sequence: o.BlocksToMaturity(),
|
|
|
|
})
|
|
|
|
|
|
|
|
if lt, ok := o.RequiredLockTime(); ok {
|
|
|
|
if locktime != -1 && locktime != int32(lt) {
|
2023-10-25 08:13:23 +02:00
|
|
|
return nil, 0, ErrLocktimeConflict
|
2020-11-16 12:52:22 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
locktime = int32(lt)
|
|
|
|
}
|
|
|
|
|
|
|
|
totalInput += btcutil.Amount(o.SignDesc().Output.Value)
|
|
|
|
}
|
|
|
|
|
2021-01-14 11:49:27 +01:00
|
|
|
// Add the outputs given, if any.
|
|
|
|
for _, o := range outputs {
|
|
|
|
sweepTx.AddTxOut(o)
|
|
|
|
requiredOutput += btcutil.Amount(o.Value)
|
|
|
|
}
|
|
|
|
|
|
|
|
if requiredOutput+txFee > totalInput {
|
2023-10-25 08:13:23 +02:00
|
|
|
return nil, 0, fmt.Errorf("insufficient input to create sweep "+
|
sweep: remove all unconfirmed descendant transactions when a sweep conflicts
Before this commit, we we were trying to sweep an anchor output, and
that output was spent by someone else (not the sweeper), then we would
report this back to the original resolver (allowing it to be cleaned
up), and also remove the set of inputs spent by that transaction from
the set we need to sweep.
However, it's possible that if a user is spending unconfirmed outputs,
then the wallet is holding onto an invalid transaction, as the outputs
that were used as inputs have been double spent elsewhere.
In this commit, we fix this issue by recursively removing all descendant
transactions of our past sweeps that have an intersecting input set as
the spending transaction. In cases where a user spent an unconfirmed
output to funding a channel, and that output was a descendant of the now
swept anchor output, the funds will now properly be marked as available.
Fixes #6241
2022-02-18 03:25:30 +01:00
|
|
|
"tx: input_sum=%v, output_sum=%v", totalInput,
|
|
|
|
requiredOutput+txFee)
|
2021-01-14 11:49:27 +01:00
|
|
|
}
|
|
|
|
|
2020-11-16 12:52:22 +01:00
|
|
|
// The value remaining after the required output and fees, go to
|
|
|
|
// change. Not that this fee is what we would have to pay in case the
|
|
|
|
// sweep tx has a change output.
|
|
|
|
changeAmt := totalInput - requiredOutput - txFee
|
2018-12-06 11:37:54 +01:00
|
|
|
|
2021-09-23 21:40:37 +02:00
|
|
|
// We'll calculate the dust limit for the given changePkScript since it
|
|
|
|
// is variable.
|
|
|
|
changeLimit := lnwallet.DustLimitForSize(len(changePkScript))
|
|
|
|
|
2020-11-06 19:35:01 +01:00
|
|
|
// The txn will sweep the amount after fees to the pkscript generated
|
|
|
|
// above.
|
2021-09-23 21:40:37 +02:00
|
|
|
if changeAmt >= changeLimit {
|
2020-11-06 19:44:12 +01:00
|
|
|
sweepTx.AddTxOut(&wire.TxOut{
|
2021-01-14 11:49:27 +01:00
|
|
|
PkScript: changePkScript,
|
2020-11-16 12:52:22 +01:00
|
|
|
Value: int64(changeAmt),
|
2020-11-06 19:44:12 +01:00
|
|
|
})
|
2020-12-09 12:24:01 +01:00
|
|
|
} else {
|
|
|
|
log.Infof("Change amt %v below dustlimit %v, not adding "+
|
2021-09-23 21:40:37 +02:00
|
|
|
"change output", changeAmt, changeLimit)
|
2023-10-25 08:13:23 +02:00
|
|
|
|
|
|
|
// The dust amount is added to the fee as the miner will
|
|
|
|
// collect it.
|
|
|
|
txFee += changeAmt
|
2020-11-06 19:44:12 +01:00
|
|
|
}
|
2018-12-06 11:37:54 +01:00
|
|
|
|
2020-11-06 19:35:01 +01:00
|
|
|
// We'll default to using the current block height as locktime, if none
|
|
|
|
// of the inputs commits to a different locktime.
|
2018-12-06 11:37:54 +01:00
|
|
|
sweepTx.LockTime = currentBlockHeight
|
2020-11-06 19:35:01 +01:00
|
|
|
if locktime != -1 {
|
|
|
|
sweepTx.LockTime = uint32(locktime)
|
2018-12-06 11:37:54 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
// Before signing the transaction, check to ensure that it meets some
|
|
|
|
// basic validity requirements.
|
|
|
|
//
|
|
|
|
// TODO(conner): add more control to sanity checks, allowing us to
|
|
|
|
// delay spending "problem" outputs, e.g. possibly batching with other
|
|
|
|
// classes if fees are too low.
|
|
|
|
btx := btcutil.NewTx(sweepTx)
|
|
|
|
if err := blockchain.CheckTransactionSanity(btx); err != nil {
|
2023-10-25 08:13:23 +02:00
|
|
|
return nil, 0, err
|
2018-12-06 11:37:54 +01:00
|
|
|
}
|
|
|
|
|
2022-03-18 18:37:44 +01:00
|
|
|
prevInputFetcher, err := input.MultiPrevOutFetcher(inputs)
|
|
|
|
if err != nil {
|
2023-10-25 08:13:23 +02:00
|
|
|
return nil, 0, fmt.Errorf("error creating prev input fetcher "+
|
2022-03-18 18:37:44 +01:00
|
|
|
"for hash cache: %v", err)
|
|
|
|
}
|
|
|
|
hashCache := txscript.NewTxSigHashes(sweepTx, prevInputFetcher)
|
2018-12-06 11:37:54 +01:00
|
|
|
|
2018-11-18 05:48:41 +01:00
|
|
|
// With all the inputs in place, use each output's unique input script
|
2018-12-06 11:37:54 +01:00
|
|
|
// function to generate the final witness required for spending.
|
2019-01-16 15:47:43 +01:00
|
|
|
addInputScript := func(idx int, tso input.Input) error {
|
2018-11-18 05:48:41 +01:00
|
|
|
inputScript, err := tso.CraftInputScript(
|
2022-03-18 18:37:44 +01:00
|
|
|
signer, sweepTx, hashCache, prevInputFetcher, idx,
|
2018-12-06 11:37:54 +01:00
|
|
|
)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
2018-11-18 05:48:41 +01:00
|
|
|
sweepTx.TxIn[idx].Witness = inputScript.Witness
|
|
|
|
|
|
|
|
if len(inputScript.SigScript) != 0 {
|
2023-10-25 08:13:23 +02:00
|
|
|
sweepTx.TxIn[idx].SignatureScript =
|
|
|
|
inputScript.SigScript
|
2018-11-18 05:48:41 +01:00
|
|
|
}
|
2018-12-06 11:37:54 +01:00
|
|
|
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2020-12-07 09:34:13 +01:00
|
|
|
for idx, inp := range idxs {
|
|
|
|
if err := addInputScript(idx, inp); err != nil {
|
2023-10-25 08:13:23 +02:00
|
|
|
return nil, 0, err
|
2018-12-06 11:37:54 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2024-02-29 06:18:59 +01:00
|
|
|
log.Debugf("Creating sweep transaction %v for %v inputs (%s) "+
|
|
|
|
"using %v, tx_weight=%v, tx_fee=%v, parents_count=%v, "+
|
|
|
|
"parents_fee=%v, parents_weight=%v, current_height=%v",
|
2020-09-04 11:28:17 +02:00
|
|
|
sweepTx.TxHash(), len(inputs),
|
2023-10-30 13:43:33 +01:00
|
|
|
inputTypeSummary(inputs), feeRate,
|
2020-09-04 11:28:17 +02:00
|
|
|
estimator.weight(), txFee,
|
|
|
|
len(estimator.parents), estimator.parentsFee,
|
2024-02-29 06:18:59 +01:00
|
|
|
estimator.parentsWeight, currentBlockHeight)
|
2020-03-19 16:51:32 +01:00
|
|
|
|
2023-10-25 08:13:23 +02:00
|
|
|
return sweepTx, txFee, nil
|
2018-12-06 11:37:54 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
// getWeightEstimate returns a weight estimate for the given inputs.
|
|
|
|
// Additionally, it returns counts for the number of csv and cltv inputs.
|
2021-01-14 11:49:27 +01:00
|
|
|
func getWeightEstimate(inputs []input.Input, outputs []*wire.TxOut,
|
2023-08-22 05:06:51 +02:00
|
|
|
feeRate, maxFeeRate chainfee.SatPerKWeight,
|
2024-06-04 08:08:37 +02:00
|
|
|
outputPkScripts [][]byte) ([]input.Input, *weightEstimator, error) {
|
2020-09-04 11:28:17 +02:00
|
|
|
|
2018-12-06 11:37:54 +01:00
|
|
|
// We initialize a weight estimator so we can accurately asses the
|
|
|
|
// amount of fees we need to pay for this sweep transaction.
|
|
|
|
//
|
|
|
|
// TODO(roasbeef): can be more intelligent about buffering outputs to
|
|
|
|
// be more efficient on-chain.
|
2023-08-22 05:06:51 +02:00
|
|
|
weightEstimate := newWeightEstimator(feeRate, maxFeeRate)
|
2018-12-06 11:37:54 +01:00
|
|
|
|
2021-01-14 11:49:27 +01:00
|
|
|
// Our sweep transaction will always pay to the given set of outputs.
|
|
|
|
for _, o := range outputs {
|
|
|
|
weightEstimate.addOutput(o)
|
|
|
|
}
|
|
|
|
|
|
|
|
// If there is any leftover change after paying to the given outputs
|
2022-03-18 18:37:46 +01:00
|
|
|
// and required outputs, it will go to a single segwit p2wkh or p2tr
|
2024-06-04 08:08:37 +02:00
|
|
|
// address. This will be our change address, so ensure it contributes
|
|
|
|
// to our weight estimate. Note that if we have other outputs, we might
|
|
|
|
// end up creating a sweep tx without a change output. It is okay to
|
|
|
|
// add the change output to the weight estimate regardless, since the
|
|
|
|
// estimated fee will just be subtracted from this already dust output,
|
|
|
|
// and trimmed.
|
|
|
|
for _, outputPkScript := range outputPkScripts {
|
|
|
|
switch {
|
|
|
|
case txscript.IsPayToTaproot(outputPkScript):
|
|
|
|
weightEstimate.addP2TROutput()
|
|
|
|
|
|
|
|
case txscript.IsPayToWitnessScriptHash(outputPkScript):
|
|
|
|
weightEstimate.addP2WSHOutput()
|
|
|
|
|
|
|
|
case txscript.IsPayToWitnessPubKeyHash(outputPkScript):
|
|
|
|
weightEstimate.addP2WKHOutput()
|
|
|
|
|
|
|
|
case txscript.IsPayToPubKeyHash(outputPkScript):
|
|
|
|
weightEstimate.estimator.AddP2PKHOutput()
|
|
|
|
|
|
|
|
case txscript.IsPayToScriptHash(outputPkScript):
|
|
|
|
weightEstimate.estimator.AddP2SHOutput()
|
|
|
|
|
|
|
|
default:
|
|
|
|
// Unknown script type.
|
2024-09-27 10:07:53 +02:00
|
|
|
return nil, nil, fmt.Errorf("unknown script "+
|
|
|
|
"type: %x", outputPkScript)
|
2024-06-04 08:08:37 +02:00
|
|
|
}
|
2022-03-18 18:37:46 +01:00
|
|
|
}
|
2018-12-06 11:37:54 +01:00
|
|
|
|
|
|
|
// For each output, use its witness type to determine the estimate
|
|
|
|
// weight of its witness, and add it to the proper set of spendable
|
|
|
|
// outputs.
|
2019-10-23 13:00:25 +02:00
|
|
|
var sweepInputs []input.Input
|
2018-12-06 11:37:54 +01:00
|
|
|
for i := range inputs {
|
2019-01-16 15:47:43 +01:00
|
|
|
inp := inputs[i]
|
2018-12-06 11:37:54 +01:00
|
|
|
|
2020-09-04 14:03:14 +02:00
|
|
|
err := weightEstimate.add(inp)
|
2018-12-06 11:37:54 +01:00
|
|
|
if err != nil {
|
2024-03-21 10:12:17 +01:00
|
|
|
// TODO(yy): check if this is even possible? If so, we
|
|
|
|
// should return the error here instead of filtering!
|
|
|
|
log.Errorf("Failed to get weight estimate for "+
|
|
|
|
"input=%v, witnessType=%v: %v ", inp.OutPoint(),
|
|
|
|
inp.WitnessType(), err)
|
2018-12-06 11:37:54 +01:00
|
|
|
|
|
|
|
// Skip inputs for which no weight estimate can be
|
|
|
|
// given.
|
|
|
|
continue
|
|
|
|
}
|
2019-10-23 13:00:25 +02:00
|
|
|
|
2020-11-16 12:52:22 +01:00
|
|
|
// If this input comes with a committed output, add that as
|
|
|
|
// well.
|
|
|
|
if inp.RequiredTxOut() != nil {
|
|
|
|
weightEstimate.addOutput(inp.RequiredTxOut())
|
|
|
|
}
|
|
|
|
|
2019-01-16 15:47:43 +01:00
|
|
|
sweepInputs = append(sweepInputs, inp)
|
2018-12-06 11:37:54 +01:00
|
|
|
}
|
|
|
|
|
2022-07-15 22:25:31 +02:00
|
|
|
return sweepInputs, weightEstimate, nil
|
2019-10-23 13:00:25 +02:00
|
|
|
}
|
2018-12-06 11:37:54 +01:00
|
|
|
|
2019-10-23 13:00:25 +02:00
|
|
|
// inputSummary returns a string containing a human readable summary about the
|
|
|
|
// witness types of a list of inputs.
|
|
|
|
func inputTypeSummary(inputs []input.Input) string {
|
2020-03-19 16:51:32 +01:00
|
|
|
// Sort inputs by witness type.
|
|
|
|
sortedInputs := make([]input.Input, len(inputs))
|
|
|
|
copy(sortedInputs, inputs)
|
|
|
|
sort.Slice(sortedInputs, func(i, j int) bool {
|
|
|
|
return sortedInputs[i].WitnessType().String() <
|
|
|
|
sortedInputs[j].WitnessType().String()
|
|
|
|
})
|
2019-10-23 13:00:25 +02:00
|
|
|
|
|
|
|
var parts []string
|
2020-03-19 16:51:32 +01:00
|
|
|
for _, i := range sortedInputs {
|
2024-03-27 10:07:48 +01:00
|
|
|
part := fmt.Sprintf("%v (%v)", i.OutPoint(), i.WitnessType())
|
2019-10-23 13:00:25 +02:00
|
|
|
parts = append(parts, part)
|
|
|
|
}
|
|
|
|
return strings.Join(parts, ", ")
|
2018-12-06 11:37:54 +01:00
|
|
|
}
|