Merge pull request #9491 from ziggie1984/closechannel-rpc
Allow coop closing a channel with HTLCs on it via lncli
This commit is contained in:
13 files changed
+2472
-2286
No files matched your search
@@ -1011,6 +1011,11 @@ var closeChannelCommand = cli.Command{
|
|||||||
comparison is the end boundary of the fee negotiation, if not specified
|
comparison is the end boundary of the fee negotiation, if not specified
|
||||||
it's always x3 of the starting value. Increasing this value increases
|
it's always x3 of the starting value. Increasing this value increases
|
||||||
the chance of a successful negotiation.
|
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
|
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
|
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",
|
Usage: "attempt an uncooperative closure",
|
||||||
},
|
},
|
||||||
cli.BoolFlag{
|
cli.BoolFlag{
|
||||||
Name: "block",
|
Name: "block",
|
||||||
Usage: "block until the channel is closed",
|
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{
|
cli.Int64Flag{
|
||||||
Name: "conf_target",
|
Name: "conf_target",
|
||||||
@@ -1117,6 +1124,9 @@ func closeChannel(ctx *cli.Context) error {
|
|||||||
SatPerVbyte: ctx.Uint64(feeRateFlag),
|
SatPerVbyte: ctx.Uint64(feeRateFlag),
|
||||||
DeliveryAddress: ctx.String("delivery_addr"),
|
DeliveryAddress: ctx.String("delivery_addr"),
|
||||||
MaxFeePerVbyte: ctx.Uint64("max_fee_rate"),
|
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
|
// 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
|
// 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 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
|
// 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,
|
func executeChannelClose(ctxc context.Context, client lnrpc.LightningClient,
|
||||||
req *lnrpc.CloseChannelRequest, txidChan chan<- string, block bool) error {
|
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) {
|
switch update := resp.Update.(type) {
|
||||||
case *lnrpc.CloseStatusUpdate_CloseInstant:
|
case *lnrpc.CloseStatusUpdate_CloseInstant:
|
||||||
if req.NoWait {
|
fmt.Fprintln(os.Stderr, "Channel close successfully "+
|
||||||
return nil
|
"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:
|
case *lnrpc.CloseStatusUpdate_ClosePending:
|
||||||
closingHash := update.ClosePending.Txid
|
closingHash := update.ClosePending.Txid
|
||||||
txid, err := chainhash.NewHash(closingHash)
|
txid, err := chainhash.NewHash(closingHash)
|
||||||
@@ -1183,12 +1203,22 @@ func executeChannelClose(ctxc context.Context, client lnrpc.LightningClient,
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fmt.Fprintf(os.Stderr, "Channel close transaction "+
|
||||||
|
"broadcasted: %v\n", txid)
|
||||||
|
|
||||||
txidChan <- txid.String()
|
txidChan <- txid.String()
|
||||||
|
|
||||||
if !block {
|
if !block {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fmt.Fprintln(os.Stderr, "Waiting for channel close "+
|
||||||
|
"confirmation ...")
|
||||||
|
|
||||||
case *lnrpc.CloseStatusUpdate_ChanClose:
|
case *lnrpc.CloseStatusUpdate_ChanClose:
|
||||||
|
fmt.Fprintln(os.Stderr, "Channel close successfully "+
|
||||||
|
"confirmed")
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -93,6 +93,10 @@
|
|||||||
config is added `disable-backup-archive`, with default set to false, to
|
config is added `disable-backup-archive`, with default set to false, to
|
||||||
determine if previous channel backups should be archived or not.
|
determine if previous channel backups should be archived or not.
|
||||||
|
|
||||||
|
* [The max fee rate](https://github.com/lightningnetwork/lnd/pull/9491) is now
|
||||||
|
respected when a coop close is initiated. Before the max fee rate would only
|
||||||
|
be effective for the remote party in the negotiation.
|
||||||
|
|
||||||
## Functional Enhancements
|
## Functional Enhancements
|
||||||
* [Add ability](https://github.com/lightningnetwork/lnd/pull/8998) to paginate
|
* [Add ability](https://github.com/lightningnetwork/lnd/pull/8998) to paginate
|
||||||
wallet transactions.
|
wallet transactions.
|
||||||
@@ -137,6 +141,11 @@
|
|||||||
the misnomer of `chan_id` which was describing the short channel
|
the misnomer of `chan_id` which was describing the short channel
|
||||||
id to `scid` to represent what it really is.
|
id to `scid` to represent what it really is.
|
||||||
|
|
||||||
|
* [In the coop close](https://github.com/lightningnetwork/lnd/pull/9491) case
|
||||||
|
we always initiate the cooperative close flow even if there are HTLCs active
|
||||||
|
on the channel. LND will disable the channel for new HTLCs and kick off the
|
||||||
|
cooperative close flow automatically when the channel has no HTLCs left.
|
||||||
|
|
||||||
# Improvements
|
# Improvements
|
||||||
## Functional Updates
|
## Functional Updates
|
||||||
|
|
||||||
|
|||||||
@@ -1556,7 +1556,7 @@ out:
|
|||||||
s.indexMtx.RUnlock()
|
s.indexMtx.RUnlock()
|
||||||
|
|
||||||
peerPub := link.PeerPubKey()
|
peerPub := link.PeerPubKey()
|
||||||
log.Debugf("Requesting local channel close: peer=%v, "+
|
log.Debugf("Requesting local channel close: peer=%x, "+
|
||||||
"chan_id=%x", link.PeerPubKey(), chanID[:])
|
"chan_id=%x", link.PeerPubKey(), chanID[:])
|
||||||
|
|
||||||
go s.cfg.LocalChannelClose(peerPub[:], req)
|
go s.cfg.LocalChannelClose(peerPub[:], req)
|
||||||
|
|||||||
@@ -602,6 +602,10 @@ var allTestCases = []*lntest.TestCase{
|
|||||||
Name: "coop close with htlcs",
|
Name: "coop close with htlcs",
|
||||||
TestFunc: testCoopCloseWithHtlcs,
|
TestFunc: testCoopCloseWithHtlcs,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
Name: "coop close exceeds max fee",
|
||||||
|
TestFunc: testCoopCloseExceedsMaxFee,
|
||||||
|
},
|
||||||
{
|
{
|
||||||
Name: "open channel locked balance",
|
Name: "open channel locked balance",
|
||||||
TestFunc: testOpenChannelLockedBalance,
|
TestFunc: testOpenChannelLockedBalance,
|
||||||
|
|||||||
@@ -283,14 +283,16 @@ func (c *chanRestoreScenario) testScenario(ht *lntest.HarnessTest,
|
|||||||
|
|
||||||
// We don't get an error directly but only when reading the first
|
// We don't get an error directly but only when reading the first
|
||||||
// message of the stream.
|
// message of the stream.
|
||||||
err := ht.CloseChannelAssertErr(
|
req := &lnrpc.CloseChannelRequest{
|
||||||
dave, &lnrpc.ChannelPoint{
|
ChannelPoint: &lnrpc.ChannelPoint{
|
||||||
FundingTxid: &lnrpc.ChannelPoint_FundingTxidStr{
|
FundingTxid: &lnrpc.ChannelPoint_FundingTxidStr{
|
||||||
FundingTxidStr: chanPointParts[0],
|
FundingTxidStr: chanPointParts[0],
|
||||||
},
|
},
|
||||||
OutputIndex: uint32(chanPointIndex),
|
OutputIndex: uint32(chanPointIndex),
|
||||||
}, true,
|
},
|
||||||
)
|
Force: true,
|
||||||
|
}
|
||||||
|
err := ht.CloseChannelAssertErr(dave, req)
|
||||||
require.Contains(ht, err.Error(), "cannot close channel with state: ")
|
require.Contains(ht, err.Error(), "cannot close channel with state: ")
|
||||||
require.Contains(ht, err.Error(), "ChanStatusRestored")
|
require.Contains(ht, err.Error(), "ChanStatusRestored")
|
||||||
|
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ import (
|
|||||||
"github.com/lightningnetwork/lnd/lntest"
|
"github.com/lightningnetwork/lnd/lntest"
|
||||||
"github.com/lightningnetwork/lnd/lntest/wait"
|
"github.com/lightningnetwork/lnd/lntest/wait"
|
||||||
"github.com/lightningnetwork/lnd/lntypes"
|
"github.com/lightningnetwork/lnd/lntypes"
|
||||||
|
"github.com/lightningnetwork/lnd/lnwallet/chainfee"
|
||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -93,14 +94,17 @@ func coopCloseWithHTLCs(ht *lntest.HarnessTest) {
|
|||||||
// closure is set up. Let's settle the invoice.
|
// closure is set up. Let's settle the invoice.
|
||||||
alice.RPC.SettleInvoice(preimage[:])
|
alice.RPC.SettleInvoice(preimage[:])
|
||||||
|
|
||||||
// Pull the instant update off the wire to clear the path for the
|
// Pull the instant update off the wire and make sure the number of
|
||||||
// close pending update.
|
// pending HTLCs is as expected.
|
||||||
_, err := closeClient.Recv()
|
update, err := closeClient.Recv()
|
||||||
require.NoError(ht, err)
|
require.NoError(ht, err)
|
||||||
|
closeInstant := update.GetCloseInstant()
|
||||||
|
require.NotNil(ht, closeInstant)
|
||||||
|
require.Equal(ht, closeInstant.NumPendingHtlcs, int32(1))
|
||||||
|
|
||||||
// Wait for the next channel closure update. Now that we have settled
|
// Wait for the next channel closure update. Now that we have settled
|
||||||
// the only HTLC this should be imminent.
|
// the only HTLC this should be imminent.
|
||||||
update, err := closeClient.Recv()
|
update, err = closeClient.Recv()
|
||||||
require.NoError(ht, err)
|
require.NoError(ht, err)
|
||||||
|
|
||||||
// This next update should be a GetClosePending as it should be the
|
// This next update should be a GetClosePending as it should be the
|
||||||
@@ -243,3 +247,76 @@ func coopCloseWithHTLCsWithRestart(ht *lntest.HarnessTest) {
|
|||||||
// Show that the address used is the one she requested.
|
// Show that the address used is the one she requested.
|
||||||
require.Equal(ht, outputDetail.Address, newAddr.Address)
|
require.Equal(ht, outputDetail.Address, newAddr.Address)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// testCoopCloseExceedsMaxFee tests that we fail the coop close process if
|
||||||
|
// the max fee rate exceeds the expected fee rate for the initial closing fee
|
||||||
|
// proposal.
|
||||||
|
func testCoopCloseExceedsMaxFee(ht *lntest.HarnessTest) {
|
||||||
|
const chanAmt = 1000000
|
||||||
|
|
||||||
|
// Create a channel Alice->Bob.
|
||||||
|
chanPoints, nodes := ht.CreateSimpleNetwork(
|
||||||
|
[][]string{nil, nil}, lntest.OpenChannelParams{
|
||||||
|
Amt: chanAmt,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
alice, _ := nodes[0], nodes[1]
|
||||||
|
chanPoint := chanPoints[0]
|
||||||
|
|
||||||
|
// Set the fee estimate for one block to 10 sat/vbyte.
|
||||||
|
ht.SetFeeEstimateWithConf(chainfee.SatPerVByte(10).FeePerKWeight(), 1)
|
||||||
|
|
||||||
|
// Have alice attempt to close the channel. We expect the initial fee
|
||||||
|
// rate to exceed the max fee rate for the closing transaction so we
|
||||||
|
// fail the closing process.
|
||||||
|
req := &lnrpc.CloseChannelRequest{
|
||||||
|
ChannelPoint: chanPoint,
|
||||||
|
MaxFeePerVbyte: 5,
|
||||||
|
NoWait: true,
|
||||||
|
TargetConf: 1,
|
||||||
|
}
|
||||||
|
err := ht.CloseChannelAssertErr(alice, req)
|
||||||
|
require.Contains(ht, err.Error(), "max_fee_per_vbyte (1250 sat/kw) is "+
|
||||||
|
"less than the required fee rate (2500 sat/kw)")
|
||||||
|
|
||||||
|
// Now close the channel with a appropriate max fee rate.
|
||||||
|
closeClient := alice.RPC.CloseChannel(&lnrpc.CloseChannelRequest{
|
||||||
|
ChannelPoint: chanPoint,
|
||||||
|
NoWait: true,
|
||||||
|
TargetConf: 1,
|
||||||
|
MaxFeePerVbyte: 10,
|
||||||
|
})
|
||||||
|
|
||||||
|
// Pull the instant update off the wire to clear the path for the
|
||||||
|
// close pending update. Moreover confirm that there are no pending
|
||||||
|
// HTLCs on the channel.
|
||||||
|
update, err := closeClient.Recv()
|
||||||
|
require.NoError(ht, err)
|
||||||
|
closeInstant := update.GetCloseInstant()
|
||||||
|
require.NotNil(ht, closeInstant)
|
||||||
|
require.Equal(ht, closeInstant.NumPendingHtlcs, int32(0))
|
||||||
|
|
||||||
|
// Wait for the channel to be closed.
|
||||||
|
update, err = closeClient.Recv()
|
||||||
|
require.NoError(ht, err)
|
||||||
|
|
||||||
|
// This next update should be a GetClosePending as it should be the
|
||||||
|
// negotiation of the coop close tx.
|
||||||
|
closePending := update.GetClosePending()
|
||||||
|
require.NotNil(ht, closePending)
|
||||||
|
|
||||||
|
// Convert the txid we get from the PendingUpdate to a Hash so we can
|
||||||
|
// wait for it to be mined.
|
||||||
|
var closeTxid chainhash.Hash
|
||||||
|
require.NoError(
|
||||||
|
ht, closeTxid.SetBytes(closePending.Txid),
|
||||||
|
"invalid closing txid",
|
||||||
|
)
|
||||||
|
|
||||||
|
// Wait for the close tx to be in the Mempool.
|
||||||
|
ht.AssertTxInMempool(closeTxid)
|
||||||
|
|
||||||
|
// Wait for it to get mined and finish tearing down.
|
||||||
|
ht.AssertStreamChannelCoopClosed(alice, chanPoint, false, closeClient)
|
||||||
|
}
|
||||||
@@ -674,7 +674,10 @@ func runExternalFundingScriptEnforced(ht *lntest.HarnessTest) {
|
|||||||
// First, we'll try to close the channel as Carol, the initiator. This
|
// First, we'll try to close the channel as Carol, the initiator. This
|
||||||
// should fail as a frozen channel only allows the responder to
|
// should fail as a frozen channel only allows the responder to
|
||||||
// initiate a channel close.
|
// initiate a channel close.
|
||||||
err := ht.CloseChannelAssertErr(carol, chanPoint2, false)
|
req := &lnrpc.CloseChannelRequest{
|
||||||
|
ChannelPoint: chanPoint2,
|
||||||
|
}
|
||||||
|
err := ht.CloseChannelAssertErr(carol, req)
|
||||||
require.Contains(ht, err.Error(), "cannot co-op close frozen channel")
|
require.Contains(ht, err.Error(), "cannot co-op close frozen channel")
|
||||||
|
|
||||||
// Before Dave closes the channel, he needs to check the invoice is
|
// Before Dave closes the channel, he needs to check the invoice is
|
||||||
@@ -831,7 +834,10 @@ func runExternalFundingTaproot(ht *lntest.HarnessTest) {
|
|||||||
// First, we'll try to close the channel as Carol, the initiator. This
|
// First, we'll try to close the channel as Carol, the initiator. This
|
||||||
// should fail as a frozen channel only allows the responder to
|
// should fail as a frozen channel only allows the responder to
|
||||||
// initiate a channel close.
|
// initiate a channel close.
|
||||||
err := ht.CloseChannelAssertErr(carol, chanPoint2, false)
|
req := &lnrpc.CloseChannelRequest{
|
||||||
|
ChannelPoint: chanPoint2,
|
||||||
|
}
|
||||||
|
err := ht.CloseChannelAssertErr(carol, req)
|
||||||
require.Contains(ht, err.Error(), "cannot co-op close frozen channel")
|
require.Contains(ht, err.Error(), "cannot co-op close frozen channel")
|
||||||
|
|
||||||
// Before Dave closes the channel, he needs to check the invoice is
|
// Before Dave closes the channel, he needs to check the invoice is
|
||||||
|
|||||||
+2273
-2254
File diff suppressed because it is too large.
Load diff
+11
-3
@@ -2146,9 +2146,13 @@ message CloseChannelRequest {
|
|||||||
// NOTE: This field is only respected if we're the initiator of the channel.
|
// NOTE: This field is only respected if we're the initiator of the channel.
|
||||||
uint64 max_fee_per_vbyte = 7;
|
uint64 max_fee_per_vbyte = 7;
|
||||||
|
|
||||||
// If true, then the rpc call will not block while it awaits a closing txid.
|
// If true, then the rpc call will not block while it awaits a closing txid
|
||||||
// Consequently this RPC call will not return a closing txid if this value
|
// to be broadcasted to the mempool. To obtain the closing tx one has to
|
||||||
// is set.
|
// listen to the stream for the particular updates. Moreover if a coop close
|
||||||
|
// is specified and this flag is set to true the coop closing flow will be
|
||||||
|
// initiated even if HTLCs are active on the channel. The channel will wait
|
||||||
|
// until all HTLCs are resolved and then start the coop closing process. The
|
||||||
|
// channel will be disabled in the meantime and will disallow any new HTLCs.
|
||||||
bool no_wait = 8;
|
bool no_wait = 8;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -2166,6 +2170,10 @@ message PendingUpdate {
|
|||||||
}
|
}
|
||||||
|
|
||||||
message InstantUpdate {
|
message InstantUpdate {
|
||||||
|
// The number of pending HTLCs that are currently active on the channel.
|
||||||
|
// These HTLCs need to be resolved before the channel can be closed
|
||||||
|
// cooperatively.
|
||||||
|
int32 num_pending_htlcs = 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
message ReadyForPsbtFunding {
|
message ReadyForPsbtFunding {
|
||||||
|
|||||||
@@ -878,7 +878,7 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "no_wait",
|
"name": "no_wait",
|
||||||
"description": "If true, then the rpc call will not block while it awaits a closing txid.\nConsequently this RPC call will not return a closing txid if this value\nis set.",
|
"description": "If true, then the rpc call will not block while it awaits a closing txid\nto be broadcasted to the mempool. To obtain the closing tx one has to\nlisten to the stream for the particular updates. Moreover if a coop close\nis specified and this flag is set to true the coop closing flow will be\ninitiated even if HTLCs are active on the channel. The channel will wait\nuntil all HTLCs are resolved and then start the coop closing process. The\nchannel will be disabled in the meantime and will disallow any new HTLCs.",
|
||||||
"in": "query",
|
"in": "query",
|
||||||
"required": false,
|
"required": false,
|
||||||
"type": "boolean"
|
"type": "boolean"
|
||||||
@@ -5475,7 +5475,14 @@
|
|||||||
"default": "INITIATOR_UNKNOWN"
|
"default": "INITIATOR_UNKNOWN"
|
||||||
},
|
},
|
||||||
"lnrpcInstantUpdate": {
|
"lnrpcInstantUpdate": {
|
||||||
"type": "object"
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"num_pending_htlcs": {
|
||||||
|
"type": "integer",
|
||||||
|
"format": "int32",
|
||||||
|
"description": "The number of pending HTLCs that are currently active on the channel.\nThese HTLCs need to be resolved before the channel can be closed\ncooperatively."
|
||||||
|
}
|
||||||
|
}
|
||||||
},
|
},
|
||||||
"lnrpcInterceptFeedback": {
|
"lnrpcInterceptFeedback": {
|
||||||
"type": "object",
|
"type": "object",
|
||||||
|
|||||||
+2
-6
@@ -1296,14 +1296,10 @@ func (h *HarnessTest) ForceCloseChannel(hn *node.HarnessNode,
|
|||||||
// CloseChannelAssertErr closes the given channel and asserts an error
|
// CloseChannelAssertErr closes the given channel and asserts an error
|
||||||
// returned.
|
// returned.
|
||||||
func (h *HarnessTest) CloseChannelAssertErr(hn *node.HarnessNode,
|
func (h *HarnessTest) CloseChannelAssertErr(hn *node.HarnessNode,
|
||||||
cp *lnrpc.ChannelPoint, force bool) error {
|
req *lnrpc.CloseChannelRequest) error {
|
||||||
|
|
||||||
// Calls the rpc to close the channel.
|
// Calls the rpc to close the channel.
|
||||||
closeReq := &lnrpc.CloseChannelRequest{
|
stream := hn.RPC.CloseChannel(req)
|
||||||
ChannelPoint: cp,
|
|
||||||
Force: force,
|
|
||||||
}
|
|
||||||
stream := hn.RPC.CloseChannel(closeReq)
|
|
||||||
|
|
||||||
// Consume the "channel close" update in order to wait for the closing
|
// Consume the "channel close" update in order to wait for the closing
|
||||||
// transaction to be broadcast, then wait for the closing tx to be seen
|
// transaction to be broadcast, then wait for the closing tx to be seen
|
||||||
|
|||||||
@@ -360,6 +360,9 @@ func (c *ChanCloser) initFeeBaseline() {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODO(ziggie): Make sure the ideal fee is not higher than the max fee.
|
||||||
|
// Either error out or cap the ideal fee at the max fee.
|
||||||
|
|
||||||
chancloserLog.Infof("Ideal fee for closure of ChannelPoint(%v) "+
|
chancloserLog.Infof("Ideal fee for closure of ChannelPoint(%v) "+
|
||||||
"is: %v sat (max_fee=%v sat)", c.cfg.Channel.ChannelPoint(),
|
"is: %v sat (max_fee=%v sat)", c.cfg.Channel.ChannelPoint(),
|
||||||
int64(c.idealFeeSat), int64(c.maxFee))
|
int64(c.idealFeeSat), int64(c.maxFee))
|
||||||
|
|||||||
+30
-5
@@ -2716,6 +2716,9 @@ func (r *rpcServer) CloseChannel(in *lnrpc.CloseChannelRequest,
|
|||||||
return err
|
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
|
// If a force closure was requested, then we'll handle all the details
|
||||||
// around the creation and broadcast of the unilateral closure
|
// around the creation and broadcast of the unilateral closure
|
||||||
// transaction here rather than going to the switch as we don't require
|
// 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
|
// If the user hasn't specified NoWait, then before we attempt
|
||||||
// to close the channel we ensure there are no active HTLCs on
|
// to close the channel we ensure there are no active HTLCs on
|
||||||
// the link.
|
// the link.
|
||||||
if !in.NoWait && len(channel.ActiveHtlcs()) != 0 {
|
if !in.NoWait && len(activeHtlcs) != 0 {
|
||||||
return fmt.Errorf("cannot co-op close channel " +
|
return fmt.Errorf("cannot coop close channel with "+
|
||||||
"with active htlcs")
|
"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
|
// Otherwise, the caller has requested a regular interactive
|
||||||
@@ -2871,6 +2877,15 @@ func (r *rpcServer) CloseChannel(in *lnrpc.CloseChannelRequest,
|
|||||||
maxFee := chainfee.SatPerKVByte(
|
maxFee := chainfee.SatPerKVByte(
|
||||||
in.MaxFeePerVbyte * 1000,
|
in.MaxFeePerVbyte * 1000,
|
||||||
).FeePerKWeight()
|
).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(
|
updateChan, errChan = r.server.htlcSwitch.CloseLink(
|
||||||
chanPoint, contractcourt.CloseRegular, feeRate,
|
chanPoint, contractcourt.CloseRegular, feeRate,
|
||||||
maxFee, deliveryScript,
|
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
|
// 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 {
|
if in.NoWait {
|
||||||
rpcsLog.Trace("[closechannel] sending instant update")
|
rpcsLog.Trace("[closechannel] sending instant update")
|
||||||
if err := updateStream.Send(
|
if err := updateStream.Send(
|
||||||
|
//nolint:ll
|
||||||
&lnrpc.CloseStatusUpdate{
|
&lnrpc.CloseStatusUpdate{
|
||||||
Update: &lnrpc.CloseStatusUpdate_CloseInstant{},
|
Update: &lnrpc.CloseStatusUpdate_CloseInstant{
|
||||||
|
CloseInstant: &lnrpc.InstantUpdate{
|
||||||
|
NumPendingHtlcs: int32(len(activeHtlcs)),
|
||||||
|
},
|
||||||
|
},
|
||||||
},
|
},
|
||||||
); err != nil {
|
); err != nil {
|
||||||
return err
|
return err
|
||||||
@@ -2895,7 +2917,9 @@ out:
|
|||||||
case err := <-errChan:
|
case err := <-errChan:
|
||||||
rpcsLog.Errorf("[closechannel] unable to close "+
|
rpcsLog.Errorf("[closechannel] unable to close "+
|
||||||
"ChannelPoint(%v): %v", chanPoint, err)
|
"ChannelPoint(%v): %v", chanPoint, err)
|
||||||
|
|
||||||
return err
|
return err
|
||||||
|
|
||||||
case closingUpdate := <-updateChan:
|
case closingUpdate := <-updateChan:
|
||||||
rpcClosingUpdate, err := createRPCCloseUpdate(
|
rpcClosingUpdate, err := createRPCCloseUpdate(
|
||||||
closingUpdate,
|
closingUpdate,
|
||||||
@@ -2934,6 +2958,7 @@ out:
|
|||||||
"txid(%v)", h)
|
"txid(%v)", h)
|
||||||
break out
|
break out
|
||||||
}
|
}
|
||||||
|
|
||||||
case <-r.quit:
|
case <-r.quit:
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in new issue
Block a user