github.com/decred/dcrlnd@v0.7.6/sweep/tx_input_set.go (about)

     1  package sweep
     2  
     3  import (
     4  	"fmt"
     5  	"math"
     6  
     7  	"github.com/decred/dcrd/dcrutil/v4"
     8  	"github.com/decred/dcrd/txscript/v4"
     9  	"github.com/decred/dcrd/wire"
    10  	"github.com/decred/dcrlnd/input"
    11  	"github.com/decred/dcrlnd/lnwallet"
    12  	"github.com/decred/dcrlnd/lnwallet/chainfee"
    13  )
    14  
    15  // addConstraints defines the constraints to apply when adding an input.
    16  type addConstraints uint8
    17  
    18  const (
    19  	// constraintsRegular is for regular input sweeps that should have a positive
    20  	// yield.
    21  	constraintsRegular addConstraints = iota
    22  
    23  	// constraintsWallet is for wallet inputs that are only added to bring up the tx
    24  	// output value.
    25  	constraintsWallet
    26  
    27  	// constraintsForce is for inputs that should be swept even with a negative
    28  	// yield at the set fee rate.
    29  	constraintsForce
    30  )
    31  
    32  type txInputSetState struct {
    33  	// feeRate is the fee rate to use for the sweep transaction.
    34  	feeRate chainfee.AtomPerKByte
    35  
    36  	// inputTotal is the total value of all inputs.
    37  	inputTotal dcrutil.Amount
    38  
    39  	// requiredOutput is the sum of the outputs committed to by the inputs.
    40  	requiredOutput dcrutil.Amount
    41  
    42  	// changeOutput is the value of the change output. This will be what is
    43  	// left over after subtracting the requiredOutput and the tx fee from
    44  	// the inputTotal.
    45  	//
    46  	// NOTE: This might be below the dust limit, or even negative since it
    47  	// is the change remaining in csse we pay the fee for a change output.
    48  	changeOutput dcrutil.Amount
    49  
    50  	// inputs is the set of tx inputs.
    51  	inputs []input.Input
    52  
    53  	// walletInputTotal is the total value of inputs coming from the wallet.
    54  	walletInputTotal dcrutil.Amount
    55  
    56  	// force indicates that this set must be swept even if the total yield
    57  	// is negative.
    58  	force bool
    59  }
    60  
    61  // sizeEstimate is the (worst case) tx size with the current set of
    62  // inputs. It takes a parameter whether to add a change output or not.
    63  func (t *txInputSetState) sizeEstimate(change bool) *sizeEstimator {
    64  	sizeEstimate := newSizeEstimator(t.feeRate)
    65  	for _, i := range t.inputs {
    66  		// Can ignore error, because it has already been checked when
    67  		// calculating the yields.
    68  		_ = sizeEstimate.add(i)
    69  
    70  		r := i.RequiredTxOut()
    71  		if r != nil {
    72  			sizeEstimate.addOutput(r)
    73  		}
    74  	}
    75  
    76  	// Add a change output to the weight estimate if requested.
    77  	if change {
    78  		sizeEstimate.addP2PKHOutput()
    79  	}
    80  
    81  	return sizeEstimate
    82  }
    83  
    84  // totalOutput is the total amount left for us after paying fees.
    85  //
    86  // NOTE: This might be dust.
    87  func (t *txInputSetState) totalOutput() dcrutil.Amount {
    88  	return t.requiredOutput + t.changeOutput
    89  }
    90  
    91  func (t *txInputSetState) clone() txInputSetState {
    92  	s := txInputSetState{
    93  		feeRate:          t.feeRate,
    94  		inputTotal:       t.inputTotal,
    95  		changeOutput:     t.changeOutput,
    96  		requiredOutput:   t.requiredOutput,
    97  		walletInputTotal: t.walletInputTotal,
    98  		force:            t.force,
    99  		inputs:           make([]input.Input, len(t.inputs)),
   100  	}
   101  	copy(s.inputs, t.inputs)
   102  
   103  	return s
   104  }
   105  
   106  // txInputSet is an object that accumulates tx inputs and keeps running counters
   107  // on various properties of the tx.
   108  type txInputSet struct {
   109  	txInputSetState
   110  
   111  	// maxInputs is the maximum number of inputs that will be accepted in
   112  	// the set.
   113  	maxInputs int
   114  
   115  	// wallet contains wallet functionality required by the input set to
   116  	// retrieve utxos.
   117  	wallet Wallet
   118  }
   119  
   120  // newTxInputSet constructs a new, empty input set.
   121  func newTxInputSet(wallet Wallet, feePerKB chainfee.AtomPerKByte,
   122  	maxInputs int) *txInputSet {
   123  
   124  	state := txInputSetState{
   125  		feeRate: feePerKB,
   126  	}
   127  
   128  	b := txInputSet{
   129  		maxInputs:       maxInputs,
   130  		wallet:          wallet,
   131  		txInputSetState: state,
   132  	}
   133  
   134  	return &b
   135  }
   136  
   137  // enoughInput returns true if we've accumulated enough inputs to pay the fees
   138  // and have at least one output that meets the dust limit.
   139  func (t *txInputSet) enoughInput() bool {
   140  	// If we have a change output above dust, then we certainly have enough
   141  	// inputs to the transaction.
   142  	if t.changeOutput >= lnwallet.DustLimitForSize(input.P2PKHPkScriptSize) {
   143  		return true
   144  	}
   145  
   146  	// We did not have enough input for a change output. Check if we have
   147  	// enough input to pay the fees for a transaction with no change
   148  	// output.
   149  	fee := t.sizeEstimate(false).fee()
   150  	if t.inputTotal < t.requiredOutput+fee {
   151  		return false
   152  	}
   153  
   154  	// We could pay the fees, but we still need at least one output to be
   155  	// above the dust limit for the tx to be valid (we assume that these
   156  	// required outputs only get added if they are above dust)
   157  	for _, inp := range t.inputs {
   158  		if inp.RequiredTxOut() != nil {
   159  			return true
   160  		}
   161  	}
   162  
   163  	return false
   164  }
   165  
   166  // add adds a new input to the set. It returns a bool indicating whether the
   167  // input was added to the set. An input is rejected if it decreases the tx
   168  // output value after paying fees.
   169  func (t *txInputSet) addToState(inp input.Input, constraints addConstraints) *txInputSetState {
   170  	// Stop if max inputs is reached. Do not count additional wallet inputs,
   171  	// because we don't know in advance how many we may need.
   172  	if constraints != constraintsWallet &&
   173  		len(t.inputs) >= t.maxInputs {
   174  
   175  		return nil
   176  	}
   177  
   178  	// If the input comes with a required tx out that is below dust, we
   179  	// won't add it.
   180  	reqOut := inp.RequiredTxOut()
   181  	if reqOut != nil {
   182  		// Fetch the dust limit for this output.
   183  		dustLimit := lnwallet.DustLimitForSize(int64(len(reqOut.PkScript)))
   184  		if dcrutil.Amount(reqOut.Value) < dustLimit {
   185  			return nil
   186  		}
   187  	}
   188  
   189  	// Clone the current set state.
   190  	s := t.clone()
   191  
   192  	// Add the new input.
   193  	s.inputs = append(s.inputs, inp)
   194  
   195  	// Add the value of the new input.
   196  	value := dcrutil.Amount(inp.SignDesc().Output.Value)
   197  	s.inputTotal += value
   198  
   199  	// Recalculate the tx fee.
   200  	fee := s.sizeEstimate(true).fee()
   201  
   202  	// Calculate the new output value.
   203  	if reqOut != nil {
   204  		s.requiredOutput += dcrutil.Amount(reqOut.Value)
   205  	}
   206  	s.changeOutput = s.inputTotal - s.requiredOutput - fee
   207  
   208  	// Calculate the yield of this input from the change in total tx output
   209  	// value.
   210  	inputYield := s.totalOutput() - t.totalOutput()
   211  
   212  	switch constraints {
   213  
   214  	// Don't sweep inputs that cost us more to sweep than they give us.
   215  	case constraintsRegular:
   216  		if inputYield <= 0 {
   217  			return nil
   218  		}
   219  
   220  	// For force adds, no further constraints apply.
   221  	case constraintsForce:
   222  		s.force = true
   223  
   224  	// We are attaching a wallet input to raise the tx output value above
   225  	// the dust limit.
   226  	case constraintsWallet:
   227  		// Skip this wallet input if adding it would lower the output
   228  		// value.
   229  		if inputYield <= 0 {
   230  			return nil
   231  		}
   232  
   233  		// Calculate the total value that we spend in this tx from the
   234  		// wallet if we'd add this wallet input.
   235  		s.walletInputTotal += value
   236  
   237  		// In any case, we don't want to lose money by sweeping. If we
   238  		// don't get more out of the tx then we put in ourselves, do not
   239  		// add this wallet input. If there is at least one force sweep
   240  		// in the set, this does no longer apply.
   241  		//
   242  		// We should only add wallet inputs to get the tx output value
   243  		// above the dust limit, otherwise we'd only burn into fees.
   244  		// This is guarded by tryAddWalletInputsIfNeeded.
   245  		//
   246  		// TODO(joostjager): Possibly require a max ratio between the
   247  		// value of the wallet input and what we get out of this
   248  		// transaction. To prevent attaching and locking a big utxo for
   249  		// very little benefit.
   250  		if !s.force && s.walletInputTotal >= s.totalOutput() {
   251  			log.Debugf("Rejecting wallet input of %v, because it "+
   252  				"would make a negative yielding transaction "+
   253  				"(%v)",
   254  				value, s.totalOutput()-s.walletInputTotal)
   255  
   256  			return nil
   257  		}
   258  	}
   259  
   260  	return &s
   261  }
   262  
   263  // add adds a new input to the set. It returns a bool indicating whether the
   264  // input was added to the set. An input is rejected if it decreases the tx
   265  // output value after paying fees.
   266  func (t *txInputSet) add(input input.Input, constraints addConstraints) bool {
   267  	newState := t.addToState(input, constraints)
   268  	if newState == nil {
   269  		return false
   270  	}
   271  
   272  	t.txInputSetState = *newState
   273  	return true
   274  }
   275  
   276  // addPositiveYieldInputs adds sweepableInputs that have a positive yield to the
   277  // input set. This function assumes that the list of inputs is sorted descending
   278  // by yield.
   279  //
   280  // TODO(roasbeef): Consider including some negative yield inputs too to clean
   281  // up the utxo set even if it costs us some fees up front.  In the spirit of
   282  // minimizing any negative externalities we cause for the Bitcoin system as a
   283  // whole.
   284  func (t *txInputSet) addPositiveYieldInputs(sweepableInputs []txInput) {
   285  	for i, inp := range sweepableInputs {
   286  		// Apply relaxed constraints for force sweeps.
   287  		constraints := constraintsRegular
   288  		if inp.parameters().Force {
   289  			constraints = constraintsForce
   290  		}
   291  
   292  		// Try to add the input to the transaction. If that doesn't
   293  		// succeed because it wouldn't increase the output value,
   294  		// return. Assuming inputs are sorted by yield, any further
   295  		// inputs wouldn't increase the output value either.
   296  		if !t.add(inp, constraints) {
   297  			var rem []input.Input
   298  			for j := i; j < len(sweepableInputs); j++ {
   299  				rem = append(rem, sweepableInputs[j])
   300  			}
   301  			log.Debugf("%d negative yield inputs not added to "+
   302  				"input set: %v", len(rem),
   303  				inputTypeSummary(rem))
   304  			return
   305  		}
   306  
   307  		log.Debugf("Added positive yield input %v to input set",
   308  			inputTypeSummary([]input.Input{inp}))
   309  	}
   310  
   311  	// We managed to add all inputs to the set.
   312  }
   313  
   314  // tryAddWalletInputsIfNeeded retrieves utxos from the wallet and tries adding
   315  // as many as required to bring the tx output value above the given minimum.
   316  func (t *txInputSet) tryAddWalletInputsIfNeeded() error {
   317  	// If we've already have enough to pay the transaction fees and have at
   318  	// least one output materialize, no action is needed.
   319  	if t.enoughInput() {
   320  		return nil
   321  	}
   322  
   323  	// Retrieve wallet utxos. Only consider confirmed utxos to prevent
   324  	// problems around RBF rules for unconfirmed inputs. This currently
   325  	// ignores the configured coin selection strategy.
   326  	utxos, err := t.wallet.ListUnspentWitnessFromDefaultAccount(
   327  		1, math.MaxInt32,
   328  	)
   329  	if err != nil {
   330  		return err
   331  	}
   332  
   333  	for _, utxo := range utxos {
   334  		input, err := createWalletTxInput(utxo)
   335  		if err != nil {
   336  			return err
   337  		}
   338  
   339  		// If the wallet input isn't positively-yielding at this fee
   340  		// rate, skip it.
   341  		if !t.add(input, constraintsWallet) {
   342  			continue
   343  		}
   344  
   345  		// Return if we've reached the minimum output amount.
   346  		if t.enoughInput() {
   347  			return nil
   348  		}
   349  	}
   350  
   351  	// We were not able to reach the minimum output amount.
   352  	return nil
   353  }
   354  
   355  // createWalletTxInput converts a wallet utxo into an object that can be added
   356  // to the other inputs to sweep.
   357  func createWalletTxInput(utxo *lnwallet.Utxo) (input.Input, error) {
   358  	var witnessType input.WitnessType
   359  	switch utxo.AddressType {
   360  	case lnwallet.PubKeyHash:
   361  		witnessType = input.PublicKeyHash
   362  	default:
   363  		return nil, fmt.Errorf("unknown address type %v",
   364  			utxo.AddressType)
   365  	}
   366  
   367  	signDesc := &input.SignDescriptor{
   368  		Output: &wire.TxOut{
   369  			PkScript: utxo.PkScript,
   370  			Value:    int64(utxo.Value),
   371  		},
   372  		HashType: txscript.SigHashAll,
   373  	}
   374  
   375  	// A height hint doesn't need to be set, because we don't monitor these
   376  	// inputs for spend.
   377  	heightHint := uint32(0)
   378  
   379  	return input.NewBaseInput(
   380  		&utxo.OutPoint, witnessType, signDesc, heightHint,
   381  	), nil
   382  }