mirror of
https://github.com/lightningnetwork/lnd.git
synced 2025-10-10 19:33:23 +02:00
itest: refactor taproot test
This commit is contained in:
@@ -23,29 +23,43 @@ import (
|
|||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
testTaprootKeyFamily = 77
|
||||||
|
testAmount = 800_000
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
dummyInternalKeyBytes, _ = hex.DecodeString(
|
||||||
|
"03464805f5468e294d88cf15a3f06aef6c89d63ef1bd7b42db2e0c74c1ac" +
|
||||||
|
"eb90fe",
|
||||||
|
)
|
||||||
|
dummyInternalKey, _ = btcec.ParsePubKey(dummyInternalKeyBytes)
|
||||||
|
)
|
||||||
|
|
||||||
// testTaproot ensures that the daemon can send to and spend from taproot (p2tr)
|
// testTaproot ensures that the daemon can send to and spend from taproot (p2tr)
|
||||||
// outputs.
|
// outputs.
|
||||||
func testTaproot(net *lntest.NetworkHarness, t *harnessTest) {
|
func testTaproot(net *lntest.NetworkHarness, t *harnessTest) {
|
||||||
ctxb := context.Background()
|
ctxb := context.Background()
|
||||||
ctxt, cancel := context.WithTimeout(ctxb, defaultTimeout)
|
ctxt, cancel := context.WithTimeout(ctxb, 2*defaultTimeout)
|
||||||
defer cancel()
|
defer cancel()
|
||||||
|
|
||||||
testTaprootKeySpend(ctxt, t, net)
|
testTaprootComputeInputScriptKeySpendBip86(ctxt, t, net.Alice, net)
|
||||||
testTaprootScriptSpend(ctxt, t, net)
|
testTaprootSignOutputRawScriptSpend(ctxt, t, net.Alice, net)
|
||||||
testTaprootKeySpendRPC(ctxt, t, net)
|
testTaprootSignOutputRawKeySpendRootHash(ctxt, t, net.Alice, net)
|
||||||
}
|
}
|
||||||
|
|
||||||
// testTaprootKeySpend tests sending to and spending from p2tr key spend only
|
// testTaprootComputeInputScriptKeySpendBip86 tests sending to and spending from
|
||||||
// (BIP-0086) addresses.
|
// p2tr key spend only (BIP-0086) addresses through the SendCoins RPC which
|
||||||
func testTaprootKeySpend(ctxt context.Context, t *harnessTest,
|
// internally uses the ComputeInputScript method for signing.
|
||||||
net *lntest.NetworkHarness) {
|
func testTaprootComputeInputScriptKeySpendBip86(ctxt context.Context,
|
||||||
|
t *harnessTest, alice *lntest.HarnessNode, net *lntest.NetworkHarness) {
|
||||||
|
|
||||||
// We'll start the test by sending Alice some coins, which she'll use to
|
// We'll start the test by sending Alice some coins, which she'll use to
|
||||||
// send to herself on a p2tr output.
|
// send to herself on a p2tr output.
|
||||||
net.SendCoins(t.t, btcutil.SatoshiPerBitcoin, net.Alice)
|
net.SendCoins(t.t, btcutil.SatoshiPerBitcoin, alice)
|
||||||
|
|
||||||
// Let's create a p2tr address now.
|
// Let's create a p2tr address now.
|
||||||
p2trResp, err := net.Alice.NewAddress(ctxt, &lnrpc.NewAddressRequest{
|
p2trResp, err := alice.NewAddress(ctxt, &lnrpc.NewAddressRequest{
|
||||||
Type: lnrpc.AddressType_TAPROOT_PUBKEY,
|
Type: lnrpc.AddressType_TAPROOT_PUBKEY,
|
||||||
})
|
})
|
||||||
require.NoError(t.t, err)
|
require.NoError(t.t, err)
|
||||||
@@ -57,7 +71,7 @@ func testTaprootKeySpend(ctxt context.Context, t *harnessTest,
|
|||||||
|
|
||||||
// Send the coins from Alice's wallet to her own, but to the new p2tr
|
// Send the coins from Alice's wallet to her own, but to the new p2tr
|
||||||
// address.
|
// address.
|
||||||
_, err = net.Alice.SendCoins(ctxt, &lnrpc.SendCoinsRequest{
|
_, err = alice.SendCoins(ctxt, &lnrpc.SendCoinsRequest{
|
||||||
Addr: p2trResp.Address,
|
Addr: p2trResp.Address,
|
||||||
Amount: 0.5 * btcutil.SatoshiPerBitcoin,
|
Amount: 0.5 * btcutil.SatoshiPerBitcoin,
|
||||||
})
|
})
|
||||||
@@ -72,80 +86,38 @@ func testTaprootKeySpend(ctxt context.Context, t *harnessTest,
|
|||||||
TxidBytes: txid[:],
|
TxidBytes: txid[:],
|
||||||
OutputIndex: uint32(p2trOutputIndex),
|
OutputIndex: uint32(p2trOutputIndex),
|
||||||
}
|
}
|
||||||
assertWalletUnspent(t, net.Alice, op)
|
assertWalletUnspent(t, alice, op)
|
||||||
|
|
||||||
// Mine a block to clean up the mempool.
|
// Mine a block to clean up the mempool.
|
||||||
mineBlocks(t, net, 1, 1)
|
mineBlocks(t, net, 1, 1)
|
||||||
|
|
||||||
// Let's sweep the whole wallet to a new p2tr address, making sure we
|
// Let's sweep the whole wallet to a new p2tr address, making sure we
|
||||||
// can sign transactions with v0 and v1 inputs.
|
// can sign transactions with v0 and v1 inputs.
|
||||||
p2trResp, err = net.Alice.NewAddress(ctxt, &lnrpc.NewAddressRequest{
|
p2trResp, err = alice.NewAddress(ctxt, &lnrpc.NewAddressRequest{
|
||||||
Type: lnrpc.AddressType_TAPROOT_PUBKEY,
|
Type: lnrpc.AddressType_TAPROOT_PUBKEY,
|
||||||
})
|
})
|
||||||
require.NoError(t.t, err)
|
require.NoError(t.t, err)
|
||||||
|
|
||||||
_, err = net.Alice.SendCoins(ctxt, &lnrpc.SendCoinsRequest{
|
_, err = alice.SendCoins(ctxt, &lnrpc.SendCoinsRequest{
|
||||||
Addr: p2trResp.Address,
|
Addr: p2trResp.Address,
|
||||||
SendAll: true,
|
SendAll: true,
|
||||||
})
|
})
|
||||||
require.NoError(t.t, err)
|
require.NoError(t.t, err)
|
||||||
|
|
||||||
// Wait until the wallet cleaning sweep tx is found.
|
// Make sure the coins sent to the address are confirmed correctly,
|
||||||
txid, err = waitForTxInMempool(net.Miner.Client, minerMempoolTimeout)
|
// including the confirmation notification.
|
||||||
require.NoError(t.t, err)
|
confirmAddress(ctxt, t, net, alice, p2trResp.Address)
|
||||||
|
|
||||||
// Wait until bob has seen the tx and considers it as owned.
|
|
||||||
p2trOutputIndex = getOutputIndex(t, net.Miner, txid, p2trResp.Address)
|
|
||||||
op = &lnrpc.OutPoint{
|
|
||||||
TxidBytes: txid[:],
|
|
||||||
OutputIndex: uint32(p2trOutputIndex),
|
|
||||||
}
|
|
||||||
assertWalletUnspent(t, net.Alice, op)
|
|
||||||
|
|
||||||
// Before we confirm the transaction, let's register a confirmation
|
|
||||||
// listener for it, which we expect to fire after mining a block.
|
|
||||||
p2trAddr, err := btcutil.DecodeAddress(
|
|
||||||
p2trResp.Address, harnessNetParams,
|
|
||||||
)
|
|
||||||
require.NoError(t.t, err)
|
|
||||||
p2trPkScript, err := txscript.PayToAddrScript(p2trAddr)
|
|
||||||
require.NoError(t.t, err)
|
|
||||||
|
|
||||||
_, currentHeight, err := net.Miner.Client.GetBestBlock()
|
|
||||||
require.NoError(t.t, err)
|
|
||||||
confClient, err := net.Alice.ChainClient.RegisterConfirmationsNtfn(
|
|
||||||
ctxt, &chainrpc.ConfRequest{
|
|
||||||
Script: p2trPkScript,
|
|
||||||
Txid: txid[:],
|
|
||||||
HeightHint: uint32(currentHeight),
|
|
||||||
NumConfs: 1,
|
|
||||||
},
|
|
||||||
)
|
|
||||||
require.NoError(t.t, err)
|
|
||||||
|
|
||||||
// Mine another block to clean up the mempool.
|
|
||||||
mineBlocks(t, net, 1, 1)
|
|
||||||
|
|
||||||
// We now expect our confirmation to go through.
|
|
||||||
confMsg, err := confClient.Recv()
|
|
||||||
require.NoError(t.t, err)
|
|
||||||
conf := confMsg.GetConf()
|
|
||||||
require.NotNil(t.t, conf)
|
|
||||||
require.Equal(t.t, conf.BlockHeight, uint32(currentHeight+1))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// testTaprootScriptSpend tests sending to and spending from p2tr script
|
// testTaprootSignOutputRawScriptSpend tests sending to and spending from p2tr
|
||||||
// addresses.
|
// script addresses using the script path with the SignOutputRaw RPC.
|
||||||
func testTaprootScriptSpend(ctxt context.Context, t *harnessTest,
|
func testTaprootSignOutputRawScriptSpend(ctxt context.Context, t *harnessTest,
|
||||||
net *lntest.NetworkHarness) {
|
alice *lntest.HarnessNode, net *lntest.NetworkHarness) {
|
||||||
|
|
||||||
// For the next step, we need a public key. Let's use a special family
|
// For the next step, we need a public key. Let's use a special family
|
||||||
// for this.
|
// for this.
|
||||||
const taprootKeyFamily = 77
|
keyDesc, err := alice.WalletKitClient.DeriveNextKey(
|
||||||
keyDesc, err := net.Alice.WalletKitClient.DeriveNextKey(
|
ctxt, &walletrpc.KeyReq{KeyFamily: testTaprootKeyFamily},
|
||||||
ctxt, &walletrpc.KeyReq{
|
|
||||||
KeyFamily: taprootKeyFamily,
|
|
||||||
},
|
|
||||||
)
|
)
|
||||||
require.NoError(t.t, err)
|
require.NoError(t.t, err)
|
||||||
|
|
||||||
@@ -159,50 +131,20 @@ func testTaprootScriptSpend(ctxt context.Context, t *harnessTest,
|
|||||||
// Let's add a second script output as well to test the partial reveal.
|
// Let's add a second script output as well to test the partial reveal.
|
||||||
leaf2 := testScriptSchnorrSig(t.t, leafSigningKey)
|
leaf2 := testScriptSchnorrSig(t.t, leafSigningKey)
|
||||||
|
|
||||||
dummyInternalKeyBytes, _ := hex.DecodeString(
|
|
||||||
"03464805f5468e294d88cf15a3f06aef6c89d63ef1bd7b42db2e0c74c1ac" +
|
|
||||||
"eb90fe",
|
|
||||||
)
|
|
||||||
dummyInternalKey, _ := btcec.ParsePubKey(dummyInternalKeyBytes)
|
|
||||||
|
|
||||||
tapscript := input.TapscriptPartialReveal(
|
tapscript := input.TapscriptPartialReveal(
|
||||||
dummyInternalKey, leaf2, leaf1.TapHash(),
|
dummyInternalKey, leaf2, leaf1.TapHash(),
|
||||||
)
|
)
|
||||||
taprootKey, err := tapscript.TaprootKey()
|
taprootKey, err := tapscript.TaprootKey()
|
||||||
require.NoError(t.t, err)
|
require.NoError(t.t, err)
|
||||||
|
|
||||||
tapScriptAddr, err := btcutil.NewAddressTaproot(
|
|
||||||
schnorr.SerializePubKey(taprootKey), harnessNetParams,
|
|
||||||
)
|
|
||||||
require.NoError(t.t, err)
|
|
||||||
p2trPkScript, err := txscript.PayToAddrScript(tapScriptAddr)
|
|
||||||
require.NoError(t.t, err)
|
|
||||||
|
|
||||||
// Send some coins to the generated tapscript address.
|
// Send some coins to the generated tapscript address.
|
||||||
_, err = net.Alice.SendCoins(ctxt, &lnrpc.SendCoinsRequest{
|
p2trOutpoint, p2trPkScript := sendToTaprootOutput(
|
||||||
Addr: tapScriptAddr.String(),
|
ctxt, t, net, alice, taprootKey, testAmount,
|
||||||
Amount: 800_000,
|
|
||||||
})
|
|
||||||
require.NoError(t.t, err)
|
|
||||||
|
|
||||||
// Wait until the TX is found in the mempool.
|
|
||||||
txid, err := waitForTxInMempool(net.Miner.Client, minerMempoolTimeout)
|
|
||||||
require.NoError(t.t, err)
|
|
||||||
|
|
||||||
p2trOutputIndex := getOutputIndex(
|
|
||||||
t, net.Miner, txid, tapScriptAddr.String(),
|
|
||||||
)
|
)
|
||||||
p2trOutpoint := wire.OutPoint{
|
|
||||||
Hash: *txid,
|
|
||||||
Index: uint32(p2trOutputIndex),
|
|
||||||
}
|
|
||||||
|
|
||||||
// Clear the mempool.
|
|
||||||
mineBlocks(t, net, 1, 1)
|
|
||||||
|
|
||||||
// Spend the output again, this time back to a p2wkh address.
|
// Spend the output again, this time back to a p2wkh address.
|
||||||
p2wkhAddr, p2wkhPkScript := newAddrWithScript(
|
p2wkhAddr, p2wkhPkScript := newAddrWithScript(
|
||||||
ctxt, t.t, net.Alice, lnrpc.AddressType_WITNESS_PUBKEY_HASH,
|
ctxt, t.t, alice, lnrpc.AddressType_WITNESS_PUBKEY_HASH,
|
||||||
)
|
)
|
||||||
|
|
||||||
// Create fee estimation for a p2tr input and p2wkh output.
|
// Create fee estimation for a p2tr input and p2wkh output.
|
||||||
@@ -219,7 +161,7 @@ func testTaprootScriptSpend(ctxt context.Context, t *harnessTest,
|
|||||||
tx.TxIn = []*wire.TxIn{{
|
tx.TxIn = []*wire.TxIn{{
|
||||||
PreviousOutPoint: p2trOutpoint,
|
PreviousOutPoint: p2trOutpoint,
|
||||||
}}
|
}}
|
||||||
value := int64(800_000 - requiredFee)
|
value := int64(testAmount - requiredFee)
|
||||||
tx.TxOut = []*wire.TxOut{{
|
tx.TxOut = []*wire.TxOut{{
|
||||||
PkScript: p2wkhPkScript,
|
PkScript: p2wkhPkScript,
|
||||||
Value: value,
|
Value: value,
|
||||||
@@ -230,13 +172,13 @@ func testTaprootScriptSpend(ctxt context.Context, t *harnessTest,
|
|||||||
|
|
||||||
utxoInfo := []*signrpc.TxOut{{
|
utxoInfo := []*signrpc.TxOut{{
|
||||||
PkScript: p2trPkScript,
|
PkScript: p2trPkScript,
|
||||||
Value: 800_000,
|
Value: testAmount,
|
||||||
}}
|
}}
|
||||||
|
|
||||||
// Before we actually sign, we want to make sure that we get an error
|
// Before we actually sign, we want to make sure that we get an error
|
||||||
// when we try to sign for a Taproot output without specifying all UTXO
|
// when we try to sign for a Taproot output without specifying all UTXO
|
||||||
// information.
|
// information.
|
||||||
_, err = net.Alice.SignerClient.SignOutputRaw(
|
_, err = alice.SignerClient.SignOutputRaw(
|
||||||
ctxt, &signrpc.SignReq{
|
ctxt, &signrpc.SignReq{
|
||||||
RawTxBytes: buf.Bytes(),
|
RawTxBytes: buf.Bytes(),
|
||||||
SignDescs: []*signrpc.SignDescriptor{{
|
SignDescs: []*signrpc.SignDescriptor{{
|
||||||
@@ -255,7 +197,7 @@ func testTaprootScriptSpend(ctxt context.Context, t *harnessTest,
|
|||||||
)
|
)
|
||||||
|
|
||||||
// Do the actual signing now.
|
// Do the actual signing now.
|
||||||
signResp, err := net.Alice.SignerClient.SignOutputRaw(
|
signResp, err := alice.SignerClient.SignOutputRaw(
|
||||||
ctxt, &signrpc.SignReq{
|
ctxt, &signrpc.SignReq{
|
||||||
RawTxBytes: buf.Bytes(),
|
RawTxBytes: buf.Bytes(),
|
||||||
SignDescs: []*signrpc.SignDescriptor{{
|
SignDescs: []*signrpc.SignDescriptor{{
|
||||||
@@ -280,93 +222,31 @@ func testTaprootScriptSpend(ctxt context.Context, t *harnessTest,
|
|||||||
controlBlockBytes,
|
controlBlockBytes,
|
||||||
}
|
}
|
||||||
|
|
||||||
buf.Reset()
|
// Serialize, weigh and publish the TX now, then make sure the
|
||||||
require.NoError(t.t, tx.Serialize(&buf))
|
// coins are sent and confirmed to the final sweep destination address.
|
||||||
|
publishTxAndConfirmSweep(
|
||||||
// Since Schnorr signatures are fixed size, we must be able to estimate
|
ctxt, t, net, alice, tx, estimatedWeight,
|
||||||
// the size of this transaction exactly.
|
&chainrpc.SpendRequest{
|
||||||
txWeight := blockchain.GetTransactionWeight(btcutil.NewTx(tx))
|
|
||||||
require.Equal(t.t, txWeight, estimatedWeight)
|
|
||||||
|
|
||||||
// Before we publish the tx that spends the p2tr transaction, we want to
|
|
||||||
// register a spend listener that we expect to fire after mining the
|
|
||||||
// block.
|
|
||||||
_, currentHeight, err := net.Miner.Client.GetBestBlock()
|
|
||||||
require.NoError(t.t, err)
|
|
||||||
|
|
||||||
// For a Taproot output we cannot leave the outpoint empty. Let's make
|
|
||||||
// sure the API returns the correct error here.
|
|
||||||
spendClient, err := net.Alice.ChainClient.RegisterSpendNtfn(
|
|
||||||
ctxt, &chainrpc.SpendRequest{
|
|
||||||
Script: p2trPkScript,
|
|
||||||
HeightHint: uint32(currentHeight),
|
|
||||||
},
|
|
||||||
)
|
|
||||||
require.NoError(t.t, err)
|
|
||||||
|
|
||||||
// The error is only thrown when trying to read a message.
|
|
||||||
_, err = spendClient.Recv()
|
|
||||||
require.Contains(
|
|
||||||
t.t, err.Error(),
|
|
||||||
"cannot register witness v1 spend request without outpoint",
|
|
||||||
)
|
|
||||||
|
|
||||||
// Now try again, this time with the outpoint set.
|
|
||||||
spendClient, err = net.Alice.ChainClient.RegisterSpendNtfn(
|
|
||||||
ctxt, &chainrpc.SpendRequest{
|
|
||||||
Outpoint: &chainrpc.Outpoint{
|
Outpoint: &chainrpc.Outpoint{
|
||||||
Hash: p2trOutpoint.Hash[:],
|
Hash: p2trOutpoint.Hash[:],
|
||||||
Index: p2trOutpoint.Index,
|
Index: p2trOutpoint.Index,
|
||||||
},
|
},
|
||||||
Script: p2trPkScript,
|
Script: p2trPkScript,
|
||||||
HeightHint: uint32(currentHeight),
|
|
||||||
},
|
},
|
||||||
|
p2wkhAddr.String(),
|
||||||
)
|
)
|
||||||
require.NoError(t.t, err)
|
|
||||||
|
|
||||||
_, err = net.Alice.WalletKitClient.PublishTransaction(
|
|
||||||
ctxt, &walletrpc.Transaction{
|
|
||||||
TxHex: buf.Bytes(),
|
|
||||||
},
|
|
||||||
)
|
|
||||||
require.NoError(t.t, err)
|
|
||||||
|
|
||||||
// Wait until the spending tx is found.
|
|
||||||
txid, err = waitForTxInMempool(net.Miner.Client, minerMempoolTimeout)
|
|
||||||
require.NoError(t.t, err)
|
|
||||||
p2wpkhOutputIndex := getOutputIndex(
|
|
||||||
t, net.Miner, txid, p2wkhAddr.String(),
|
|
||||||
)
|
|
||||||
op := &lnrpc.OutPoint{
|
|
||||||
TxidBytes: txid[:],
|
|
||||||
OutputIndex: uint32(p2wpkhOutputIndex),
|
|
||||||
}
|
|
||||||
assertWalletUnspent(t, net.Alice, op)
|
|
||||||
|
|
||||||
// Mine another block to clean up the mempool and to make sure the spend
|
|
||||||
// tx is actually included in a block.
|
|
||||||
mineBlocks(t, net, 1, 1)
|
|
||||||
|
|
||||||
// We now expect our spend event to go through.
|
|
||||||
spendMsg, err := spendClient.Recv()
|
|
||||||
require.NoError(t.t, err)
|
|
||||||
spend := spendMsg.GetSpend()
|
|
||||||
require.NotNil(t.t, spend)
|
|
||||||
require.Equal(t.t, spend.SpendingHeight, uint32(currentHeight+1))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// testTaprootKeySpendRPC tests that a tapscript address can also be spent using
|
// testTaprootSignOutputRawKeySpendRootHash tests that a tapscript address can
|
||||||
// the key spend path through the RPC.
|
// also be spent using the key spend path through the SignOutputRaw RPC using a
|
||||||
func testTaprootKeySpendRPC(ctxt context.Context, t *harnessTest,
|
// tapscript root hash.
|
||||||
net *lntest.NetworkHarness) {
|
func testTaprootSignOutputRawKeySpendRootHash(ctxt context.Context,
|
||||||
|
t *harnessTest, alice *lntest.HarnessNode, net *lntest.NetworkHarness) {
|
||||||
|
|
||||||
// For the next step, we need a public key. Let's use a special family
|
// For the next step, we need a public key. Let's use a special family
|
||||||
// for this.
|
// for this.
|
||||||
const taprootKeyFamily = 77
|
keyDesc, err := alice.WalletKitClient.DeriveNextKey(
|
||||||
keyDesc, err := net.Alice.WalletKitClient.DeriveNextKey(
|
ctxt, &walletrpc.KeyReq{KeyFamily: testTaprootKeyFamily},
|
||||||
ctxt, &walletrpc.KeyReq{
|
|
||||||
KeyFamily: taprootKeyFamily,
|
|
||||||
},
|
|
||||||
)
|
)
|
||||||
require.NoError(t.t, err)
|
require.NoError(t.t, err)
|
||||||
|
|
||||||
@@ -385,34 +265,14 @@ func testTaprootKeySpendRPC(ctxt context.Context, t *harnessTest,
|
|||||||
rootHash := leaf1.TapHash()
|
rootHash := leaf1.TapHash()
|
||||||
taprootKey := txscript.ComputeTaprootOutputKey(internalKey, rootHash[:])
|
taprootKey := txscript.ComputeTaprootOutputKey(internalKey, rootHash[:])
|
||||||
|
|
||||||
tapScriptAddr, err := btcutil.NewAddressTaproot(
|
|
||||||
schnorr.SerializePubKey(taprootKey), harnessNetParams,
|
|
||||||
)
|
|
||||||
require.NoError(t.t, err)
|
|
||||||
p2trPkScript, err := txscript.PayToAddrScript(tapScriptAddr)
|
|
||||||
require.NoError(t.t, err)
|
|
||||||
|
|
||||||
// Send some coins to the generated tapscript address.
|
// Send some coins to the generated tapscript address.
|
||||||
_, err = net.Alice.SendCoins(ctxt, &lnrpc.SendCoinsRequest{
|
p2trOutpoint, p2trPkScript := sendToTaprootOutput(
|
||||||
Addr: tapScriptAddr.String(),
|
ctxt, t, net, alice, taprootKey, testAmount,
|
||||||
Amount: 800_000,
|
|
||||||
})
|
|
||||||
require.NoError(t.t, err)
|
|
||||||
|
|
||||||
// Wait until the TX is found in the mempool.
|
|
||||||
txid, err := waitForTxInMempool(net.Miner.Client, minerMempoolTimeout)
|
|
||||||
require.NoError(t.t, err)
|
|
||||||
|
|
||||||
p2trOutputIndex := getOutputIndex(
|
|
||||||
t, net.Miner, txid, tapScriptAddr.String(),
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// Clear the mempool.
|
|
||||||
mineBlocks(t, net, 1, 1)
|
|
||||||
|
|
||||||
// Spend the output again, this time back to a p2wkh address.
|
// Spend the output again, this time back to a p2wkh address.
|
||||||
p2wkhAddr, p2wkhPkScript := newAddrWithScript(
|
p2wkhAddr, p2wkhPkScript := newAddrWithScript(
|
||||||
ctxt, t.t, net.Alice, lnrpc.AddressType_WITNESS_PUBKEY_HASH,
|
ctxt, t.t, alice, lnrpc.AddressType_WITNESS_PUBKEY_HASH,
|
||||||
)
|
)
|
||||||
|
|
||||||
// Create fee estimation for a p2tr input and p2wkh output.
|
// Create fee estimation for a p2tr input and p2wkh output.
|
||||||
@@ -425,12 +285,9 @@ func testTaprootKeySpendRPC(ctxt context.Context, t *harnessTest,
|
|||||||
|
|
||||||
tx := wire.NewMsgTx(2)
|
tx := wire.NewMsgTx(2)
|
||||||
tx.TxIn = []*wire.TxIn{{
|
tx.TxIn = []*wire.TxIn{{
|
||||||
PreviousOutPoint: wire.OutPoint{
|
PreviousOutPoint: p2trOutpoint,
|
||||||
Hash: *txid,
|
|
||||||
Index: uint32(p2trOutputIndex),
|
|
||||||
},
|
|
||||||
}}
|
}}
|
||||||
value := int64(800_000 - requiredFee)
|
value := int64(testAmount - requiredFee)
|
||||||
tx.TxOut = []*wire.TxOut{{
|
tx.TxOut = []*wire.TxOut{{
|
||||||
PkScript: p2wkhPkScript,
|
PkScript: p2wkhPkScript,
|
||||||
Value: value,
|
Value: value,
|
||||||
@@ -441,9 +298,9 @@ func testTaprootKeySpendRPC(ctxt context.Context, t *harnessTest,
|
|||||||
|
|
||||||
utxoInfo := []*signrpc.TxOut{{
|
utxoInfo := []*signrpc.TxOut{{
|
||||||
PkScript: p2trPkScript,
|
PkScript: p2trPkScript,
|
||||||
Value: 800_000,
|
Value: testAmount,
|
||||||
}}
|
}}
|
||||||
signResp, err := net.Alice.SignerClient.SignOutputRaw(
|
signResp, err := alice.SignerClient.SignOutputRaw(
|
||||||
ctxt, &signrpc.SignReq{
|
ctxt, &signrpc.SignReq{
|
||||||
RawTxBytes: buf.Bytes(),
|
RawTxBytes: buf.Bytes(),
|
||||||
SignDescs: []*signrpc.SignDescriptor{{
|
SignDescs: []*signrpc.SignDescriptor{{
|
||||||
@@ -464,36 +321,19 @@ func testTaprootKeySpendRPC(ctxt context.Context, t *harnessTest,
|
|||||||
signResp.RawSigs[0],
|
signResp.RawSigs[0],
|
||||||
}
|
}
|
||||||
|
|
||||||
buf.Reset()
|
// Serialize, weigh and publish the TX now, then make sure the
|
||||||
require.NoError(t.t, tx.Serialize(&buf))
|
// coins are sent and confirmed to the final sweep destination address.
|
||||||
|
publishTxAndConfirmSweep(
|
||||||
// Since Schnorr signatures are fixed size, we must be able to estimate
|
ctxt, t, net, alice, tx, estimatedWeight,
|
||||||
// the size of this transaction exactly.
|
&chainrpc.SpendRequest{
|
||||||
txWeight := blockchain.GetTransactionWeight(btcutil.NewTx(tx))
|
Outpoint: &chainrpc.Outpoint{
|
||||||
require.Equal(t.t, txWeight, estimatedWeight)
|
Hash: p2trOutpoint.Hash[:],
|
||||||
|
Index: p2trOutpoint.Index,
|
||||||
_, err = net.Alice.WalletKitClient.PublishTransaction(
|
},
|
||||||
ctxt, &walletrpc.Transaction{
|
Script: p2trPkScript,
|
||||||
TxHex: buf.Bytes(),
|
|
||||||
},
|
},
|
||||||
|
p2wkhAddr.String(),
|
||||||
)
|
)
|
||||||
require.NoError(t.t, err)
|
|
||||||
|
|
||||||
// Wait until the spending tx is found.
|
|
||||||
txid, err = waitForTxInMempool(net.Miner.Client, minerMempoolTimeout)
|
|
||||||
require.NoError(t.t, err)
|
|
||||||
p2wpkhOutputIndex := getOutputIndex(
|
|
||||||
t, net.Miner, txid, p2wkhAddr.String(),
|
|
||||||
)
|
|
||||||
op := &lnrpc.OutPoint{
|
|
||||||
TxidBytes: txid[:],
|
|
||||||
OutputIndex: uint32(p2wpkhOutputIndex),
|
|
||||||
}
|
|
||||||
assertWalletUnspent(t, net.Alice, op)
|
|
||||||
|
|
||||||
// Mine another block to clean up the mempool and to make sure the spend
|
|
||||||
// tx is actually included in a block.
|
|
||||||
mineBlocks(t, net, 1, 1)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// testScriptHashLock returns a simple bitcoin script that locks the funds to
|
// testScriptHashLock returns a simple bitcoin script that locks the funds to
|
||||||
@@ -542,3 +382,159 @@ func newAddrWithScript(ctx context.Context, t *testing.T,
|
|||||||
|
|
||||||
return p2wkhAddr, p2wkhPkScript
|
return p2wkhAddr, p2wkhPkScript
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// sendToTaprootOutput sends coins to a p2tr output of the given taproot key and
|
||||||
|
// mines a block to confirm the coins.
|
||||||
|
func sendToTaprootOutput(ctx context.Context, t *harnessTest,
|
||||||
|
net *lntest.NetworkHarness, node *lntest.HarnessNode,
|
||||||
|
taprootKey *btcec.PublicKey, amt int64) (wire.OutPoint, []byte) {
|
||||||
|
|
||||||
|
tapScriptAddr, err := btcutil.NewAddressTaproot(
|
||||||
|
schnorr.SerializePubKey(taprootKey), harnessNetParams,
|
||||||
|
)
|
||||||
|
require.NoError(t.t, err)
|
||||||
|
p2trPkScript, err := txscript.PayToAddrScript(tapScriptAddr)
|
||||||
|
require.NoError(t.t, err)
|
||||||
|
|
||||||
|
// Send some coins to the generated tapscript address.
|
||||||
|
_, err = node.SendCoins(ctx, &lnrpc.SendCoinsRequest{
|
||||||
|
Addr: tapScriptAddr.String(),
|
||||||
|
Amount: amt,
|
||||||
|
})
|
||||||
|
require.NoError(t.t, err)
|
||||||
|
|
||||||
|
// Wait until the TX is found in the mempool.
|
||||||
|
txid, err := waitForTxInMempool(net.Miner.Client, minerMempoolTimeout)
|
||||||
|
require.NoError(t.t, err)
|
||||||
|
|
||||||
|
p2trOutputIndex := getOutputIndex(
|
||||||
|
t, net.Miner, txid, tapScriptAddr.String(),
|
||||||
|
)
|
||||||
|
p2trOutpoint := wire.OutPoint{
|
||||||
|
Hash: *txid,
|
||||||
|
Index: uint32(p2trOutputIndex),
|
||||||
|
}
|
||||||
|
|
||||||
|
// Clear the mempool.
|
||||||
|
mineBlocks(t, net, 1, 1)
|
||||||
|
|
||||||
|
return p2trOutpoint, p2trPkScript
|
||||||
|
}
|
||||||
|
|
||||||
|
// publishTxAndConfirmSweep is a helper function that publishes a transaction
|
||||||
|
// after checking its weight against an estimate. After asserting the given
|
||||||
|
// spend request, the given sweep address' balance is verified to be seen as
|
||||||
|
// funds belonging to the wallet.
|
||||||
|
func publishTxAndConfirmSweep(ctx context.Context, t *harnessTest,
|
||||||
|
net *lntest.NetworkHarness, node *lntest.HarnessNode, tx *wire.MsgTx,
|
||||||
|
estimatedWeight int64, spendRequest *chainrpc.SpendRequest,
|
||||||
|
sweepAddr string) {
|
||||||
|
|
||||||
|
// Before we publish the tx that spends the p2tr transaction, we want to
|
||||||
|
// register a spend listener that we expect to fire after mining the
|
||||||
|
// block.
|
||||||
|
_, currentHeight, err := net.Miner.Client.GetBestBlock()
|
||||||
|
require.NoError(t.t, err)
|
||||||
|
|
||||||
|
// For a Taproot output we cannot leave the outpoint empty. Let's make
|
||||||
|
// sure the API returns the correct error here.
|
||||||
|
spendClient, err := node.ChainClient.RegisterSpendNtfn(
|
||||||
|
ctx, &chainrpc.SpendRequest{
|
||||||
|
Script: spendRequest.Script,
|
||||||
|
HeightHint: uint32(currentHeight),
|
||||||
|
},
|
||||||
|
)
|
||||||
|
require.NoError(t.t, err)
|
||||||
|
|
||||||
|
// The error is only thrown when trying to read a message.
|
||||||
|
_, err = spendClient.Recv()
|
||||||
|
require.Contains(
|
||||||
|
t.t, err.Error(),
|
||||||
|
"cannot register witness v1 spend request without outpoint",
|
||||||
|
)
|
||||||
|
|
||||||
|
// Now try again, this time with the outpoint set.
|
||||||
|
spendClient, err = node.ChainClient.RegisterSpendNtfn(
|
||||||
|
ctx, &chainrpc.SpendRequest{
|
||||||
|
Outpoint: spendRequest.Outpoint,
|
||||||
|
Script: spendRequest.Script,
|
||||||
|
HeightHint: uint32(currentHeight),
|
||||||
|
},
|
||||||
|
)
|
||||||
|
require.NoError(t.t, err)
|
||||||
|
|
||||||
|
var buf bytes.Buffer
|
||||||
|
require.NoError(t.t, tx.Serialize(&buf))
|
||||||
|
|
||||||
|
// Since Schnorr signatures are fixed size, we must be able to estimate
|
||||||
|
// the size of this transaction exactly.
|
||||||
|
txWeight := blockchain.GetTransactionWeight(btcutil.NewTx(tx))
|
||||||
|
require.Equal(t.t, estimatedWeight, txWeight)
|
||||||
|
|
||||||
|
_, err = node.WalletKitClient.PublishTransaction(
|
||||||
|
ctx, &walletrpc.Transaction{
|
||||||
|
TxHex: buf.Bytes(),
|
||||||
|
},
|
||||||
|
)
|
||||||
|
require.NoError(t.t, err)
|
||||||
|
|
||||||
|
// Make sure the coins sent to the address are confirmed correctly,
|
||||||
|
// including the confirmation notification.
|
||||||
|
confirmAddress(ctx, t, net, node, sweepAddr)
|
||||||
|
|
||||||
|
// We now expect our spend event to go through.
|
||||||
|
spendMsg, err := spendClient.Recv()
|
||||||
|
require.NoError(t.t, err)
|
||||||
|
spend := spendMsg.GetSpend()
|
||||||
|
require.NotNil(t.t, spend)
|
||||||
|
require.Equal(t.t, spend.SpendingHeight, uint32(currentHeight+1))
|
||||||
|
}
|
||||||
|
|
||||||
|
// confirmAddress makes sure that a transaction in the mempool spends funds to
|
||||||
|
// the given address. It also checks that a confirmation notification for the
|
||||||
|
// address is triggered when the transaction is mined.
|
||||||
|
func confirmAddress(ctx context.Context, t *harnessTest,
|
||||||
|
net *lntest.NetworkHarness, node *lntest.HarnessNode,
|
||||||
|
addrString string) {
|
||||||
|
|
||||||
|
// Wait until the tx that sends to the address is found.
|
||||||
|
txid, err := waitForTxInMempool(net.Miner.Client, minerMempoolTimeout)
|
||||||
|
require.NoError(t.t, err)
|
||||||
|
|
||||||
|
// Wait until bob has seen the tx and considers it as owned.
|
||||||
|
addrOutputIndex := getOutputIndex(t, net.Miner, txid, addrString)
|
||||||
|
op := &lnrpc.OutPoint{
|
||||||
|
TxidBytes: txid[:],
|
||||||
|
OutputIndex: uint32(addrOutputIndex),
|
||||||
|
}
|
||||||
|
assertWalletUnspent(t, node, op)
|
||||||
|
|
||||||
|
// Before we confirm the transaction, let's register a confirmation
|
||||||
|
// listener for it, which we expect to fire after mining a block.
|
||||||
|
parsedAddr, err := btcutil.DecodeAddress(addrString, harnessNetParams)
|
||||||
|
require.NoError(t.t, err)
|
||||||
|
addrPkScript, err := txscript.PayToAddrScript(parsedAddr)
|
||||||
|
require.NoError(t.t, err)
|
||||||
|
|
||||||
|
_, currentHeight, err := net.Miner.Client.GetBestBlock()
|
||||||
|
require.NoError(t.t, err)
|
||||||
|
confClient, err := node.ChainClient.RegisterConfirmationsNtfn(
|
||||||
|
ctx, &chainrpc.ConfRequest{
|
||||||
|
Script: addrPkScript,
|
||||||
|
Txid: txid[:],
|
||||||
|
HeightHint: uint32(currentHeight),
|
||||||
|
NumConfs: 1,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
require.NoError(t.t, err)
|
||||||
|
|
||||||
|
// Mine another block to clean up the mempool.
|
||||||
|
mineBlocks(t, net, 1, 1)
|
||||||
|
|
||||||
|
// We now expect our confirmation to go through.
|
||||||
|
confMsg, err := confClient.Recv()
|
||||||
|
require.NoError(t.t, err)
|
||||||
|
conf := confMsg.GetConf()
|
||||||
|
require.NotNil(t.t, conf)
|
||||||
|
require.Equal(t.t, conf.BlockHeight, uint32(currentHeight+1))
|
||||||
|
}
|
||||||
|
Reference in New Issue
Block a user