Skip to content

Commit a1cd773

Browse files
authored
Merge pull request #6633 from Roasbeef/segwit-any-shutdown
multi: send P2TR addrs by default for co-op close, add new `option_any_segwit` feature bit
2 parents 559160e + cd7c705 commit a1cd773

File tree

18 files changed

+339
-91
lines changed

18 files changed

+339
-91
lines changed

docs/release-notes/release-notes-0.15.1.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,8 @@
2424
[`lnd` will now refuse to start if it detects the full node backned does not
2525
support Tapoot](https://github.com/lightningnetwork/lnd/pull/6798).
2626

27+
[`lnd` will now use taproot addresses for co-op closes if the remote peer
28+
supports the feature.](https://github.com/lightningnetwork/lnd/pull/6633)
2729

2830
## `lncli`
2931

feature/default_sets.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -79,4 +79,8 @@ var defaultSetDesc = setDesc{
7979
SetInit: {}, // I
8080
SetNodeAnn: {}, // N
8181
},
82+
lnwire.ShutdownAnySegwitOptional: {
83+
SetInit: {}, // I
84+
SetNodeAnn: {}, // N
85+
},
8286
}

feature/manager.go

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,10 @@ type Config struct {
4040
// channels. This should be used instead of NoOptionScidAlias to still
4141
// keep option-scid-alias support.
4242
NoZeroConf bool
43+
44+
// NoAnySegwit unsets any bits that signal support for using other
45+
// segwit witness versions for co-op closes.
46+
NoAnySegwit bool
4347
}
4448

4549
// Manager is responsible for generating feature vectors for different requested
@@ -142,6 +146,10 @@ func newManager(cfg Config, desc setDesc) (*Manager, error) {
142146
raw.Unset(lnwire.ZeroConfOptional)
143147
raw.Unset(lnwire.ZeroConfRequired)
144148
}
149+
if cfg.NoAnySegwit {
150+
raw.Unset(lnwire.ShutdownAnySegwitOptional)
151+
raw.Unset(lnwire.ShutdownAnySegwitRequired)
152+
}
145153

146154
// Ensure that all of our feature sets properly set any
147155
// dependent features.

funding/manager.go

Lines changed: 32 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -131,7 +131,7 @@ var (
131131
// via a local signal such as RPC.
132132
//
133133
// TODO(roasbeef): actually use the context package
134-
// * deadlines, etc.
134+
// - deadlines, etc.
135135
type reservationWithCtx struct {
136136
reservation *lnwallet.ChannelReservation
137137
peer lnpeer.Peer
@@ -1483,16 +1483,7 @@ func (f *Manager) handleFundingOpen(peer lnpeer.Peer,
14831483
// (if any) in lieu of user input.
14841484
shutdown, err := getUpfrontShutdownScript(
14851485
f.cfg.EnableUpfrontShutdown, peer, acceptorResp.UpfrontShutdown,
1486-
func() (lnwire.DeliveryAddress, error) {
1487-
addr, err := f.cfg.Wallet.NewAddress(
1488-
lnwallet.WitnessPubKey, false,
1489-
lnwallet.DefaultAccountName,
1490-
)
1491-
if err != nil {
1492-
return nil, err
1493-
}
1494-
return txscript.PayToAddrScript(addr)
1495-
},
1486+
f.selectShutdownScript,
14961487
)
14971488
if err != nil {
14981489
f.failFundingFlow(
@@ -3686,7 +3677,7 @@ func (f *Manager) InitFundingWorkflow(msg *InitFundingMsg) {
36863677
// upfront shutdown scripts automatically.
36873678
func getUpfrontShutdownScript(enableUpfrontShutdown bool, peer lnpeer.Peer,
36883679
script lnwire.DeliveryAddress,
3689-
getScript func() (lnwire.DeliveryAddress, error)) (lnwire.DeliveryAddress,
3680+
getScript func(bool) (lnwire.DeliveryAddress, error)) (lnwire.DeliveryAddress,
36903681
error) {
36913682

36923683
// Check whether the remote peer supports upfront shutdown scripts.
@@ -3718,7 +3709,12 @@ func getUpfrontShutdownScript(enableUpfrontShutdown bool, peer lnpeer.Peer,
37183709
return nil, nil
37193710
}
37203711

3721-
return getScript()
3712+
// We can safely send a taproot address iff, both sides have negotiated
3713+
// the shutdown-any-segwit feature.
3714+
taprootOK := peer.RemoteFeatures().HasFeature(lnwire.ShutdownAnySegwitOptional) &&
3715+
peer.LocalFeatures().HasFeature(lnwire.ShutdownAnySegwitOptional)
3716+
3717+
return getScript(taprootOK)
37223718
}
37233719

37243720
// handleInitFundingMsg creates a channel reservation within the daemon's
@@ -3777,18 +3773,8 @@ func (f *Manager) handleInitFundingMsg(msg *InitFundingMsg) {
37773773
// address from the wallet if our node is configured to set shutdown
37783774
// address by default).
37793775
shutdown, err := getUpfrontShutdownScript(
3780-
f.cfg.EnableUpfrontShutdown, msg.Peer,
3781-
msg.ShutdownScript,
3782-
func() (lnwire.DeliveryAddress, error) {
3783-
addr, err := f.cfg.Wallet.NewAddress(
3784-
lnwallet.WitnessPubKey, false,
3785-
lnwallet.DefaultAccountName,
3786-
)
3787-
if err != nil {
3788-
return nil, err
3789-
}
3790-
return txscript.PayToAddrScript(addr)
3791-
},
3776+
f.cfg.EnableUpfrontShutdown, msg.Peer, msg.ShutdownScript,
3777+
f.selectShutdownScript,
37923778
)
37933779
if err != nil {
37943780
msg.Err <- err
@@ -4267,3 +4253,24 @@ func (f *Manager) deleteChannelOpeningState(chanPoint *wire.OutPoint) error {
42674253
outpointBytes.Bytes(),
42684254
)
42694255
}
4256+
4257+
// selectShutdownScript selects the shutdown script we should send to the peer.
4258+
// If we can use taproot, then we prefer that, otherwise we'll use a p2wkh
4259+
// script.
4260+
func (f *Manager) selectShutdownScript(taprootOK bool,
4261+
) (lnwire.DeliveryAddress, error) {
4262+
4263+
addrType := lnwallet.WitnessPubKey
4264+
if taprootOK {
4265+
addrType = lnwallet.TaprootPubkey
4266+
}
4267+
4268+
addr, err := f.cfg.Wallet.NewAddress(
4269+
addrType, false, lnwallet.DefaultAccountName,
4270+
)
4271+
if err != nil {
4272+
return nil, err
4273+
}
4274+
4275+
return txscript.PayToAddrScript(addr)
4276+
}

funding/manager_test.go

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3331,13 +3331,13 @@ func TestGetUpfrontShutdownScript(t *testing.T) {
33313331
upfrontScript := []byte("upfront script")
33323332
generatedScript := []byte("generated script")
33333333

3334-
getScript := func() (lnwire.DeliveryAddress, error) {
3334+
getScript := func(_ bool) (lnwire.DeliveryAddress, error) {
33353335
return generatedScript, nil
33363336
}
33373337

33383338
tests := []struct {
33393339
name string
3340-
getScript func() (lnwire.DeliveryAddress, error)
3340+
getScript func(bool) (lnwire.DeliveryAddress, error)
33413341
upfrontScript lnwire.DeliveryAddress
33423342
peerEnabled bool
33433343
localEnabled bool
@@ -3628,14 +3628,14 @@ func TestFundingManagerUpfrontShutdown(t *testing.T) {
36283628
pkscript: []byte("\xa9\x14\xfe\x44\x10\x65\xb6\x53" +
36293629
"\x22\x31\xde\x2f\xac\x56\x31\x52\x20\x5e" +
36303630
"\xc4\xf5\x9c\x74\x87"),
3631-
expectErr: false,
3631+
expectErr: true,
36323632
},
36333633
{
36343634
name: "p2pkh script",
36353635
pkscript: []byte("\x76\xa9\x14\x64\x1a\xd5\x05\x1e" +
36363636
"\xdd\x97\x02\x9a\x00\x3f\xe9\xef\xb2\x93" +
36373637
"\x59\xfc\xee\x40\x9d\x88\xac"),
3638-
expectErr: false,
3638+
expectErr: true,
36393639
},
36403640
{
36413641
name: "p2wpkh script",

lncfg/protocol.go

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,10 @@ type ProtocolOptions struct {
3838
// OptionZeroConf should be set if we want to signal the zero-conf
3939
// feature bit.
4040
OptionZeroConf bool `long:"zero-conf" description:"enable support for zero-conf channels, must have option-scid-alias set also"`
41+
42+
// NoOptionAnySegwit should be set to true if we don't want to use any
43+
// Taproot (and beyond) addresses for co-op closing.
44+
NoOptionAnySegwit bool `long:"no-any-segwit" description:"disallow using any segiwt witness version as a co-op close address"`
4145
}
4246

4347
// Wumbo returns true if lnd should permit the creation and acceptance of wumbo
@@ -67,3 +71,9 @@ func (l *ProtocolOptions) ScidAlias() bool {
6771
func (l *ProtocolOptions) ZeroConf() bool {
6872
return l.OptionZeroConf
6973
}
74+
75+
// NoAnySegwit returns true if we don't signal that we understand other newer
76+
// segwit witness versions for co-op close addresses.
77+
func (l *ProtocolOptions) NoAnySegwit() bool {
78+
return l.NoOptionAnySegwit
79+
}

lncfg/protocol_rpctest.go

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,10 @@ type ProtocolOptions struct {
3939
// OptionZeroConf should be set if we want to signal the zero-conf
4040
// feature bit.
4141
OptionZeroConf bool `long:"zero-conf" description:"enable support for zero-conf channels, must have option-scid-alias set also"`
42+
43+
// NoOptionAnySegwit should be set to true if we don't want to use any
44+
// Taproot (and beyond) addresses for co-op closing.
45+
NoOptionAnySegwit bool `long:"no-any-segwit" description:"disallow using any segiwt witness version as a co-op close address"`
4246
}
4347

4448
// Wumbo returns true if lnd should permit the creation and acceptance of wumbo
@@ -68,3 +72,9 @@ func (l *ProtocolOptions) ScidAlias() bool {
6872
func (l *ProtocolOptions) ZeroConf() bool {
6973
return l.OptionZeroConf
7074
}
75+
76+
// NoAnySegwit returns true if we don't signal that we understand other newer
77+
// segwit witness versions for co-op close addresses.
78+
func (l *ProtocolOptions) NoAnySegwit() bool {
79+
return l.NoOptionAnySegwit
80+
}

lntest/itest/lnd_taproot_test.go

Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,8 +11,10 @@ import (
1111
"github.com/btcsuite/btcd/btcec/v2"
1212
"github.com/btcsuite/btcd/btcec/v2/schnorr"
1313
"github.com/btcsuite/btcd/btcutil"
14+
"github.com/btcsuite/btcd/chaincfg/chainhash"
1415
"github.com/btcsuite/btcd/txscript"
1516
"github.com/btcsuite/btcd/wire"
17+
"github.com/lightningnetwork/lnd/funding"
1618
"github.com/lightningnetwork/lnd/input"
1719
"github.com/lightningnetwork/lnd/lnrpc"
1820
"github.com/lightningnetwork/lnd/lnrpc/chainrpc"
@@ -1498,3 +1500,82 @@ func createMuSigSessions(ctx context.Context, t *harnessTest,
14981500

14991501
return internalKey, combinedKey, sessResp1, sessResp2, sessResp3
15001502
}
1503+
1504+
// assertTaprootDeliveryUsed returns true if a Taproot addr was used in the
1505+
// co-op close transaction.
1506+
func assertTaprootDeliveryUsed(net *lntest.NetworkHarness,
1507+
t *harnessTest, closingTxid *chainhash.Hash) bool {
1508+
1509+
tx, err := net.Miner.Client.GetRawTransaction(closingTxid)
1510+
require.NoError(t.t, err, "unable to get closing tx")
1511+
1512+
for _, txOut := range tx.MsgTx().TxOut {
1513+
if !txscript.IsPayToTaproot(txOut.PkScript) {
1514+
return false
1515+
}
1516+
}
1517+
1518+
return true
1519+
}
1520+
1521+
// testTaprootCoopClose asserts that if both peers signal ShutdownAnySegwit,
1522+
// then a taproot closing addr is used. Otherwise, we shouldn't expect one to
1523+
// be used.
1524+
func testTaprootCoopClose(net *lntest.NetworkHarness, t *harnessTest) {
1525+
// We'll start by making two new nodes, and funding a channel between
1526+
// them.
1527+
carol := net.NewNode(t.t, "Carol", nil)
1528+
defer shutdownAndAssert(net, t, carol)
1529+
1530+
net.SendCoins(t.t, btcutil.SatoshiPerBitcoin, carol)
1531+
1532+
dave := net.NewNode(t.t, "Dave", nil)
1533+
defer shutdownAndAssert(net, t, dave)
1534+
1535+
net.EnsureConnected(t.t, carol, dave)
1536+
1537+
chanAmt := funding.MaxBtcFundingAmount
1538+
pushAmt := btcutil.Amount(100000)
1539+
satPerVbyte := btcutil.Amount(1)
1540+
1541+
// We'll now open a channel between Carol and Dave.
1542+
chanPoint := openChannelAndAssert(
1543+
t, net, carol, dave,
1544+
lntest.OpenChannelParams{
1545+
Amt: chanAmt,
1546+
PushAmt: pushAmt,
1547+
SatPerVByte: satPerVbyte,
1548+
},
1549+
)
1550+
1551+
// We'll now close out the channel and obtain the closing TXID.
1552+
closingTxid := closeChannelAndAssert(t, net, carol, chanPoint, false)
1553+
1554+
// We expect that the closing transaction only has P2TR addresses.
1555+
require.True(t.t, assertTaprootDeliveryUsed(net, t, closingTxid),
1556+
"taproot addr not used!")
1557+
1558+
// Now we'll bring Eve into the mix, Eve is running older software that
1559+
// doesn't understand Taproot.
1560+
eveArgs := []string{"--protocol.no-any-segwit"}
1561+
eve := net.NewNode(t.t, "Eve", eveArgs)
1562+
defer shutdownAndAssert(net, t, eve)
1563+
1564+
net.EnsureConnected(t.t, carol, eve)
1565+
1566+
// We'll now open up a chanel again between Carol and Eve.
1567+
chanPoint = openChannelAndAssert(
1568+
t, net, carol, eve,
1569+
lntest.OpenChannelParams{
1570+
Amt: chanAmt,
1571+
PushAmt: pushAmt,
1572+
SatPerVByte: satPerVbyte,
1573+
},
1574+
)
1575+
1576+
// We'll now close out this channel and expect that no Taproot
1577+
// addresses are used in the co-op close transaction.
1578+
closingTxid = closeChannelAndAssert(t, net, carol, chanPoint, false)
1579+
require.False(t.t, assertTaprootDeliveryUsed(net, t, closingTxid),
1580+
"taproot addr shouldn't be used!")
1581+
}

lntest/itest/lnd_test_list_on_test.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -431,4 +431,8 @@ var allTestCases = []*testCase{
431431
name: "nonstd sweep",
432432
test: testNonstdSweep,
433433
},
434+
{
435+
name: "taproot coop close",
436+
test: testTaprootCoopClose,
437+
},
434438
}

0 commit comments

Comments
 (0)