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

View File

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

View File

@ -6,6 +6,7 @@ import (
"testing" "testing"
"time" "time"
"github.com/btcsuite/btcd/btcec/v2"
"github.com/btcsuite/btcd/btcutil" "github.com/btcsuite/btcd/btcutil"
"github.com/btcsuite/btcd/chaincfg/chainhash" "github.com/btcsuite/btcd/chaincfg/chainhash"
"github.com/btcsuite/btcd/txscript" "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 // open or an error occurs in the funding process. A series of
// assertions will be executed to ensure the funding process completed // assertions will be executed to ensure the funding process completed
// successfully. // successfully.
chanPoint := ht.OpenChannel( chanPoint := ht.OpenChannel(alice, bob, lntest.OpenChannelParams{
alice, bob, lntest.OpenChannelParams{ Private: privateChan,
Private: privateChan, Amt: chanAmt,
Amt: chanAmt, PushAmt: pushAmt,
PushAmt: pushAmt, FundingShim: fundingShim,
FundingShim: fundingShim, SatPerVByte: satPerVbyte,
SatPerVByte: satPerVbyte, CommitmentType: commitTypeParam,
CommitmentType: commitTypeParam, })
},
)
cType := ht.GetChannelCommitType(alice, chanPoint) 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 // channel funding workflow given a channel point that was constructed outside
// the main daemon. // the main daemon.
func testExternalFundingChanPoint(ht *lntest.HarnessTest) { 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 // First, we'll create two new nodes that we'll use to open channel
// between for this test. // between for this test.
carol := ht.NewNode("carol", nil) carol := ht.NewNode("carol", nil)
dave := ht.NewNode("dave", 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 // Carol will be funding the channel, so we'll send some coins over to
// her and ensure they have enough confirmations before we proceed. // her and ensure they have enough confirmations before we proceed.
@ -560,7 +568,7 @@ func testExternalFundingChanPoint(ht *lntest.HarnessTest) {
const thawHeight uint32 = 10 const thawHeight uint32 = 10
const chanSize = funding.MaxBtcFundingAmount const chanSize = funding.MaxBtcFundingAmount
fundingShim1, chanPoint1 := deriveFundingShim( fundingShim1, chanPoint1 := deriveFundingShim(
ht, carol, dave, chanSize, thawHeight, false, ht, carol, dave, chanSize, thawHeight, false, commitmentType,
) )
ht.OpenChannelAssertPending( ht.OpenChannelAssertPending(
carol, dave, lntest.OpenChannelParams{ 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 // do exactly that now. For this one we publish the transaction so we
// can mine it later. // can mine it later.
fundingShim2, chanPoint2 := deriveFundingShim( 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 // 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) 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 // testFundingPersistence is intended to ensure that the Funding Manager
// persists the state of new channels prior to broadcasting the channel's // persists the state of new channels prior to broadcasting the channel's
// funding transaction. This ensures that the daemon maintains an up-to-date // 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 // deriveFundingShim creates a channel funding shim by deriving the necessary
// keys on both sides. // keys on both sides.
func deriveFundingShim(ht *lntest.HarnessTest, func deriveFundingShim(ht *lntest.HarnessTest, carol, dave *node.HarnessNode,
carol, dave *node.HarnessNode, chanSize btcutil.Amount, chanSize btcutil.Amount, thawHeight uint32, publish bool,
thawHeight uint32, publish bool) (*lnrpc.FundingShim, commitType lnrpc.CommitmentType) (*lnrpc.FundingShim,
*lnrpc.ChannelPoint) { *lnrpc.ChannelPoint) {
keyLoc := &signrpc.KeyLocator{KeyFamily: 9999} keyLoc := &signrpc.KeyLocator{KeyFamily: 9999}
@ -991,11 +1152,31 @@ func deriveFundingShim(ht *lntest.HarnessTest,
// immediately create and broadcast a transaction paying out an exact // immediately create and broadcast a transaction paying out an exact
// amount. Normally this would reside in the mempool, but we just // amount. Normally this would reside in the mempool, but we just
// confirm it now for simplicity. // confirm it now for simplicity.
_, fundingOutput, err := input.GenFundingPkScript( var (
carolFundingKey.RawKeyBytes, daveFundingKey.RawKeyBytes, fundingOutput *wire.TxOut
int64(chanSize), 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 var txid *chainhash.Hash
targetOutputs := []*wire.TxOut{fundingOutput} targetOutputs := []*wire.TxOut{fundingOutput}
@ -1034,6 +1215,7 @@ func deriveFundingShim(ht *lntest.HarnessTest,
RemoteKey: carolFundingKey.RawKeyBytes, RemoteKey: carolFundingKey.RawKeyBytes,
PendingChanId: pendingChanID, PendingChanId: pendingChanID,
ThawHeight: thawHeight, ThawHeight: thawHeight,
Musig2: musig2,
} }
fundingShim := &lnrpc.FundingShim{ fundingShim := &lnrpc.FundingShim{
Shim: &lnrpc.FundingShim_ChanPointShim{ Shim: &lnrpc.FundingShim_ChanPointShim{

View File

@ -1942,7 +1942,7 @@ func createThreeHopNetwork(ht *lntest.HarnessTest,
_, minerHeight := ht.Miner.GetBestBlock() _, minerHeight := ht.Miner.GetBestBlock()
thawHeight = uint32(minerHeight + thawHeightDelta) thawHeight = uint32(minerHeight + thawHeightDelta)
aliceFundingShim, _ = deriveFundingShim( 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 var bobFundingShim *lnrpc.FundingShim
if c == lnrpc.CommitmentType_SCRIPT_ENFORCED_LEASE { if c == lnrpc.CommitmentType_SCRIPT_ENFORCED_LEASE {
bobFundingShim, _ = deriveFundingShim( 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. the value is less than 500,000, or as an absolute height otherwise.
*/ */
uint32 thaw_height = 6; uint32 thaw_height = 6;
/*
Indicates that the funding output is using a MuSig2 multi-sig output.
*/
bool musig2 = 7;
} }
message PsbtShim { message PsbtShim {

View File

@ -3469,6 +3469,10 @@
"type": "integer", "type": "integer",
"format": "int64", "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." "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( return chanfunding.NewCannedAssembler(
chanPointShim.ThawHeight, *chanPoint, chanPointShim.ThawHeight, *chanPoint,
btcutil.Amount(chanPointShim.Amt), &localKeyDesc, btcutil.Amount(chanPointShim.Amt), &localKeyDesc,
remoteKey, initiator, false, remoteKey, initiator, chanPointShim.Musig2,
), nil ), nil
} }