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

     1  package sweep
     2  
     3  import (
     4  	"fmt"
     5  	"math"
     6  
     7  	"github.com/decred/dcrd/chaincfg/v3"
     8  	"github.com/decred/dcrd/dcrutil/v4"
     9  	"github.com/decred/dcrd/txscript/v4"
    10  	"github.com/decred/dcrd/txscript/v4/stdaddr"
    11  	"github.com/decred/dcrd/txscript/v4/stdscript"
    12  	"github.com/decred/dcrd/wire"
    13  	"github.com/decred/dcrlnd/input"
    14  	"github.com/decred/dcrlnd/lnwallet"
    15  	"github.com/decred/dcrlnd/lnwallet/chainfee"
    16  )
    17  
    18  const (
    19  	// defaultNumBlocksEstimate is the number of blocks that we fall back
    20  	// to issuing an estimate for if a fee pre fence doesn't specify an
    21  	// explicit conf target or fee rate.
    22  	defaultNumBlocksEstimate = 6
    23  
    24  	scriptVersion uint16 = 0
    25  )
    26  
    27  // FeePreference allows callers to express their time value for inclusion of a
    28  // transaction into a block via either a confirmation target, or a fee rate.
    29  type FeePreference struct {
    30  	// ConfTarget if non-zero, signals a fee preference expressed in the
    31  	// number of desired blocks between first broadcast, and confirmation.
    32  	ConfTarget uint32
    33  
    34  	// FeeRate if non-zero, signals a fee pre fence expressed in the fee
    35  	// rate expressed in atom/KB for a particular transaction.
    36  	FeeRate chainfee.AtomPerKByte
    37  }
    38  
    39  // String returns a human-readable string of the fee preference.
    40  func (p FeePreference) String() string {
    41  	if p.ConfTarget != 0 {
    42  		return fmt.Sprintf("%v blocks", p.ConfTarget)
    43  	}
    44  	return p.FeeRate.String()
    45  }
    46  
    47  // DetermineFeePerKB will determine the fee in atom/KB that should be paid
    48  // given an estimator, a confirmation target, and a manual value for sat/byte.
    49  // A value is chosen based on the two free parameters as one, or both of them
    50  // can be zero.
    51  func DetermineFeePerKB(feeEstimator chainfee.Estimator,
    52  	feePref FeePreference) (chainfee.AtomPerKByte, error) {
    53  
    54  	switch {
    55  	// If both values are set, then we'll return an error as we require a
    56  	// strict directive.
    57  	case feePref.FeeRate != 0 && feePref.ConfTarget != 0:
    58  		return 0, fmt.Errorf("only FeeRate or ConfTarget should " +
    59  			"be set for FeePreferences")
    60  
    61  	// If the target number of confirmations is set, then we'll use that to
    62  	// consult our fee estimator for an adequate fee.
    63  	case feePref.ConfTarget != 0:
    64  		feePerKB, err := feeEstimator.EstimateFeePerKB(
    65  			feePref.ConfTarget,
    66  		)
    67  		if err != nil {
    68  			return 0, fmt.Errorf("unable to query fee "+
    69  				"estimator: %v", err)
    70  		}
    71  
    72  		return feePerKB, nil
    73  
    74  	// If a manual sat/byte fee rate is set, then we'll use that directly.
    75  	// We'll need to convert it to atom/KB as this is what we use
    76  	// internally.
    77  	case feePref.FeeRate != 0:
    78  		feePerKB := feePref.FeeRate
    79  		if feePerKB < chainfee.FeePerKBFloor {
    80  			log.Infof("Manual fee rate input of %d atom/KB is "+
    81  				"too low, using %d atom/KB instead", feePerKB,
    82  				chainfee.FeePerKBFloor)
    83  
    84  			feePerKB = chainfee.FeePerKBFloor
    85  		}
    86  
    87  		return feePerKB, nil
    88  
    89  	// Otherwise, we'll attempt a relaxed confirmation target for the
    90  	// transaction
    91  	default:
    92  		feePerKB, err := feeEstimator.EstimateFeePerKB(
    93  			defaultNumBlocksEstimate,
    94  		)
    95  		if err != nil {
    96  			return 0, fmt.Errorf("unable to query fee estimator: "+
    97  				"%v", err)
    98  		}
    99  
   100  		return feePerKB, nil
   101  	}
   102  }
   103  
   104  // UtxoSource is an interface that allows a caller to access a source of UTXOs
   105  // to use when crafting sweep transactions.
   106  type UtxoSource interface {
   107  	// ListUnspentWitness returns all UTXOs from the default wallet account
   108  	// that have between minConfs and maxConfs number of confirmations.
   109  	ListUnspentWitnessFromDefaultAccount(minConfs, maxConfs int32) (
   110  		[]*lnwallet.Utxo, error)
   111  }
   112  
   113  // CoinSelectionLocker is an interface that allows the caller to perform an
   114  // operation, which is synchronized with all coin selection attempts. This can
   115  // be used when an operation requires that all coin selection operations cease
   116  // forward progress. Think of this as an exclusive lock on coin selection
   117  // operations.
   118  type CoinSelectionLocker interface {
   119  	// WithCoinSelectLock will execute the passed function closure in a
   120  	// synchronized manner preventing any coin selection operations from
   121  	// proceeding while the closure is executing. This can be seen as the
   122  	// ability to execute a function closure under an exclusive coin
   123  	// selection lock.
   124  	WithCoinSelectLock(func() error) error
   125  }
   126  
   127  // OutpointLocker allows a caller to lock/unlock an outpoint. When locked, the
   128  // outpoints shouldn't be used for any sort of channel funding of coin
   129  // selection. Locked outpoints are not expected to be persisted between restarts.
   130  type OutpointLocker interface {
   131  	// LockOutpoint locks a target outpoint, rendering it unusable for coin
   132  	// selection.
   133  	LockOutpoint(o wire.OutPoint)
   134  
   135  	// UnlockOutpoint unlocks a target outpoint, allowing it to be used for
   136  	// coin selection once again.
   137  	UnlockOutpoint(o wire.OutPoint)
   138  }
   139  
   140  // WalletSweepPackage is a package that gives the caller the ability to sweep
   141  // ALL funds from a wallet in a single transaction. We also package a function
   142  // closure that allows one to abort the operation.
   143  type WalletSweepPackage struct {
   144  	// SweepTx is a fully signed, and valid transaction that is broadcast,
   145  	// will sweep ALL confirmed coins in the wallet with a single
   146  	// transaction.
   147  	SweepTx *wire.MsgTx
   148  
   149  	// CancelSweepAttempt allows the caller to cancel the sweep attempt.
   150  	//
   151  	// NOTE: If the sweeping transaction isn't or cannot be broadcast, then
   152  	// this closure MUST be called, otherwise all selected utxos will be
   153  	// unable to be used.
   154  	CancelSweepAttempt func()
   155  }
   156  
   157  // DeliveryAddr is a pair of (address, amount) used to craft a transaction
   158  // paying to more than one specified address.
   159  type DeliveryAddr struct {
   160  	// Addr is the address to pay to.
   161  	Addr stdaddr.Address
   162  
   163  	// Amt is the amount to pay to the given address.
   164  	Amt dcrutil.Amount
   165  }
   166  
   167  // CraftSweepAllTx attempts to craft a WalletSweepPackage which will allow the
   168  // caller to sweep ALL outputs within the wallet to a list of outputs. Any
   169  // leftover amount after these outputs and transaction fee, is sent to a single
   170  // output, as specified by the change address. The sweep transaction will be
   171  // crafted with the target fee rate, and will use the utxoSource and
   172  // outpointLocker as sources for wallet funds.
   173  func CraftSweepAllTx(feeRate chainfee.AtomPerKByte, blockHeight uint32,
   174  	deliveryAddrs []DeliveryAddr, changeAddr stdaddr.Address,
   175  	coinSelectLocker CoinSelectionLocker, utxoSource UtxoSource,
   176  	outpointLocker OutpointLocker, feeEstimator chainfee.Estimator,
   177  	signer input.Signer, netParams *chaincfg.Params, minConfs int32) (*WalletSweepPackage, error) {
   178  
   179  	// TODO(roasbeef): turn off ATPL as well when available?
   180  
   181  	var allOutputs []*lnwallet.Utxo
   182  
   183  	// We'll make a function closure up front that allows us to unlock all
   184  	// selected outputs to ensure that they become available again in the
   185  	// case of an error after the outputs have been locked, but before we
   186  	// can actually craft a sweeping transaction.
   187  	unlockOutputs := func() {
   188  		for _, utxo := range allOutputs {
   189  			outpointLocker.UnlockOutpoint(utxo.OutPoint)
   190  		}
   191  	}
   192  
   193  	// Next, we'll use the coinSelectLocker to ensure that no coin
   194  	// selection takes place while we fetch and lock all outputs the wallet
   195  	// knows of.  Otherwise, it may be possible for a new funding flow to
   196  	// lock an output while we fetch the set of unspent witnesses.
   197  	err := coinSelectLocker.WithCoinSelectLock(func() error {
   198  		// Now that we can be sure that no other coin selection
   199  		// operations are going on, we can grab a clean snapshot of the
   200  		// current UTXO state of the wallet.
   201  		utxos, err := utxoSource.ListUnspentWitnessFromDefaultAccount(
   202  			minConfs, math.MaxInt32,
   203  		)
   204  		if err != nil {
   205  			return err
   206  		}
   207  
   208  		// We'll now lock each UTXO to ensure that other callers don't
   209  		// attempt to use these UTXOs in transactions while we're
   210  		// crafting out sweep all transaction.
   211  		for _, utxo := range utxos {
   212  			outpointLocker.LockOutpoint(utxo.OutPoint)
   213  		}
   214  
   215  		allOutputs = append(allOutputs, utxos...)
   216  
   217  		return nil
   218  	})
   219  	if err != nil {
   220  		// If we failed at all, we'll unlock any outputs selected just
   221  		// in case we had any lingering outputs.
   222  		unlockOutputs()
   223  
   224  		return nil, fmt.Errorf("unable to fetch+lock wallet "+
   225  			"utxos: %v", err)
   226  	}
   227  
   228  	// Now that we've locked all the potential outputs to sweep, we'll
   229  	// assemble an input for each of them, so we can hand it off to the
   230  	// sweeper to generate and sign a transaction for us.
   231  	var inputsToSweep []input.Input
   232  	for _, output := range allOutputs {
   233  		// As we'll be signing for outputs under control of the wallet,
   234  		// we only need to populate the output value and output script.
   235  		// The rest of the items will be populated internally within
   236  		// the sweeper via the witness generation function.
   237  		signDesc := &input.SignDescriptor{
   238  			Output: &wire.TxOut{
   239  				PkScript: output.PkScript,
   240  				Value:    int64(output.Value),
   241  			},
   242  			HashType: txscript.SigHashAll,
   243  		}
   244  
   245  		pkScript := output.PkScript
   246  		scriptClass := stdscript.DetermineScriptType(
   247  			scriptVersion, pkScript,
   248  		)
   249  
   250  		// Based on the output type, we'll map it to the proper witness
   251  		// type so we can generate the set of input scripts needed to
   252  		// sweep the output.
   253  		var witnessType input.WitnessType
   254  		switch {
   255  
   256  		// We only support redeeming standard p2pkh outputs for the
   257  		// moment.
   258  		case scriptClass == stdscript.STPubKeyHashEcdsaSecp256k1:
   259  			witnessType = input.PublicKeyHash
   260  
   261  		// All other output types we count as unknown and will fail to
   262  		// sweep.
   263  		default:
   264  			unlockOutputs()
   265  
   266  			return nil, fmt.Errorf("unable to sweep coins, "+
   267  				"unknown script: %x", pkScript[:])
   268  		}
   269  
   270  		// Now that we've constructed the items required, we'll make an
   271  		// input which can be passed to the sweeper for ultimate
   272  		// sweeping.
   273  		input := input.MakeBaseInput(
   274  			&output.OutPoint, witnessType, signDesc, 0, nil,
   275  		)
   276  		inputsToSweep = append(inputsToSweep, &input)
   277  	}
   278  
   279  	// Create a list of TxOuts from the given delivery addresses.
   280  	var txOuts []*wire.TxOut
   281  	for _, d := range deliveryAddrs {
   282  		version, pkScript := d.Addr.PaymentScript()
   283  
   284  		txOuts = append(txOuts, &wire.TxOut{
   285  			Version:  version,
   286  			PkScript: pkScript,
   287  			Value:    int64(d.Amt),
   288  		})
   289  	}
   290  
   291  	// Next, we'll convert the change addr to a pkScript that we can use
   292  	// to create the sweep transaction.
   293  	changePkScript, err := input.PayToAddrScript(changeAddr)
   294  	if err != nil {
   295  		unlockOutputs()
   296  
   297  		return nil, err
   298  	}
   299  
   300  	// Finally, we'll ask the sweeper to craft a sweep transaction which
   301  	// respects our fee preference and targets all the UTXOs of the wallet.
   302  	sweepTx, err := createSweepTx(
   303  		inputsToSweep, txOuts, changePkScript, blockHeight, feeRate,
   304  		signer, netParams,
   305  	)
   306  	if err != nil {
   307  		unlockOutputs()
   308  
   309  		return nil, err
   310  	}
   311  
   312  	return &WalletSweepPackage{
   313  		SweepTx:            sweepTx,
   314  		CancelSweepAttempt: unlockOutputs,
   315  	}, nil
   316  }