diff --git a/htlcswitch.go b/htlcswitch.go index 188070050..0bf3f13b6 100644 --- a/htlcswitch.go +++ b/htlcswitch.go @@ -611,7 +611,7 @@ type unregisterLinkMsg struct { done chan struct{} } -// UnregisterLink requets the htlcSwitch to unregiser the new active link. An +// UnregisterLink requets the htlcSwitch to register the new active link. An // unregistered link will no longer be considered a candidate to forward // HTLC's. func (h *htlcSwitch) UnregisterLink(remotePub *btcec.PublicKey, chanPoint *wire.OutPoint) { @@ -633,14 +633,10 @@ func (h *htlcSwitch) UnregisterLink(remotePub *btcec.PublicKey, chanPoint *wire. type LinkCloseType uint8 const ( - // CloseRegular indicates a regular cooperative channel closure should be attempted. + // CloseRegular indicates a regular cooperative channel closure should + // be attempted. CloseRegular LinkCloseType = iota - // CloseForce indicates that the channel should be forcefully closed. - // This entails the broadcast of the commitment transaction directly on - // chain unilaterally. - CloseForce - // CloseBreach indicates that a channel breach has been dtected, and // the link should immediately be marked as unavailable. CloseBreach diff --git a/lnd_test.go b/lnd_test.go index 68dbdaed6..c79f469b9 100644 --- a/lnd_test.go +++ b/lnd_test.go @@ -7,7 +7,6 @@ import ( "io/ioutil" "os" "path/filepath" - "strings" "sync" "testing" "time" @@ -1235,16 +1234,10 @@ func testRevokedCloseRetribution(net *networkHarness, t *harnessTest) { t.Fatalf("db copy failed: %v", bobChan.NumUpdates) } - err = net.ConnectNodes(ctxb, net.Alice, net.Bob) - if err != nil && !strings.Contains(err.Error(), "already connected") { - t.Fatalf("unable to connect bob and alice: %v", err) - } - // Now force Bob to execute a *force* channel closure by unilaterally // broadcasting his current channel state. This is actually the // commitment transaction of a prior *revoked* state, so he'll soon // feel the wrath of Alice's retribution. - time.Sleep(time.Second * 2) breachTXID := closeChannelAndAssert(t, net, ctxb, net.Bob, chanPoint, true) @@ -1340,7 +1333,7 @@ var testsCases = []*testCase{ test: testSingleHopInvoice, }, { - name: "list of outgoing payments", + name: "list outgoing payments", test: testListPayments, }, { diff --git a/peer.go b/peer.go index 01bde9e66..667a0c623 100644 --- a/peer.go +++ b/peer.go @@ -690,39 +690,6 @@ out: p.wg.Done() } -// executeForceClose executes a unilateral close of the target channel by -// broadcasting the current commitment state directly on-chain. Once the -// commitment transaction has been broadcast, a struct describing the final -// state of the channel is sent to the utxoNursery in order to ultimately sweep -// the immature outputs. -func (p *peer) executeForceClose(channel *lnwallet.LightningChannel) (*wire.ShaHash, error) { - // Execute a unilateral close shutting down all further channel - // operation. - closeSummary, err := channel.ForceClose() - if err != nil { - return nil, err - } - - closeTx := closeSummary.CloseTx - txid := closeTx.TxSha() - - // With the close transaction in hand, broadcast the transaction to the - // network, thereby entering the psot channel resolution state. - peerLog.Infof("Broadcasting force close transaction, ChannelPoint(%v): %v", - channel.ChannelPoint(), newLogClosure(func() string { - return spew.Sdump(closeTx) - })) - if err := p.server.lnwallet.PublishTransaction(closeTx); err != nil { - return nil, err - } - - // Send the closed channel summary over to the utxoNursery in order to - // have its outputs swept back into the wallet once they're mature. - p.server.utxoNursery.incubateOutputs(closeSummary) - - return &txid, nil -} - // executeCooperativeClose executes the initial phase of a user-executed // cooperative channel close. The channel state machine is transitioned to the // closing phase, then our half of the closing witness is sent over to the @@ -771,13 +738,6 @@ func (p *peer) handleLocalClose(req *closeLinkReq) { p.activeChanMtx.RUnlock() switch req.CloseType { - // A type of CloseForce indicates that the user has opted for - // unilaterally close the channel on-chain. - case CloseForce: - closingTxid, err = p.executeForceClose(channel) - peerLog.Infof("Force closing ChannelPoint(%v) with txid: %v", - req.chanPoint, closingTxid) - // A type of CloseRegular indicates that the user has opted to close // out this channel on-chian, so we execute the cooperative channel // closre workflow. diff --git a/rpcserver.go b/rpcserver.go index 6973f7039..23eda1f77 100644 --- a/rpcserver.go +++ b/rpcserver.go @@ -359,29 +359,106 @@ func (r *rpcServer) CloseChannel(in *lnrpc.CloseChannelRequest, rpcsLog.Errorf("[closechannel] invalid txid: %v", err) return err } - targetChannelPoint := wire.NewOutPoint(txid, index) + chanPoint := wire.NewOutPoint(txid, index) rpcsLog.Tracef("[closechannel] request for ChannelPoint(%v)", - targetChannelPoint) + chanPoint) - var closeType LinkCloseType - switch force { - case true: - // TODO(roasbeef): should be able to force close w/o connection - // to peer - closeType = CloseForce - case false: - closeType = CloseRegular + var ( + updateChan chan *lnrpc.CloseStatusUpdate + errChan chan error + ) + + // If a force closure was requested, then we'll handle all the details + // around the creation and broadcast of the unilateral closure + // transaction here rather than going to the switch as we don't require + // interaction from the peer. + if force { + // As the first part of the force closure, we first fetch the + // channel from the database, then execute a direct force + // closure broadcasting our current commitment transaction. + channel, err := r.fetchActiveChannel(*chanPoint) + if err != nil { + return err + } + closingTxid, err := r.forceCloseChan(channel) + if err != nil { + return err + } + + updateChan = make(chan *lnrpc.CloseStatusUpdate) + errChan = make(chan error) + go func() { + // With the transaction broadcast, we send our first + // update to the client. + updateChan <- &lnrpc.CloseStatusUpdate{ + Update: &lnrpc.CloseStatusUpdate_ClosePending{ + ClosePending: &lnrpc.PendingUpdate{ + Txid: closingTxid[:], + }, + }, + } + + // Next, we enter the second phase, waiting for the + // channel to be confirmed before we finalize the force + // closure. + notifier := r.server.chainNotifier + confNtfn, err := notifier.RegisterConfirmationsNtfn(closingTxid, 1) + if err != nil { + errChan <- err + return + } + + select { + case txConf, ok := <-confNtfn.Confirmed: + if !ok { + return + } + + // As the channel has been closed, we can now + // delete it's state from the database. + rpcsLog.Infof("ChannelPoint(%v) is now "+ + "closed at height %v", chanPoint, + txConf.BlockHeight) + if err := channel.DeleteState(); err != nil { + errChan <- err + return + } + case <-r.quit: + return + } + + // Respond to the local sub-system which requested the + // channel closure. + updateChan <- &lnrpc.CloseStatusUpdate{ + Update: &lnrpc.CloseStatusUpdate_ChanClose{ + ChanClose: &lnrpc.ChannelCloseUpdate{ + ClosingTxid: closingTxid[:], + Success: true, + }, + }, + } + + // Finally, signal to the breachArbiter that it no + // longer needs to watch the channel as it's been + // closed. + r.server.breachArbiter.settledContracts <- chanPoint + }() + + } else { + // Otherwise, the caller has requested a regular interactive + // cooperative channel closure. So we'll forward the request to + // the htlc switch which will handle the negotiation and + // broadcast details. + updateChan, errChan = r.server.htlcSwitch.CloseLink(chanPoint, + CloseRegular) } - - updateChan, errChan := r.server.htlcSwitch.CloseLink(targetChannelPoint, closeType) - out: for { select { case err := <-errChan: rpcsLog.Errorf("[closechannel] unable to close "+ - "ChannelPoint(%v): %v", targetChannelPoint, err) + "ChannelPoint(%v): %v", chanPoint, err) return err case closingUpdate := <-updateChan: rpcsLog.Tracef("[closechannel] sending update: %v", @@ -408,6 +485,69 @@ out: return nil } +// fetchActiveChannel attempts to locate a channel identified by it's channel +// point from the database's set of all currently opened channels. +func (r *rpcServer) fetchActiveChannel(chanPoint wire.OutPoint) (*lnwallet.LightningChannel, error) { + dbChannels, err := r.server.chanDB.FetchAllChannels() + if err != nil { + return nil, err + } + + // With the channels fetched, attempt to locate the target channel + // according to its channel point. + var dbChan *channeldb.OpenChannel + for _, dbChannel := range dbChannels { + if *dbChannel.ChanID == chanPoint { + dbChan = dbChannel + break + } + } + + // If the channel cannot be located, then we exit with an error to the + // caller. + if dbChan == nil { + return nil, fmt.Errorf("unable to find channel") + } + + // Otherwise, we create a fully populated channel state machine which + // uses the db channel as backing storage. + return lnwallet.NewLightningChannel(r.server.lnwallet.Signer, + r.server.bio, r.server.chainNotifier, dbChan) +} + +// forceCloseChan executes a unilateral close of the target channel by +// broadcasting the current commitment state directly on-chain. Once the +// commitment transaction has been broadcast, a struct describing the final +// state of the channel is sent to the utxoNursery in order to ultimately sweep +// the immature outputs. +func (r *rpcServer) forceCloseChan(channel *lnwallet.LightningChannel) (*wire.ShaHash, error) { + // Execute a unilateral close shutting down all further channel + // operation. + closeSummary, err := channel.ForceClose() + if err != nil { + return nil, err + } + + closeTx := closeSummary.CloseTx + txid := closeTx.TxSha() + + // With the close transaction in hand, broadcast the transaction to the + // network, thereby entering the psot channel resolution state. + rpcsLog.Infof("Broadcasting force close transaction, ChannelPoint(%v): %v", + channel.ChannelPoint(), newLogClosure(func() string { + return spew.Sdump(closeTx) + })) + if err := r.server.lnwallet.PublishTransaction(closeTx); err != nil { + return nil, err + } + + // Send the closed channel summary over to the utxoNursery in order to + // have its outputs swept back into the wallet once they're mature. + r.server.utxoNursery.incubateOutputs(closeSummary) + + return &txid, nil +} + // GetInfo serves a request to the "getinfo" RPC call. This call returns // general information concerning the lightning node including it's LN ID, // identity address, and information concerning the number of open+pending