From 8980471d579b18b1d5096ddde9e70b9c46f68281 Mon Sep 17 00:00:00 2001 From: Oliver Gugger Date: Tue, 10 May 2022 18:01:14 +0200 Subject: [PATCH 1/7] chanfunding: support p2tr input fee calculation With this commit we support fee calculation in coin selection for p2tr inputs. We assume that coins in our UTXO selection are only BIP0086 coins. Any other input types with different spend paths won't be selected by the wallet assembler. --- lnwallet/chanfunding/coin_select.go | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/lnwallet/chanfunding/coin_select.go b/lnwallet/chanfunding/coin_select.go index 119194c88..761f7c032 100644 --- a/lnwallet/chanfunding/coin_select.go +++ b/lnwallet/chanfunding/coin_select.go @@ -79,6 +79,11 @@ func calculateFees(utxos []Coin, feeRate chainfee.SatPerKWeight) (btcutil.Amount case txscript.IsPayToScriptHash(utxo.PkScript): weightEstimate.AddNestedP2WKHInput() + case txscript.IsPayToTaproot(utxo.PkScript): + weightEstimate.AddTaprootKeySpendInput( + txscript.SigHashDefault, + ) + default: return 0, 0, &errUnsupportedInput{utxo.PkScript} } From 631b2af81869858ecc6115613d2a93b96f069630 Mon Sep 17 00:00:00 2001 From: Oliver Gugger Date: Tue, 10 May 2022 18:03:29 +0200 Subject: [PATCH 2/7] btcwallet: support p2tr input info type detection --- lnwallet/btcwallet/signer.go | 2 ++ 1 file changed, 2 insertions(+) diff --git a/lnwallet/btcwallet/signer.go b/lnwallet/btcwallet/signer.go index d1c88509c..6b571ea1c 100644 --- a/lnwallet/btcwallet/signer.go +++ b/lnwallet/btcwallet/signer.go @@ -41,6 +41,8 @@ func (b *BtcWallet) FetchInputInfo(prevOut *wire.OutPoint) (*lnwallet.Utxo, erro addressType = lnwallet.WitnessPubKey case txscript.IsPayToScriptHash(txOut.PkScript): addressType = lnwallet.NestedWitnessPubKey + case txscript.IsPayToTaproot(txOut.PkScript): + addressType = lnwallet.TaprootPubkey } return &lnwallet.Utxo{ From 18cf06ddd1968b949a3319bb13fc69c1d5b758d4 Mon Sep 17 00:00:00 2001 From: Oliver Gugger Date: Tue, 10 May 2022 18:13:00 +0200 Subject: [PATCH 3/7] chanfunding: fix sighash type for p2tr inputs This commit fixes the default sighash type for p2tr channel funding transaction inputs. --- lnwallet/chanfunding/wallet_assembler.go | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/lnwallet/chanfunding/wallet_assembler.go b/lnwallet/chanfunding/wallet_assembler.go index cc14dcac0..376360901 100644 --- a/lnwallet/chanfunding/wallet_assembler.go +++ b/lnwallet/chanfunding/wallet_assembler.go @@ -111,7 +111,6 @@ func (f *FullIntent) CompileFundingTx(extraInputs []*wire.TxIn, f.coinSource, extraInputs, ) signDesc := input.SignDescriptor{ - HashType: txscript.SigHashAll, SigHashes: txscript.NewTxSigHashes( fundingTx, prevOutFetcher, ), @@ -136,6 +135,13 @@ func (f *FullIntent) CompileFundingTx(extraInputs []*wire.TxIn, } signDesc.InputIndex = i + // We support spending a p2tr input ourselves. But not as part + // of their inputs. + signDesc.HashType = txscript.SigHashAll + if txscript.IsPayToTaproot(info.PkScript) { + signDesc.HashType = txscript.SigHashDefault + } + // Finally, we'll sign the input as is, and populate the input // with the witness and sigScript (if needed). inputScript, err := f.signer.ComputeInputScript( @@ -398,7 +404,9 @@ var _ txscript.PrevOutputFetcher = (*SegWitV0DualFundingPrevOutputFetcher)(nil) // // NOTE: Since the actual pkScript and amounts aren't passed in, this will just // make sure that nothing will panic when creating a SegWit v0 sighash. But this -// code will NOT WORK for transactions that spend any Taproot inputs! +// code will NOT WORK for transactions that spend any _remote_ Taproot inputs! +// So basically dual-funding won't work with Taproot inputs unless the UTXO info +// is exchanged between the peers. func NewSegWitV0DualFundingPrevOutputFetcher(localSource CoinSource, remoteInputs []*wire.TxIn) txscript.PrevOutputFetcher { From e27a9fec66b70e03f63e67406c066dd98e716952 Mon Sep 17 00:00:00 2001 From: Oliver Gugger Date: Tue, 10 May 2022 18:31:18 +0200 Subject: [PATCH 4/7] lntest: rename sendCoins to SendCoinsOfType --- lntest/harness_net.go | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/lntest/harness_net.go b/lntest/harness_net.go index fc88e28fb..5fde91419 100644 --- a/lntest/harness_net.go +++ b/lntest/harness_net.go @@ -1417,7 +1417,7 @@ func (n *NetworkHarness) DumpLogs(node *HarnessNode) (string, error) { func (n *NetworkHarness) SendCoins(t *testing.T, amt btcutil.Amount, target *HarnessNode) { - err := n.sendCoins( + err := n.SendCoinsOfType( amt, target, lnrpc.AddressType_WITNESS_PUBKEY_HASH, true, ) require.NoErrorf(t, err, "unable to send coins for %s", target.Cfg.Name) @@ -1429,7 +1429,7 @@ func (n *NetworkHarness) SendCoins(t *testing.T, amt btcutil.Amount, func (n *NetworkHarness) SendCoinsUnconfirmed(t *testing.T, amt btcutil.Amount, target *HarnessNode) { - err := n.sendCoins( + err := n.SendCoinsOfType( amt, target, lnrpc.AddressType_WITNESS_PUBKEY_HASH, false, ) require.NoErrorf( @@ -1443,7 +1443,7 @@ func (n *NetworkHarness) SendCoinsUnconfirmed(t *testing.T, amt btcutil.Amount, func (n *NetworkHarness) SendCoinsNP2WKH(t *testing.T, amt btcutil.Amount, target *HarnessNode) { - err := n.sendCoins( + err := n.SendCoinsOfType( amt, target, lnrpc.AddressType_NESTED_PUBKEY_HASH, true, ) require.NoErrorf( @@ -1457,16 +1457,18 @@ func (n *NetworkHarness) SendCoinsNP2WKH(t *testing.T, amt btcutil.Amount, func (n *NetworkHarness) SendCoinsP2TR(t *testing.T, amt btcutil.Amount, target *HarnessNode) { - err := n.sendCoins(amt, target, lnrpc.AddressType_TAPROOT_PUBKEY, true) + err := n.SendCoinsOfType( + amt, target, lnrpc.AddressType_TAPROOT_PUBKEY, true, + ) require.NoErrorf( t, err, "unable to send P2TR coins for %s", target.Cfg.Name, ) } -// sendCoins attempts to send amt satoshis from the internal mining node to the -// targeted lightning node. The confirmed boolean indicates whether the +// SendCoinsOfType attempts to send amt satoshis from the internal mining node +// to the targeted lightning node. The confirmed boolean indicates whether the // transaction that pays to the target should confirm. -func (n *NetworkHarness) sendCoins(amt btcutil.Amount, target *HarnessNode, +func (n *NetworkHarness) SendCoinsOfType(amt btcutil.Amount, target *HarnessNode, addrType lnrpc.AddressType, confirmed bool) error { ctx, cancel := context.WithTimeout(n.runCtx, DefaultTimeout) From fd7fe2ae5a1b99278d70da1a16eac44fb0b7ccab Mon Sep 17 00:00:00 2001 From: Oliver Gugger Date: Tue, 10 May 2022 18:39:45 +0200 Subject: [PATCH 5/7] itest: use SendCoinsUnconfirmed in funding test --- lntest/itest/lnd_funding_test.go | 23 ++--------------------- 1 file changed, 2 insertions(+), 21 deletions(-) diff --git a/lntest/itest/lnd_funding_test.go b/lntest/itest/lnd_funding_test.go index 9fafa8139..bee7c1077 100644 --- a/lntest/itest/lnd_funding_test.go +++ b/lntest/itest/lnd_funding_test.go @@ -244,8 +244,6 @@ func basicChannelFundingTest(t *harnessTest, net *lntest.NetworkHarness, // testUnconfirmedChannelFunding tests that our unconfirmed change outputs can // be used to fund channels. func testUnconfirmedChannelFunding(net *lntest.NetworkHarness, t *harnessTest) { - ctxb := context.Background() - const ( chanAmt = funding.MaxBtcFundingAmount pushAmt = btcutil.Amount(100000) @@ -256,27 +254,10 @@ func testUnconfirmedChannelFunding(net *lntest.NetworkHarness, t *harnessTest) { defer shutdownAndAssert(net, t, carol) // We'll send her some confirmed funds. - net.SendCoins(t.t, 2*chanAmt, carol) - - // Now let Carol send some funds to herself, making a unconfirmed - // change output. - addrReq := &lnrpc.NewAddressRequest{ - Type: lnrpc.AddressType_WITNESS_PUBKEY_HASH, - } - ctxt, _ := context.WithTimeout(ctxb, defaultTimeout) - resp, err := carol.NewAddress(ctxt, addrReq) - require.NoError(t.t, err, "unable to get new address") - - sendReq := &lnrpc.SendCoinsRequest{ - Addr: resp.Address, - Amount: int64(chanAmt) / 5, - } - ctxt, _ = context.WithTimeout(ctxb, defaultTimeout) - _, err = carol.SendCoins(ctxt, sendReq) - require.NoError(t.t, err, "unable to send coins") + net.SendCoinsUnconfirmed(t.t, chanAmt*2, carol) // Make sure the unconfirmed tx is seen in the mempool. - _, err = waitForTxInMempool(net.Miner.Client, minerMempoolTimeout) + _, err := waitForTxInMempool(net.Miner.Client, minerMempoolTimeout) require.NoError(t.t, err, "failed to find tx in miner mempool") // Now, we'll connect her to Alice so that they can open a channel From 4fd43b4afe6fbdfac6e6ec2b1c391fc9f6d8626a Mon Sep 17 00:00:00 2001 From: Oliver Gugger Date: Tue, 10 May 2022 18:40:08 +0200 Subject: [PATCH 6/7] itest: add new funding test with all input types --- lntest/itest/lnd_funding_test.go | 135 ++++++++++++++++++++++++++ lntest/itest/lnd_test_list_on_test.go | 4 + 2 files changed, 139 insertions(+) diff --git a/lntest/itest/lnd_funding_test.go b/lntest/itest/lnd_funding_test.go index bee7c1077..d6717b026 100644 --- a/lntest/itest/lnd_funding_test.go +++ b/lntest/itest/lnd_funding_test.go @@ -343,6 +343,141 @@ func testUnconfirmedChannelFunding(net *lntest.NetworkHarness, t *harnessTest) { closeChannelAndAssert(t, net, carol, chanPoint, false) } +// testChannelFundingInputTypes tests that any type of supported input type can +// be used to fund channels. +func testChannelFundingInputTypes(net *lntest.NetworkHarness, t *harnessTest) { + const ( + chanAmt = funding.MaxBtcFundingAmount + burnAddr = "bcrt1qxsnqpdc842lu8c0xlllgvejt6rhy49u6fmpgyz" + ) + addrTypes := []lnrpc.AddressType{ + lnrpc.AddressType_WITNESS_PUBKEY_HASH, + lnrpc.AddressType_NESTED_PUBKEY_HASH, + lnrpc.AddressType_TAPROOT_PUBKEY, + } + + // We'll start off by creating a node for Carol. + carol := net.NewNode(t.t, "Carol", nil) + defer shutdownAndAssert(net, t, carol) + + // Now, we'll connect her to Alice so that they can open a + // channel together. + net.ConnectNodes(t.t, carol, net.Alice) + + for _, addrType := range addrTypes { + // We'll send her some confirmed funds. + err := net.SendCoinsOfType(chanAmt*2, carol, addrType, true) + require.NoErrorf( + t.t, err, "unable to send coins for carol and addr "+ + "type %v", addrType, + ) + + chanOpenUpdate := openChannelStream( + t, net, carol, net.Alice, lntest.OpenChannelParams{ + Amt: chanAmt, + }, + ) + + // Creates a helper closure to be used below which asserts the + // proper response to a channel balance RPC. + checkChannelBalance := func(node *lntest.HarnessNode, + local, remote, pendingLocal, + pendingRemote btcutil.Amount) { + + expectedResponse := &lnrpc.ChannelBalanceResponse{ + LocalBalance: &lnrpc.Amount{ + Sat: uint64(local), + Msat: uint64(lnwire.NewMSatFromSatoshis( + local, + )), + }, + RemoteBalance: &lnrpc.Amount{ + Sat: uint64(remote), + Msat: uint64(lnwire.NewMSatFromSatoshis( + remote, + )), + }, + PendingOpenLocalBalance: &lnrpc.Amount{ + Sat: uint64(pendingLocal), + Msat: uint64(lnwire.NewMSatFromSatoshis( + pendingLocal, + )), + }, + PendingOpenRemoteBalance: &lnrpc.Amount{ + Sat: uint64(pendingRemote), + Msat: uint64(lnwire.NewMSatFromSatoshis( + pendingRemote, + )), + }, + UnsettledLocalBalance: &lnrpc.Amount{}, + UnsettledRemoteBalance: &lnrpc.Amount{}, + // Deprecated fields. + Balance: int64(local), + PendingOpenBalance: int64(pendingLocal), + } + assertChannelBalanceResp(t, node, expectedResponse) + } + + // As the channel is pending open, it's expected Carol has both + // zero local and remote balances, and pending local/remote + // should not be zero. + // + // Note that atm we haven't obtained the chanPoint yet, so we + // use the type directly. + cType := lnrpc.CommitmentType_STATIC_REMOTE_KEY + carolLocalBalance := chanAmt - calcStaticFee(cType, 0) + checkChannelBalance(carol, 0, 0, carolLocalBalance, 0) + + // For Alice, her local/remote balances should be zero, and the + // local/remote balances are the mirror of Carol's. + checkChannelBalance(net.Alice, 0, 0, 0, carolLocalBalance) + + // Confirm the channel and wait for it to be recognized by both + // parties. Two transactions should be mined, the unconfirmed + // spend and the funding tx. + mineBlocks(t, net, 6, 1) + chanPoint, err := net.WaitForChannelOpen(chanOpenUpdate) + require.NoError( + t.t, err, "error while waiting for channel open", + ) + + // With the channel open, we'll check the balances on each side + // of the channel as a sanity check to ensure things worked out + // as intended. + checkChannelBalance(carol, carolLocalBalance, 0, 0, 0) + checkChannelBalance(net.Alice, 0, carolLocalBalance, 0, 0) + + // Now that we're done with the test, the channel can be closed. + closeChannelAndAssert(t, net, carol, chanPoint, false) + + // Empty out the wallet so there aren't any lingering coins. + sendAllCoinsConfirm(net, carol, t, burnAddr) + } +} + +// sendAllCoinsConfirm sends all coins of the node's wallet to the given address +// and awaits one confirmation. +func sendAllCoinsConfirm(net *lntest.NetworkHarness, node *lntest.HarnessNode, + t *harnessTest, addr string) { + + ctxb := context.Background() + ctxt, cancel := context.WithTimeout(ctxb, defaultTimeout) + defer cancel() + + sweepReq := &lnrpc.SendCoinsRequest{ + Addr: addr, + SendAll: true, + } + _, err := node.SendCoins(ctxt, sweepReq) + require.NoError(t.t, err) + + // Make sure the unconfirmed tx is seen in the mempool. + _, err = waitForTxInMempool(net.Miner.Client, minerMempoolTimeout) + require.NoError(t.t, err, "failed to find tx in miner mempool") + + mineBlocks(t, net, 1, 1) +} + // testExternalFundingChanPoint tests that we're able to carry out a normal // channel funding workflow given a channel point that was constructed outside // the main daemon. diff --git a/lntest/itest/lnd_test_list_on_test.go b/lntest/itest/lnd_test_list_on_test.go index 9f526c565..82b71e9f1 100644 --- a/lntest/itest/lnd_test_list_on_test.go +++ b/lntest/itest/lnd_test_list_on_test.go @@ -24,6 +24,10 @@ var allTestCases = []*testCase{ name: "basic funding flow", test: testBasicChannelFunding, }, + { + name: "basic funding flow with all input types", + test: testChannelFundingInputTypes, + }, { name: "unconfirmed channel funding", test: testUnconfirmedChannelFunding, From 066fe07de5b364926f59f643e247b93de18258ac Mon Sep 17 00:00:00 2001 From: Oliver Gugger Date: Tue, 10 May 2022 18:49:56 +0200 Subject: [PATCH 7/7] docs: add release notes --- docs/release-notes/release-notes-0.15.0.md | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/docs/release-notes/release-notes-0.15.0.md b/docs/release-notes/release-notes-0.15.0.md index d3207ad74..7c26e4199 100644 --- a/docs/release-notes/release-notes-0.15.0.md +++ b/docs/release-notes/release-notes-0.15.0.md @@ -60,6 +60,10 @@ releases. Backward compatibility is not guaranteed! 155](https://github.com/lightningnetwork/lnd/pull/6468), allowing it to connect to Bitcoin nodes that advertise a Tor v3 onion service address. +[A new neutrino sub-server](https://github.com/lightningnetwork/lnd/pull/5652) +capable of status checks, adding, disconnecting and listing peers, fetching +compact filters and block/block headers. + ## Bug Fixes * [Pipelining an UpdateFulfillHTLC message now only happens when the related UpdateAddHTLC is locked-in.](https://github.com/lightningnetwork/lnd/pull/6246) @@ -87,12 +91,6 @@ to Bitcoin nodes that advertise a Tor v3 onion service address. first](https://github.com/lightningnetwork/lnd/pull/6214). * [Fixed crash in MuSig2Combine](https://github.com/lightningnetwork/lnd/pull/6502) - -## Neutrino - -* [New neutrino sub-server](https://github.com/lightningnetwork/lnd/pull/5652) - capable of status checks, adding, disconnecting and listing - peers, fetching compact filters and block/block headers. * [Added signature length validation](https://github.com/lightningnetwork/lnd/pull/6314) when calling @@ -141,6 +139,9 @@ from occurring that would result in an erroneous force close.](https://github.co * [Ignore addresses with unknown types in NodeAnnouncements]( https://github.com/lightningnetwork/lnd/pull/6435) +* [Taproot wallet inputs can also be used to fund + channels](https://github.com/lightningnetwork/lnd/pull/6521) + ## Routing * [Add a new `time_pref` parameter to the QueryRoutes and SendPayment APIs](https://github.com/lightningnetwork/lnd/pull/6024) that