lntest: allow the main test files to be buildable w/o the rpctest build tag

In this commit, we modify our build tag set up to allow the main test
files to be buildable w/o the current rpctest tag. We do this so that
those of us that use extensions which will compile live files like
vim-go can once again fix compile errors as we go in our editors.

In order to do this, we now make an external `testsCases` variable, and
have two variants: one that's empty (no build tag), and one that's fully
populated with all our tests (build tag active). As a result, the main
file will now always build regardless of if the build tag is active or
not, but we'll only actually execute tests if the `testCases` variable
has been populated.

As sample run w/ the tag off:
```
=== RUN   TestLightningNetworkDaemon
--- PASS: TestLightningNetworkDaemon (0.00s)
PASS
ok  	github.com/lightningnetwork/lnd/lntest/itest	0.051s
```
This commit is contained in:
Olaoluwa Osuntokun 2020-01-10 15:27:48 +01:00 committed by Oliver Gugger
parent acd615aca4
commit c769247198
No known key found for this signature in database
GPG Key ID: 8E4256593F177720
25 changed files with 456 additions and 485 deletions

@ -1,5 +1,3 @@
// +build rpctest
package itest
import (

@ -1,5 +1,3 @@
// +build rpctest
package itest
import (

@ -1,5 +1,3 @@
// +build rpctest
package itest
import (

@ -1,5 +1,3 @@
// +build rpctest
package itest
import (

@ -1,5 +1,3 @@
// +build rpctest
package itest
import (

@ -1,5 +1,3 @@
// +build rpctest
package itest
import (

@ -1,5 +1,3 @@
// +build rpctest
package itest
import (

@ -1,5 +1,3 @@
// +build rpctest
package itest
import (

@ -1,5 +1,3 @@
// +build rpctest
package itest
import (

@ -1,5 +1,3 @@
// +build rpctest
package itest
import (

@ -1,5 +1,3 @@
// +build rpctest
package itest
import (

@ -1,5 +1,3 @@
// +build rpctest
package itest
import (

@ -1,5 +1,3 @@
// +build rpctest
package itest
import (

@ -1,5 +1,3 @@
// +build rpctest
package itest
import (

@ -1,5 +1,3 @@
// +build rpctest
package itest
import (

@ -1,5 +1,3 @@
// +build rpctest
package itest
import (

@ -1,5 +1,3 @@
// +build rpctest
package itest
import (

@ -1,5 +1,3 @@
// +build rpctest
package itest
import (

@ -1,5 +1,3 @@
// +build rpctest
package itest
import (

@ -1,5 +1,3 @@
// +build rpctest
package itest
import (

@ -1,5 +1,3 @@
// +build rpctest
package itest
import (
@ -24,7 +22,6 @@ import (
"github.com/btcsuite/btcd/blockchain"
"github.com/btcsuite/btcd/btcjson"
"github.com/btcsuite/btcd/chaincfg"
"github.com/btcsuite/btcd/chaincfg/chainhash"
"github.com/btcsuite/btcd/integration/rpctest"
"github.com/btcsuite/btcd/rpcclient"
@ -55,66 +52,6 @@ import (
"github.com/stretchr/testify/require"
)
var (
harnessNetParams = &chaincfg.RegressionNetParams
)
const (
testFeeBase = 1e+6
defaultCSV = lntest.DefaultCSV
defaultTimeout = lntest.DefaultTimeout
minerMempoolTimeout = lntest.MinerMempoolTimeout
channelOpenTimeout = lntest.ChannelOpenTimeout
channelCloseTimeout = lntest.ChannelCloseTimeout
itestLndBinary = "../../lnd-itest"
anchorSize = 330
noFeeLimitMsat = math.MaxInt64
)
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) {
t.t.Helper()
err := wait.NoError(func() error {
ctxt, cancel := context.WithTimeout(context.Background(), defaultTimeout)
defer cancel()
unspent, err := node.ListUnspent(ctxt, &lnrpc.ListUnspentRequest{})
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)
if err != nil {
t.Fatalf("outpoint %s not unspent by %s's wallet: %v", out,
node.Name(), err)
}
}
func rpcPointToWirePoint(t *harnessTest, chanPoint *lnrpc.ChannelPoint) wire.OutPoint {
txid, err := lnd.GetChanPointFundingTxid(chanPoint)
if err != nil {
@ -127,50 +64,6 @@ func rpcPointToWirePoint(t *harnessTest, chanPoint *lnrpc.ChannelPoint) wire.Out
}
}
// 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 mineBlocks(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.Node, numTxs, minerMempoolTimeout,
)
if err != nil {
t.Fatalf("unable to find txns in mempool: %v", err)
}
}
blocks := make([]*wire.MsgBlock, num)
blockHashes, err := net.Miner.Node.Generate(num)
if err != nil {
t.Fatalf("unable to generate blocks: %v", err)
}
for i, blockHash := range blockHashes {
block, err := net.Miner.Node.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
}
// openChannelStream blocks until an OpenChannel request for a channel funding
// by alice succeeds. If it does, a stream client is returned to receive events
// about the opening channel.
@ -774,11 +667,6 @@ func getChanInfo(ctx context.Context, node *lntest.HarnessNode) (
return channelInfo.Channels[0], nil
}
const (
AddrTypeWitnessPubkeyHash = lnrpc.AddressType_WITNESS_PUBKEY_HASH
AddrTypeNestedPubkeyHash = lnrpc.AddressType_NESTED_PUBKEY_HASH
)
// testGetRecoveryInfo checks whether lnd gives the right information about
// the wallet recovery process.
func testGetRecoveryInfo(net *lntest.NetworkHarness, t *harnessTest) {
@ -7460,50 +7348,6 @@ func testMaxPendingChannels(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
}
}
}
}
// getNTxsFromMempool polls until finding the desired number of transactions in
// the provided miner's mempool and returns the full transactions to the caller.
func getNTxsFromMempool(miner *rpcclient.Client, n int,
@ -14078,296 +13922,14 @@ func getPaymentResult(stream routerrpc.Router_SendPaymentV2Client) (
}
}
type testCase struct {
name string
test func(net *lntest.NetworkHarness, t *harnessTest)
}
var testsCases = []*testCase{
{
name: "sweep coins",
test: testSweepAllCoins,
},
{
name: "recovery info",
test: testGetRecoveryInfo,
},
{
name: "onchain fund recovery",
test: testOnchainFundRecovery,
},
{
name: "basic funding flow",
test: testBasicChannelFunding,
},
{
name: "unconfirmed channel funding",
test: testUnconfirmedChannelFunding,
},
{
name: "update channel policy",
test: testUpdateChannelPolicy,
},
{
name: "open channel reorg test",
test: testOpenChannelAfterReorg,
},
{
name: "disconnecting target peer",
test: testDisconnectingTargetPeer,
},
{
name: "graph topology notifications",
test: testGraphTopologyNotifications,
},
{
name: "funding flow persistence",
test: testChannelFundingPersistence,
},
{
name: "channel force closure",
test: testChannelForceClosure,
},
{
name: "channel balance",
test: testChannelBalance,
},
{
name: "channel unsettled balance",
test: testChannelUnsettledBalance,
},
{
name: "single hop invoice",
test: testSingleHopInvoice,
},
{
name: "sphinx replay persistence",
test: testSphinxReplayPersistence,
},
{
name: "list channels",
test: testListChannels,
},
{
name: "list outgoing payments",
test: testListPayments,
},
{
name: "max pending channel",
test: testMaxPendingChannels,
},
{
name: "multi-hop payments",
test: testMultiHopPayments,
},
{
name: "single-hop send to route",
test: testSingleHopSendToRoute,
},
{
name: "multi-hop send to route",
test: testMultiHopSendToRoute,
},
{
name: "send to route error propagation",
test: testSendToRouteErrorPropagation,
},
{
name: "unannounced channels",
test: testUnannouncedChannels,
},
{
name: "private channels",
test: testPrivateChannels,
},
{
name: "invoice routing hints",
test: testInvoiceRoutingHints,
},
{
name: "multi-hop payments over private channels",
test: testMultiHopOverPrivateChannels,
},
{
name: "multiple channel creation and update subscription",
test: testBasicChannelCreationAndUpdates,
},
{
name: "invoice update subscription",
test: testInvoiceSubscriptions,
},
{
name: "multi-hop htlc error propagation",
test: testHtlcErrorPropagation,
},
{
name: "reject onward htlc",
test: testRejectHTLC,
},
// TODO(roasbeef): multi-path integration test
{
name: "node announcement",
test: testNodeAnnouncement,
},
{
name: "node sign verify",
test: testNodeSignVerify,
},
{
name: "derive shared key",
test: testDeriveSharedKey,
},
{
name: "async payments benchmark",
test: testAsyncPayments,
},
{
name: "async bidirectional payments",
test: testBidirectionalAsyncPayments,
},
{
name: "test multi-hop htlc",
test: testMultiHopHtlcClaims,
},
{
name: "switch circuit persistence",
test: testSwitchCircuitPersistence,
},
{
name: "switch offline delivery",
test: testSwitchOfflineDelivery,
},
{
name: "switch offline delivery persistence",
test: testSwitchOfflineDeliveryPersistence,
},
{
name: "switch offline delivery outgoing offline",
test: testSwitchOfflineDeliveryOutgoingOffline,
},
{
// TODO(roasbeef): test always needs to be last as Bob's state
// is borked since we trick him into attempting to cheat Alice?
name: "revoked uncooperative close retribution",
test: testRevokedCloseRetribution,
},
{
name: "failing link",
test: testFailingChannel,
},
{
name: "garbage collect link nodes",
test: testGarbageCollectLinkNodes,
},
{
name: "abandonchannel",
test: testAbandonChannel,
},
{
name: "revoked uncooperative close retribution zero value remote output",
test: testRevokedCloseRetributionZeroValueRemoteOutput,
},
{
name: "revoked uncooperative close retribution remote hodl",
test: testRevokedCloseRetributionRemoteHodl,
},
{
name: "revoked uncooperative close retribution altruist watchtower",
test: testRevokedCloseRetributionAltruistWatchtower,
},
{
name: "data loss protection",
test: testDataLossProtection,
},
{
name: "query routes",
test: testQueryRoutes,
},
{
name: "route fee cutoff",
test: testRouteFeeCutoff,
},
{
name: "send update disable channel",
test: testSendUpdateDisableChannel,
},
{
name: "streaming channel backup update",
test: testChannelBackupUpdates,
},
{
name: "export channel backup",
test: testExportChannelBackup,
},
{
name: "channel backup restore",
test: testChannelBackupRestore,
},
{
name: "hold invoice sender persistence",
test: testHoldInvoicePersistence,
},
{
name: "cpfp",
test: testCPFP,
},
{
name: "macaroon authentication",
test: testMacaroonAuthentication,
},
{
name: "bake macaroon",
test: testBakeMacaroon,
},
{
name: "delete macaroon id",
test: testDeleteMacaroonID,
},
{
name: "immediate payment after channel opened",
test: testPaymentFollowingChannelOpen,
},
{
name: "external channel funding",
test: testExternalFundingChanPoint,
},
{
name: "psbt channel funding",
test: testPsbtChanFunding,
},
{
name: "sendtoroute multi path payment",
test: testSendToRouteMultiPath,
},
{
name: "send multi path payment",
test: testSendMultiPathPayment,
},
{
name: "REST API",
test: testRestApi,
},
{
name: "intercept forwarded htlc packets",
test: testForwardInterceptor,
},
{
name: "wumbo channels",
test: testWumboChannels,
},
{
name: "maximum channel size",
test: testMaxChannelSize,
},
{
name: "connection timeout",
test: testNetworkConnectionTimeout,
},
}
// TestLightningNetworkDaemon performs a series of integration tests amongst a
// programmatically driven network of lnd nodes.
func TestLightningNetworkDaemon(t *testing.T) {
// If no tests are regsitered, then we can exit early.
if len(testsCases) == 0 {
t.Skip("integration tests not selected with flag 'rpctest'")
}
ht := newHarnessTest(t, nil)
// Declare the network harness here to gain access to its

@ -0,0 +1,5 @@
// +build !rpctest
package itest
var testsCases = []*testCase{}

@ -0,0 +1,285 @@
// +build rpctest
package itest
var testsCases = []*testCase{
{
name: "sweep coins",
test: testSweepAllCoins,
},
{
name: "recovery info",
test: testGetRecoveryInfo,
},
{
name: "onchain fund recovery",
test: testOnchainFundRecovery,
},
{
name: "basic funding flow",
test: testBasicChannelFunding,
},
{
name: "unconfirmed channel funding",
test: testUnconfirmedChannelFunding,
},
{
name: "update channel policy",
test: testUpdateChannelPolicy,
},
{
name: "open channel reorg test",
test: testOpenChannelAfterReorg,
},
{
name: "disconnecting target peer",
test: testDisconnectingTargetPeer,
},
{
name: "graph topology notifications",
test: testGraphTopologyNotifications,
},
{
name: "funding flow persistence",
test: testChannelFundingPersistence,
},
{
name: "channel force closure",
test: testChannelForceClosure,
},
{
name: "channel balance",
test: testChannelBalance,
},
{
name: "channel unsettled balance",
test: testChannelUnsettledBalance,
},
{
name: "single hop invoice",
test: testSingleHopInvoice,
},
{
name: "sphinx replay persistence",
test: testSphinxReplayPersistence,
},
{
name: "list channels",
test: testListChannels,
},
{
name: "list outgoing payments",
test: testListPayments,
},
{
name: "max pending channel",
test: testMaxPendingChannels,
},
{
name: "multi-hop payments",
test: testMultiHopPayments,
},
{
name: "single-hop send to route",
test: testSingleHopSendToRoute,
},
{
name: "multi-hop send to route",
test: testMultiHopSendToRoute,
},
{
name: "send to route error propagation",
test: testSendToRouteErrorPropagation,
},
{
name: "unannounced channels",
test: testUnannouncedChannels,
},
{
name: "private channels",
test: testPrivateChannels,
},
{
name: "invoice routing hints",
test: testInvoiceRoutingHints,
},
{
name: "multi-hop payments over private channels",
test: testMultiHopOverPrivateChannels,
},
{
name: "multiple channel creation and update subscription",
test: testBasicChannelCreationAndUpdates,
},
{
name: "invoice update subscription",
test: testInvoiceSubscriptions,
},
{
name: "multi-hop htlc error propagation",
test: testHtlcErrorPropagation,
},
{
name: "reject onward htlc",
test: testRejectHTLC,
},
// TODO(roasbeef): multi-path integration test
{
name: "node announcement",
test: testNodeAnnouncement,
},
{
name: "node sign verify",
test: testNodeSignVerify,
},
{
name: "derive shared key",
test: testDeriveSharedKey,
},
{
name: "async payments benchmark",
test: testAsyncPayments,
},
{
name: "async bidirectional payments",
test: testBidirectionalAsyncPayments,
},
{
name: "test multi-hop htlc",
test: testMultiHopHtlcClaims,
},
{
name: "switch circuit persistence",
test: testSwitchCircuitPersistence,
},
{
name: "switch offline delivery",
test: testSwitchOfflineDelivery,
},
{
name: "switch offline delivery persistence",
test: testSwitchOfflineDeliveryPersistence,
},
{
name: "switch offline delivery outgoing offline",
test: testSwitchOfflineDeliveryOutgoingOffline,
},
{
// TODO(roasbeef): test always needs to be last as Bob's state
// is borked since we trick him into attempting to cheat Alice?
name: "revoked uncooperative close retribution",
test: testRevokedCloseRetribution,
},
{
name: "failing link",
test: testFailingChannel,
},
{
name: "garbage collect link nodes",
test: testGarbageCollectLinkNodes,
},
{
name: "abandonchannel",
test: testAbandonChannel,
},
{
name: "revoked uncooperative close retribution zero value remote output",
test: testRevokedCloseRetributionZeroValueRemoteOutput,
},
{
name: "revoked uncooperative close retribution remote hodl",
test: testRevokedCloseRetributionRemoteHodl,
},
{
name: "revoked uncooperative close retribution altruist watchtower",
test: testRevokedCloseRetributionAltruistWatchtower,
},
{
name: "data loss protection",
test: testDataLossProtection,
},
{
name: "query routes",
test: testQueryRoutes,
},
{
name: "route fee cutoff",
test: testRouteFeeCutoff,
},
{
name: "send update disable channel",
test: testSendUpdateDisableChannel,
},
{
name: "streaming channel backup update",
test: testChannelBackupUpdates,
},
{
name: "export channel backup",
test: testExportChannelBackup,
},
{
name: "channel backup restore",
test: testChannelBackupRestore,
},
{
name: "hold invoice sender persistence",
test: testHoldInvoicePersistence,
},
{
name: "cpfp",
test: testCPFP,
},
{
name: "macaroon authentication",
test: testMacaroonAuthentication,
},
{
name: "bake macaroon",
test: testBakeMacaroon,
},
{
name: "delete macaroon id",
test: testDeleteMacaroonID,
},
{
name: "immediate payment after channel opened",
test: testPaymentFollowingChannelOpen,
},
{
name: "external channel funding",
test: testExternalFundingChanPoint,
},
{
name: "psbt channel funding",
test: testPsbtChanFunding,
},
{
name: "sendtoroute multi path payment",
test: testSendToRouteMultiPath,
},
{
name: "send multi path payment",
test: testSendMultiPathPayment,
},
{
name: "REST API",
test: testRestApi,
},
{
name: "intercept forwarded htlc packets",
test: testForwardInterceptor,
},
{
name: "wumbo channels",
test: testWumboChannels,
},
{
name: "maximum channel size",
test: testMaxChannelSize,
},
{
name: "connection timeout",
test: testNetworkConnectionTimeout,
},
}

@ -1,5 +1,3 @@
// +build rpctest
package itest
import (

@ -1,11 +1,40 @@
package itest
import (
"bytes"
"context"
"fmt"
"math"
"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/lntest"
"github.com/lightningnetwork/lnd/lntest/wait"
)
var (
harnessNetParams = &chaincfg.RegressionNetParams
)
const (
testFeeBase = 1e+6
defaultCSV = lntest.DefaultCSV
defaultTimeout = lntest.DefaultTimeout
minerMempoolTimeout = lntest.MinerMempoolTimeout
channelOpenTimeout = lntest.ChannelOpenTimeout
channelCloseTimeout = lntest.ChannelCloseTimeout
itestLndBinary = "../../lnd-itest"
anchorSize = 330
noFeeLimitMsat = math.MaxInt64
AddrTypeWitnessPubkeyHash = lnrpc.AddressType_WITNESS_PUBKEY_HASH
AddrTypeNestedPubkeyHash = lnrpc.AddressType_NESTED_PUBKEY_HASH
)
// harnessTest wraps a regular testing.T providing enhanced error detection
@ -88,3 +117,135 @@ 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 mineBlocks(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.Node, numTxs, minerMempoolTimeout,
)
if err != nil {
t.Fatalf("unable to find txns in mempool: %v", err)
}
}
blocks := make([]*wire.MsgBlock, num)
blockHashes, err := net.Miner.Node.Generate(num)
if err != nil {
t.Fatalf("unable to generate blocks: %v", err)
}
for i, blockHash := range blockHashes {
block, err := net.Miner.Node.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
}
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) {
t.t.Helper()
err := wait.NoError(func() error {
ctxt, cancel := context.WithTimeout(context.Background(), defaultTimeout)
defer cancel()
unspent, err := node.ListUnspent(ctxt, &lnrpc.ListUnspentRequest{})
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)
if err != nil {
t.Fatalf("outpoint %s not unspent by %s's wallet: %v", out,
node.Name(), err)
}
}