cae4f43c19
This commit restores the sweep behavior of filtering inputs with unknown witness types from the final sweep transaction. Currently, such inputs will be included without adding anything to the weight estimate, possibly creating a transaction with insufficient fees.
240 lines
7.2 KiB
Go
240 lines
7.2 KiB
Go
package sweep
|
|
|
|
import (
|
|
"github.com/btcsuite/btcd/blockchain"
|
|
"github.com/btcsuite/btcd/txscript"
|
|
"github.com/btcsuite/btcd/wire"
|
|
"github.com/btcsuite/btcutil"
|
|
"github.com/lightningnetwork/lnd/lnwallet"
|
|
)
|
|
|
|
// UtxoSweeper provides the functionality to generate sweep txes. The plan is to
|
|
// extend UtxoSweeper in the future to also manage the actual sweeping process
|
|
// by itself.
|
|
type UtxoSweeper struct {
|
|
cfg *UtxoSweeperConfig
|
|
}
|
|
|
|
// UtxoSweeperConfig contains dependencies of UtxoSweeper.
|
|
type UtxoSweeperConfig struct {
|
|
// GenSweepScript generates a P2WKH script belonging to the wallet where
|
|
// funds can be swept.
|
|
GenSweepScript func() ([]byte, error)
|
|
|
|
// Estimator is used when crafting sweep transactions to estimate the
|
|
// necessary fee relative to the expected size of the sweep transaction.
|
|
Estimator lnwallet.FeeEstimator
|
|
|
|
// Signer is used by the sweeper to generate valid witnesses at the
|
|
// time the incubated outputs need to be spent.
|
|
Signer lnwallet.Signer
|
|
|
|
// ConfTarget specifies a target for the number of blocks until an
|
|
// initial confirmation.
|
|
ConfTarget uint32
|
|
}
|
|
|
|
// New returns a new UtxoSweeper instance.
|
|
func New(cfg *UtxoSweeperConfig) *UtxoSweeper {
|
|
return &UtxoSweeper{
|
|
cfg: cfg,
|
|
}
|
|
}
|
|
|
|
// CreateSweepTx accepts a list of inputs and signs and generates a txn that
|
|
// spends from them. This method also makes an accurate fee estimate before
|
|
// generating the required witnesses.
|
|
//
|
|
// The created transaction has a single output sending all the funds back to the
|
|
// source wallet, after accounting for the fee estimate.
|
|
//
|
|
// The value of currentBlockHeight argument will be set as the tx locktime. This
|
|
// function assumes that all CLTV inputs will be unlocked after
|
|
// currentBlockHeight. Reasons not to use the maximum of all actual CLTV expiry
|
|
// values of the inputs:
|
|
//
|
|
// - Make handling re-orgs easier.
|
|
// - Thwart future possible fee sniping attempts.
|
|
// - Make us blend in with the bitcoind wallet.
|
|
func (s *UtxoSweeper) CreateSweepTx(inputs []Input,
|
|
currentBlockHeight uint32) (*wire.MsgTx, error) {
|
|
|
|
// Generate the receiving script to which the funds will be swept.
|
|
pkScript, err := s.cfg.GenSweepScript()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
// Using the txn weight estimate, compute the required txn fee.
|
|
feePerKw, err := s.cfg.Estimator.EstimateFeePerKW(s.cfg.ConfTarget)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
inputs, txWeight, csvCount, cltvCount := s.getWeightEstimate(inputs)
|
|
log.Infof("Creating sweep transaction for %v inputs (%v CSV, %v CLTV) "+
|
|
"using %v sat/kw", len(inputs), csvCount, cltvCount,
|
|
int64(feePerKw))
|
|
|
|
txFee := feePerKw.FeeForWeight(txWeight)
|
|
|
|
// Sum up the total value contained in the inputs.
|
|
var totalSum btcutil.Amount
|
|
for _, o := range inputs {
|
|
totalSum += btcutil.Amount(o.SignDesc().Output.Value)
|
|
}
|
|
|
|
// Sweep as much possible, after subtracting txn fees.
|
|
sweepAmt := int64(totalSum - txFee)
|
|
|
|
// Create the sweep transaction that we will be building. We use
|
|
// version 2 as it is required for CSV. The txn will sweep the amount
|
|
// after fees to the pkscript generated above.
|
|
sweepTx := wire.NewMsgTx(2)
|
|
sweepTx.AddTxOut(&wire.TxOut{
|
|
PkScript: pkScript,
|
|
Value: sweepAmt,
|
|
})
|
|
|
|
sweepTx.LockTime = currentBlockHeight
|
|
|
|
// Add all inputs to the sweep transaction. Ensure that for each
|
|
// csvInput, we set the sequence number properly.
|
|
|
|
for _, input := range inputs {
|
|
sweepTx.AddTxIn(&wire.TxIn{
|
|
PreviousOutPoint: *input.OutPoint(),
|
|
Sequence: input.BlocksToMaturity(),
|
|
})
|
|
}
|
|
|
|
// 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 {
|
|
return nil, err
|
|
}
|
|
|
|
hashCache := txscript.NewTxSigHashes(sweepTx)
|
|
|
|
// With all the inputs in place, use each output's unique witness
|
|
// function to generate the final witness required for spending.
|
|
addWitness := func(idx int, tso Input) error {
|
|
witness, err := tso.BuildWitness(
|
|
s.cfg.Signer, sweepTx, hashCache, idx,
|
|
)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
sweepTx.TxIn[idx].Witness = witness
|
|
|
|
return nil
|
|
}
|
|
|
|
// Finally we'll attach a valid witness to each csv and cltv input
|
|
// within the sweeping transaction.
|
|
for i, input := range inputs {
|
|
if err := addWitness(i, input); err != nil {
|
|
return nil, err
|
|
}
|
|
}
|
|
|
|
return sweepTx, nil
|
|
}
|
|
|
|
// getWeightEstimate returns a weight estimate for the given inputs.
|
|
// Additionally, it returns counts for the number of csv and cltv inputs.
|
|
func (s *UtxoSweeper) getWeightEstimate(inputs []Input) ([]Input, int64, int, int) {
|
|
|
|
// Create a transaction which sweeps all the newly mature outputs into
|
|
// an output controlled by the wallet.
|
|
|
|
// TODO(roasbeef): can be more intelligent about buffering outputs to
|
|
// be more efficient on-chain.
|
|
|
|
var weightEstimate lnwallet.TxWeightEstimator
|
|
|
|
// Our sweep transaction will pay to a single segwit p2wkh address,
|
|
// ensure it contributes to our weight estimate.
|
|
weightEstimate.AddP2WKHOutput()
|
|
|
|
// 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.
|
|
csvCount := 0
|
|
cltvCount := 0
|
|
|
|
var sweepInputs []Input
|
|
for i := range inputs {
|
|
input := inputs[i]
|
|
|
|
switch input.WitnessType() {
|
|
|
|
// Outputs on a remote commitment transaction that pay directly
|
|
// to us.
|
|
case lnwallet.CommitmentNoDelay:
|
|
weightEstimate.AddP2WKHInput()
|
|
sweepInputs = append(sweepInputs, input)
|
|
|
|
// Outputs on a past commitment transaction that pay directly
|
|
// to us.
|
|
case lnwallet.CommitmentTimeLock:
|
|
weightEstimate.AddWitnessInput(
|
|
lnwallet.ToLocalTimeoutWitnessSize,
|
|
)
|
|
sweepInputs = append(sweepInputs, input)
|
|
csvCount++
|
|
|
|
// Outgoing second layer HTLC's that have confirmed within the
|
|
// chain, and the output they produced is now mature enough to
|
|
// sweep.
|
|
case lnwallet.HtlcOfferedTimeoutSecondLevel:
|
|
weightEstimate.AddWitnessInput(
|
|
lnwallet.ToLocalTimeoutWitnessSize,
|
|
)
|
|
sweepInputs = append(sweepInputs, input)
|
|
csvCount++
|
|
|
|
// Incoming second layer HTLC's that have confirmed within the
|
|
// chain, and the output they produced is now mature enough to
|
|
// sweep.
|
|
case lnwallet.HtlcAcceptedSuccessSecondLevel:
|
|
weightEstimate.AddWitnessInput(
|
|
lnwallet.ToLocalTimeoutWitnessSize,
|
|
)
|
|
sweepInputs = append(sweepInputs, input)
|
|
csvCount++
|
|
|
|
// An HTLC on the commitment transaction of the remote party,
|
|
// that has had its absolute timelock expire.
|
|
case lnwallet.HtlcOfferedRemoteTimeout:
|
|
weightEstimate.AddWitnessInput(
|
|
lnwallet.AcceptedHtlcTimeoutWitnessSize,
|
|
)
|
|
sweepInputs = append(sweepInputs, input)
|
|
cltvCount++
|
|
|
|
// An HTLC on the commitment transaction of the remote party,
|
|
// that can be swept with the preimage.
|
|
case lnwallet.HtlcAcceptedRemoteSuccess:
|
|
weightEstimate.AddWitnessInput(
|
|
lnwallet.OfferedHtlcSuccessWitnessSize,
|
|
)
|
|
sweepInputs = append(sweepInputs, input)
|
|
|
|
default:
|
|
log.Warnf("kindergarten output in nursery store "+
|
|
"contains unexpected witness type: %v",
|
|
input.WitnessType())
|
|
}
|
|
}
|
|
|
|
txWeight := int64(weightEstimate.Weight())
|
|
|
|
return sweepInputs, txWeight, csvCount, cltvCount
|
|
}
|