Merge pull request #6521 from guggero/p2tr-open-channel
chanfunding: properly support p2tr inputs in funding TX
This commit is contained in:
7 files changed
+174
-36
No files matched your search
@@ -60,6 +60,10 @@ releases. Backward compatibility is not guaranteed!
|
|||||||
155](https://github.com/lightningnetwork/lnd/pull/6468), allowing it to connect
|
155](https://github.com/lightningnetwork/lnd/pull/6468), allowing it to connect
|
||||||
to Bitcoin nodes that advertise a Tor v3 onion service address.
|
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
|
## Bug Fixes
|
||||||
|
|
||||||
* [Pipelining an UpdateFulfillHTLC message now only happens when the related UpdateAddHTLC is locked-in.](https://github.com/lightningnetwork/lnd/pull/6246)
|
* [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).
|
first](https://github.com/lightningnetwork/lnd/pull/6214).
|
||||||
|
|
||||||
* [Fixed crash in MuSig2Combine](https://github.com/lightningnetwork/lnd/pull/6502)
|
* [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
|
* [Added signature length
|
||||||
validation](https://github.com/lightningnetwork/lnd/pull/6314) when calling
|
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](
|
* [Ignore addresses with unknown types in NodeAnnouncements](
|
||||||
https://github.com/lightningnetwork/lnd/pull/6435)
|
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
|
## Routing
|
||||||
|
|
||||||
* [Add a new `time_pref` parameter to the QueryRoutes and SendPayment APIs](https://github.com/lightningnetwork/lnd/pull/6024) that
|
* [Add a new `time_pref` parameter to the QueryRoutes and SendPayment APIs](https://github.com/lightningnetwork/lnd/pull/6024) that
|
||||||
|
|||||||
@@ -1417,7 +1417,7 @@ func (n *NetworkHarness) DumpLogs(node *HarnessNode) (string, error) {
|
|||||||
func (n *NetworkHarness) SendCoins(t *testing.T, amt btcutil.Amount,
|
func (n *NetworkHarness) SendCoins(t *testing.T, amt btcutil.Amount,
|
||||||
target *HarnessNode) {
|
target *HarnessNode) {
|
||||||
|
|
||||||
err := n.sendCoins(
|
err := n.SendCoinsOfType(
|
||||||
amt, target, lnrpc.AddressType_WITNESS_PUBKEY_HASH, true,
|
amt, target, lnrpc.AddressType_WITNESS_PUBKEY_HASH, true,
|
||||||
)
|
)
|
||||||
require.NoErrorf(t, err, "unable to send coins for %s", target.Cfg.Name)
|
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,
|
func (n *NetworkHarness) SendCoinsUnconfirmed(t *testing.T, amt btcutil.Amount,
|
||||||
target *HarnessNode) {
|
target *HarnessNode) {
|
||||||
|
|
||||||
err := n.sendCoins(
|
err := n.SendCoinsOfType(
|
||||||
amt, target, lnrpc.AddressType_WITNESS_PUBKEY_HASH, false,
|
amt, target, lnrpc.AddressType_WITNESS_PUBKEY_HASH, false,
|
||||||
)
|
)
|
||||||
require.NoErrorf(
|
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,
|
func (n *NetworkHarness) SendCoinsNP2WKH(t *testing.T, amt btcutil.Amount,
|
||||||
target *HarnessNode) {
|
target *HarnessNode) {
|
||||||
|
|
||||||
err := n.sendCoins(
|
err := n.SendCoinsOfType(
|
||||||
amt, target, lnrpc.AddressType_NESTED_PUBKEY_HASH, true,
|
amt, target, lnrpc.AddressType_NESTED_PUBKEY_HASH, true,
|
||||||
)
|
)
|
||||||
require.NoErrorf(
|
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,
|
func (n *NetworkHarness) SendCoinsP2TR(t *testing.T, amt btcutil.Amount,
|
||||||
target *HarnessNode) {
|
target *HarnessNode) {
|
||||||
|
|
||||||
err := n.sendCoins(amt, target, lnrpc.AddressType_TAPROOT_PUBKEY, true)
|
err := n.SendCoinsOfType(
|
||||||
|
amt, target, lnrpc.AddressType_TAPROOT_PUBKEY, true,
|
||||||
|
)
|
||||||
require.NoErrorf(
|
require.NoErrorf(
|
||||||
t, err, "unable to send P2TR coins for %s", target.Cfg.Name,
|
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
|
// SendCoinsOfType attempts to send amt satoshis from the internal mining node
|
||||||
// targeted lightning node. The confirmed boolean indicates whether the
|
// to the targeted lightning node. The confirmed boolean indicates whether the
|
||||||
// transaction that pays to the target should confirm.
|
// 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 {
|
addrType lnrpc.AddressType, confirmed bool) error {
|
||||||
|
|
||||||
ctx, cancel := context.WithTimeout(n.runCtx, DefaultTimeout)
|
ctx, cancel := context.WithTimeout(n.runCtx, DefaultTimeout)
|
||||||
|
|||||||
@@ -244,8 +244,6 @@ func basicChannelFundingTest(t *harnessTest, net *lntest.NetworkHarness,
|
|||||||
// testUnconfirmedChannelFunding tests that our unconfirmed change outputs can
|
// testUnconfirmedChannelFunding tests that our unconfirmed change outputs can
|
||||||
// be used to fund channels.
|
// be used to fund channels.
|
||||||
func testUnconfirmedChannelFunding(net *lntest.NetworkHarness, t *harnessTest) {
|
func testUnconfirmedChannelFunding(net *lntest.NetworkHarness, t *harnessTest) {
|
||||||
ctxb := context.Background()
|
|
||||||
|
|
||||||
const (
|
const (
|
||||||
chanAmt = funding.MaxBtcFundingAmount
|
chanAmt = funding.MaxBtcFundingAmount
|
||||||
pushAmt = btcutil.Amount(100000)
|
pushAmt = btcutil.Amount(100000)
|
||||||
@@ -256,27 +254,10 @@ func testUnconfirmedChannelFunding(net *lntest.NetworkHarness, t *harnessTest) {
|
|||||||
defer shutdownAndAssert(net, t, carol)
|
defer shutdownAndAssert(net, t, carol)
|
||||||
|
|
||||||
// We'll send her some confirmed funds.
|
// We'll send her some confirmed funds.
|
||||||
net.SendCoins(t.t, 2*chanAmt, carol)
|
net.SendCoinsUnconfirmed(t.t, chanAmt*2, 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")
|
|
||||||
|
|
||||||
// Make sure the unconfirmed tx is seen in the mempool.
|
// 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")
|
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
|
// 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)
|
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
|
// testExternalFundingChanPoint tests that we're able to carry out a normal
|
||||||
// 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.
|
||||||
|
|||||||
@@ -24,6 +24,10 @@ var allTestCases = []*testCase{
|
|||||||
name: "basic funding flow",
|
name: "basic funding flow",
|
||||||
test: testBasicChannelFunding,
|
test: testBasicChannelFunding,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
name: "basic funding flow with all input types",
|
||||||
|
test: testChannelFundingInputTypes,
|
||||||
|
},
|
||||||
{
|
{
|
||||||
name: "unconfirmed channel funding",
|
name: "unconfirmed channel funding",
|
||||||
test: testUnconfirmedChannelFunding,
|
test: testUnconfirmedChannelFunding,
|
||||||
|
|||||||
@@ -41,6 +41,8 @@ func (b *BtcWallet) FetchInputInfo(prevOut *wire.OutPoint) (*lnwallet.Utxo, erro
|
|||||||
addressType = lnwallet.WitnessPubKey
|
addressType = lnwallet.WitnessPubKey
|
||||||
case txscript.IsPayToScriptHash(txOut.PkScript):
|
case txscript.IsPayToScriptHash(txOut.PkScript):
|
||||||
addressType = lnwallet.NestedWitnessPubKey
|
addressType = lnwallet.NestedWitnessPubKey
|
||||||
|
case txscript.IsPayToTaproot(txOut.PkScript):
|
||||||
|
addressType = lnwallet.TaprootPubkey
|
||||||
}
|
}
|
||||||
|
|
||||||
return &lnwallet.Utxo{
|
return &lnwallet.Utxo{
|
||||||
|
|||||||
@@ -79,6 +79,11 @@ func calculateFees(utxos []Coin, feeRate chainfee.SatPerKWeight) (btcutil.Amount
|
|||||||
case txscript.IsPayToScriptHash(utxo.PkScript):
|
case txscript.IsPayToScriptHash(utxo.PkScript):
|
||||||
weightEstimate.AddNestedP2WKHInput()
|
weightEstimate.AddNestedP2WKHInput()
|
||||||
|
|
||||||
|
case txscript.IsPayToTaproot(utxo.PkScript):
|
||||||
|
weightEstimate.AddTaprootKeySpendInput(
|
||||||
|
txscript.SigHashDefault,
|
||||||
|
)
|
||||||
|
|
||||||
default:
|
default:
|
||||||
return 0, 0, &errUnsupportedInput{utxo.PkScript}
|
return 0, 0, &errUnsupportedInput{utxo.PkScript}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -111,7 +111,6 @@ func (f *FullIntent) CompileFundingTx(extraInputs []*wire.TxIn,
|
|||||||
f.coinSource, extraInputs,
|
f.coinSource, extraInputs,
|
||||||
)
|
)
|
||||||
signDesc := input.SignDescriptor{
|
signDesc := input.SignDescriptor{
|
||||||
HashType: txscript.SigHashAll,
|
|
||||||
SigHashes: txscript.NewTxSigHashes(
|
SigHashes: txscript.NewTxSigHashes(
|
||||||
fundingTx, prevOutFetcher,
|
fundingTx, prevOutFetcher,
|
||||||
),
|
),
|
||||||
@@ -136,6 +135,13 @@ func (f *FullIntent) CompileFundingTx(extraInputs []*wire.TxIn,
|
|||||||
}
|
}
|
||||||
signDesc.InputIndex = i
|
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
|
// Finally, we'll sign the input as is, and populate the input
|
||||||
// with the witness and sigScript (if needed).
|
// with the witness and sigScript (if needed).
|
||||||
inputScript, err := f.signer.ComputeInputScript(
|
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
|
// 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
|
// 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,
|
func NewSegWitV0DualFundingPrevOutputFetcher(localSource CoinSource,
|
||||||
remoteInputs []*wire.TxIn) txscript.PrevOutputFetcher {
|
remoteInputs []*wire.TxIn) txscript.PrevOutputFetcher {
|
||||||
|
|
||||||
|
|||||||
Reference in new issue
Block a user