Skip to content

Commit 59443fa

Browse files
committed
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.
1 parent 319a0ee commit 59443fa

File tree

6 files changed

+2346
-2266
lines changed

6 files changed

+2346
-2266
lines changed

cmd/commands/commands.go

Lines changed: 35 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1011,6 +1011,11 @@ var closeChannelCommand = cli.Command{
10111011
comparison is the end boundary of the fee negotiation, if not specified
10121012
it's always x3 of the starting value. Increasing this value increases
10131013
the chance of a successful negotiation.
1014+
Moreover if the channel has active HTLCs on it, the coop close will
1015+
wait until all HTLCs are resolved and will not allow any new HTLCs on
1016+
the channel. The channel will appear as disabled in the listchannels
1017+
output. The command will block in that case until the channel close tx
1018+
is broadcasted.
10141019
10151020
In the case of a cooperative closure, one can manually set the address
10161021
to deliver funds to upon closure. This is optional, and may only be used
@@ -1042,8 +1047,10 @@ var closeChannelCommand = cli.Command{
10421047
Usage: "attempt an uncooperative closure",
10431048
},
10441049
cli.BoolFlag{
1045-
Name: "block",
1046-
Usage: "block until the channel is closed",
1050+
Name: "block",
1051+
Usage: `block will wait for the channel to be closed,
1052+
"meaning that it will wait for the channel close tx to
1053+
get 1 confirmation.`,
10471054
},
10481055
cli.Int64Flag{
10491056
Name: "conf_target",
@@ -1117,6 +1124,9 @@ func closeChannel(ctx *cli.Context) error {
11171124
SatPerVbyte: ctx.Uint64(feeRateFlag),
11181125
DeliveryAddress: ctx.String("delivery_addr"),
11191126
MaxFeePerVbyte: ctx.Uint64("max_fee_rate"),
1127+
// This makes sure that a coop close will also be executed if
1128+
// active HTLCs are present on the channel.
1129+
NoWait: true,
11201130
}
11211131

11221132
// After parsing the request, we'll spin up a goroutine that will
@@ -1154,7 +1164,9 @@ func closeChannel(ctx *cli.Context) error {
11541164
// executeChannelClose attempts to close the channel from a request. The closing
11551165
// transaction ID is sent through `txidChan` as soon as it is broadcasted to the
11561166
// network. The block boolean is used to determine if we should block until the
1157-
// closing transaction receives all of its required confirmations.
1167+
// closing transaction receives a confirmation of 1 block. The logging outputs
1168+
// are sent to stderr to avoid conflicts with the JSON output of the command
1169+
// and potential work flows which depend on a proper JSON output.
11581170
func executeChannelClose(ctxc context.Context, client lnrpc.LightningClient,
11591171
req *lnrpc.CloseChannelRequest, txidChan chan<- string, block bool) error {
11601172

@@ -1173,22 +1185,40 @@ func executeChannelClose(ctxc context.Context, client lnrpc.LightningClient,
11731185

11741186
switch update := resp.Update.(type) {
11751187
case *lnrpc.CloseStatusUpdate_CloseInstant:
1176-
if req.NoWait {
1177-
return nil
1188+
fmt.Fprintln(os.Stderr, "Channel close successfully "+
1189+
"initiated")
1190+
1191+
pendingHtlcs := update.CloseInstant.NumPendingHtlcs
1192+
if pendingHtlcs > 0 {
1193+
fmt.Fprintf(os.Stderr, "Cooperative channel "+
1194+
"close waiting for %d HTLCs to be "+
1195+
"resolved before the close process "+
1196+
"can kick off\n", pendingHtlcs)
11781197
}
1198+
11791199
case *lnrpc.CloseStatusUpdate_ClosePending:
11801200
closingHash := update.ClosePending.Txid
11811201
txid, err := chainhash.NewHash(closingHash)
11821202
if err != nil {
11831203
return err
11841204
}
11851205

1206+
fmt.Fprintf(os.Stderr, "Channel close transaction "+
1207+
"broadcasted: %v\n", txid)
1208+
11861209
txidChan <- txid.String()
11871210

11881211
if !block {
11891212
return nil
11901213
}
1214+
1215+
fmt.Fprintln(os.Stderr, "Waiting for channel close "+
1216+
"confirmation ...")
1217+
11911218
case *lnrpc.CloseStatusUpdate_ChanClose:
1219+
fmt.Fprintln(os.Stderr, "Channel close successfully "+
1220+
"confirmed")
1221+
11921222
return nil
11931223
}
11941224
}

itest/lnd_coop_close_with_htlcs_test.go

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -93,14 +93,17 @@ func coopCloseWithHTLCs(ht *lntest.HarnessTest) {
9393
// closure is set up. Let's settle the invoice.
9494
alice.RPC.SettleInvoice(preimage[:])
9595

96-
// Pull the instant update off the wire to clear the path for the
97-
// close pending update.
98-
_, err := closeClient.Recv()
96+
// Pull the instant update off the wire and make sure the number of
97+
// pending HTLCs is as expected.
98+
update, err := closeClient.Recv()
9999
require.NoError(ht, err)
100+
closeInstant := update.GetCloseInstant()
101+
require.NotNil(ht, closeInstant)
102+
require.Equal(ht, closeInstant.NumPendingHtlcs, int32(1))
100103

101104
// Wait for the next channel closure update. Now that we have settled
102105
// the only HTLC this should be imminent.
103-
update, err := closeClient.Recv()
106+
update, err = closeClient.Recv()
104107
require.NoError(ht, err)
105108

106109
// This next update should be a GetClosePending as it should be the

0 commit comments

Comments
 (0)