Skip to content

Commit f458844

Browse files
committed
lnd: add max fee rate check to closechannel rpc
1 parent 59443fa commit f458844

File tree

7 files changed

+109
-12
lines changed

7 files changed

+109
-12
lines changed

itest/list_on_test.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -602,6 +602,10 @@ var allTestCases = []*lntest.TestCase{
602602
Name: "coop close with htlcs",
603603
TestFunc: testCoopCloseWithHtlcs,
604604
},
605+
{
606+
Name: "coop close exceeds max fee",
607+
TestFunc: testCoopCloseExceedsMaxFee,
608+
},
605609
{
606610
Name: "open channel locked balance",
607611
TestFunc: testOpenChannelLockedBalance,

itest/lnd_channel_backup_test.go

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -283,14 +283,16 @@ func (c *chanRestoreScenario) testScenario(ht *lntest.HarnessTest,
283283

284284
// We don't get an error directly but only when reading the first
285285
// message of the stream.
286-
err := ht.CloseChannelAssertErr(
287-
dave, &lnrpc.ChannelPoint{
286+
req := &lnrpc.CloseChannelRequest{
287+
ChannelPoint: &lnrpc.ChannelPoint{
288288
FundingTxid: &lnrpc.ChannelPoint_FundingTxidStr{
289289
FundingTxidStr: chanPointParts[0],
290290
},
291291
OutputIndex: uint32(chanPointIndex),
292-
}, true,
293-
)
292+
},
293+
Force: true,
294+
}
295+
err := ht.CloseChannelAssertErr(dave, req)
294296
require.Contains(ht, err.Error(), "cannot close channel with state: ")
295297
require.Contains(ht, err.Error(), "ChanStatusRestored")
296298

itest/lnd_coop_close_with_htlcs_test.go

Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import (
1212
"github.com/lightningnetwork/lnd/lntest"
1313
"github.com/lightningnetwork/lnd/lntest/wait"
1414
"github.com/lightningnetwork/lnd/lntypes"
15+
"github.com/lightningnetwork/lnd/lnwallet/chainfee"
1516
"github.com/stretchr/testify/require"
1617
)
1718

@@ -246,3 +247,76 @@ func coopCloseWithHTLCsWithRestart(ht *lntest.HarnessTest) {
246247
// Show that the address used is the one she requested.
247248
require.Equal(ht, outputDetail.Address, newAddr.Address)
248249
}
250+
251+
// testCoopCloseExceedsMaxFee tests that we fail the coop close process if
252+
// the max fee rate exceeds the expected fee rate for the initial closing fee
253+
// proposal.
254+
func testCoopCloseExceedsMaxFee(ht *lntest.HarnessTest) {
255+
const chanAmt = 1000000
256+
257+
// Create a channel Alice->Bob.
258+
chanPoints, nodes := ht.CreateSimpleNetwork(
259+
[][]string{nil, nil}, lntest.OpenChannelParams{
260+
Amt: chanAmt,
261+
},
262+
)
263+
264+
alice, _ := nodes[0], nodes[1]
265+
chanPoint := chanPoints[0]
266+
267+
// Set the fee estimate for one block to 10 sat/vbyte.
268+
ht.SetFeeEstimateWithConf(chainfee.SatPerVByte(10).FeePerKWeight(), 1)
269+
270+
// Have alice attempt to close the channel. We expect the initial fee
271+
// rate to exceed the max fee rate for the closing transaction so we
272+
// fail the closing process.
273+
req := &lnrpc.CloseChannelRequest{
274+
ChannelPoint: chanPoint,
275+
MaxFeePerVbyte: 5,
276+
NoWait: true,
277+
TargetConf: 1,
278+
}
279+
err := ht.CloseChannelAssertErr(alice, req)
280+
require.Contains(ht, err.Error(), "max_fee_per_vbyte (1250 sat/kw) is "+
281+
"less than the required fee rate (2500 sat/kw)")
282+
283+
// Now close the channel with a appropriate max fee rate.
284+
closeClient := alice.RPC.CloseChannel(&lnrpc.CloseChannelRequest{
285+
ChannelPoint: chanPoint,
286+
NoWait: true,
287+
TargetConf: 1,
288+
MaxFeePerVbyte: 10,
289+
})
290+
291+
// Pull the instant update off the wire to clear the path for the
292+
// close pending update. Moreover confirm that there are no pending
293+
// HTLCs on the channel.
294+
update, err := closeClient.Recv()
295+
require.NoError(ht, err)
296+
closeInstant := update.GetCloseInstant()
297+
require.NotNil(ht, closeInstant)
298+
require.Equal(ht, closeInstant.NumPendingHtlcs, int32(0))
299+
300+
// Wait for the channel to be closed.
301+
update, err = closeClient.Recv()
302+
require.NoError(ht, err)
303+
304+
// This next update should be a GetClosePending as it should be the
305+
// negotiation of the coop close tx.
306+
closePending := update.GetClosePending()
307+
require.NotNil(ht, closePending)
308+
309+
// Convert the txid we get from the PendingUpdate to a Hash so we can
310+
// wait for it to be mined.
311+
var closeTxid chainhash.Hash
312+
require.NoError(
313+
ht, closeTxid.SetBytes(closePending.Txid),
314+
"invalid closing txid",
315+
)
316+
317+
// Wait for the close tx to be in the Mempool.
318+
ht.AssertTxInMempool(closeTxid)
319+
320+
// Wait for it to get mined and finish tearing down.
321+
ht.AssertStreamChannelCoopClosed(alice, chanPoint, false, closeClient)
322+
}

itest/lnd_funding_test.go

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -674,7 +674,10 @@ func runExternalFundingScriptEnforced(ht *lntest.HarnessTest) {
674674
// First, we'll try to close the channel as Carol, the initiator. This
675675
// should fail as a frozen channel only allows the responder to
676676
// initiate a channel close.
677-
err := ht.CloseChannelAssertErr(carol, chanPoint2, false)
677+
req := &lnrpc.CloseChannelRequest{
678+
ChannelPoint: chanPoint2,
679+
}
680+
err := ht.CloseChannelAssertErr(carol, req)
678681
require.Contains(ht, err.Error(), "cannot co-op close frozen channel")
679682

680683
// Before Dave closes the channel, he needs to check the invoice is
@@ -831,7 +834,10 @@ func runExternalFundingTaproot(ht *lntest.HarnessTest) {
831834
// First, we'll try to close the channel as Carol, the initiator. This
832835
// should fail as a frozen channel only allows the responder to
833836
// initiate a channel close.
834-
err := ht.CloseChannelAssertErr(carol, chanPoint2, false)
837+
req := &lnrpc.CloseChannelRequest{
838+
ChannelPoint: chanPoint2,
839+
}
840+
err := ht.CloseChannelAssertErr(carol, req)
835841
require.Contains(ht, err.Error(), "cannot co-op close frozen channel")
836842

837843
// Before Dave closes the channel, he needs to check the invoice is

lntest/harness.go

Lines changed: 2 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1296,14 +1296,10 @@ func (h *HarnessTest) ForceCloseChannel(hn *node.HarnessNode,
12961296
// CloseChannelAssertErr closes the given channel and asserts an error
12971297
// returned.
12981298
func (h *HarnessTest) CloseChannelAssertErr(hn *node.HarnessNode,
1299-
cp *lnrpc.ChannelPoint, force bool) error {
1299+
req *lnrpc.CloseChannelRequest) error {
13001300

13011301
// Calls the rpc to close the channel.
1302-
closeReq := &lnrpc.CloseChannelRequest{
1303-
ChannelPoint: cp,
1304-
Force: force,
1305-
}
1306-
stream := hn.RPC.CloseChannel(closeReq)
1302+
stream := hn.RPC.CloseChannel(req)
13071303

13081304
// Consume the "channel close" update in order to wait for the closing
13091305
// transaction to be broadcast, then wait for the closing tx to be seen

lnwallet/chancloser/chancloser.go

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -360,6 +360,9 @@ func (c *ChanCloser) initFeeBaseline() {
360360
)
361361
}
362362

363+
// TODO(ziggie): Make sure the ideal fee is not higher than the max fee.
364+
// Either error out or cap the ideal fee at the max fee.
365+
363366
chancloserLog.Infof("Ideal fee for closure of ChannelPoint(%v) "+
364367
"is: %v sat (max_fee=%v sat)", c.cfg.Channel.ChannelPoint(),
365368
int64(c.idealFeeSat), int64(c.maxFee))

rpcserver.go

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2878,6 +2878,15 @@ func (r *rpcServer) CloseChannel(in *lnrpc.CloseChannelRequest,
28782878
maxFee := chainfee.SatPerKVByte(
28792879
in.MaxFeePerVbyte * 1000,
28802880
).FeePerKWeight()
2881+
2882+
// In case the max fee was specified, we check if it's less than
2883+
// the initial fee rate and abort if it is.
2884+
if maxFee != 0 && maxFee < feeRate {
2885+
return fmt.Errorf("max_fee_per_vbyte (%v) is less "+
2886+
"than the required fee rate (%v)", maxFee,
2887+
feeRate)
2888+
}
2889+
28812890
updateChan, errChan = r.server.htlcSwitch.CloseLink(
28822891
chanPoint, contractcourt.CloseRegular, feeRate,
28832892
maxFee, deliveryScript,
@@ -2909,7 +2918,9 @@ out:
29092918
case err := <-errChan:
29102919
rpcsLog.Errorf("[closechannel] unable to close "+
29112920
"ChannelPoint(%v): %v", chanPoint, err)
2921+
29122922
return err
2923+
29132924
case closingUpdate := <-updateChan:
29142925
rpcClosingUpdate, err := createRPCCloseUpdate(
29152926
closingUpdate,
@@ -2948,6 +2959,7 @@ out:
29482959
"txid(%v)", h)
29492960
break out
29502961
}
2962+
29512963
case <-r.quit:
29522964
return nil
29532965
}

0 commit comments

Comments
 (0)