Merge pull request #8031 from guggero/chan-acceptor-taproot-chan

chanacceptor+lnrpc: add simple taproot channel support
This commit is contained in:
Olaoluwa Osuntokun 2023-09-26 16:08:19 -05:00 committed by GitHub
commit 01a05fdea7
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
8 changed files with 2155 additions and 1929 deletions

View file

@ -249,6 +249,7 @@ func (r *RPCAcceptor) sendAcceptRequests(errChan chan error,
acceptRequests := make(map[[32]byte]*chanAcceptInfo)
for {
//nolint:lll
select {
// Consume requests passed to us from our Accept() function and
// send them into our stream.
@ -331,6 +332,30 @@ func (r *RPCAcceptor) sendAcceptRequests(errChan chan error,
):
commitmentType = lnrpc.CommitmentType_ANCHORS
case channelFeatures.OnlyContains(
lnwire.SimpleTaprootChannelsRequiredStaging,
lnwire.ZeroConfRequired,
lnwire.ScidAliasRequired,
):
commitmentType = lnrpc.CommitmentType_SIMPLE_TAPROOT
case channelFeatures.OnlyContains(
lnwire.SimpleTaprootChannelsRequiredStaging,
lnwire.ZeroConfRequired,
):
commitmentType = lnrpc.CommitmentType_SIMPLE_TAPROOT
case channelFeatures.OnlyContains(
lnwire.SimpleTaprootChannelsRequiredStaging,
lnwire.ScidAliasRequired,
):
commitmentType = lnrpc.CommitmentType_SIMPLE_TAPROOT
case channelFeatures.OnlyContains(
lnwire.SimpleTaprootChannelsRequiredStaging,
):
commitmentType = lnrpc.CommitmentType_SIMPLE_TAPROOT
case channelFeatures.OnlyContains(
lnwire.StaticRemoteKeyRequired,
):

View file

@ -629,7 +629,7 @@ func runChanRestoreScenarioCommitTypes(ht *lntest.HarnessTest,
thawHeight := uint32(minerHeight + thawHeightDelta)
fundingShim, _ = deriveFundingShim(
ht, dave, carol, crs.params.Amt, thawHeight, true,
ht, dave, carol, crs.params.Amt, thawHeight, true, ct,
)
crs.params.FundingShim = fundingShim
}

View file

@ -6,6 +6,7 @@ import (
"testing"
"time"
"github.com/btcsuite/btcd/btcec/v2"
"github.com/btcsuite/btcd/btcutil"
"github.com/btcsuite/btcd/chaincfg/chainhash"
"github.com/btcsuite/btcd/txscript"
@ -241,16 +242,14 @@ func basicChannelFundingTest(ht *lntest.HarnessTest,
// open or an error occurs in the funding process. A series of
// assertions will be executed to ensure the funding process completed
// successfully.
chanPoint := ht.OpenChannel(
alice, bob, lntest.OpenChannelParams{
Private: privateChan,
Amt: chanAmt,
PushAmt: pushAmt,
FundingShim: fundingShim,
SatPerVByte: satPerVbyte,
CommitmentType: commitTypeParam,
},
)
chanPoint := ht.OpenChannel(alice, bob, lntest.OpenChannelParams{
Private: privateChan,
Amt: chanAmt,
PushAmt: pushAmt,
FundingShim: fundingShim,
SatPerVByte: satPerVbyte,
CommitmentType: commitTypeParam,
})
cType := ht.GetChannelCommitType(alice, chanPoint)
@ -541,10 +540,19 @@ func sendAllCoinsConfirm(ht *lntest.HarnessTest, node *node.HarnessNode,
// channel funding workflow given a channel point that was constructed outside
// the main daemon.
func testExternalFundingChanPoint(ht *lntest.HarnessTest) {
runExternalFundingScriptEnforced(ht)
runExternalFundingTaproot(ht)
}
// runExternalFundingChanPoint runs the actual test that tests we're able to
// carry out a normal channel funding workflow given a channel point that was
// constructed outside the main daemon for the script enforced channel type.
func runExternalFundingScriptEnforced(ht *lntest.HarnessTest) {
// First, we'll create two new nodes that we'll use to open channel
// between for this test.
carol := ht.NewNode("carol", nil)
dave := ht.NewNode("dave", nil)
commitmentType := lnrpc.CommitmentType_SCRIPT_ENFORCED_LEASE
// Carol will be funding the channel, so we'll send some coins over to
// her and ensure they have enough confirmations before we proceed.
@ -560,7 +568,7 @@ func testExternalFundingChanPoint(ht *lntest.HarnessTest) {
const thawHeight uint32 = 10
const chanSize = funding.MaxBtcFundingAmount
fundingShim1, chanPoint1 := deriveFundingShim(
ht, carol, dave, chanSize, thawHeight, false,
ht, carol, dave, chanSize, thawHeight, false, commitmentType,
)
ht.OpenChannelAssertPending(
carol, dave, lntest.OpenChannelParams{
@ -576,7 +584,7 @@ func testExternalFundingChanPoint(ht *lntest.HarnessTest) {
// do exactly that now. For this one we publish the transaction so we
// can mine it later.
fundingShim2, chanPoint2 := deriveFundingShim(
ht, carol, dave, chanSize, thawHeight, true,
ht, carol, dave, chanSize, thawHeight, true, commitmentType,
)
// At this point, we'll now carry out the normal basic channel funding
@ -648,6 +656,159 @@ func testExternalFundingChanPoint(ht *lntest.HarnessTest) {
ht.AssertNodesNumPendingOpenChannels(carol, dave, 0)
}
// runExternalFundingTaproot runs the actual test that tests we're able to carry
// out a normal channel funding workflow given a channel point that was
// constructed outside the main daemon for the taproot channel type.
func runExternalFundingTaproot(ht *lntest.HarnessTest) {
// First, we'll create two new nodes that we'll use to open channel
// between for this test.
commitmentType := lnrpc.CommitmentType_SIMPLE_TAPROOT
args := lntest.NodeArgsForCommitType(commitmentType)
carol := ht.NewNode("carol", args)
// We'll attempt two channels, so Dave will need to accept two pending
// ones.
dave := ht.NewNode("dave", append(args, "--maxpendingchannels=2"))
// Carol will be funding the channel, so we'll send some coins over to
// her and ensure they have enough confirmations before we proceed.
ht.FundCoins(btcutil.SatoshiPerBitcoin, carol)
// Before we start the test, we'll ensure both sides are connected to
// the funding flow can properly be executed.
ht.EnsureConnected(carol, dave)
// At this point, we're ready to simulate our external channel funding
// flow. To start with, we'll create a pending channel with a shim for
// a transaction that will never be published.
const thawHeight uint32 = 10
const chanSize = funding.MaxBtcFundingAmount
fundingShim1, chanPoint1 := deriveFundingShim(
ht, carol, dave, chanSize, thawHeight, false, commitmentType,
)
ht.OpenChannelAssertPending(carol, dave, lntest.OpenChannelParams{
Amt: chanSize,
FundingShim: fundingShim1,
CommitmentType: commitmentType,
Private: true,
})
ht.AssertNodesNumPendingOpenChannels(carol, dave, 1)
// That channel is now pending forever and normally would saturate the
// max pending channel limit for both nodes. But because the channel is
// externally funded, we should still be able to open another one. Let's
// do exactly that now. For this one we publish the transaction so we
// can mine it later.
fundingShim2, chanPoint2 := deriveFundingShim(
ht, carol, dave, chanSize, thawHeight, true, commitmentType,
)
// At this point, we'll now carry out the normal basic channel funding
// test as everything should now proceed as normal (a regular channel
// funding flow).
carolChan, daveChan, _ := basicChannelFundingTest(
ht, carol, dave, fundingShim2, true, &commitmentType,
)
// The itest harness doesn't mine blocks for private channels, so we
// want to make sure the channel with the published and mined
// transaction leaves the pending state.
ht.MineBlocks(6)
rpcChanPointToStr := func(cp *lnrpc.ChannelPoint) string {
txid, err := chainhash.NewHash(cp.GetFundingTxidBytes())
require.NoError(ht, err)
return fmt.Sprintf("%v:%d", txid.String(), cp.OutputIndex)
}
pendingCarol := carol.RPC.PendingChannels().PendingOpenChannels
require.Len(ht, pendingCarol, 1)
require.Equal(
ht, rpcChanPointToStr(chanPoint1),
pendingCarol[0].Channel.ChannelPoint,
)
openCarol := carol.RPC.ListChannels(&lnrpc.ListChannelsRequest{
ActiveOnly: true,
PrivateOnly: true,
})
require.Len(ht, openCarol.Channels, 1)
require.Equal(
ht, rpcChanPointToStr(chanPoint2),
openCarol.Channels[0].ChannelPoint,
)
pendingDave := dave.RPC.PendingChannels().PendingOpenChannels
require.Len(ht, pendingDave, 1)
require.Equal(
ht, rpcChanPointToStr(chanPoint1),
pendingDave[0].Channel.ChannelPoint,
)
openDave := dave.RPC.ListChannels(&lnrpc.ListChannelsRequest{
ActiveOnly: true,
PrivateOnly: true,
})
require.Len(ht, openDave.Channels, 1)
require.Equal(
ht, rpcChanPointToStr(chanPoint2),
openDave.Channels[0].ChannelPoint,
)
// Both channels should be marked as frozen with the proper thaw height.
require.EqualValues(ht, thawHeight, carolChan.ThawHeight, "thaw height")
require.EqualValues(ht, thawHeight, daveChan.ThawHeight, "thaw height")
// Next, to make sure the channel functions as normal, we'll make some
// payments within the channel.
payAmt := btcutil.Amount(100000)
invoice := &lnrpc.Invoice{
Memo: "new chans",
Value: int64(payAmt),
}
resp := dave.RPC.AddInvoice(invoice)
ht.CompletePaymentRequests(carol, []string{resp.PaymentRequest})
// Now that the channels are open, and we've confirmed that they're
// operational, we'll now ensure that the channels are frozen as
// intended (if requested).
//
// First, we'll try to close the channel as Carol, the initiator. This
// should fail as a frozen channel only allows the responder to
// initiate a channel close.
err := ht.CloseChannelAssertErr(carol, chanPoint2, false)
require.Contains(ht, err.Error(), "cannot co-op close frozen channel")
// Before Dave closes the channel, he needs to check the invoice is
// settled to avoid an error saying cannot close channel due to active
// HTLCs.
ht.AssertInvoiceSettled(dave, resp.PaymentAddr)
// TODO(yy): remove the sleep once the following bug is fixed.
// When the invoice is reported settled, the commitment dance is not
// yet finished, which can cause an error when closing the channel,
// saying there's active HTLCs. We need to investigate this issue and
// reverse the order to, first finish the commitment dance, then report
// the invoice as settled.
time.Sleep(2 * time.Second)
// Next we'll try but this time with Dave (the responder) as the
// initiator. This time the channel should be closed as normal.
ht.CloseChannel(dave, chanPoint2)
// Let's make sure we can abandon it.
carol.RPC.AbandonChannel(&lnrpc.AbandonChannelRequest{
ChannelPoint: chanPoint1,
PendingFundingShimOnly: true,
})
dave.RPC.AbandonChannel(&lnrpc.AbandonChannelRequest{
ChannelPoint: chanPoint1,
PendingFundingShimOnly: true,
})
// It should now not appear in the pending channels anymore.
ht.AssertNodesNumPendingOpenChannels(carol, dave, 0)
}
// testFundingPersistence is intended to ensure that the Funding Manager
// persists the state of new channels prior to broadcasting the channel's
// 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,
// deriveFundingShim creates a channel funding shim by deriving the necessary
// keys on both sides.
func deriveFundingShim(ht *lntest.HarnessTest,
carol, dave *node.HarnessNode, chanSize btcutil.Amount,
thawHeight uint32, publish bool) (*lnrpc.FundingShim,
func deriveFundingShim(ht *lntest.HarnessTest, carol, dave *node.HarnessNode,
chanSize btcutil.Amount, thawHeight uint32, publish bool,
commitType lnrpc.CommitmentType) (*lnrpc.FundingShim,
*lnrpc.ChannelPoint) {
keyLoc := &signrpc.KeyLocator{KeyFamily: 9999}
@ -991,11 +1152,31 @@ func deriveFundingShim(ht *lntest.HarnessTest,
// immediately create and broadcast a transaction paying out an exact
// amount. Normally this would reside in the mempool, but we just
// confirm it now for simplicity.
_, fundingOutput, err := input.GenFundingPkScript(
carolFundingKey.RawKeyBytes, daveFundingKey.RawKeyBytes,
int64(chanSize),
var (
fundingOutput *wire.TxOut
musig2 bool
err error
)
require.NoError(ht, err)
if commitType == lnrpc.CommitmentType_SIMPLE_TAPROOT {
var carolKey, daveKey *btcec.PublicKey
carolKey, err = btcec.ParsePubKey(carolFundingKey.RawKeyBytes)
require.NoError(ht, err)
daveKey, err = btcec.ParsePubKey(daveFundingKey.RawKeyBytes)
require.NoError(ht, err)
_, fundingOutput, err = input.GenTaprootFundingScript(
carolKey, daveKey, int64(chanSize),
)
require.NoError(ht, err)
musig2 = true
} else {
_, fundingOutput, err = input.GenFundingPkScript(
carolFundingKey.RawKeyBytes, daveFundingKey.RawKeyBytes,
int64(chanSize),
)
require.NoError(ht, err)
}
var txid *chainhash.Hash
targetOutputs := []*wire.TxOut{fundingOutput}
@ -1034,6 +1215,7 @@ func deriveFundingShim(ht *lntest.HarnessTest,
RemoteKey: carolFundingKey.RawKeyBytes,
PendingChanId: pendingChanID,
ThawHeight: thawHeight,
Musig2: musig2,
}
fundingShim := &lnrpc.FundingShim{
Shim: &lnrpc.FundingShim_ChanPointShim{

View file

@ -1942,7 +1942,7 @@ func createThreeHopNetwork(ht *lntest.HarnessTest,
_, minerHeight := ht.Miner.GetBestBlock()
thawHeight = uint32(minerHeight + thawHeightDelta)
aliceFundingShim, _ = deriveFundingShim(
ht, alice, bob, chanAmt, thawHeight, true,
ht, alice, bob, chanAmt, thawHeight, true, c,
)
}
@ -1970,7 +1970,7 @@ func createThreeHopNetwork(ht *lntest.HarnessTest,
var bobFundingShim *lnrpc.FundingShim
if c == lnrpc.CommitmentType_SCRIPT_ENFORCED_LEASE {
bobFundingShim, _ = deriveFundingShim(
ht, bob, carol, chanAmt, thawHeight, true,
ht, bob, carol, chanAmt, thawHeight, true, c,
)
}

File diff suppressed because it is too large Load diff

View file

@ -2453,6 +2453,11 @@ message ChanPointShim {
the value is less than 500,000, or as an absolute height otherwise.
*/
uint32 thaw_height = 6;
/*
Indicates that the funding output is using a MuSig2 multi-sig output.
*/
bool musig2 = 7;
}
message PsbtShim {

View file

@ -3469,6 +3469,10 @@
"type": "integer",
"format": "int64",
"description": "This uint32 indicates if this channel is to be considered 'frozen'. A frozen\nchannel does not allow a cooperative channel close by the initiator. The\nthaw_height is the height that this restriction stops applying to the\nchannel. The height can be interpreted in two ways: as a relative height if\nthe value is less than 500,000, or as an absolute height otherwise."
},
"musig2": {
"type": "boolean",
"description": "Indicates that the funding output is using a MuSig2 multi-sig output."
}
}
},

View file

@ -1824,7 +1824,7 @@ func newFundingShimAssembler(chanPointShim *lnrpc.ChanPointShim, initiator bool,
return chanfunding.NewCannedAssembler(
chanPointShim.ThawHeight, *chanPoint,
btcutil.Amount(chanPointShim.Amt), &localKeyDesc,
remoteKey, initiator, false,
remoteKey, initiator, chanPointShim.Musig2,
), nil
}