Skip to content

Commit 580935a

Browse files
authored
Merge pull request #9669 from Roasbeef/rbf-taproot-downgrade
multi: downgrade to legacy coop close for taproot channels
2 parents 6a3845b + 96662ad commit 580935a

File tree

6 files changed

+127
-61
lines changed

6 files changed

+127
-61
lines changed

itest/list_on_test.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -610,6 +610,10 @@ var allTestCases = []*lntest.TestCase{
610610
Name: "coop close with htlcs",
611611
TestFunc: testCoopCloseWithHtlcs,
612612
},
613+
{
614+
Name: "coop close with htlcs restart",
615+
TestFunc: testCoopCloseWithHtlcsWithRestart,
616+
},
613617
{
614618
Name: "coop close exceeds max fee",
615619
TestFunc: testCoopCloseExceedsMaxFee,

itest/lnd_coop_close_with_htlcs_test.go

Lines changed: 90 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -18,60 +18,110 @@ import (
1818
"github.com/stretchr/testify/require"
1919
)
2020

21+
type flagCombo struct {
22+
isRbf bool
23+
isTaproot bool
24+
testName string
25+
flags []string
26+
commitType lnrpc.CommitmentType
27+
private bool
28+
}
29+
30+
// createFlagCombos generates a slice of flagCombo structs representing
31+
// different test configurations for RBF and Taproot channels.
32+
func createFlagCombos() []flagCombo {
33+
var testCases []flagCombo
34+
for _, isRbf := range []bool{true, false} {
35+
for _, isTaproot := range []bool{true, false} {
36+
flagCombo := flagCombo{
37+
isRbf: isRbf,
38+
isTaproot: isTaproot,
39+
}
40+
41+
var flags []string
42+
43+
if isRbf {
44+
flags = append(flags, node.CfgRbfClose...)
45+
}
46+
47+
if isTaproot {
48+
flags = append(flags, node.CfgSimpleTaproot...)
49+
flagCombo.commitType = lnrpc.CommitmentType_SIMPLE_TAPROOT //nolint:ll
50+
flagCombo.private = true
51+
}
52+
53+
flagCombo.flags = flags
54+
flagCombo.testName = fmt.Sprintf(
55+
"is_rbf=%v is_taproot=%v", isRbf, isTaproot,
56+
)
57+
58+
testCases = append(testCases, flagCombo)
59+
}
60+
}
61+
62+
return testCases
63+
}
64+
2165
// testCoopCloseWithHtlcs tests whether we can successfully issue a coop close
22-
// request while there are still active htlcs on the link. In all the tests, we
66+
// request while there are still active htlcs on the link. In this test, we
2367
// will set up an HODL invoice to suspend settlement. Then we will attempt to
2468
// close the channel which should appear as a noop for the time being. Then we
2569
// will have the receiver settle the invoice and observe that the channel gets
26-
// torn down after settlement.
70+
// torn down after settlement. This test covers scenarios without node restarts.
2771
func testCoopCloseWithHtlcs(ht *lntest.HarnessTest) {
28-
rbfCoopFlags := []string{"--protocol.rbf-coop-close"}
72+
testCases := createFlagCombos()
2973

30-
for _, isRbf := range []bool{true, false} {
31-
testName := fmt.Sprintf("no restart is_rbf=%v", isRbf)
32-
ht.Run(testName, func(t *testing.T) {
74+
for _, testCase := range testCases {
75+
testCase := testCase // Capture range variable.
76+
ht.Run(testCase.testName, func(t *testing.T) {
3377
tt := ht.Subtest(t)
3478

35-
var flags []string
36-
if isRbf {
37-
flags = rbfCoopFlags
38-
}
39-
40-
alice := ht.NewNodeWithCoins("Alice", flags)
41-
bob := ht.NewNodeWithCoins("bob", flags)
79+
alice := ht.NewNodeWithCoins("Alice", testCase.flags)
80+
bob := ht.NewNodeWithCoins("bob", testCase.flags)
4281

43-
coopCloseWithHTLCs(tt, alice, bob)
82+
runCoopCloseWithHTLCsScenario(tt, alice, bob, testCase)
4483
})
4584
}
85+
}
4686

47-
for _, isRbf := range []bool{true, false} {
48-
testName := fmt.Sprintf("with restart is_rbf=%v", isRbf)
49-
ht.Run(testName, func(t *testing.T) {
87+
// testCoopCloseWithHtlcsWithRestart tests whether we can successfully issue a
88+
// coop close request while there are still active htlcs on the link, even
89+
// after a node restart. In this test, we will set up an HODL invoice to
90+
// suspend settlement. Then we will attempt to close the channel, restart the
91+
// nodes, and then have the receiver settle the invoice, observing that the
92+
// channel gets torn down after settlement and restart.
93+
func testCoopCloseWithHtlcsWithRestart(ht *lntest.HarnessTest) {
94+
testCases := createFlagCombos()
95+
96+
for _, testCase := range testCases {
97+
testCase := testCase // Capture range variable.
98+
ht.Run(testCase.testName, func(t *testing.T) {
5099
tt := ht.Subtest(t)
51100

52-
var flags []string
53-
if isRbf {
54-
flags = rbfCoopFlags
55-
}
56-
57-
alice := ht.NewNodeWithCoins("Alice", flags)
58-
bob := ht.NewNodeWithCoins("bob", flags)
101+
alice := ht.NewNodeWithCoins("Alice", testCase.flags)
102+
bob := ht.NewNodeWithCoins("bob", testCase.flags)
59103

60-
coopCloseWithHTLCsWithRestart(tt, alice, bob)
104+
runCoopCloseWithHTLCsWithRestartScenario(
105+
tt, alice, bob, testCase,
106+
)
61107
})
62108
}
63109
}
64110

65-
// coopCloseWithHTLCs tests the basic coop close scenario which occurs when one
66-
// channel party initiates a channel shutdown while an HTLC is still pending on
67-
// the channel.
68-
func coopCloseWithHTLCs(ht *lntest.HarnessTest, alice, bob *node.HarnessNode) {
111+
// runCoopCloseWithHTLCsScenario tests the basic coop close scenario which
112+
// occurs when one channel party initiates a channel shutdown while an HTLC is
113+
// still pending on the channel.
114+
func runCoopCloseWithHTLCsScenario(ht *lntest.HarnessTest, alice,
115+
bob *node.HarnessNode, testCase flagCombo) {
116+
69117
ht.ConnectNodes(alice, bob)
70118

71119
// Here we set up a channel between Alice and Bob, beginning with a
72120
// balance on Bob's side.
73121
chanPoint := ht.OpenChannel(bob, alice, lntest.OpenChannelParams{
74-
Amt: btcutil.Amount(1000000),
122+
Amt: btcutil.Amount(1000000),
123+
CommitmentType: testCase.commitType,
124+
Private: testCase.private,
75125
})
76126

77127
// Wait for Bob to understand that the channel is ready to use.
@@ -153,12 +203,12 @@ func coopCloseWithHTLCs(ht *lntest.HarnessTest, alice, bob *node.HarnessNode) {
153203
ht.AssertStreamChannelCoopClosed(alice, chanPoint, false, closeClient)
154204
}
155205

156-
// coopCloseWithHTLCsWithRestart also tests the coop close flow when an HTLC
157-
// is still pending on the channel but this time it ensures that the shutdown
158-
// process continues as expected even if a channel re-establish happens after
159-
// one party has already initiated the shutdown.
160-
func coopCloseWithHTLCsWithRestart(ht *lntest.HarnessTest, alice,
161-
bob *node.HarnessNode) {
206+
// runCoopCloseWithHTLCsWithRestartScenario also tests the coop close flow when
207+
// an HTLC is still pending on the channel but this time it ensures that the
208+
// shutdown process continues as expected even if a channel re-establish
209+
// happens after one party has already initiated the shutdown.
210+
func runCoopCloseWithHTLCsWithRestartScenario(ht *lntest.HarnessTest, alice,
211+
bob *node.HarnessNode, testCase flagCombo) {
162212

163213
ht.ConnectNodes(alice, bob)
164214

@@ -167,8 +217,10 @@ func coopCloseWithHTLCsWithRestart(ht *lntest.HarnessTest, alice,
167217
// so that we can assert that the correct delivery address gets used by
168218
// the channel close initiator.
169219
chanPoint := ht.OpenChannel(bob, alice, lntest.OpenChannelParams{
170-
Amt: btcutil.Amount(1000000),
171-
PushAmt: btcutil.Amount(1000000 / 2),
220+
Amt: btcutil.Amount(1000000),
221+
PushAmt: btcutil.Amount(1000000 / 2),
222+
CommitmentType: testCase.commitType,
223+
Private: testCase.private,
172224
})
173225

174226
// Wait for Bob to understand that the channel is ready to use.

lncfg/protocol.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@ type ProtocolOptions struct {
3737

3838
// RbfCoopClose should be set if we want to signal that we support for
3939
// the new experimental RBF coop close feature.
40-
RbfCoopClose bool `long:"rbf-coop-close" description:"if set, then lnd will signal that it supports the new RBF based coop close protocol"`
40+
RbfCoopClose bool `long:"rbf-coop-close" description:"if set, then lnd will signal that it supports the new RBF based coop close protocol, taproot channels are not supported"`
4141

4242
// NoAnchors should be set if we don't want to support opening or accepting
4343
// channels having the anchor commitment type.

lntest/node/config.go

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,12 @@ var (
6868
"--protocol.simple-taproot-chans",
6969
}
7070

71+
// CfgRbfCoopClose specifies the config used to create a node that
72+
// supports the new RBF close protocol.
73+
CfgRbfClose = []string{
74+
"--protocol.rbf-coop-close",
75+
}
76+
7177
// CfgZeroConf specifies the config used to create a node that uses the
7278
// zero-conf channel feature.
7379
CfgZeroConf = []string{

peer/brontide.go

Lines changed: 26 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1231,14 +1231,17 @@ func (p *Brontide) loadActiveChannels(chans []*channeldb.OpenChannel) (
12311231
return nil, err
12321232
}
12331233

1234+
isTaprootChan := lnChan.ChanType().IsTaproot()
1235+
12341236
var (
12351237
shutdownMsg fn.Option[lnwire.Shutdown]
12361238
shutdownInfoErr error
12371239
)
12381240
shutdownInfo.WhenSome(func(info channeldb.ShutdownInfo) {
12391241
// If we can use the new RBF close feature, we don't
1240-
// need to create the legacy closer.
1241-
if p.rbfCoopCloseAllowed() {
1242+
// need to create the legacy closer. However for taproot
1243+
// channels, we'll continue to use the legacy closer.
1244+
if p.rbfCoopCloseAllowed() && !isTaprootChan {
12421245
return
12431246
}
12441247

@@ -1315,8 +1318,10 @@ func (p *Brontide) loadActiveChannels(chans []*channeldb.OpenChannel) (
13151318
p.activeChannels.Store(chanID, lnChan)
13161319

13171320
// We're using the old co-op close, so we don't need to init
1318-
// the new RBF chan closer.
1319-
if !p.rbfCoopCloseAllowed() {
1321+
// the new RBF chan closer. If we have a taproot chan, then
1322+
// we'll also use the legacy type, so we don't need to make the
1323+
// new closer.
1324+
if !p.rbfCoopCloseAllowed() || isTaprootChan {
13201325
continue
13211326
}
13221327

@@ -3268,6 +3273,8 @@ func chooseDeliveryScript(upfront, requested lnwire.DeliveryAddress,
32683273
func (p *Brontide) restartCoopClose(lnChan *lnwallet.LightningChannel) (
32693274
*lnwire.Shutdown, error) {
32703275

3276+
isTaprootChan := lnChan.ChanType().IsTaproot()
3277+
32713278
// If this channel has status ChanStatusCoopBroadcasted and does not
32723279
// have a closing transaction, then the cooperative close process was
32733280
// started but never finished. We'll re-create the chanCloser state
@@ -3320,8 +3327,8 @@ func (p *Brontide) restartCoopClose(lnChan *lnwallet.LightningChannel) (
33203327

33213328
// If the new RBF co-op close is negotiated, then we'll init and start
33223329
// that state machine, skipping the steps for the negotiate machine
3323-
// below.
3324-
if p.rbfCoopCloseAllowed() {
3330+
// below. We don't support this close type for taproot channels though.
3331+
if p.rbfCoopCloseAllowed() && !isTaprootChan {
33253332
_, err := p.initRbfChanCloser(lnChan)
33263333
if err != nil {
33273334
return nil, fmt.Errorf("unable to init rbf chan "+
@@ -4045,7 +4052,7 @@ func (p *Brontide) startRbfChanCloser(shutdown shutdownInit,
40454052
chanID := lnwire.NewChanIDFromOutPoint(chanPoint)
40464053
chanCloser, found := p.activeChanCloses.Load(chanID)
40474054
if !found {
4048-
return fmt.Errorf("rbf can closer not found for channel %v",
4055+
return fmt.Errorf("rbf chan closer not found for channel %v",
40494056
chanPoint)
40504057
}
40514058

@@ -4165,6 +4172,8 @@ func (p *Brontide) handleLocalCloseReq(req *htlcswitch.ChanClose) {
41654172
return
41664173
}
41674174

4175+
isTaprootChan := channel.ChanType().IsTaproot()
4176+
41684177
switch req.CloseType {
41694178
// A type of CloseRegular indicates that the user has opted to close
41704179
// out this channel on-chain, so we execute the cooperative channel
@@ -4176,7 +4185,10 @@ func (p *Brontide) handleLocalCloseReq(req *htlcswitch.ChanClose) {
41764185
// it to send the shutdown message. This also might be an RBF
41774186
// iteration, in which case we'll be obtaining a new
41784187
// transaction w/ a higher fee rate.
4179-
case p.rbfCoopCloseAllowed():
4188+
//
4189+
// We don't support this close type for taproot channels yet
4190+
// however.
4191+
case !isTaprootChan && p.rbfCoopCloseAllowed():
41804192
err = p.startRbfChanCloser(
41814193
newRPCShutdownInit(req), channel.ChannelPoint(),
41824194
)
@@ -5135,9 +5147,12 @@ func (p *Brontide) addActiveChannel(c *lnpeer.NewChannel) error {
51355147
"peer", chanPoint)
51365148
}
51375149

5138-
// We're using the old co-op close, so we don't need to init the new
5139-
// RBF chan closer.
5140-
if !p.rbfCoopCloseAllowed() {
5150+
isTaprootChan := c.ChanType.IsTaproot()
5151+
5152+
// We're using the old co-op close, so we don't need to init the new RBF
5153+
// chan closer. If this is a taproot channel, then we'll also fall
5154+
// through, as we don't support this type yet w/ rbf close.
5155+
if !p.rbfCoopCloseAllowed() || isTaprootChan {
51415156
return nil
51425157
}
51435158

server.go

Lines changed: 0 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -612,17 +612,6 @@ func newServer(cfg *Config, listenAddrs []net.Addr,
612612
"aux controllers")
613613
}
614614

615-
// For now, the RBF coop close flag and the taproot channel type cannot
616-
// be used together.
617-
//
618-
// TODO(roasbeef): fix
619-
if cfg.ProtocolOptions.RbfCoopClose &&
620-
cfg.ProtocolOptions.TaprootChans {
621-
622-
return nil, fmt.Errorf("RBF coop close and taproot " +
623-
"channels cannot be used together")
624-
}
625-
626615
//nolint:ll
627616
featureMgr, err := feature.NewManager(feature.Config{
628617
NoTLVOnion: cfg.ProtocolOptions.LegacyOnion(),

0 commit comments

Comments
 (0)