mirror of
https://github.com/lightningnetwork/lnd.git
synced 2025-09-08 23:01:53 +02:00
multi: allow force channel closures while not connected to peer
This commit adds a much needed feature to the daemon, namely the ability to force close a channel while the source daemon doesn’t have an active connection to the counter party. Previously this wasn’t possible as ALL channel closures were routed through the htlcSwitch which is only able to trigger a channel closure if the peer is online. To remedy this, if the closure type is “force” then, we now handle the channel closure and related RPC streaming updates from the call handler site of the RPC itself. As a result, there are now only two htlcSwitch channel closure types: breach, and regular. The logic that’s now in the rpcSever should likely be refactored into a distinct sub-system, but getting the initial functionality in is important. Finally, the channel breach integration test has been modified to skip connection the peers before attempting the forceful channel closure of a revoked state as the remote peer no longer needs to be online.
This commit is contained in:
168
rpcserver.go
168
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
|
||||
|
Reference in New Issue
Block a user