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

+4
View File
@@ -602,6 +602,10 @@ var allTestCases = []*lntest.TestCase{
Name: "coop close with htlcs",
TestFunc: testCoopCloseWithHtlcs,
},
{
Name: "coop close exceeds max fee",
TestFunc: testCoopCloseExceedsMaxFee,
},
{
Name: "open channel locked balance",
TestFunc: testOpenChannelLockedBalance,
+6 -4
View File
@@ -283,14 +283,16 @@ func (c *chanRestoreScenario) testScenario(ht *lntest.HarnessTest,
// We don't get an error directly but only when reading the first
// message of the stream.
err := ht.CloseChannelAssertErr(
dave, &lnrpc.ChannelPoint{
req := &lnrpc.CloseChannelRequest{
ChannelPoint: &lnrpc.ChannelPoint{
FundingTxid: &lnrpc.ChannelPoint_FundingTxidStr{
FundingTxidStr: chanPointParts[0],
},
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(), "ChanStatusRestored")
+81 -4
View File
@@ -12,6 +12,7 @@ import (
"github.com/lightningnetwork/lnd/lntest"
"github.com/lightningnetwork/lnd/lntest/wait"
"github.com/lightningnetwork/lnd/lntypes"
"github.com/lightningnetwork/lnd/lnwallet/chainfee"
"github.com/stretchr/testify/require"
)
@@ -93,14 +94,17 @@ func coopCloseWithHTLCs(ht *lntest.HarnessTest) {
// closure is set up. Let's settle the invoice.
alice.RPC.SettleInvoice(preimage[:])
// Pull the instant update off the wire to clear the path for the
// close pending update.
_, err := closeClient.Recv()
// Pull the instant update off the wire and make sure the number of
// pending HTLCs is as expected.
update, err := closeClient.Recv()
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
// the only HTLC this should be imminent.
update, err := closeClient.Recv()
update, err = closeClient.Recv()
require.NoError(ht, err)
// 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.
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)
}
+8 -2
View File
@@ -674,7 +674,10 @@ func runExternalFundingScriptEnforced(ht *lntest.HarnessTest) {
// First, we'll try to close the channel as Carol, the initiator. This
// should fail as a frozen channel only allows the responder to
// 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")
// 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
// should fail as a frozen channel only allows the responder to
// 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")
// Before Dave closes the channel, he needs to check the invoice is