Merge pull request #9669 from Roasbeef/rbf-taproot-downgrade
multi: downgrade to legacy coop close for taproot channels
This commit is contained in:
6 files changed
+133
-67
No files matched your search
@@ -610,6 +610,10 @@ var allTestCases = []*lntest.TestCase{
|
|||||||
Name: "coop close with htlcs",
|
Name: "coop close with htlcs",
|
||||||
TestFunc: testCoopCloseWithHtlcs,
|
TestFunc: testCoopCloseWithHtlcs,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
Name: "coop close with htlcs restart",
|
||||||
|
TestFunc: testCoopCloseWithHtlcsWithRestart,
|
||||||
|
},
|
||||||
{
|
{
|
||||||
Name: "coop close exceeds max fee",
|
Name: "coop close exceeds max fee",
|
||||||
TestFunc: testCoopCloseExceedsMaxFee,
|
TestFunc: testCoopCloseExceedsMaxFee,
|
||||||
|
|||||||
@@ -18,60 +18,110 @@ import (
|
|||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
type flagCombo struct {
|
||||||
|
isRbf bool
|
||||||
|
isTaproot bool
|
||||||
|
testName string
|
||||||
|
flags []string
|
||||||
|
commitType lnrpc.CommitmentType
|
||||||
|
private bool
|
||||||
|
}
|
||||||
|
|
||||||
|
// createFlagCombos generates a slice of flagCombo structs representing
|
||||||
|
// different test configurations for RBF and Taproot channels.
|
||||||
|
func createFlagCombos() []flagCombo {
|
||||||
|
var testCases []flagCombo
|
||||||
|
for _, isRbf := range []bool{true, false} {
|
||||||
|
for _, isTaproot := range []bool{true, false} {
|
||||||
|
flagCombo := flagCombo{
|
||||||
|
isRbf: isRbf,
|
||||||
|
isTaproot: isTaproot,
|
||||||
|
}
|
||||||
|
|
||||||
|
var flags []string
|
||||||
|
|
||||||
|
if isRbf {
|
||||||
|
flags = append(flags, node.CfgRbfClose...)
|
||||||
|
}
|
||||||
|
|
||||||
|
if isTaproot {
|
||||||
|
flags = append(flags, node.CfgSimpleTaproot...)
|
||||||
|
flagCombo.commitType = lnrpc.CommitmentType_SIMPLE_TAPROOT //nolint:ll
|
||||||
|
flagCombo.private = true
|
||||||
|
}
|
||||||
|
|
||||||
|
flagCombo.flags = flags
|
||||||
|
flagCombo.testName = fmt.Sprintf(
|
||||||
|
"is_rbf=%v is_taproot=%v", isRbf, isTaproot,
|
||||||
|
)
|
||||||
|
|
||||||
|
testCases = append(testCases, flagCombo)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return testCases
|
||||||
|
}
|
||||||
|
|
||||||
// testCoopCloseWithHtlcs tests whether we can successfully issue a coop close
|
// testCoopCloseWithHtlcs tests whether we can successfully issue a coop close
|
||||||
// request while there are still active htlcs on the link. In all the tests, we
|
// request while there are still active htlcs on the link. In this test, we
|
||||||
// will set up an HODL invoice to suspend settlement. Then we will attempt to
|
// will set up an HODL invoice to suspend settlement. Then we will attempt to
|
||||||
// close the channel which should appear as a noop for the time being. Then we
|
// close the channel which should appear as a noop for the time being. Then we
|
||||||
// will have the receiver settle the invoice and observe that the channel gets
|
// will have the receiver settle the invoice and observe that the channel gets
|
||||||
// torn down after settlement.
|
// torn down after settlement. This test covers scenarios without node restarts.
|
||||||
func testCoopCloseWithHtlcs(ht *lntest.HarnessTest) {
|
func testCoopCloseWithHtlcs(ht *lntest.HarnessTest) {
|
||||||
rbfCoopFlags := []string{"--protocol.rbf-coop-close"}
|
testCases := createFlagCombos()
|
||||||
|
|
||||||
for _, isRbf := range []bool{true, false} {
|
for _, testCase := range testCases {
|
||||||
testName := fmt.Sprintf("no restart is_rbf=%v", isRbf)
|
testCase := testCase // Capture range variable.
|
||||||
ht.Run(testName, func(t *testing.T) {
|
ht.Run(testCase.testName, func(t *testing.T) {
|
||||||
tt := ht.Subtest(t)
|
tt := ht.Subtest(t)
|
||||||
|
|
||||||
var flags []string
|
alice := ht.NewNodeWithCoins("Alice", testCase.flags)
|
||||||
if isRbf {
|
bob := ht.NewNodeWithCoins("bob", testCase.flags)
|
||||||
flags = rbfCoopFlags
|
|
||||||
}
|
|
||||||
|
|
||||||
alice := ht.NewNodeWithCoins("Alice", flags)
|
runCoopCloseWithHTLCsScenario(tt, alice, bob, testCase)
|
||||||
bob := ht.NewNodeWithCoins("bob", flags)
|
|
||||||
|
|
||||||
coopCloseWithHTLCs(tt, alice, bob)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, isRbf := range []bool{true, false} {
|
|
||||||
testName := fmt.Sprintf("with restart is_rbf=%v", isRbf)
|
|
||||||
ht.Run(testName, func(t *testing.T) {
|
|
||||||
tt := ht.Subtest(t)
|
|
||||||
|
|
||||||
var flags []string
|
|
||||||
if isRbf {
|
|
||||||
flags = rbfCoopFlags
|
|
||||||
}
|
|
||||||
|
|
||||||
alice := ht.NewNodeWithCoins("Alice", flags)
|
|
||||||
bob := ht.NewNodeWithCoins("bob", flags)
|
|
||||||
|
|
||||||
coopCloseWithHTLCsWithRestart(tt, alice, bob)
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// coopCloseWithHTLCs tests the basic coop close scenario which occurs when one
|
// testCoopCloseWithHtlcsWithRestart tests whether we can successfully issue a
|
||||||
// channel party initiates a channel shutdown while an HTLC is still pending on
|
// coop close request while there are still active htlcs on the link, even
|
||||||
// the channel.
|
// after a node restart. In this test, we will set up an HODL invoice to
|
||||||
func coopCloseWithHTLCs(ht *lntest.HarnessTest, alice, bob *node.HarnessNode) {
|
// suspend settlement. Then we will attempt to close the channel, restart the
|
||||||
|
// nodes, and then have the receiver settle the invoice, observing that the
|
||||||
|
// channel gets torn down after settlement and restart.
|
||||||
|
func testCoopCloseWithHtlcsWithRestart(ht *lntest.HarnessTest) {
|
||||||
|
testCases := createFlagCombos()
|
||||||
|
|
||||||
|
for _, testCase := range testCases {
|
||||||
|
testCase := testCase // Capture range variable.
|
||||||
|
ht.Run(testCase.testName, func(t *testing.T) {
|
||||||
|
tt := ht.Subtest(t)
|
||||||
|
|
||||||
|
alice := ht.NewNodeWithCoins("Alice", testCase.flags)
|
||||||
|
bob := ht.NewNodeWithCoins("bob", testCase.flags)
|
||||||
|
|
||||||
|
runCoopCloseWithHTLCsWithRestartScenario(
|
||||||
|
tt, alice, bob, testCase,
|
||||||
|
)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// runCoopCloseWithHTLCsScenario tests the basic coop close scenario which
|
||||||
|
// occurs when one channel party initiates a channel shutdown while an HTLC is
|
||||||
|
// still pending on the channel.
|
||||||
|
func runCoopCloseWithHTLCsScenario(ht *lntest.HarnessTest, alice,
|
||||||
|
bob *node.HarnessNode, testCase flagCombo) {
|
||||||
|
|
||||||
ht.ConnectNodes(alice, bob)
|
ht.ConnectNodes(alice, bob)
|
||||||
|
|
||||||
// Here we set up a channel between Alice and Bob, beginning with a
|
// Here we set up a channel between Alice and Bob, beginning with a
|
||||||
// balance on Bob's side.
|
// balance on Bob's side.
|
||||||
chanPoint := ht.OpenChannel(bob, alice, lntest.OpenChannelParams{
|
chanPoint := ht.OpenChannel(bob, alice, lntest.OpenChannelParams{
|
||||||
Amt: btcutil.Amount(1000000),
|
Amt: btcutil.Amount(1000000),
|
||||||
|
CommitmentType: testCase.commitType,
|
||||||
|
Private: testCase.private,
|
||||||
})
|
})
|
||||||
|
|
||||||
// Wait for Bob to understand that the channel is ready to use.
|
// 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) {
|
|||||||
ht.AssertStreamChannelCoopClosed(alice, chanPoint, false, closeClient)
|
ht.AssertStreamChannelCoopClosed(alice, chanPoint, false, closeClient)
|
||||||
}
|
}
|
||||||
|
|
||||||
// coopCloseWithHTLCsWithRestart also tests the coop close flow when an HTLC
|
// runCoopCloseWithHTLCsWithRestartScenario also tests the coop close flow when
|
||||||
// is still pending on the channel but this time it ensures that the shutdown
|
// an HTLC is still pending on the channel but this time it ensures that the
|
||||||
// process continues as expected even if a channel re-establish happens after
|
// shutdown process continues as expected even if a channel re-establish
|
||||||
// one party has already initiated the shutdown.
|
// happens after one party has already initiated the shutdown.
|
||||||
func coopCloseWithHTLCsWithRestart(ht *lntest.HarnessTest, alice,
|
func runCoopCloseWithHTLCsWithRestartScenario(ht *lntest.HarnessTest, alice,
|
||||||
bob *node.HarnessNode) {
|
bob *node.HarnessNode, testCase flagCombo) {
|
||||||
|
|
||||||
ht.ConnectNodes(alice, bob)
|
ht.ConnectNodes(alice, bob)
|
||||||
|
|
||||||
@@ -167,8 +217,10 @@ func coopCloseWithHTLCsWithRestart(ht *lntest.HarnessTest, alice,
|
|||||||
// so that we can assert that the correct delivery address gets used by
|
// so that we can assert that the correct delivery address gets used by
|
||||||
// the channel close initiator.
|
// the channel close initiator.
|
||||||
chanPoint := ht.OpenChannel(bob, alice, lntest.OpenChannelParams{
|
chanPoint := ht.OpenChannel(bob, alice, lntest.OpenChannelParams{
|
||||||
Amt: btcutil.Amount(1000000),
|
Amt: btcutil.Amount(1000000),
|
||||||
PushAmt: btcutil.Amount(1000000 / 2),
|
PushAmt: btcutil.Amount(1000000 / 2),
|
||||||
|
CommitmentType: testCase.commitType,
|
||||||
|
Private: testCase.private,
|
||||||
})
|
})
|
||||||
|
|
||||||
// Wait for Bob to understand that the channel is ready to use.
|
// Wait for Bob to understand that the channel is ready to use.
|
||||||
|
|||||||
+1
-1
@@ -37,7 +37,7 @@ type ProtocolOptions struct {
|
|||||||
|
|
||||||
// RbfCoopClose should be set if we want to signal that we support for
|
// RbfCoopClose should be set if we want to signal that we support for
|
||||||
// the new experimental RBF coop close feature.
|
// the new experimental RBF coop close feature.
|
||||||
RbfCoopClose bool `long:"rbf-coop-close" description:"if set, then lnd will signal that it supports the new RBF based coop close protocol"`
|
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"`
|
||||||
|
|
||||||
// NoAnchors should be set if we don't want to support opening or accepting
|
// NoAnchors should be set if we don't want to support opening or accepting
|
||||||
// channels having the anchor commitment type.
|
// channels having the anchor commitment type.
|
||||||
|
|||||||
@@ -68,6 +68,12 @@ var (
|
|||||||
"--protocol.simple-taproot-chans",
|
"--protocol.simple-taproot-chans",
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// CfgRbfCoopClose specifies the config used to create a node that
|
||||||
|
// supports the new RBF close protocol.
|
||||||
|
CfgRbfClose = []string{
|
||||||
|
"--protocol.rbf-coop-close",
|
||||||
|
}
|
||||||
|
|
||||||
// CfgZeroConf specifies the config used to create a node that uses the
|
// CfgZeroConf specifies the config used to create a node that uses the
|
||||||
// zero-conf channel feature.
|
// zero-conf channel feature.
|
||||||
CfgZeroConf = []string{
|
CfgZeroConf = []string{
|
||||||
|
|||||||
+26
-11
@@ -1231,14 +1231,17 @@ func (p *Brontide) loadActiveChannels(chans []*channeldb.OpenChannel) (
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
isTaprootChan := lnChan.ChanType().IsTaproot()
|
||||||
|
|
||||||
var (
|
var (
|
||||||
shutdownMsg fn.Option[lnwire.Shutdown]
|
shutdownMsg fn.Option[lnwire.Shutdown]
|
||||||
shutdownInfoErr error
|
shutdownInfoErr error
|
||||||
)
|
)
|
||||||
shutdownInfo.WhenSome(func(info channeldb.ShutdownInfo) {
|
shutdownInfo.WhenSome(func(info channeldb.ShutdownInfo) {
|
||||||
// If we can use the new RBF close feature, we don't
|
// If we can use the new RBF close feature, we don't
|
||||||
// need to create the legacy closer.
|
// need to create the legacy closer. However for taproot
|
||||||
if p.rbfCoopCloseAllowed() {
|
// channels, we'll continue to use the legacy closer.
|
||||||
|
if p.rbfCoopCloseAllowed() && !isTaprootChan {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1315,8 +1318,10 @@ func (p *Brontide) loadActiveChannels(chans []*channeldb.OpenChannel) (
|
|||||||
p.activeChannels.Store(chanID, lnChan)
|
p.activeChannels.Store(chanID, lnChan)
|
||||||
|
|
||||||
// We're using the old co-op close, so we don't need to init
|
// We're using the old co-op close, so we don't need to init
|
||||||
// the new RBF chan closer.
|
// the new RBF chan closer. If we have a taproot chan, then
|
||||||
if !p.rbfCoopCloseAllowed() {
|
// we'll also use the legacy type, so we don't need to make the
|
||||||
|
// new closer.
|
||||||
|
if !p.rbfCoopCloseAllowed() || isTaprootChan {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -3268,6 +3273,8 @@ func chooseDeliveryScript(upfront, requested lnwire.DeliveryAddress,
|
|||||||
func (p *Brontide) restartCoopClose(lnChan *lnwallet.LightningChannel) (
|
func (p *Brontide) restartCoopClose(lnChan *lnwallet.LightningChannel) (
|
||||||
*lnwire.Shutdown, error) {
|
*lnwire.Shutdown, error) {
|
||||||
|
|
||||||
|
isTaprootChan := lnChan.ChanType().IsTaproot()
|
||||||
|
|
||||||
// If this channel has status ChanStatusCoopBroadcasted and does not
|
// If this channel has status ChanStatusCoopBroadcasted and does not
|
||||||
// have a closing transaction, then the cooperative close process was
|
// have a closing transaction, then the cooperative close process was
|
||||||
// started but never finished. We'll re-create the chanCloser state
|
// started but never finished. We'll re-create the chanCloser state
|
||||||
@@ -3320,8 +3327,8 @@ func (p *Brontide) restartCoopClose(lnChan *lnwallet.LightningChannel) (
|
|||||||
|
|
||||||
// If the new RBF co-op close is negotiated, then we'll init and start
|
// If the new RBF co-op close is negotiated, then we'll init and start
|
||||||
// that state machine, skipping the steps for the negotiate machine
|
// that state machine, skipping the steps for the negotiate machine
|
||||||
// below.
|
// below. We don't support this close type for taproot channels though.
|
||||||
if p.rbfCoopCloseAllowed() {
|
if p.rbfCoopCloseAllowed() && !isTaprootChan {
|
||||||
_, err := p.initRbfChanCloser(lnChan)
|
_, err := p.initRbfChanCloser(lnChan)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("unable to init rbf chan "+
|
return nil, fmt.Errorf("unable to init rbf chan "+
|
||||||
@@ -4045,7 +4052,7 @@ func (p *Brontide) startRbfChanCloser(shutdown shutdownInit,
|
|||||||
chanID := lnwire.NewChanIDFromOutPoint(chanPoint)
|
chanID := lnwire.NewChanIDFromOutPoint(chanPoint)
|
||||||
chanCloser, found := p.activeChanCloses.Load(chanID)
|
chanCloser, found := p.activeChanCloses.Load(chanID)
|
||||||
if !found {
|
if !found {
|
||||||
return fmt.Errorf("rbf can closer not found for channel %v",
|
return fmt.Errorf("rbf chan closer not found for channel %v",
|
||||||
chanPoint)
|
chanPoint)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -4165,6 +4172,8 @@ func (p *Brontide) handleLocalCloseReq(req *htlcswitch.ChanClose) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
isTaprootChan := channel.ChanType().IsTaproot()
|
||||||
|
|
||||||
switch req.CloseType {
|
switch req.CloseType {
|
||||||
// A type of CloseRegular indicates that the user has opted to close
|
// A type of CloseRegular indicates that the user has opted to close
|
||||||
// out this channel on-chain, so we execute the cooperative channel
|
// out this channel on-chain, so we execute the cooperative channel
|
||||||
@@ -4176,7 +4185,10 @@ func (p *Brontide) handleLocalCloseReq(req *htlcswitch.ChanClose) {
|
|||||||
// it to send the shutdown message. This also might be an RBF
|
// it to send the shutdown message. This also might be an RBF
|
||||||
// iteration, in which case we'll be obtaining a new
|
// iteration, in which case we'll be obtaining a new
|
||||||
// transaction w/ a higher fee rate.
|
// transaction w/ a higher fee rate.
|
||||||
case p.rbfCoopCloseAllowed():
|
//
|
||||||
|
// We don't support this close type for taproot channels yet
|
||||||
|
// however.
|
||||||
|
case !isTaprootChan && p.rbfCoopCloseAllowed():
|
||||||
err = p.startRbfChanCloser(
|
err = p.startRbfChanCloser(
|
||||||
newRPCShutdownInit(req), channel.ChannelPoint(),
|
newRPCShutdownInit(req), channel.ChannelPoint(),
|
||||||
)
|
)
|
||||||
@@ -5135,9 +5147,12 @@ func (p *Brontide) addActiveChannel(c *lnpeer.NewChannel) error {
|
|||||||
"peer", chanPoint)
|
"peer", chanPoint)
|
||||||
}
|
}
|
||||||
|
|
||||||
// We're using the old co-op close, so we don't need to init the new
|
isTaprootChan := c.ChanType.IsTaproot()
|
||||||
// RBF chan closer.
|
|
||||||
if !p.rbfCoopCloseAllowed() {
|
// We're using the old co-op close, so we don't need to init the new RBF
|
||||||
|
// chan closer. If this is a taproot channel, then we'll also fall
|
||||||
|
// through, as we don't support this type yet w/ rbf close.
|
||||||
|
if !p.rbfCoopCloseAllowed() || isTaprootChan {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -612,17 +612,6 @@ func newServer(cfg *Config, listenAddrs []net.Addr,
|
|||||||
"aux controllers")
|
"aux controllers")
|
||||||
}
|
}
|
||||||
|
|
||||||
// For now, the RBF coop close flag and the taproot channel type cannot
|
|
||||||
// be used together.
|
|
||||||
//
|
|
||||||
// TODO(roasbeef): fix
|
|
||||||
if cfg.ProtocolOptions.RbfCoopClose &&
|
|
||||||
cfg.ProtocolOptions.TaprootChans {
|
|
||||||
|
|
||||||
return nil, fmt.Errorf("RBF coop close and taproot " +
|
|
||||||
"channels cannot be used together")
|
|
||||||
}
|
|
||||||
|
|
||||||
//nolint:ll
|
//nolint:ll
|
||||||
featureMgr, err := feature.NewManager(feature.Config{
|
featureMgr, err := feature.NewManager(feature.Config{
|
||||||
NoTLVOnion: cfg.ProtocolOptions.LegacyOnion(),
|
NoTLVOnion: cfg.ProtocolOptions.LegacyOnion(),
|
||||||
|
|||||||
Reference in new issue
Block a user