Skip to content

Commit 01a05fd

Browse files
authored
Merge pull request #8031 from guggero/chan-acceptor-taproot-chan
chanacceptor+lnrpc: add simple taproot channel support
2 parents 3821baa + d16e7a5 commit 01a05fd

8 files changed

+2151
-1925
lines changed

chanacceptor/rpcacceptor.go

+25
Original file line numberDiff line numberDiff line change
@@ -249,6 +249,7 @@ func (r *RPCAcceptor) sendAcceptRequests(errChan chan error,
249249
acceptRequests := make(map[[32]byte]*chanAcceptInfo)
250250

251251
for {
252+
//nolint:lll
252253
select {
253254
// Consume requests passed to us from our Accept() function and
254255
// send them into our stream.
@@ -331,6 +332,30 @@ func (r *RPCAcceptor) sendAcceptRequests(errChan chan error,
331332
):
332333
commitmentType = lnrpc.CommitmentType_ANCHORS
333334

335+
case channelFeatures.OnlyContains(
336+
lnwire.SimpleTaprootChannelsRequiredStaging,
337+
lnwire.ZeroConfRequired,
338+
lnwire.ScidAliasRequired,
339+
):
340+
commitmentType = lnrpc.CommitmentType_SIMPLE_TAPROOT
341+
342+
case channelFeatures.OnlyContains(
343+
lnwire.SimpleTaprootChannelsRequiredStaging,
344+
lnwire.ZeroConfRequired,
345+
):
346+
commitmentType = lnrpc.CommitmentType_SIMPLE_TAPROOT
347+
348+
case channelFeatures.OnlyContains(
349+
lnwire.SimpleTaprootChannelsRequiredStaging,
350+
lnwire.ScidAliasRequired,
351+
):
352+
commitmentType = lnrpc.CommitmentType_SIMPLE_TAPROOT
353+
354+
case channelFeatures.OnlyContains(
355+
lnwire.SimpleTaprootChannelsRequiredStaging,
356+
):
357+
commitmentType = lnrpc.CommitmentType_SIMPLE_TAPROOT
358+
334359
case channelFeatures.OnlyContains(
335360
lnwire.StaticRemoteKeyRequired,
336361
):

itest/lnd_channel_backup_test.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -629,7 +629,7 @@ func runChanRestoreScenarioCommitTypes(ht *lntest.HarnessTest,
629629
thawHeight := uint32(minerHeight + thawHeightDelta)
630630

631631
fundingShim, _ = deriveFundingShim(
632-
ht, dave, carol, crs.params.Amt, thawHeight, true,
632+
ht, dave, carol, crs.params.Amt, thawHeight, true, ct,
633633
)
634634
crs.params.FundingShim = fundingShim
635635
}

itest/lnd_funding_test.go

+201-19
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import (
66
"testing"
77
"time"
88

9+
"github.com/btcsuite/btcd/btcec/v2"
910
"github.com/btcsuite/btcd/btcutil"
1011
"github.com/btcsuite/btcd/chaincfg/chainhash"
1112
"github.com/btcsuite/btcd/txscript"
@@ -241,16 +242,14 @@ func basicChannelFundingTest(ht *lntest.HarnessTest,
241242
// open or an error occurs in the funding process. A series of
242243
// assertions will be executed to ensure the funding process completed
243244
// successfully.
244-
chanPoint := ht.OpenChannel(
245-
alice, bob, lntest.OpenChannelParams{
246-
Private: privateChan,
247-
Amt: chanAmt,
248-
PushAmt: pushAmt,
249-
FundingShim: fundingShim,
250-
SatPerVByte: satPerVbyte,
251-
CommitmentType: commitTypeParam,
252-
},
253-
)
245+
chanPoint := ht.OpenChannel(alice, bob, lntest.OpenChannelParams{
246+
Private: privateChan,
247+
Amt: chanAmt,
248+
PushAmt: pushAmt,
249+
FundingShim: fundingShim,
250+
SatPerVByte: satPerVbyte,
251+
CommitmentType: commitTypeParam,
252+
})
254253

255254
cType := ht.GetChannelCommitType(alice, chanPoint)
256255

@@ -541,10 +540,19 @@ func sendAllCoinsConfirm(ht *lntest.HarnessTest, node *node.HarnessNode,
541540
// channel funding workflow given a channel point that was constructed outside
542541
// the main daemon.
543542
func testExternalFundingChanPoint(ht *lntest.HarnessTest) {
543+
runExternalFundingScriptEnforced(ht)
544+
runExternalFundingTaproot(ht)
545+
}
546+
547+
// runExternalFundingChanPoint runs the actual test that tests we're able to
548+
// carry out a normal channel funding workflow given a channel point that was
549+
// constructed outside the main daemon for the script enforced channel type.
550+
func runExternalFundingScriptEnforced(ht *lntest.HarnessTest) {
544551
// First, we'll create two new nodes that we'll use to open channel
545552
// between for this test.
546553
carol := ht.NewNode("carol", nil)
547554
dave := ht.NewNode("dave", nil)
555+
commitmentType := lnrpc.CommitmentType_SCRIPT_ENFORCED_LEASE
548556

549557
// Carol will be funding the channel, so we'll send some coins over to
550558
// her and ensure they have enough confirmations before we proceed.
@@ -560,7 +568,7 @@ func testExternalFundingChanPoint(ht *lntest.HarnessTest) {
560568
const thawHeight uint32 = 10
561569
const chanSize = funding.MaxBtcFundingAmount
562570
fundingShim1, chanPoint1 := deriveFundingShim(
563-
ht, carol, dave, chanSize, thawHeight, false,
571+
ht, carol, dave, chanSize, thawHeight, false, commitmentType,
564572
)
565573
ht.OpenChannelAssertPending(
566574
carol, dave, lntest.OpenChannelParams{
@@ -576,7 +584,7 @@ func testExternalFundingChanPoint(ht *lntest.HarnessTest) {
576584
// do exactly that now. For this one we publish the transaction so we
577585
// can mine it later.
578586
fundingShim2, chanPoint2 := deriveFundingShim(
579-
ht, carol, dave, chanSize, thawHeight, true,
587+
ht, carol, dave, chanSize, thawHeight, true, commitmentType,
580588
)
581589

582590
// At this point, we'll now carry out the normal basic channel funding
@@ -648,6 +656,159 @@ func testExternalFundingChanPoint(ht *lntest.HarnessTest) {
648656
ht.AssertNodesNumPendingOpenChannels(carol, dave, 0)
649657
}
650658

659+
// runExternalFundingTaproot runs the actual test that tests we're able to carry
660+
// out a normal channel funding workflow given a channel point that was
661+
// constructed outside the main daemon for the taproot channel type.
662+
func runExternalFundingTaproot(ht *lntest.HarnessTest) {
663+
// First, we'll create two new nodes that we'll use to open channel
664+
// between for this test.
665+
commitmentType := lnrpc.CommitmentType_SIMPLE_TAPROOT
666+
args := lntest.NodeArgsForCommitType(commitmentType)
667+
carol := ht.NewNode("carol", args)
668+
669+
// We'll attempt two channels, so Dave will need to accept two pending
670+
// ones.
671+
dave := ht.NewNode("dave", append(args, "--maxpendingchannels=2"))
672+
673+
// Carol will be funding the channel, so we'll send some coins over to
674+
// her and ensure they have enough confirmations before we proceed.
675+
ht.FundCoins(btcutil.SatoshiPerBitcoin, carol)
676+
677+
// Before we start the test, we'll ensure both sides are connected to
678+
// the funding flow can properly be executed.
679+
ht.EnsureConnected(carol, dave)
680+
681+
// At this point, we're ready to simulate our external channel funding
682+
// flow. To start with, we'll create a pending channel with a shim for
683+
// a transaction that will never be published.
684+
const thawHeight uint32 = 10
685+
const chanSize = funding.MaxBtcFundingAmount
686+
fundingShim1, chanPoint1 := deriveFundingShim(
687+
ht, carol, dave, chanSize, thawHeight, false, commitmentType,
688+
)
689+
ht.OpenChannelAssertPending(carol, dave, lntest.OpenChannelParams{
690+
Amt: chanSize,
691+
FundingShim: fundingShim1,
692+
CommitmentType: commitmentType,
693+
Private: true,
694+
})
695+
ht.AssertNodesNumPendingOpenChannels(carol, dave, 1)
696+
697+
// That channel is now pending forever and normally would saturate the
698+
// max pending channel limit for both nodes. But because the channel is
699+
// externally funded, we should still be able to open another one. Let's
700+
// do exactly that now. For this one we publish the transaction so we
701+
// can mine it later.
702+
fundingShim2, chanPoint2 := deriveFundingShim(
703+
ht, carol, dave, chanSize, thawHeight, true, commitmentType,
704+
)
705+
706+
// At this point, we'll now carry out the normal basic channel funding
707+
// test as everything should now proceed as normal (a regular channel
708+
// funding flow).
709+
carolChan, daveChan, _ := basicChannelFundingTest(
710+
ht, carol, dave, fundingShim2, true, &commitmentType,
711+
)
712+
713+
// The itest harness doesn't mine blocks for private channels, so we
714+
// want to make sure the channel with the published and mined
715+
// transaction leaves the pending state.
716+
ht.MineBlocks(6)
717+
718+
rpcChanPointToStr := func(cp *lnrpc.ChannelPoint) string {
719+
txid, err := chainhash.NewHash(cp.GetFundingTxidBytes())
720+
require.NoError(ht, err)
721+
return fmt.Sprintf("%v:%d", txid.String(), cp.OutputIndex)
722+
}
723+
724+
pendingCarol := carol.RPC.PendingChannels().PendingOpenChannels
725+
require.Len(ht, pendingCarol, 1)
726+
require.Equal(
727+
ht, rpcChanPointToStr(chanPoint1),
728+
pendingCarol[0].Channel.ChannelPoint,
729+
)
730+
openCarol := carol.RPC.ListChannels(&lnrpc.ListChannelsRequest{
731+
ActiveOnly: true,
732+
PrivateOnly: true,
733+
})
734+
require.Len(ht, openCarol.Channels, 1)
735+
require.Equal(
736+
ht, rpcChanPointToStr(chanPoint2),
737+
openCarol.Channels[0].ChannelPoint,
738+
)
739+
740+
pendingDave := dave.RPC.PendingChannels().PendingOpenChannels
741+
require.Len(ht, pendingDave, 1)
742+
require.Equal(
743+
ht, rpcChanPointToStr(chanPoint1),
744+
pendingDave[0].Channel.ChannelPoint,
745+
)
746+
747+
openDave := dave.RPC.ListChannels(&lnrpc.ListChannelsRequest{
748+
ActiveOnly: true,
749+
PrivateOnly: true,
750+
})
751+
require.Len(ht, openDave.Channels, 1)
752+
require.Equal(
753+
ht, rpcChanPointToStr(chanPoint2),
754+
openDave.Channels[0].ChannelPoint,
755+
)
756+
757+
// Both channels should be marked as frozen with the proper thaw height.
758+
require.EqualValues(ht, thawHeight, carolChan.ThawHeight, "thaw height")
759+
require.EqualValues(ht, thawHeight, daveChan.ThawHeight, "thaw height")
760+
761+
// Next, to make sure the channel functions as normal, we'll make some
762+
// payments within the channel.
763+
payAmt := btcutil.Amount(100000)
764+
invoice := &lnrpc.Invoice{
765+
Memo: "new chans",
766+
Value: int64(payAmt),
767+
}
768+
resp := dave.RPC.AddInvoice(invoice)
769+
ht.CompletePaymentRequests(carol, []string{resp.PaymentRequest})
770+
771+
// Now that the channels are open, and we've confirmed that they're
772+
// operational, we'll now ensure that the channels are frozen as
773+
// intended (if requested).
774+
//
775+
// First, we'll try to close the channel as Carol, the initiator. This
776+
// should fail as a frozen channel only allows the responder to
777+
// initiate a channel close.
778+
err := ht.CloseChannelAssertErr(carol, chanPoint2, false)
779+
require.Contains(ht, err.Error(), "cannot co-op close frozen channel")
780+
781+
// Before Dave closes the channel, he needs to check the invoice is
782+
// settled to avoid an error saying cannot close channel due to active
783+
// HTLCs.
784+
ht.AssertInvoiceSettled(dave, resp.PaymentAddr)
785+
786+
// TODO(yy): remove the sleep once the following bug is fixed.
787+
// When the invoice is reported settled, the commitment dance is not
788+
// yet finished, which can cause an error when closing the channel,
789+
// saying there's active HTLCs. We need to investigate this issue and
790+
// reverse the order to, first finish the commitment dance, then report
791+
// the invoice as settled.
792+
time.Sleep(2 * time.Second)
793+
794+
// Next we'll try but this time with Dave (the responder) as the
795+
// initiator. This time the channel should be closed as normal.
796+
ht.CloseChannel(dave, chanPoint2)
797+
798+
// Let's make sure we can abandon it.
799+
carol.RPC.AbandonChannel(&lnrpc.AbandonChannelRequest{
800+
ChannelPoint: chanPoint1,
801+
PendingFundingShimOnly: true,
802+
})
803+
dave.RPC.AbandonChannel(&lnrpc.AbandonChannelRequest{
804+
ChannelPoint: chanPoint1,
805+
PendingFundingShimOnly: true,
806+
})
807+
808+
// It should now not appear in the pending channels anymore.
809+
ht.AssertNodesNumPendingOpenChannels(carol, dave, 0)
810+
}
811+
651812
// testFundingPersistence is intended to ensure that the Funding Manager
652813
// persists the state of new channels prior to broadcasting the channel's
653814
// funding transaction. This ensures that the daemon maintains an up-to-date
@@ -977,9 +1138,9 @@ func ensurePolicy(ht *lntest.HarnessTest, alice, peer *node.HarnessNode,
9771138

9781139
// deriveFundingShim creates a channel funding shim by deriving the necessary
9791140
// keys on both sides.
980-
func deriveFundingShim(ht *lntest.HarnessTest,
981-
carol, dave *node.HarnessNode, chanSize btcutil.Amount,
982-
thawHeight uint32, publish bool) (*lnrpc.FundingShim,
1141+
func deriveFundingShim(ht *lntest.HarnessTest, carol, dave *node.HarnessNode,
1142+
chanSize btcutil.Amount, thawHeight uint32, publish bool,
1143+
commitType lnrpc.CommitmentType) (*lnrpc.FundingShim,
9831144
*lnrpc.ChannelPoint) {
9841145

9851146
keyLoc := &signrpc.KeyLocator{KeyFamily: 9999}
@@ -991,11 +1152,31 @@ func deriveFundingShim(ht *lntest.HarnessTest,
9911152
// immediately create and broadcast a transaction paying out an exact
9921153
// amount. Normally this would reside in the mempool, but we just
9931154
// confirm it now for simplicity.
994-
_, fundingOutput, err := input.GenFundingPkScript(
995-
carolFundingKey.RawKeyBytes, daveFundingKey.RawKeyBytes,
996-
int64(chanSize),
1155+
var (
1156+
fundingOutput *wire.TxOut
1157+
musig2 bool
1158+
err error
9971159
)
998-
require.NoError(ht, err)
1160+
if commitType == lnrpc.CommitmentType_SIMPLE_TAPROOT {
1161+
var carolKey, daveKey *btcec.PublicKey
1162+
carolKey, err = btcec.ParsePubKey(carolFundingKey.RawKeyBytes)
1163+
require.NoError(ht, err)
1164+
daveKey, err = btcec.ParsePubKey(daveFundingKey.RawKeyBytes)
1165+
require.NoError(ht, err)
1166+
1167+
_, fundingOutput, err = input.GenTaprootFundingScript(
1168+
carolKey, daveKey, int64(chanSize),
1169+
)
1170+
require.NoError(ht, err)
1171+
1172+
musig2 = true
1173+
} else {
1174+
_, fundingOutput, err = input.GenFundingPkScript(
1175+
carolFundingKey.RawKeyBytes, daveFundingKey.RawKeyBytes,
1176+
int64(chanSize),
1177+
)
1178+
require.NoError(ht, err)
1179+
}
9991180

10001181
var txid *chainhash.Hash
10011182
targetOutputs := []*wire.TxOut{fundingOutput}
@@ -1034,6 +1215,7 @@ func deriveFundingShim(ht *lntest.HarnessTest,
10341215
RemoteKey: carolFundingKey.RawKeyBytes,
10351216
PendingChanId: pendingChanID,
10361217
ThawHeight: thawHeight,
1218+
Musig2: musig2,
10371219
}
10381220
fundingShim := &lnrpc.FundingShim{
10391221
Shim: &lnrpc.FundingShim_ChanPointShim{

itest/lnd_multi-hop_test.go

+2-2
Original file line numberDiff line numberDiff line change
@@ -1942,7 +1942,7 @@ func createThreeHopNetwork(ht *lntest.HarnessTest,
19421942
_, minerHeight := ht.Miner.GetBestBlock()
19431943
thawHeight = uint32(minerHeight + thawHeightDelta)
19441944
aliceFundingShim, _ = deriveFundingShim(
1945-
ht, alice, bob, chanAmt, thawHeight, true,
1945+
ht, alice, bob, chanAmt, thawHeight, true, c,
19461946
)
19471947
}
19481948

@@ -1970,7 +1970,7 @@ func createThreeHopNetwork(ht *lntest.HarnessTest,
19701970
var bobFundingShim *lnrpc.FundingShim
19711971
if c == lnrpc.CommitmentType_SCRIPT_ENFORCED_LEASE {
19721972
bobFundingShim, _ = deriveFundingShim(
1973-
ht, bob, carol, chanAmt, thawHeight, true,
1973+
ht, bob, carol, chanAmt, thawHeight, true, c,
19741974
)
19751975
}
19761976

0 commit comments

Comments
 (0)