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)