-
Notifications
You must be signed in to change notification settings - Fork 2.2k
lncli: show fee_per_vbyte and local_close_tx in closechannel and closeallchannels output #10300
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -1099,6 +1099,13 @@ var closeChannelCommand = cli.Command{ | |
| Action: actionDecorator(closeChannel), | ||
| } | ||
|
|
||
| // CloseInfo contains information about a channel close transaction. | ||
| type CloseInfo struct { | ||
| ClosingTxid string `json:"closing_txid"` | ||
| FeePerVbyte int64 `json:"fee_per_vbyte,omitempty"` | ||
| LocalCloseTx bool `json:"local_close_tx,omitempty"` | ||
| } | ||
|
|
||
| func closeChannel(ctx *cli.Context) error { | ||
| ctxc := getContext() | ||
| client, cleanUp := getClient(ctx) | ||
|
|
@@ -1138,51 +1145,52 @@ func closeChannel(ctx *cli.Context) error { | |
| } | ||
|
|
||
| // After parsing the request, we'll spin up a goroutine that will | ||
| // retrieve the closing transaction ID when attempting to close the | ||
| // retrieve the closing transaction details when attempting to close the | ||
| // channel. We do this to because `executeChannelClose` can block, so we | ||
| // would like to present the closing transaction ID to the user as soon | ||
| // would like to present the closing transaction details to the user as soon | ||
| // as it is broadcasted. | ||
| var wg sync.WaitGroup | ||
| txidChan := make(chan string, 1) | ||
| closeInfoChan := make(chan CloseInfo, 1) | ||
|
|
||
| wg.Add(1) | ||
| go func() { | ||
| defer wg.Done() | ||
|
|
||
| printJSON(struct { | ||
| ClosingTxid string `json:"closing_txid"` | ||
| }{ | ||
| ClosingTxid: <-txidChan, | ||
| }) | ||
| closeInfo := <-closeInfoChan | ||
| printJSON(closeInfo) | ||
| }() | ||
|
|
||
| err = executeChannelClose(ctxc, client, req, txidChan, ctx.Bool("block")) | ||
| err = executeChannelClose(ctxc, client, req, closeInfoChan, ctx.Bool("block")) | ||
| if err != nil { | ||
| return err | ||
| } | ||
|
|
||
| // In the case that the user did not provide the `block` flag, then we | ||
| // need to wait for the goroutine to be done to prevent it from being | ||
| // destroyed when exiting before printing the closing transaction ID. | ||
| // destroyed when exiting before printing the closing transaction details. | ||
| wg.Wait() | ||
|
|
||
| return nil | ||
| } | ||
|
|
||
| // 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 | ||
| // transaction information is sent through `closeInfoChan` 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 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 { | ||
| req *lnrpc.CloseChannelRequest, closeInfoChan chan<- CloseInfo, block bool) error { | ||
|
|
||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. To prevent a potential deadlock in the calling functions ( defer close(closeInfoChan) |
||
| stream, err := client.CloseChannel(ctxc, req) | ||
| if err != nil { | ||
| return err | ||
| } | ||
|
|
||
| // Track if we've already sent close info to prevent duplicate sends | ||
| // when ClosePending is received multiple times (RBF, rebroadcast, etc.) | ||
| var sentCloseInfo bool | ||
|
|
||
| for { | ||
| resp, err := stream.Recv() | ||
| if err == io.EOF { | ||
|
|
@@ -1211,10 +1219,21 @@ func executeChannelClose(ctxc context.Context, client lnrpc.LightningClient, | |
| return err | ||
| } | ||
|
|
||
| fmt.Fprintf(os.Stderr, "Channel close transaction "+ | ||
| "broadcasted: %v\n", txid) | ||
|
|
||
| txidChan <- txid.String() | ||
| feeRate := update.ClosePending.FeePerVbyte | ||
| isLocalClose := update.ClosePending.LocalCloseTx | ||
|
|
||
| // Only send close info on the first ClosePending event. | ||
| // Subsequent events (from RBF, rebroadcast, etc.) are | ||
| // ignored to prevent duplicate output. | ||
| if !sentCloseInfo { | ||
| closeInfo := CloseInfo{ | ||
| ClosingTxid: txid.String(), | ||
| FeePerVbyte: feeRate, | ||
| LocalCloseTx: isLocalClose, | ||
| } | ||
| closeInfoChan <- closeInfo | ||
| sentCloseInfo = true | ||
| } | ||
|
Comment on lines
+1228
to
+1236
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Q: In which case could the There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Well based on my understanding one would be 'Manual Fee Bumping via |
||
|
|
||
| if !block { | ||
| return nil | ||
|
|
@@ -1393,13 +1412,15 @@ func closeAllChannels(ctx *cli.Context) error { | |
| } | ||
|
|
||
| // result defines the result of closing a channel. The closing | ||
| // transaction ID is populated if a channel is successfully closed. | ||
| // transaction information is populated if a channel is successfully closed. | ||
| // Otherwise, the error that prevented closing the channel is populated. | ||
| type result struct { | ||
| RemotePubKey string `json:"remote_pub_key"` | ||
| ChannelPoint string `json:"channel_point"` | ||
| ClosingTxid string `json:"closing_txid"` | ||
| FailErr string `json:"error"` | ||
| RemotePubKey string `json:"remote_pub_key"` | ||
| ChannelPoint string `json:"channel_point"` | ||
| ClosingTxid string `json:"closing_txid"` | ||
| FeePerVbyte int64 `json:"fee_per_vbyte,omitempty"` | ||
| LocalCloseTx bool `json:"local_close_tx,omitempty"` | ||
| FailErr string `json:"error"` | ||
| } | ||
|
|
||
| // Launch each channel closure in a goroutine in order to execute them | ||
|
|
@@ -1442,15 +1463,18 @@ func closeAllChannels(ctx *cli.Context) error { | |
| SatPerVbyte: ctx.Uint64(feeRateFlag), | ||
| } | ||
|
|
||
| txidChan := make(chan string, 1) | ||
| err = executeChannelClose(ctxc, client, req, txidChan, false) | ||
| closeInfoChan := make(chan CloseInfo, 1) | ||
| err = executeChannelClose(ctxc, client, req, closeInfoChan, false) | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Could you please point out what I am missing? |
||
| if err != nil { | ||
| res.FailErr = fmt.Sprintf("unable to close "+ | ||
| "channel: %v", err) | ||
| return | ||
| } | ||
|
|
||
| res.ClosingTxid = <-txidChan | ||
| closeInfo := <-closeInfoChan | ||
| res.ClosingTxid = closeInfo.ClosingTxid | ||
| res.FeePerVbyte = closeInfo.FeePerVbyte | ||
| res.LocalCloseTx = closeInfo.LocalCloseTx | ||
|
Comment on lines
+1474
to
+1477
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This blocking read from closeInfo, ok := <-closeInfoChan
if !ok {
res.FailErr = "channel close stream ended " +
"before sending closing transaction"
return
}
res.ClosingTxid = closeInfo.ClosingTxid
res.FeePerVbyte = closeInfo.FeePerVbyte
res.LocalCloseTx = closeInfo.LocalCloseTx |
||
| }(channel) | ||
| } | ||
|
|
||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
To complement the proposed change of always closing
closeInfoChaninexecuteChannelClose, this read operation should be updated to handle the case where the channel is closed without a value being sent. Using the two-variable assignment form of a channel receive (value, ok := <-ch) allows checking whether a value was received before attempting to use it, preventing the printing of an empty JSON object if no close information is sent.