Merge pull request #9491 from ziggie1984/closechannel-rpc

Allow coop closing a channel with HTLCs on it via lncli
This commit is contained in:
Oliver Gugger authored and GitHub committed 2025-02-21 05:05:53 -06:00
commit 1227eb1cce
13 files changed
+2472 -2286

No files matched your search

+30 -5
View File
@@ -2716,6 +2716,9 @@ func (r *rpcServer) CloseChannel(in *lnrpc.CloseChannelRequest,
return err
}
// Retrieve the number of active HTLCs on the channel.
activeHtlcs := channel.ActiveHtlcs()
// 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
@@ -2832,9 +2835,12 @@ func (r *rpcServer) CloseChannel(in *lnrpc.CloseChannelRequest,
// If the user hasn't specified NoWait, then before we attempt
// to close the channel we ensure there are no active HTLCs on
// the link.
if !in.NoWait && len(channel.ActiveHtlcs()) != 0 {
return fmt.Errorf("cannot co-op close channel " +
"with active htlcs")
if !in.NoWait && len(activeHtlcs) != 0 {
return fmt.Errorf("cannot coop close channel with "+
"active htlcs (number of active htlcs: %d), "+
"bypass this check and initiate the coop "+
"close by setting no_wait=true",
len(activeHtlcs))
}
// Otherwise, the caller has requested a regular interactive
@@ -2871,6 +2877,15 @@ func (r *rpcServer) CloseChannel(in *lnrpc.CloseChannelRequest,
maxFee := chainfee.SatPerKVByte(
in.MaxFeePerVbyte * 1000,
).FeePerKWeight()
// In case the max fee was specified, we check if it's less than
// the initial fee rate and abort if it is.
if maxFee != 0 && maxFee < feeRate {
return fmt.Errorf("max_fee_per_vbyte (%v) is less "+
"than the required fee rate (%v)", maxFee,
feeRate)
}
updateChan, errChan = r.server.htlcSwitch.CloseLink(
chanPoint, contractcourt.CloseRegular, feeRate,
maxFee, deliveryScript,
@@ -2878,12 +2893,19 @@ func (r *rpcServer) CloseChannel(in *lnrpc.CloseChannelRequest,
}
// If the user doesn't want to wait for the txid to come back then we
// will send an empty update to kick off the stream.
// will send an empty update to kick off the stream. This is also used
// when active htlcs are still on the channel to give the client
// immediate feedback.
if in.NoWait {
rpcsLog.Trace("[closechannel] sending instant update")
if err := updateStream.Send(
//nolint:ll
&lnrpc.CloseStatusUpdate{
Update: &lnrpc.CloseStatusUpdate_CloseInstant{},
Update: &lnrpc.CloseStatusUpdate_CloseInstant{
CloseInstant: &lnrpc.InstantUpdate{
NumPendingHtlcs: int32(len(activeHtlcs)),
},
},
},
); err != nil {
return err
@@ -2895,7 +2917,9 @@ out:
case err := <-errChan:
rpcsLog.Errorf("[closechannel] unable to close "+
"ChannelPoint(%v): %v", chanPoint, err)
return err
case closingUpdate := <-updateChan:
rpcClosingUpdate, err := createRPCCloseUpdate(
closingUpdate,
@@ -2934,6 +2958,7 @@ out:
"txid(%v)", h)
break out
}
case <-r.quit:
return nil
}