multi: coop close with active HTLCs on the channel

For the lncli cmd we now always initiate the coop close even if
there are active HTLCs on the channel. In case HTLCs are on the
channel and the coop close is initiated LND handles the closing
flow in the background and the lncli cmd will block until the
transaction is broadcasted to the mempool. In the background LND
disallows any new HTLCs and waits until all HTLCs are resolved
before kicking of the negotiation process.
Moreover if active HTLCs are present and the no_wait param is not
set the error msg is now highlightning it so the user can react
accordingly.
This commit is contained in:
ziggie
2025-02-13 22:16:08 +01:00
parent 319a0ee470
commit 59443faa36
6 changed files with 2353 additions and 2273 deletions

View File

@@ -1011,6 +1011,11 @@ var closeChannelCommand = cli.Command{
comparison is the end boundary of the fee negotiation, if not specified
it's always x3 of the starting value. Increasing this value increases
the chance of a successful negotiation.
Moreover if the channel has active HTLCs on it, the coop close will
wait until all HTLCs are resolved and will not allow any new HTLCs on
the channel. The channel will appear as disabled in the listchannels
output. The command will block in that case until the channel close tx
is broadcasted.
In the case of a cooperative closure, one can manually set the address
to deliver funds to upon closure. This is optional, and may only be used
@@ -1042,8 +1047,10 @@ var closeChannelCommand = cli.Command{
Usage: "attempt an uncooperative closure",
},
cli.BoolFlag{
Name: "block",
Usage: "block until the channel is closed",
Name: "block",
Usage: `block will wait for the channel to be closed,
"meaning that it will wait for the channel close tx to
get 1 confirmation.`,
},
cli.Int64Flag{
Name: "conf_target",
@@ -1117,6 +1124,9 @@ func closeChannel(ctx *cli.Context) error {
SatPerVbyte: ctx.Uint64(feeRateFlag),
DeliveryAddress: ctx.String("delivery_addr"),
MaxFeePerVbyte: ctx.Uint64("max_fee_rate"),
// This makes sure that a coop close will also be executed if
// active HTLCs are present on the channel.
NoWait: true,
}
// After parsing the request, we'll spin up a goroutine that will
@@ -1154,7 +1164,9 @@ func closeChannel(ctx *cli.Context) error {
// executeChannelClose attempts to close the channel from a request. The closing
// transaction ID is sent through `txidChan` as soon as it is broadcasted to the
// network. The block boolean is used to determine if we should block until the
// closing transaction receives all of its required confirmations.
// closing transaction receives a confirmation of 1 block. The logging outputs
// are sent to stderr to avoid conflicts with the JSON output of the command
// and potential work flows which depend on a proper JSON output.
func executeChannelClose(ctxc context.Context, client lnrpc.LightningClient,
req *lnrpc.CloseChannelRequest, txidChan chan<- string, block bool) error {
@@ -1173,9 +1185,17 @@ func executeChannelClose(ctxc context.Context, client lnrpc.LightningClient,
switch update := resp.Update.(type) {
case *lnrpc.CloseStatusUpdate_CloseInstant:
if req.NoWait {
return nil
fmt.Fprintln(os.Stderr, "Channel close successfully "+
"initiated")
pendingHtlcs := update.CloseInstant.NumPendingHtlcs
if pendingHtlcs > 0 {
fmt.Fprintf(os.Stderr, "Cooperative channel "+
"close waiting for %d HTLCs to be "+
"resolved before the close process "+
"can kick off\n", pendingHtlcs)
}
case *lnrpc.CloseStatusUpdate_ClosePending:
closingHash := update.ClosePending.Txid
txid, err := chainhash.NewHash(closingHash)
@@ -1183,12 +1203,22 @@ func executeChannelClose(ctxc context.Context, client lnrpc.LightningClient,
return err
}
fmt.Fprintf(os.Stderr, "Channel close transaction "+
"broadcasted: %v\n", txid)
txidChan <- txid.String()
if !block {
return nil
}
fmt.Fprintln(os.Stderr, "Waiting for channel close "+
"confirmation ...")
case *lnrpc.CloseStatusUpdate_ChanClose:
fmt.Fprintln(os.Stderr, "Channel close successfully "+
"confirmed")
return nil
}
}