package sweep
import (
"fmt"
"math"
"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"
"github.com/lightningnetwork/lnd/lnwallet/chainfee"
)
type addConstraints uint8
const (
constraintsRegular addConstraints = iota
constraintsWallet
constraintsForce
)
type txInputSet struct {
weightEstimate input.TxWeightEstimator
inputTotal btcutil.Amount
outputValue btcutil.Amount
feePerKW chainfee.SatPerKWeight
inputs []input.Input
dustLimit btcutil.Amount
maxInputs int
walletInputTotal btcutil.Amount
wallet Wallet
force bool
}
func newTxInputSet(wallet Wallet, feePerKW,
relayFee chainfee.SatPerKWeight, maxInputs int) *txInputSet {
dustLimit := txrules.GetDustThreshold(
input.P2WPKHSize,
btcutil.Amount(relayFee.FeePerKVByte()),
)
b := txInputSet{
feePerKW: feePerKW,
dustLimit: dustLimit,
maxInputs: maxInputs,
wallet: wallet,
}
b.weightEstimate.AddP2WKHOutput()
return &b
}
func (t *txInputSet) dustLimitReached() bool {
return t.outputValue >= t.dustLimit
}
func (t *txInputSet) add(input input.Input, constraints addConstraints) bool {
if constraints != constraintsWallet &&
len(t.inputs) >= t.maxInputs {
return false
}
size, isNestedP2SH, _ := input.WitnessType().SizeUpperBound()
newWeightEstimate := t.weightEstimate
if isNestedP2SH {
newWeightEstimate.AddNestedP2WSHInput(size)
} else {
newWeightEstimate.AddWitnessInput(size)
}
value := btcutil.Amount(input.SignDesc().Output.Value)
newInputTotal := t.inputTotal + value
weight := newWeightEstimate.Weight()
fee := t.feePerKW.FeeForWeight(int64(weight))
newOutputValue := newInputTotal - fee
newWalletTotal := t.walletInputTotal
inputYield := newOutputValue - t.outputValue
switch constraints {
case constraintsRegular:
if inputYield <= 0 {
return false
}
case constraintsForce:
t.force = true
case constraintsWallet:
if inputYield <= 0 {
return false
}
newWalletTotal += value
if !t.force && newWalletTotal >= newOutputValue {
log.Debugf("Rejecting wallet input of %v, because it "+
"would make a negative yielding transaction "+
"(%v)",
value, newOutputValue-newWalletTotal)
return false
}
}
t.inputTotal = newInputTotal
t.outputValue = newOutputValue
t.inputs = append(t.inputs, input)
t.weightEstimate = newWeightEstimate
t.walletInputTotal = newWalletTotal
return true
}
func (t *txInputSet) addPositiveYieldInputs(sweepableInputs []txInput) {
for _, input := range sweepableInputs {
constraints := constraintsRegular
if input.parameters().Force {
constraints = constraintsForce
}
if !t.add(input, constraints) {
return
}
}
}
func (t *txInputSet) tryAddWalletInputsIfNeeded() error {
if t.dustLimitReached() {
return nil
}
utxos, err := t.wallet.ListUnspentWitness(1, math.MaxInt32)
if err != nil {
return err
}
for _, utxo := range utxos {
input, err := createWalletTxInput(utxo)
if err != nil {
return err
}
if !t.add(input, constraintsWallet) {
continue
}
if t.dustLimitReached() {
return nil
}
}
return nil
}
func createWalletTxInput(utxo *lnwallet.Utxo) (input.Input, error) {
var witnessType input.WitnessType
switch utxo.AddressType {
case lnwallet.WitnessPubKey:
witnessType = input.WitnessKeyHash
case lnwallet.NestedWitnessPubKey:
witnessType = input.NestedWitnessKeyHash
default:
return nil, fmt.Errorf("unknown address type %v",
utxo.AddressType)
}
signDesc := &input.SignDescriptor{
Output: &wire.TxOut{
PkScript: utxo.PkScript,
Value: int64(utxo.Value),
},
HashType: txscript.SigHashAll,
}
heightHint := uint32(0)
return input.NewBaseInput(
&utxo.OutPoint, witnessType, signDesc, heightHint,
), nil
}