github.com/decred/dcrlnd@v0.7.6/funding/batch.go (about) 1 package funding 2 3 import ( 4 "bytes" 5 "context" 6 "crypto/rand" 7 "encoding/base64" 8 "errors" 9 "fmt" 10 11 "github.com/decred/dcrd/chaincfg/chainhash" 12 "github.com/decred/dcrd/chaincfg/v3" 13 "github.com/decred/dcrd/dcrutil/v4" 14 "github.com/decred/dcrd/wire" 15 "github.com/decred/dcrlnd/internal/psbt" 16 "github.com/decred/dcrlnd/labels" 17 "github.com/decred/dcrlnd/lnrpc" 18 "github.com/decred/dcrlnd/lnrpc/walletrpc" 19 "github.com/decred/dcrlnd/lnwallet/chanfunding" 20 "golang.org/x/sync/errgroup" 21 ) 22 23 var ( 24 // errShuttingDown is the error that is returned if a signal on the 25 // quit channel is received which means the whole server is shutting 26 // down. 27 errShuttingDown = errors.New("shutting down") 28 29 // emptyChannelID is a channel ID that consists of all zeros. 30 emptyChannelID = [32]byte{} 31 ) 32 33 // batchChannel is a struct that keeps track of a single channel's state within 34 // the batch funding process. 35 type batchChannel struct { 36 fundingReq *InitFundingMsg 37 pendingChanID [32]byte 38 updateChan chan *lnrpc.OpenStatusUpdate 39 errChan chan error 40 fundingAddr string 41 chanPoint *wire.OutPoint 42 isPending bool 43 } 44 45 // processPsbtUpdate processes the first channel update message that is sent 46 // once the initial part of the negotiation has completed and the funding output 47 // (and therefore address) is known. 48 func (c *batchChannel) processPsbtUpdate(u *lnrpc.OpenStatusUpdate) error { 49 psbtUpdate := u.GetPsbtFund() 50 if psbtUpdate == nil { 51 return fmt.Errorf("got unexpected channel update %v", u.Update) 52 } 53 54 if psbtUpdate.FundingAmount != int64(c.fundingReq.LocalFundingAmt) { 55 return fmt.Errorf("got unexpected funding amount %d, wanted "+ 56 "%d", psbtUpdate.FundingAmount, 57 c.fundingReq.LocalFundingAmt) 58 } 59 60 c.fundingAddr = psbtUpdate.FundingAddress 61 62 return nil 63 } 64 65 // processPendingUpdate is the second channel update message that is sent once 66 // the negotiation with the peer has completed and the channel is now pending. 67 func (c *batchChannel) processPendingUpdate(u *lnrpc.OpenStatusUpdate) error { 68 pendingUpd := u.GetChanPending() 69 if pendingUpd == nil { 70 return fmt.Errorf("got unexpected channel update %v", u.Update) 71 } 72 73 hash, err := chainhash.NewHash(pendingUpd.Txid) 74 if err != nil { 75 return fmt.Errorf("could not parse outpoint TX hash: %v", err) 76 } 77 78 c.chanPoint = &wire.OutPoint{ 79 Index: pendingUpd.OutputIndex, 80 Hash: *hash, 81 } 82 c.isPending = true 83 84 return nil 85 } 86 87 // RequestParser is a function that parses an incoming RPC request into the 88 // internal funding initialization message. 89 type RequestParser func(*lnrpc.OpenChannelRequest) (*InitFundingMsg, error) 90 91 // ChannelOpener is a function that kicks off the initial channel open 92 // negotiation with the peer. 93 type ChannelOpener func(*InitFundingMsg) (chan *lnrpc.OpenStatusUpdate, 94 chan error) 95 96 // ChannelAbandoner is a function that can abandon a channel in the local 97 // database, graph and arbitrator state. 98 type ChannelAbandoner func(*wire.OutPoint) error 99 100 // WalletKitServer is a local interface that abstracts away the methods we need 101 // from the wallet kit sub server instance. 102 type WalletKitServer interface { 103 // FundPsbt creates a fully populated PSBT that contains enough inputs 104 // to fund the outputs specified in the template. 105 FundPsbt(context.Context, 106 *walletrpc.FundPsbtRequest) (*walletrpc.FundPsbtResponse, error) 107 108 // FinalizePsbt expects a partial transaction with all inputs and 109 // outputs fully declared and tries to sign all inputs that belong to 110 // the wallet. 111 FinalizePsbt(context.Context, 112 *walletrpc.FinalizePsbtRequest) (*walletrpc.FinalizePsbtResponse, 113 error) 114 115 // ReleaseOutput unlocks an output, allowing it to be available for coin 116 // selection if it remains unspent. The ID should match the one used to 117 // originally lock the output. 118 ReleaseOutput(context.Context, 119 *walletrpc.ReleaseOutputRequest) (*walletrpc.ReleaseOutputResponse, 120 error) 121 } 122 123 // Wallet is a local interface that abstracts away the methods we need from the 124 // internal lightning wallet instance. 125 type Wallet interface { 126 // PsbtFundingVerify looks up a previously registered funding intent by 127 // its pending channel ID and tries to advance the state machine by 128 // verifying the passed PSBT. 129 PsbtFundingVerify([32]byte, *psbt.Packet, bool) error 130 131 // PsbtFundingFinalize looks up a previously registered funding intent 132 // by its pending channel ID and tries to advance the state machine by 133 // finalizing the passed PSBT. 134 PsbtFundingFinalize([32]byte, *psbt.Packet, *wire.MsgTx) error 135 136 // PublishTransaction performs cursory validation (dust checks, etc), 137 // then finally broadcasts the passed transaction to the Bitcoin network. 138 PublishTransaction(*wire.MsgTx, string) error 139 140 // CancelFundingIntent allows a caller to cancel a previously registered 141 // funding intent. If no intent was found, then an error will be 142 // returned. 143 CancelFundingIntent([32]byte) error 144 } 145 146 // BatchConfig is the configuration for executing a single batch transaction for 147 // opening multiple channels atomically. 148 type BatchConfig struct { 149 // RequestParser is the function that parses an incoming RPC request 150 // into the internal funding initialization message. 151 RequestParser RequestParser 152 153 // ChannelOpener is the function that kicks off the initial channel open 154 // negotiation with the peer. 155 ChannelOpener ChannelOpener 156 157 // ChannelAbandoner is the function that can abandon a channel in the 158 // local database, graph and arbitrator state. 159 ChannelAbandoner ChannelAbandoner 160 161 // WalletKitServer is an instance of the wallet kit sub server that can 162 // handle PSBT funding and finalization. 163 WalletKitServer WalletKitServer 164 165 // Wallet is an instance of the internal lightning wallet. 166 Wallet Wallet 167 168 // NetParams contains the current bitcoin network parameters. 169 NetParams *chaincfg.Params 170 171 // Quit is the channel that is selected on to recognize if the main 172 // server is shutting down. 173 Quit chan struct{} 174 } 175 176 // Batcher is a type that can be used to perform an atomic funding of multiple 177 // channels within a single on-chain transaction. 178 type Batcher struct { 179 cfg *BatchConfig 180 181 channels []*batchChannel 182 lockedUTXOs []*walletrpc.UtxoLease 183 184 didPublish bool 185 } 186 187 // NewBatcher returns a new batch channel funding helper. 188 func NewBatcher(cfg *BatchConfig) *Batcher { 189 return &Batcher{ 190 cfg: cfg, 191 } 192 } 193 194 // BatchFund starts the atomic batch channel funding process. 195 // 196 // NOTE: This method should only be called once per instance. 197 func (b *Batcher) BatchFund(ctx context.Context, 198 req *lnrpc.BatchOpenChannelRequest) ([]*lnrpc.PendingUpdate, error) { 199 200 label, err := labels.ValidateAPI(req.Label) 201 if err != nil { 202 return nil, err 203 } 204 205 // Parse and validate each individual channel. 206 b.channels = make([]*batchChannel, 0, len(req.Channels)) 207 for idx, rpcChannel := range req.Channels { 208 // If the user specifies a channel ID, it must be exactly 32 209 // bytes long. 210 if len(rpcChannel.PendingChanId) > 0 && 211 len(rpcChannel.PendingChanId) != 32 { 212 213 return nil, fmt.Errorf("invalid temp chan ID %x", 214 rpcChannel.PendingChanId) 215 } 216 217 var pendingChanID [32]byte 218 if len(rpcChannel.PendingChanId) == 32 { 219 copy(pendingChanID[:], rpcChannel.PendingChanId) 220 221 // Don't allow the user to be clever by just setting an 222 // all zero channel ID, we need a "real" value here. 223 if pendingChanID == emptyChannelID { 224 return nil, fmt.Errorf("invalid empty temp " + 225 "chan ID") 226 } 227 } else if _, err := rand.Read(pendingChanID[:]); err != nil { 228 return nil, fmt.Errorf("error making temp chan ID: %v", 229 err) 230 } 231 232 fundingReq, err := b.cfg.RequestParser(&lnrpc.OpenChannelRequest{ 233 AtomsPerByte: req.AtomsPerByte, 234 NodePubkey: rpcChannel.NodePubkey, 235 LocalFundingAmount: rpcChannel.LocalFundingAmount, 236 PushAtoms: rpcChannel.PushAtoms, 237 TargetConf: req.TargetConf, 238 Private: rpcChannel.Private, 239 MinHtlcMAtoms: rpcChannel.MinHtlcMAtoms, 240 RemoteCsvDelay: rpcChannel.RemoteCsvDelay, 241 MinConfs: req.MinConfs, 242 SpendUnconfirmed: req.SpendUnconfirmed, 243 CloseAddress: rpcChannel.CloseAddress, 244 CommitmentType: rpcChannel.CommitmentType, 245 FundingShim: &lnrpc.FundingShim{ 246 Shim: &lnrpc.FundingShim_PsbtShim{ 247 PsbtShim: &lnrpc.PsbtShim{ 248 PendingChanId: pendingChanID[:], 249 NoPublish: true, 250 }, 251 }, 252 }, 253 }) 254 if err != nil { 255 return nil, fmt.Errorf("error parsing channel %d: %v", 256 idx, err) 257 } 258 259 // Prepare the stuff that we'll need for the internal PSBT 260 // funding. 261 fundingReq.PendingChanID = pendingChanID 262 fundingReq.ChanFunder = chanfunding.NewPsbtAssembler( 263 dcrutil.Amount(rpcChannel.LocalFundingAmount), nil, 264 b.cfg.NetParams, false, 265 ) 266 267 b.channels = append(b.channels, &batchChannel{ 268 pendingChanID: pendingChanID, 269 fundingReq: fundingReq, 270 }) 271 } 272 273 // From this point on we can fail for any of the channels and for any 274 // number of reasons. This deferred function makes sure that the full 275 // operation is actually atomic: We either succeed and publish a 276 // transaction for the full batch or we clean up everything. 277 defer b.cleanup(ctx) 278 279 // Now that we know the user input is sane, we need to kick off the 280 // channel funding negotiation with the peers. Because we specified a 281 // PSBT assembler, we'll get a special response in the channel once the 282 // funding output script is known (which we need to craft the TX). 283 eg := &errgroup.Group{} 284 for _, channel := range b.channels { 285 channel.updateChan, channel.errChan = b.cfg.ChannelOpener( 286 channel.fundingReq, 287 ) 288 289 // Launch a goroutine that waits for the initial response on 290 // either the update or error chan. 291 channel := channel 292 eg.Go(func() error { 293 return b.waitForUpdate(channel, true) 294 }) 295 } 296 297 // Wait for all goroutines to report back. Any error at this stage means 298 // we need to abort. 299 if err := eg.Wait(); err != nil { 300 return nil, fmt.Errorf("error batch opening channel, initial "+ 301 "negotiation failed: %v", err) 302 } 303 304 // We can now assemble all outputs that we're going to give to the PSBT 305 // funding method of the wallet kit server. 306 txTemplate := &walletrpc.TxTemplate{ 307 Outputs: make(map[string]uint64), 308 } 309 for _, channel := range b.channels { 310 txTemplate.Outputs[channel.fundingAddr] = uint64( 311 channel.fundingReq.LocalFundingAmt, 312 ) 313 } 314 315 // Great, we've now started the channel negotiation successfully with 316 // all peers. This means we know the channel outputs for all channels 317 // and can craft our PSBT now. We take the fee rate and min conf 318 // settings from the first request as all of them should be equal 319 // anyway. 320 firstReq := b.channels[0].fundingReq 321 feeRateAtomPerKByte := firstReq.FundingFeePerKB 322 fundPsbtReq := &walletrpc.FundPsbtRequest{ 323 Template: &walletrpc.FundPsbtRequest_Raw{ 324 Raw: txTemplate, 325 }, 326 Fees: &walletrpc.FundPsbtRequest_AtomsPerByte{ 327 AtomsPerByte: uint64(feeRateAtomPerKByte) / 1000, 328 }, 329 MinConfs: firstReq.MinConfs, 330 SpendUnconfirmed: firstReq.MinConfs == 0, 331 } 332 fundPsbtResp, err := b.cfg.WalletKitServer.FundPsbt(ctx, fundPsbtReq) 333 if err != nil { 334 return nil, fmt.Errorf("error funding PSBT for batch channel "+ 335 "open: %v", err) 336 } 337 338 // Funding was successful. This means there are some UTXOs that are now 339 // locked for us. We need to make sure we release them if we don't 340 // complete the publish process. 341 b.lockedUTXOs = fundPsbtResp.LockedUtxos 342 343 // Parse and log the funded PSBT for debugging purposes. 344 unsignedPacket, err := psbt.NewFromRawBytes( 345 bytes.NewReader(fundPsbtResp.FundedPsbt), false, 346 ) 347 if err != nil { 348 return nil, fmt.Errorf("error parsing funded PSBT for batch "+ 349 "channel open: %v", err) 350 } 351 log.Tracef("[batchopenchannel] funded PSBT: %s", 352 base64.StdEncoding.EncodeToString(fundPsbtResp.FundedPsbt)) 353 354 // With the funded PSBT we can now advance the funding state machine of 355 // each of the channels. 356 for _, channel := range b.channels { 357 err = b.cfg.Wallet.PsbtFundingVerify( 358 channel.pendingChanID, unsignedPacket, false, 359 ) 360 if err != nil { 361 return nil, fmt.Errorf("error verifying PSBT: %v", err) 362 } 363 } 364 365 // The funded PSBT was accepted by each of the assemblers, let's now 366 // sign/finalize it. 367 finalizePsbtResp, err := b.cfg.WalletKitServer.FinalizePsbt( 368 ctx, &walletrpc.FinalizePsbtRequest{ 369 FundedPsbt: fundPsbtResp.FundedPsbt, 370 }, 371 ) 372 if err != nil { 373 return nil, fmt.Errorf("error finalizing PSBT for batch "+ 374 "channel open: %v", err) 375 } 376 finalTx := &wire.MsgTx{} 377 txReader := bytes.NewReader(finalizePsbtResp.RawFinalTx) 378 if err := finalTx.Deserialize(txReader); err != nil { 379 return nil, fmt.Errorf("error parsing signed raw TX: %v", err) 380 } 381 log.Tracef("[batchopenchannel] signed PSBT: %s", 382 base64.StdEncoding.EncodeToString(finalizePsbtResp.SignedPsbt)) 383 384 // Advance the funding state machine of each of the channels a last time 385 // to complete the negotiation with the now signed funding TX. 386 for _, channel := range b.channels { 387 err = b.cfg.Wallet.PsbtFundingFinalize( 388 channel.pendingChanID, nil, finalTx, 389 ) 390 if err != nil { 391 return nil, fmt.Errorf("error finalizing PSBT: %v", err) 392 } 393 } 394 395 // Now every channel should be ready for the funding transaction to be 396 // broadcast. Let's wait for the updates that actually confirm this 397 // state. 398 eg = &errgroup.Group{} 399 for _, channel := range b.channels { 400 // Launch another goroutine that waits for the channel pending 401 // response on the update chan. 402 channel := channel 403 eg.Go(func() error { 404 return b.waitForUpdate(channel, false) 405 }) 406 } 407 408 // Wait for all updates and make sure we're still good to proceed. 409 if err := eg.Wait(); err != nil { 410 return nil, fmt.Errorf("error batch opening channel, final "+ 411 "negotiation failed: %v", err) 412 } 413 414 // Great, we're now finally ready to publish the transaction. 415 err = b.cfg.Wallet.PublishTransaction(finalTx, label) 416 if err != nil { 417 return nil, fmt.Errorf("error publishing final batch "+ 418 "transaction: %v", err) 419 } 420 b.didPublish = true 421 422 rpcPoints := make([]*lnrpc.PendingUpdate, len(b.channels)) 423 for idx, channel := range b.channels { 424 rpcPoints[idx] = &lnrpc.PendingUpdate{ 425 Txid: channel.chanPoint.Hash.CloneBytes(), 426 OutputIndex: channel.chanPoint.Index, 427 } 428 } 429 430 return rpcPoints, nil 431 } 432 433 // waitForUpdate waits for an incoming channel update (or error) for a single 434 // channel. 435 // 436 // NOTE: Must be called in a goroutine as this blocks until an update or error 437 // is received. 438 func (b *Batcher) waitForUpdate(channel *batchChannel, firstUpdate bool) error { 439 select { 440 // If an error occurs then immediately return the error to the client. 441 case err := <-channel.errChan: 442 log.Errorf("unable to open channel to NodeKey(%x): %v", 443 channel.fundingReq.TargetPubkey.SerializeCompressed(), 444 err) 445 return err 446 447 // Otherwise, wait for the next channel update. The first update sent 448 // must be the signal to start the PSBT funding in our case since we 449 // specified a PSBT shim. The second update will be the signal that the 450 // channel is now pending. 451 case fundingUpdate := <-channel.updateChan: 452 log.Tracef("[batchopenchannel] received update: %v", 453 fundingUpdate) 454 455 // Depending on what update we were waiting for the batch 456 // channel knows what to do with it. 457 if firstUpdate { 458 return channel.processPsbtUpdate(fundingUpdate) 459 } 460 461 return channel.processPendingUpdate(fundingUpdate) 462 463 case <-b.cfg.Quit: 464 return errShuttingDown 465 } 466 } 467 468 // cleanup tries to remove any pending state or UTXO locks in case we had to 469 // abort before finalizing and publishing the funding transaction. 470 func (b *Batcher) cleanup(ctx context.Context) { 471 // Did we publish a transaction? Then there's nothing to clean up since 472 // we succeeded. 473 if b.didPublish { 474 return 475 } 476 477 // Make sure the error message doesn't sound too scary. These might be 478 // logged quite frequently depending on where exactly things were 479 // aborted. We could just not log any cleanup errors though it might be 480 // helpful to debug things if something doesn't go as expected. 481 const errMsgTpl = "Attempted to clean up after failed batch channel " + 482 "open but could not %s: %v" 483 484 // If we failed, we clean up in reverse order. First, let's unlock the 485 // leased outputs. 486 for _, lockedUTXO := range b.lockedUTXOs { 487 rpcOP := &lnrpc.OutPoint{ 488 OutputIndex: lockedUTXO.Outpoint.OutputIndex, 489 TxidBytes: lockedUTXO.Outpoint.TxidBytes, 490 } 491 _, err := b.cfg.WalletKitServer.ReleaseOutput( 492 ctx, &walletrpc.ReleaseOutputRequest{ 493 Id: lockedUTXO.Id, 494 Outpoint: rpcOP, 495 }, 496 ) 497 if err != nil { 498 log.Debugf(errMsgTpl, "release locked output "+ 499 lockedUTXO.Outpoint.String(), err) 500 } 501 } 502 503 // Then go through all channels that ever got into a pending state and 504 // remove the pending channel by abandoning them. 505 for _, channel := range b.channels { 506 if !channel.isPending { 507 continue 508 } 509 510 err := b.cfg.ChannelAbandoner(channel.chanPoint) 511 if err != nil { 512 log.Debugf(errMsgTpl, "abandon pending open channel", 513 err) 514 } 515 } 516 517 // And finally clean up the funding shim for each channel that didn't 518 // make it into a pending state. 519 for _, channel := range b.channels { 520 if channel.isPending { 521 continue 522 } 523 524 err := b.cfg.Wallet.CancelFundingIntent(channel.pendingChanID) 525 if err != nil { 526 log.Debugf(errMsgTpl, "cancel funding shim", err) 527 } 528 } 529 }