itest: refactor taproot test

This commit is contained in:
Oliver Gugger
2022-04-27 22:20:31 +02:00
parent dfac94bf9b
commit fee2b28d1b

View File

@@ -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{
TxHex: buf.Bytes(),
}, },
Script: p2trPkScript,
},
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))
}