mirror of
https://github.com/lightningnetwork/lnd.git
synced 2024-11-20 02:27:21 +01:00
c45d0f6b07
We add basic integration test coverage for importing Tapscript addresses. Note that FundPsbt is not supported with those types of imported keys, so we need to manually fill in all information in the PSBT to be able to sweep the funds again.
352 lines
9.5 KiB
Go
352 lines
9.5 KiB
Go
package itest
|
|
|
|
import (
|
|
"bytes"
|
|
"context"
|
|
"flag"
|
|
"fmt"
|
|
"math"
|
|
"os"
|
|
"path/filepath"
|
|
"runtime"
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/btcsuite/btcd/chaincfg"
|
|
"github.com/btcsuite/btcd/chaincfg/chainhash"
|
|
"github.com/btcsuite/btcd/rpcclient"
|
|
"github.com/btcsuite/btcd/wire"
|
|
"github.com/go-errors/errors"
|
|
"github.com/lightningnetwork/lnd/lnrpc"
|
|
"github.com/lightningnetwork/lnd/lnrpc/walletrpc"
|
|
"github.com/lightningnetwork/lnd/lntest"
|
|
"github.com/lightningnetwork/lnd/lntest/wait"
|
|
"github.com/stretchr/testify/require"
|
|
)
|
|
|
|
var (
|
|
harnessNetParams = &chaincfg.RegressionNetParams
|
|
|
|
// lndExecutable is the full path to the lnd binary.
|
|
lndExecutable = flag.String(
|
|
"lndexec", itestLndBinary, "full path to lnd binary",
|
|
)
|
|
|
|
slowMineDelay = 20 * time.Millisecond
|
|
)
|
|
|
|
const (
|
|
testFeeBase = 1e+6
|
|
defaultCSV = lntest.DefaultCSV
|
|
defaultTimeout = lntest.DefaultTimeout
|
|
minerMempoolTimeout = lntest.MinerMempoolTimeout
|
|
channelCloseTimeout = lntest.ChannelCloseTimeout
|
|
itestLndBinary = "../../lnd-itest"
|
|
anchorSize = 330
|
|
noFeeLimitMsat = math.MaxInt64
|
|
|
|
AddrTypeWitnessPubkeyHash = lnrpc.AddressType_WITNESS_PUBKEY_HASH
|
|
AddrTypeNestedPubkeyHash = lnrpc.AddressType_NESTED_PUBKEY_HASH
|
|
AddrTypeTaprootPubkey = lnrpc.AddressType_TAPROOT_PUBKEY
|
|
)
|
|
|
|
// harnessTest wraps a regular testing.T providing enhanced error detection
|
|
// and propagation. All error will be augmented with a full stack-trace in
|
|
// order to aid in debugging. Additionally, any panics caused by active
|
|
// test cases will also be handled and represented as fatals.
|
|
type harnessTest struct {
|
|
t *testing.T
|
|
|
|
// testCase is populated during test execution and represents the
|
|
// current test case.
|
|
testCase *testCase
|
|
|
|
// lndHarness is a reference to the current network harness. Will be
|
|
// nil if not yet set up.
|
|
lndHarness *lntest.NetworkHarness
|
|
}
|
|
|
|
// newHarnessTest creates a new instance of a harnessTest from a regular
|
|
// testing.T instance.
|
|
func newHarnessTest(t *testing.T, net *lntest.NetworkHarness) *harnessTest {
|
|
return &harnessTest{t, nil, net}
|
|
}
|
|
|
|
// Skipf calls the underlying testing.T's Skip method, causing the current test
|
|
// to be skipped.
|
|
func (h *harnessTest) Skipf(format string, args ...interface{}) {
|
|
h.t.Skipf(format, args...)
|
|
}
|
|
|
|
// Fatalf causes the current active test case to fail with a fatal error. All
|
|
// integration tests should mark test failures solely with this method due to
|
|
// the error stack traces it produces.
|
|
func (h *harnessTest) Fatalf(format string, a ...interface{}) {
|
|
if h.lndHarness != nil {
|
|
h.lndHarness.SaveProfilesPages(h.t)
|
|
}
|
|
|
|
stacktrace := errors.Wrap(fmt.Sprintf(format, a...), 1).ErrorStack()
|
|
|
|
if h.testCase != nil {
|
|
h.t.Fatalf("Failed: (%v): exited with error: \n"+
|
|
"%v", h.testCase.name, stacktrace)
|
|
} else {
|
|
h.t.Fatalf("Error outside of test: %v", stacktrace)
|
|
}
|
|
}
|
|
|
|
// RunTestCase executes a harness test case. Any errors or panics will be
|
|
// represented as fatal.
|
|
func (h *harnessTest) RunTestCase(testCase *testCase) {
|
|
h.testCase = testCase
|
|
defer func() {
|
|
h.testCase = nil
|
|
}()
|
|
|
|
defer func() {
|
|
if err := recover(); err != nil {
|
|
description := errors.Wrap(err, 2).ErrorStack()
|
|
h.t.Fatalf("Failed: (%v) panicked with: \n%v",
|
|
h.testCase.name, description)
|
|
}
|
|
}()
|
|
|
|
testCase.test(h.lndHarness, h)
|
|
}
|
|
|
|
func (h *harnessTest) Logf(format string, args ...interface{}) {
|
|
h.t.Logf(format, args...)
|
|
}
|
|
|
|
func (h *harnessTest) Log(args ...interface{}) {
|
|
h.t.Log(args...)
|
|
}
|
|
|
|
func (h *harnessTest) getLndBinary() string {
|
|
binary := itestLndBinary
|
|
lndExec := ""
|
|
if lndExecutable != nil && *lndExecutable != "" {
|
|
lndExec = *lndExecutable
|
|
}
|
|
if lndExec == "" && runtime.GOOS == "windows" {
|
|
// Windows (even in a bash like environment like git bash as on
|
|
// Travis) doesn't seem to like relative paths to exe files...
|
|
currentDir, err := os.Getwd()
|
|
if err != nil {
|
|
h.Fatalf("unable to get working directory: %v", err)
|
|
}
|
|
targetPath := filepath.Join(currentDir, "../../lnd-itest.exe")
|
|
binary, err = filepath.Abs(targetPath)
|
|
if err != nil {
|
|
h.Fatalf("unable to get absolute path: %v", err)
|
|
}
|
|
} else if lndExec != "" {
|
|
binary = lndExec
|
|
}
|
|
|
|
return binary
|
|
}
|
|
|
|
type testCase struct {
|
|
name string
|
|
test func(net *lntest.NetworkHarness, t *harnessTest)
|
|
}
|
|
|
|
// waitForTxInMempool polls until finding one transaction in the provided
|
|
// miner's mempool. An error is returned if *one* transaction isn't found within
|
|
// the given timeout.
|
|
func waitForTxInMempool(miner *rpcclient.Client,
|
|
timeout time.Duration) (*chainhash.Hash, error) {
|
|
|
|
txs, err := waitForNTxsInMempool(miner, 1, timeout)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return txs[0], err
|
|
}
|
|
|
|
// waitForNTxsInMempool polls until finding the desired number of transactions
|
|
// in the provided miner's mempool. An error is returned if this number is not
|
|
// met after the given timeout.
|
|
func waitForNTxsInMempool(miner *rpcclient.Client, n int,
|
|
timeout time.Duration) ([]*chainhash.Hash, error) {
|
|
|
|
breakTimeout := time.After(timeout)
|
|
ticker := time.NewTicker(50 * time.Millisecond)
|
|
defer ticker.Stop()
|
|
|
|
var err error
|
|
var mempool []*chainhash.Hash
|
|
for {
|
|
select {
|
|
case <-breakTimeout:
|
|
return nil, fmt.Errorf("wanted %v, found %v txs "+
|
|
"in mempool: %v", n, len(mempool), mempool)
|
|
case <-ticker.C:
|
|
mempool, err = miner.GetRawMempool()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
if len(mempool) == n {
|
|
return mempool, nil
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// mineBlocks mine 'num' of blocks and check that blocks are present in
|
|
// node blockchain. numTxs should be set to the number of transactions
|
|
// (excluding the coinbase) we expect to be included in the first mined block.
|
|
func mineBlocksFast(t *harnessTest, net *lntest.NetworkHarness,
|
|
num uint32, numTxs int) []*wire.MsgBlock {
|
|
|
|
// If we expect transactions to be included in the blocks we'll mine,
|
|
// we wait here until they are seen in the miner's mempool.
|
|
var txids []*chainhash.Hash
|
|
var err error
|
|
if numTxs > 0 {
|
|
txids, err = waitForNTxsInMempool(
|
|
net.Miner.Client, numTxs, minerMempoolTimeout,
|
|
)
|
|
if err != nil {
|
|
t.Fatalf("unable to find txns in mempool: %v", err)
|
|
}
|
|
}
|
|
|
|
blocks := make([]*wire.MsgBlock, num)
|
|
|
|
blockHashes, err := net.Miner.Client.Generate(num)
|
|
if err != nil {
|
|
t.Fatalf("unable to generate blocks: %v", err)
|
|
}
|
|
|
|
for i, blockHash := range blockHashes {
|
|
block, err := net.Miner.Client.GetBlock(blockHash)
|
|
if err != nil {
|
|
t.Fatalf("unable to get block: %v", err)
|
|
}
|
|
|
|
blocks[i] = block
|
|
}
|
|
|
|
// Finally, assert that all the transactions were included in the first
|
|
// block.
|
|
for _, txid := range txids {
|
|
assertTxInBlock(t, blocks[0], txid)
|
|
}
|
|
|
|
return blocks
|
|
}
|
|
|
|
// mineBlocksSlow mines 'num' of blocks and checks that blocks are present in
|
|
// the mining node's blockchain. numTxs should be set to the number of
|
|
// transactions (excluding the coinbase) we expect to be included in the first
|
|
// mined block. Between each mined block an artificial delay is introduced to
|
|
// give all network participants time to catch up.
|
|
//
|
|
// NOTE: This function currently is just an alias for mineBlocksSlow.
|
|
func mineBlocks(t *harnessTest, net *lntest.NetworkHarness,
|
|
num uint32, numTxs int) []*wire.MsgBlock {
|
|
|
|
return mineBlocksSlow(t, net, num, numTxs)
|
|
}
|
|
|
|
// mineBlocksSlow mines 'num' of blocks and checks that blocks are present in
|
|
// the mining node's blockchain. numTxs should be set to the number of
|
|
// transactions (excluding the coinbase) we expect to be included in the first
|
|
// mined block. Between each mined block an artificial delay is introduced to
|
|
// give all network participants time to catch up.
|
|
func mineBlocksSlow(t *harnessTest, net *lntest.NetworkHarness,
|
|
num uint32, numTxs int) []*wire.MsgBlock {
|
|
|
|
t.t.Helper()
|
|
|
|
// If we expect transactions to be included in the blocks we'll mine,
|
|
// we wait here until they are seen in the miner's mempool.
|
|
var txids []*chainhash.Hash
|
|
var err error
|
|
if numTxs > 0 {
|
|
txids, err = waitForNTxsInMempool(
|
|
net.Miner.Client, numTxs, minerMempoolTimeout,
|
|
)
|
|
require.NoError(t.t, err, "unable to find txns in mempool")
|
|
}
|
|
|
|
blocks := make([]*wire.MsgBlock, num)
|
|
blockHashes := make([]*chainhash.Hash, 0, num)
|
|
|
|
for i := uint32(0); i < num; i++ {
|
|
generatedHashes, err := net.Miner.Client.Generate(1)
|
|
require.NoError(t.t, err, "generate blocks")
|
|
blockHashes = append(blockHashes, generatedHashes...)
|
|
|
|
time.Sleep(slowMineDelay)
|
|
}
|
|
|
|
for i, blockHash := range blockHashes {
|
|
block, err := net.Miner.Client.GetBlock(blockHash)
|
|
require.NoError(t.t, err, "get blocks")
|
|
|
|
blocks[i] = block
|
|
}
|
|
|
|
// Finally, assert that all the transactions were included in the first
|
|
// block.
|
|
for _, txid := range txids {
|
|
assertTxInBlock(t, blocks[0], txid)
|
|
}
|
|
|
|
return blocks
|
|
}
|
|
|
|
func assertTxInBlock(t *harnessTest, block *wire.MsgBlock, txid *chainhash.Hash) {
|
|
for _, tx := range block.Transactions {
|
|
sha := tx.TxHash()
|
|
if bytes.Equal(txid[:], sha[:]) {
|
|
return
|
|
}
|
|
}
|
|
|
|
t.Fatalf("tx was not included in block")
|
|
}
|
|
|
|
func assertWalletUnspent(t *harnessTest, node *lntest.HarnessNode,
|
|
out *lnrpc.OutPoint, account string) {
|
|
|
|
t.t.Helper()
|
|
|
|
ctxb := context.Background()
|
|
err := wait.NoError(func() error {
|
|
ctxt, cancel := context.WithTimeout(ctxb, defaultTimeout)
|
|
defer cancel()
|
|
unspent, err := node.WalletKitClient.ListUnspent(
|
|
ctxt, &walletrpc.ListUnspentRequest{
|
|
Account: account,
|
|
},
|
|
)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
err = errors.New("tx with wanted txhash never found")
|
|
for _, utxo := range unspent.Utxos {
|
|
if !bytes.Equal(utxo.Outpoint.TxidBytes, out.TxidBytes) {
|
|
continue
|
|
}
|
|
|
|
err = errors.New("wanted output is not a wallet utxo")
|
|
if utxo.Outpoint.OutputIndex != out.OutputIndex {
|
|
continue
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
return err
|
|
}, defaultTimeout)
|
|
require.NoError(t.t, err)
|
|
}
|