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 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) diff --git a/lntest/itest/lnd_funding_test.go b/lntest/itest/lnd_funding_test.go index 9fafa8139..d6717b026 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 @@ -362,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, 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{ 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} } 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 {