sweep: extract positive input set struct

A refactoring that introduces no functional changes. This prepares for
the addition of wallet utxos to push the sweep tx above the dust limit.

It also enabled access to input-specific sweep parameters during tx
generation. This will be used in later commits to control the sweep
process.
This commit is contained in:
Joost Jager committed 2019-12-17 22:00:34 +01:00
1 parent 071c57d4a4
commit b325aae4f2
4 files changed
+228 -82

No files matched your search

+26 -81
View File
@@ -9,7 +9,6 @@ import (
"github.com/btcsuite/btcd/txscript"
"github.com/btcsuite/btcd/wire"
"github.com/btcsuite/btcutil"
"github.com/btcsuite/btcwallet/wallet/txrules"
"github.com/lightningnetwork/lnd/input"
"github.com/lightningnetwork/lnd/lnwallet/chainfee"
)
@@ -21,6 +20,13 @@ var (
DefaultMaxInputsPerTx = 100
)
// txInput is an interface that provides the input data required for tx
// generation.
type txInput interface {
input.Input
parameters() Params
}
// inputSet is a set of inputs that can be used as the basis to generate a tx
// on.
type inputSet []input.Input
@@ -30,17 +36,10 @@ type inputSet []input.Input
// contains up to the configured maximum number of inputs. Negative yield
// inputs are skipped. No input sets with a total value after fees below the
// dust limit are returned.
func generateInputPartitionings(sweepableInputs []input.Input,
func generateInputPartitionings(sweepableInputs []txInput,
relayFeePerKW, feePerKW chainfee.SatPerKWeight,
maxInputsPerTx int) ([]inputSet, error) {
// Calculate dust limit based on the P2WPKH output script of the sweep
// txes.
dustLimit := txrules.GetDustThreshold(
input.P2WPKHSize,
btcutil.Amount(relayFeePerKW.FeePerKVByte()),
)
// Sort input by yield. We will start constructing input sets starting
// with the highest yield inputs. This is to prevent the construction
// of a set with an output below the dust limit, causing the sweep
@@ -75,15 +74,21 @@ func generateInputPartitionings(sweepableInputs []input.Input,
// Select blocks of inputs up to the configured maximum number.
var sets []inputSet
for len(sweepableInputs) > 0 {
// Get the maximum number of inputs from sweepableInputs that
// we can use to create a positive yielding set from.
count, outputValue := getPositiveYieldInputs(
sweepableInputs, maxInputsPerTx, feePerKW,
// Start building a set of positive-yield tx inputs under the
// condition that the tx will be published with the specified
// fee rate.
txInputs := newTxInputSet(
feePerKW, relayFeePerKW, maxInputsPerTx,
)
// If there are no positive yield inputs left, we can stop
// here.
if count == 0 {
// From the set of sweepable inputs, keep adding inputs to the
// input set until the tx output value no longer goes up or the
// maximum number of inputs is reached.
txInputs.addPositiveYieldInputs(sweepableInputs)
// If there are no positive yield inputs, we can stop here.
inputCount := len(txInputs.inputs)
if inputCount == 0 {
return sets, nil
}
@@ -91,82 +96,22 @@ func generateInputPartitionings(sweepableInputs []input.Input,
// the dust limit, stop sweeping. Because of the sorting,
// continuing with the remaining inputs will only lead to sets
// with a even lower output value.
if outputValue < dustLimit {
if !txInputs.dustLimitReached() {
log.Debugf("Set value %v below dust limit of %v",
outputValue, dustLimit)
txInputs.outputValue, txInputs.dustLimit)
return sets, nil
}
log.Infof("Candidate sweep set of size=%v, has yield=%v",
count, outputValue)
inputCount, txInputs.outputValue)
sets = append(sets, sweepableInputs[:count])
sweepableInputs = sweepableInputs[count:]
sets = append(sets, txInputs.inputs)
sweepableInputs = sweepableInputs[inputCount:]
}
return sets, nil
}
// getPositiveYieldInputs returns the maximum of a number n for which holds
// that the inputs [0,n) of sweepableInputs have a positive yield.
// Additionally, the total values of these inputs minus the fee is returned.
//
// TODO(roasbeef): Consider including some negative yield inputs too to clean
// up the utxo set even if it costs us some fees up front. In the spirit of
// minimizing any negative externalities we cause for the Bitcoin system as a
// whole.
func getPositiveYieldInputs(sweepableInputs []input.Input, maxInputs int,
feePerKW chainfee.SatPerKWeight) (int, btcutil.Amount) {
var weightEstimate input.TxWeightEstimator
// Add the sweep tx output to the weight estimate.
weightEstimate.AddP2WKHOutput()
var total, outputValue btcutil.Amount
for idx, input := range sweepableInputs {
// Can ignore error, because it has already been checked when
// calculating the yields.
size, isNestedP2SH, _ := input.WitnessType().SizeUpperBound()
// Keep a running weight estimate of the input set.
if isNestedP2SH {
weightEstimate.AddNestedP2WSHInput(size)
} else {
weightEstimate.AddWitnessInput(size)
}
newTotal := total + btcutil.Amount(input.SignDesc().Output.Value)
weight := weightEstimate.Weight()
fee := feePerKW.FeeForWeight(int64(weight))
// Calculate the output value if the current input would be
// added to the set.
newOutputValue := newTotal - fee
// If adding this input makes the total output value of the set
// decrease, this is a negative yield input. It shouldn't be
// added to the set. We return the current index as the number
// of inputs, so the current input is being excluded.
if newOutputValue <= outputValue {
return idx, outputValue
}
// Update running values.
total = newTotal
outputValue = newOutputValue
// Stop if max inputs is reached.
if idx == maxInputs-1 {
return maxInputs, outputValue
}
}
// We could add all inputs to the set, so return them all.
return len(sweepableInputs), outputValue
}
// createSweepTx builds a signed tx spending the inputs to a the output script.
func createSweepTx(inputs []input.Input, outputPkScript []byte,
currentBlockHeight uint32, feePerKw chainfee.SatPerKWeight,