github.com/decred/dcrlnd@v0.7.6/lnwallet/chanfunding/wallet_assembler.go (about)

     1  package chanfunding
     2  
     3  import (
     4  	"fmt"
     5  	"math"
     6  
     7  	"github.com/decred/dcrd/dcrec/secp256k1/v4"
     8  	"github.com/decred/dcrd/dcrutil/v4"
     9  	"github.com/decred/dcrd/dcrutil/v4/txsort"
    10  	"github.com/decred/dcrd/txscript/v4"
    11  	"github.com/decred/dcrd/wire"
    12  	"github.com/decred/dcrlnd/input"
    13  	"github.com/decred/dcrlnd/keychain"
    14  )
    15  
    16  // FullIntent is an intent that is fully backed by the internal wallet. This
    17  // intent differs from the ShimIntent, in that the funding transaction will be
    18  // constructed internally, and will consist of only inputs we wholly control.
    19  // This Intent implements a basic state machine that must be executed in order
    20  // before CompileFundingTx can be called.
    21  //
    22  // Steps to final channel provisioning:
    23  //  1. Call BindKeys to notify the intent which keys to use when constructing
    24  //     the multi-sig output.
    25  //  2. Call CompileFundingTx afterwards to obtain the funding transaction.
    26  //
    27  // If either of these steps fail, then the Cancel method MUST be called.
    28  type FullIntent struct {
    29  	ShimIntent
    30  
    31  	// InputCoins are the set of coins selected as inputs to this funding
    32  	// transaction.
    33  	InputCoins []Coin
    34  
    35  	// ChangeOutputs are the set of outputs that the Assembler will use as
    36  	// change from the main funding transaction.
    37  	ChangeOutputs []*wire.TxOut
    38  
    39  	// coinLocker is the Assembler's instance of the OutpointLocker
    40  	// interface.
    41  	coinLocker OutpointLocker
    42  
    43  	// coinSource is the Assembler's instance of the CoinSource interface.
    44  	coinSource CoinSource
    45  
    46  	// signer is the Assembler's instance of the Singer interface.
    47  	signer input.Signer
    48  }
    49  
    50  // BindKeys is a method unique to the FullIntent variant. This allows the
    51  // caller to decide precisely which keys are used in the final funding
    52  // transaction. This is kept out of the main Assembler as these may may not
    53  // necessarily be under full control of the wallet. Only after this method has
    54  // been executed will CompileFundingTx succeed.
    55  func (f *FullIntent) BindKeys(localKey *keychain.KeyDescriptor,
    56  	remoteKey *secp256k1.PublicKey) {
    57  
    58  	f.localKey = localKey
    59  	f.remoteKey = remoteKey
    60  }
    61  
    62  // CompileFundingTx is to be called after BindKeys on the sub-intent has been
    63  // called. This method will construct the final funding transaction, and fully
    64  // sign all inputs that are known by the backing CoinSource. After this method
    65  // returns, the Intent is assumed to be complete, as the output can be created
    66  // at any point.
    67  func (f *FullIntent) CompileFundingTx(extraInputs []*wire.TxIn,
    68  	extraOutputs []*wire.TxOut) (*wire.MsgTx, error) {
    69  
    70  	// Create a blank, fresh transaction. Soon to be a complete funding
    71  	// transaction which will allow opening a lightning channel.
    72  	fundingTx := wire.NewMsgTx()
    73  	fundingTx.Version = 2
    74  
    75  	// Add all multi-party inputs and outputs to the transaction.
    76  	for _, coin := range f.InputCoins {
    77  		fundingTx.AddTxIn(&wire.TxIn{
    78  			PreviousOutPoint: coin.OutPoint,
    79  		})
    80  	}
    81  	for _, theirInput := range extraInputs {
    82  		fundingTx.AddTxIn(theirInput)
    83  	}
    84  	for _, ourChangeOutput := range f.ChangeOutputs {
    85  		fundingTx.AddTxOut(ourChangeOutput)
    86  	}
    87  	for _, theirChangeOutput := range extraOutputs {
    88  		fundingTx.AddTxOut(theirChangeOutput)
    89  	}
    90  
    91  	_, fundingOutput, err := f.FundingOutput()
    92  	if err != nil {
    93  		return nil, err
    94  	}
    95  
    96  	// Sort the transaction. Since both side agree to a canonical ordering,
    97  	// by sorting we no longer need to send the entire transaction. Only
    98  	// signatures will be exchanged.
    99  	fundingTx.AddTxOut(fundingOutput)
   100  	txsort.InPlaceSort(fundingTx)
   101  
   102  	// Now that the funding tx has been fully assembled, we'll locate the
   103  	// index of the funding output so we can create our final channel
   104  	// point.
   105  	_, multiSigIndex := input.FindScriptOutputIndex(
   106  		fundingTx, fundingOutput.PkScript,
   107  	)
   108  
   109  	// Next, sign all inputs that are ours, collecting the signatures in
   110  	// order of the inputs.
   111  	signDesc := input.SignDescriptor{
   112  		HashType: txscript.SigHashAll,
   113  	}
   114  	for i, txIn := range fundingTx.TxIn {
   115  		// We can only sign this input if it's ours, so we'll ask the
   116  		// coin source if it can map this outpoint into a coin we own.
   117  		// If not, then we'll continue as it isn't our input.
   118  		info, err := f.coinSource.CoinFromOutPoint(
   119  			txIn.PreviousOutPoint,
   120  		)
   121  		if err != nil {
   122  			continue
   123  		}
   124  
   125  		// Now that we know the input is ours, we'll populate the
   126  		// signDesc with the per input unique information.
   127  		signDesc.Output = &wire.TxOut{
   128  			// TODO: add Version
   129  			Value:    info.Value,
   130  			PkScript: info.PkScript,
   131  		}
   132  		signDesc.InputIndex = i
   133  
   134  		// Finally, we'll sign the input as is, and populate the input
   135  		// with the witness and sigScript (if needed).
   136  		inputScript, err := f.signer.ComputeInputScript(
   137  			fundingTx, &signDesc,
   138  		)
   139  		if err != nil {
   140  			return nil, err
   141  		}
   142  
   143  		sigScript, err := input.WitnessStackToSigScript(inputScript.Witness)
   144  		if err != nil {
   145  			return nil, err
   146  		}
   147  		txIn.SignatureScript = sigScript
   148  	}
   149  
   150  	// Finally, we'll populate the chanPoint now that we've fully
   151  	// constructed the funding transaction.
   152  	f.chanPoint = &wire.OutPoint{
   153  		Hash:  fundingTx.TxHash(),
   154  		Index: multiSigIndex,
   155  	}
   156  
   157  	return fundingTx, nil
   158  }
   159  
   160  // Inputs returns all inputs to the final funding transaction that we
   161  // know about. Since this funding transaction is created all from our wallet,
   162  // it will be all inputs.
   163  func (f *FullIntent) Inputs() []wire.OutPoint {
   164  	var ins []wire.OutPoint
   165  	for _, coin := range f.InputCoins {
   166  		ins = append(ins, coin.OutPoint)
   167  	}
   168  
   169  	return ins
   170  }
   171  
   172  // Outputs returns all outputs of the final funding transaction that we
   173  // know about. This will be the funding output and the change outputs going
   174  // back to our wallet.
   175  func (f *FullIntent) Outputs() []*wire.TxOut {
   176  	outs := f.ShimIntent.Outputs()
   177  	outs = append(outs, f.ChangeOutputs...)
   178  
   179  	return outs
   180  }
   181  
   182  // Cancel allows the caller to cancel a funding Intent at any time.  This will
   183  // return any resources such as coins back to the eligible pool to be used in
   184  // order channel fundings.
   185  //
   186  // NOTE: Part of the chanfunding.Intent interface.
   187  func (f *FullIntent) Cancel() {
   188  	for _, coin := range f.InputCoins {
   189  		f.coinLocker.UnlockOutpoint(coin.OutPoint)
   190  	}
   191  
   192  	f.ShimIntent.Cancel()
   193  }
   194  
   195  // A compile-time check to ensure FullIntent meets the Intent interface.
   196  var _ Intent = (*FullIntent)(nil)
   197  
   198  // WalletConfig is the main config of the WalletAssembler.
   199  type WalletConfig struct {
   200  	// CoinSource is what the WalletAssembler uses to list/locate coins.
   201  	CoinSource CoinSource
   202  
   203  	// CoinSelectionLocker allows the WalletAssembler to gain exclusive
   204  	// access to the current set of coins returned by the CoinSource.
   205  	CoinSelectLocker CoinSelectionLocker
   206  
   207  	// CoinLocker is what the WalletAssembler uses to lock coins that may
   208  	// be used as inputs for a new funding transaction.
   209  	CoinLocker OutpointLocker
   210  
   211  	// Signer allows the WalletAssembler to sign inputs on any potential
   212  	// funding transactions.
   213  	Signer input.Signer
   214  
   215  	// DustLimit is the current dust limit. We'll use this to ensure that
   216  	// we don't make dust outputs on the funding transaction.
   217  	DustLimit dcrutil.Amount
   218  }
   219  
   220  // WalletAssembler is an instance of the Assembler interface that is backed by
   221  // a full wallet. This variant of the Assembler interface will produce the
   222  // entirety of the funding transaction within the wallet. This implements the
   223  // typical funding flow that is initiated either on the p2p level or using the
   224  // CLi.
   225  type WalletAssembler struct {
   226  	cfg WalletConfig
   227  }
   228  
   229  // NewWalletAssembler creates a new instance of the WalletAssembler from a
   230  // fully populated wallet config.
   231  func NewWalletAssembler(cfg WalletConfig) *WalletAssembler {
   232  	return &WalletAssembler{
   233  		cfg: cfg,
   234  	}
   235  }
   236  
   237  // ProvisionChannel is the main entry point to begin a funding workflow given a
   238  // fully populated request. The internal WalletAssembler will perform coin
   239  // selection in a goroutine safe manner, returning an Intent that will allow
   240  // the caller to finalize the funding process.
   241  //
   242  // NOTE: To cancel the funding flow the Cancel() method on the returned Intent,
   243  // MUST be called.
   244  //
   245  // NOTE: This is a part of the chanfunding.Assembler interface.
   246  func (w *WalletAssembler) ProvisionChannel(r *Request) (Intent, error) {
   247  	var intent Intent
   248  
   249  	// We hold the coin select mutex while querying for outputs, and
   250  	// performing coin selection in order to avoid inadvertent double
   251  	// spends across funding transactions.
   252  	err := w.cfg.CoinSelectLocker.WithCoinSelectLock(func() error {
   253  		log.Infof("Performing funding tx coin selection using %v "+
   254  			"atoms/kB as fee rate", int64(r.FeeRate))
   255  
   256  		// Find all unlocked unspent witness outputs that satisfy the
   257  		// minimum number of confirmations required. Coin selection in
   258  		// this function currently ignores the configured coin selection
   259  		// strategy.
   260  		coins, err := w.cfg.CoinSource.ListCoins(
   261  			r.MinConfs, math.MaxInt32,
   262  		)
   263  		if err != nil {
   264  			return err
   265  		}
   266  
   267  		var (
   268  			selectedCoins        []Coin
   269  			localContributionAmt dcrutil.Amount
   270  			changeAmt            dcrutil.Amount
   271  		)
   272  
   273  		// Perform coin selection over our available, unlocked unspent
   274  		// outputs in order to find enough coins to meet the funding
   275  		// amount requirements.
   276  		switch {
   277  		// If there's no funding amount at all (receiving an inbound
   278  		// single funder request), then we don't need to perform any
   279  		// coin selection at all.
   280  		case r.LocalAmt == 0:
   281  			break
   282  
   283  		// In case this request want the fees subtracted from the local
   284  		// amount, we'll call the specialized method for that. This
   285  		// ensures that we won't deduct more that the specified balance
   286  		// from our wallet.
   287  		case r.SubtractFees:
   288  			dustLimit := w.cfg.DustLimit
   289  			selectedCoins, localContributionAmt, changeAmt, err = CoinSelectSubtractFees(
   290  				r.FeeRate, r.LocalAmt, dustLimit, coins,
   291  			)
   292  			if err != nil {
   293  				return err
   294  			}
   295  
   296  		// Otherwise do a normal coin selection where we target a given
   297  		// funding amount.
   298  		default:
   299  			dustLimit := w.cfg.DustLimit
   300  			localContributionAmt = r.LocalAmt
   301  			selectedCoins, changeAmt, err = CoinSelect(
   302  				r.FeeRate, r.LocalAmt, dustLimit, coins,
   303  			)
   304  			if err != nil {
   305  				return err
   306  			}
   307  		}
   308  
   309  		// Sanity check: The addition of the outputs should not lead to the
   310  		// creation of dust.
   311  		if changeAmt != 0 && changeAmt < w.cfg.DustLimit {
   312  			return fmt.Errorf("change amount(%v) after coin "+
   313  				"select is below dust limit(%v)", changeAmt,
   314  				w.cfg.DustLimit)
   315  		}
   316  
   317  		// Record any change output(s) generated as a result of the
   318  		// coin selection.
   319  		var changeOutput *wire.TxOut
   320  		if changeAmt != 0 {
   321  			changeAddr, err := r.ChangeAddr()
   322  			if err != nil {
   323  				return err
   324  			}
   325  			changeScript, err := input.PayToAddrScript(changeAddr)
   326  			if err != nil {
   327  				return err
   328  			}
   329  
   330  			changeOutput = &wire.TxOut{
   331  				Value:    int64(changeAmt),
   332  				PkScript: changeScript,
   333  			}
   334  		}
   335  
   336  		// Lock the selected coins. These coins are now "reserved",
   337  		// this prevents concurrent funding requests from referring to
   338  		// and this double-spending the same set of coins.
   339  		for _, coin := range selectedCoins {
   340  			outpoint := coin.OutPoint
   341  
   342  			w.cfg.CoinLocker.LockOutpoint(outpoint)
   343  		}
   344  
   345  		newIntent := &FullIntent{
   346  			ShimIntent: ShimIntent{
   347  				localFundingAmt:  localContributionAmt,
   348  				remoteFundingAmt: r.RemoteAmt,
   349  			},
   350  			InputCoins: selectedCoins,
   351  			coinLocker: w.cfg.CoinLocker,
   352  			coinSource: w.cfg.CoinSource,
   353  			signer:     w.cfg.Signer,
   354  		}
   355  
   356  		if changeOutput != nil {
   357  			newIntent.ChangeOutputs = []*wire.TxOut{changeOutput}
   358  		}
   359  
   360  		intent = newIntent
   361  
   362  		return nil
   363  	})
   364  	if err != nil {
   365  		return nil, err
   366  	}
   367  
   368  	return intent, nil
   369  }
   370  
   371  // FundingTxAvailable is an empty method that an assembler can implement to
   372  // signal to callers that its able to provide the funding transaction for the
   373  // channel via the intent it returns.
   374  //
   375  // NOTE: This method is a part of the FundingTxAssembler interface.
   376  func (w *WalletAssembler) FundingTxAvailable() {}
   377  
   378  // A compile-time assertion to ensure the WalletAssembler meets the
   379  // FundingTxAssembler interface.
   380  var _ FundingTxAssembler = (*WalletAssembler)(nil)