package itest import ( "context" "encoding/hex" "fmt" "io" "sync/atomic" "testing" "time" "github.com/btcsuite/btcd/chaincfg/chainhash" "github.com/btcsuite/btcd/integration/rpctest" "github.com/btcsuite/btcd/rpcclient" "github.com/btcsuite/btcd/wire" "github.com/btcsuite/btcutil" "github.com/go-errors/errors" "github.com/lightningnetwork/lnd/channeldb" "github.com/lightningnetwork/lnd/lnrpc" "github.com/lightningnetwork/lnd/lnrpc/routerrpc" "github.com/lightningnetwork/lnd/lnrpc/walletrpc" "github.com/lightningnetwork/lnd/lntest" "github.com/lightningnetwork/lnd/lntest/wait" "github.com/stretchr/testify/require" "google.golang.org/protobuf/proto" ) // AddToNodeLog adds a line to the log file and asserts there's no error. func AddToNodeLog(t *testing.T, node *lntest.HarnessNode, logLine string) { err := node.AddToLog(logLine) require.NoError(t, err, "unable to add to log") } // 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. func openChannelStream(ctx context.Context, t *harnessTest, net *lntest.NetworkHarness, alice, bob *lntest.HarnessNode, p lntest.OpenChannelParams) lnrpc.Lightning_OpenChannelClient { t.t.Helper() // Wait until we are able to fund a channel successfully. This wait // prevents us from erroring out when trying to create a channel while // the node is starting up. var chanOpenUpdate lnrpc.Lightning_OpenChannelClient err := wait.NoError(func() error { var err error chanOpenUpdate, err = net.OpenChannel(ctx, alice, bob, p) return err }, defaultTimeout) require.NoError(t.t, err, "unable to open channel") return chanOpenUpdate } // openChannelAndAssert attempts to open a channel with the specified // parameters extended from Alice to Bob. Additionally, two items are asserted // after the channel is considered open: the funding transaction should be // found within a block, and that Alice can report the status of the new // channel. func openChannelAndAssert(ctx context.Context, t *harnessTest, net *lntest.NetworkHarness, alice, bob *lntest.HarnessNode, p lntest.OpenChannelParams) *lnrpc.ChannelPoint { t.t.Helper() chanOpenUpdate := openChannelStream(ctx, t, net, alice, bob, p) // Mine 6 blocks, then wait for Alice's node to notify us that the // channel has been opened. The funding transaction should be found // within the first newly mined block. We mine 6 blocks so that in the // case that the channel is public, it is announced to the network. block := mineBlocks(t, net, 6, 1)[0] fundingChanPoint, err := net.WaitForChannelOpen(ctx, chanOpenUpdate) require.NoError(t.t, err, "error while waiting for channel open") fundingTxID, err := lnrpc.GetChanPointFundingTxid(fundingChanPoint) require.NoError(t.t, err, "unable to get txid") assertTxInBlock(t, block, fundingTxID) // The channel should be listed in the peer information returned by // both peers. chanPoint := wire.OutPoint{ Hash: *fundingTxID, Index: fundingChanPoint.OutputIndex, } require.NoError( t.t, net.AssertChannelExists(ctx, alice, &chanPoint), "unable to assert channel existence", ) require.NoError( t.t, net.AssertChannelExists(ctx, bob, &chanPoint), "unable to assert channel existence", ) return fundingChanPoint } // graphSubscription houses the proxied update and error chans for a node's // graph subscriptions. type graphSubscription struct { updateChan chan *lnrpc.GraphTopologyUpdate errChan chan error quit chan struct{} } // subscribeGraphNotifications subscribes to channel graph updates and launches // a goroutine that forwards these to the returned channel. func subscribeGraphNotifications(ctxb context.Context, t *harnessTest, node *lntest.HarnessNode) graphSubscription { // We'll first start by establishing a notification client which will // send us notifications upon detected changes in the channel graph. req := &lnrpc.GraphTopologySubscription{} ctx, cancelFunc := context.WithCancel(ctxb) topologyClient, err := node.SubscribeChannelGraph(ctx, req) require.NoError(t.t, err, "unable to create topology client") // We'll launch a goroutine that will be responsible for proxying all // notifications recv'd from the client into the channel below. errChan := make(chan error, 1) quit := make(chan struct{}) graphUpdates := make(chan *lnrpc.GraphTopologyUpdate, 20) go func() { for { defer cancelFunc() select { case <-quit: return default: graphUpdate, err := topologyClient.Recv() select { case <-quit: return default: } if err == io.EOF { return } else if err != nil { select { case errChan <- err: case <-quit: } return } select { case graphUpdates <- graphUpdate: case <-quit: return } } } }() return graphSubscription{ updateChan: graphUpdates, errChan: errChan, quit: quit, } } func waitForGraphSync(t *harnessTest, node *lntest.HarnessNode) { t.t.Helper() err := wait.Predicate(func() bool { ctxb := context.Background() ctxt, _ := context.WithTimeout(ctxb, defaultTimeout) resp, err := node.GetInfo(ctxt, &lnrpc.GetInfoRequest{}) require.NoError(t.t, err) return resp.SyncedToGraph }, defaultTimeout) require.NoError(t.t, err) } // closeChannelAndAssert attempts to close a channel identified by the passed // channel point owned by the passed Lightning node. A fully blocking channel // closure is attempted, therefore the passed context should be a child derived // via timeout from a base parent. Additionally, once the channel has been // detected as closed, an assertion checks that the transaction is found within // a block. Finally, this assertion verifies that the node always sends out a // disable update when closing the channel if the channel was previously // enabled. // // NOTE: This method assumes that the provided funding point is confirmed // on-chain AND that the edge exists in the node's channel graph. If the funding // transactions was reorged out at some point, use closeReorgedChannelAndAssert. func closeChannelAndAssert(ctx context.Context, t *harnessTest, net *lntest.NetworkHarness, node *lntest.HarnessNode, fundingChanPoint *lnrpc.ChannelPoint, force bool) *chainhash.Hash { return closeChannelAndAssertType( ctx, t, net, node, fundingChanPoint, false, force, ) } func closeChannelAndAssertType(ctx context.Context, t *harnessTest, net *lntest.NetworkHarness, node *lntest.HarnessNode, fundingChanPoint *lnrpc.ChannelPoint, anchors, force bool) *chainhash.Hash { // Fetch the current channel policy. If the channel is currently // enabled, we will register for graph notifications before closing to // assert that the node sends out a disabling update as a result of the // channel being closed. curPolicy := getChannelPolicies( t, node, node.PubKeyStr, fundingChanPoint, )[0] expectDisable := !curPolicy.Disabled // If the current channel policy is enabled, begin subscribing the graph // updates before initiating the channel closure. var graphSub *graphSubscription if expectDisable { sub := subscribeGraphNotifications(ctx, t, node) graphSub = &sub defer close(graphSub.quit) } closeUpdates, _, err := net.CloseChannel( ctx, node, fundingChanPoint, force, ) require.NoError(t.t, err, "unable to close channel") // If the channel policy was enabled prior to the closure, wait until we // received the disabled update. if expectDisable { curPolicy.Disabled = true waitForChannelUpdate( t, *graphSub, []expectedChanUpdate{ {node.PubKeyStr, curPolicy, fundingChanPoint}, }, ) } return assertChannelClosed( ctx, t, net, node, fundingChanPoint, anchors, closeUpdates, ) } // closeReorgedChannelAndAssert attempts to close a channel identified by the // passed channel point owned by the passed Lightning node. A fully blocking // channel closure is attempted, therefore the passed context should be a child // derived via timeout from a base parent. Additionally, once the channel has // been detected as closed, an assertion checks that the transaction is found // within a block. // // NOTE: This method does not verify that the node sends a disable update for // the closed channel. func closeReorgedChannelAndAssert(ctx context.Context, t *harnessTest, net *lntest.NetworkHarness, node *lntest.HarnessNode, fundingChanPoint *lnrpc.ChannelPoint, force bool) *chainhash.Hash { closeUpdates, _, err := net.CloseChannel(ctx, node, fundingChanPoint, force) require.NoError(t.t, err, "unable to close channel") return assertChannelClosed( ctx, t, net, node, fundingChanPoint, false, closeUpdates, ) } // assertChannelClosed asserts that the channel is properly cleaned up after // initiating a cooperative or local close. func assertChannelClosed(ctx context.Context, t *harnessTest, net *lntest.NetworkHarness, node *lntest.HarnessNode, fundingChanPoint *lnrpc.ChannelPoint, anchors bool, closeUpdates lnrpc.Lightning_CloseChannelClient) *chainhash.Hash { txid, err := lnrpc.GetChanPointFundingTxid(fundingChanPoint) require.NoError(t.t, err, "unable to get txid") chanPointStr := fmt.Sprintf("%v:%v", txid, fundingChanPoint.OutputIndex) // If the channel appears in list channels, ensure that its state // contains ChanStatusCoopBroadcasted. ctxt, _ := context.WithTimeout(ctx, defaultTimeout) listChansRequest := &lnrpc.ListChannelsRequest{} listChansResp, err := node.ListChannels(ctxt, listChansRequest) require.NoError(t.t, err, "unable to query for list channels") for _, channel := range listChansResp.Channels { // Skip other channels. if channel.ChannelPoint != chanPointStr { continue } // Assert that the channel is in coop broadcasted. require.Contains( t.t, channel.ChanStatusFlags, channeldb.ChanStatusCoopBroadcasted.String(), "channel not coop broadcasted", ) } // At this point, the channel should now be marked as being in the // state of "waiting close". ctxt, _ = context.WithTimeout(ctx, defaultTimeout) pendingChansRequest := &lnrpc.PendingChannelsRequest{} pendingChanResp, err := node.PendingChannels(ctxt, pendingChansRequest) require.NoError(t.t, err, "unable to query for pending channels") var found bool for _, pendingClose := range pendingChanResp.WaitingCloseChannels { if pendingClose.Channel.ChannelPoint == chanPointStr { found = true break } } require.True(t.t, found, "channel not marked as waiting close") // We'll now, generate a single block, wait for the final close status // update, then ensure that the closing transaction was included in the // block. If there are anchors, we also expect an anchor sweep. expectedTxes := 1 if anchors { expectedTxes = 2 } block := mineBlocks(t, net, 1, expectedTxes)[0] closingTxid, err := net.WaitForChannelClose(ctx, closeUpdates) require.NoError(t.t, err, "error while waiting for channel close") assertTxInBlock(t, block, closingTxid) // Finally, the transaction should no longer be in the waiting close // state as we've just mined a block that should include the closing // transaction. err = wait.Predicate(func() bool { pendingChansRequest := &lnrpc.PendingChannelsRequest{} pendingChanResp, err := node.PendingChannels( ctx, pendingChansRequest, ) if err != nil { return false } for _, pendingClose := range pendingChanResp.WaitingCloseChannels { if pendingClose.Channel.ChannelPoint == chanPointStr { return false } } return true }, defaultTimeout) require.NoError( t.t, err, "closing transaction not marked as fully closed", ) return closingTxid } // findForceClosedChannel searches a pending channel response for a particular // channel, returning the force closed channel upon success. func findForceClosedChannel(pendingChanResp *lnrpc.PendingChannelsResponse, op *wire.OutPoint) (*lnrpc.PendingChannelsResponse_ForceClosedChannel, error) { for _, forceClose := range pendingChanResp.PendingForceClosingChannels { if forceClose.Channel.ChannelPoint == op.String() { return forceClose, nil } } return nil, errors.New("channel not marked as force closed") } // findWaitingCloseChannel searches a pending channel response for a particular // channel, returning the waiting close channel upon success. func findWaitingCloseChannel(pendingChanResp *lnrpc.PendingChannelsResponse, op *wire.OutPoint) (*lnrpc.PendingChannelsResponse_WaitingCloseChannel, error) { for _, waitingClose := range pendingChanResp.WaitingCloseChannels { if waitingClose.Channel.ChannelPoint == op.String() { return waitingClose, nil } } return nil, errors.New("channel not marked as waiting close") } // waitForChannelPendingForceClose waits for the node to report that the // channel is pending force close, and that the UTXO nursery is aware of it. func waitForChannelPendingForceClose(ctx context.Context, node *lntest.HarnessNode, fundingChanPoint *lnrpc.ChannelPoint) error { txid, err := lnrpc.GetChanPointFundingTxid(fundingChanPoint) if err != nil { return err } op := wire.OutPoint{ Hash: *txid, Index: fundingChanPoint.OutputIndex, } return wait.NoError(func() error { pendingChansRequest := &lnrpc.PendingChannelsRequest{} pendingChanResp, err := node.PendingChannels( ctx, pendingChansRequest, ) if err != nil { return fmt.Errorf("unable to get pending channels: %v", err) } forceClose, err := findForceClosedChannel(pendingChanResp, &op) if err != nil { return err } // We must wait until the UTXO nursery has received the channel // and is aware of its maturity height. if forceClose.MaturityHeight == 0 { return fmt.Errorf("channel had maturity height of 0") } return nil }, defaultTimeout) } // lnrpcForceCloseChannel is a short type alias for a ridiculously long type // name in the lnrpc package. type lnrpcForceCloseChannel = lnrpc.PendingChannelsResponse_ForceClosedChannel // waitForNumChannelPendingForceClose waits for the node to report a certain // number of channels in state pending force close. func waitForNumChannelPendingForceClose(ctx context.Context, node *lntest.HarnessNode, expectedNum int, perChanCheck func(channel *lnrpcForceCloseChannel) error) error { return wait.NoError(func() error { resp, err := node.PendingChannels( ctx, &lnrpc.PendingChannelsRequest{}, ) if err != nil { return fmt.Errorf("unable to get pending channels: %v", err) } forceCloseChans := resp.PendingForceClosingChannels if len(forceCloseChans) != expectedNum { return fmt.Errorf("%v should have %d pending "+ "force close channels but has %d", node.Cfg.Name, expectedNum, len(forceCloseChans)) } if perChanCheck != nil { for _, forceCloseChan := range forceCloseChans { err := perChanCheck(forceCloseChan) if err != nil { return err } } } return nil }, defaultTimeout) } // cleanupForceClose mines a force close commitment found in the mempool and // the following sweep transaction from the force closing node. func cleanupForceClose(t *harnessTest, net *lntest.NetworkHarness, node *lntest.HarnessNode, chanPoint *lnrpc.ChannelPoint) { ctxb := context.Background() // Wait for the channel to be marked pending force close. ctxt, _ := context.WithTimeout(ctxb, defaultTimeout) err := waitForChannelPendingForceClose(ctxt, node, chanPoint) require.NoError(t.t, err, "channel not pending force close") // Mine enough blocks for the node to sweep its funds from the force // closed channel. // // The commit sweep resolver is able to broadcast the sweep tx up to // one block before the CSV elapses, so wait until defaulCSV-1. _, err = net.Miner.Client.Generate(defaultCSV - 1) require.NoError(t.t, err, "unable to generate blocks") // The node should now sweep the funds, clean up by mining the sweeping // tx. mineBlocks(t, net, 1, 1) } // numOpenChannelsPending sends an RPC request to a node to get a count of the // node's channels that are currently in a pending state (with a broadcast, but // not confirmed funding transaction). func numOpenChannelsPending(ctxt context.Context, node *lntest.HarnessNode) (int, error) { pendingChansRequest := &lnrpc.PendingChannelsRequest{} resp, err := node.PendingChannels(ctxt, pendingChansRequest) if err != nil { return 0, err } return len(resp.PendingOpenChannels), nil } // assertNumOpenChannelsPending asserts that a pair of nodes have the expected // number of pending channels between them. func assertNumOpenChannelsPending(ctxt context.Context, t *harnessTest, alice, bob *lntest.HarnessNode, expected int) { err := wait.NoError(func() error { aliceNumChans, err := numOpenChannelsPending(ctxt, alice) if err != nil { return fmt.Errorf("error fetching alice's node (%v) "+ "pending channels %v", alice.NodeID, err) } bobNumChans, err := numOpenChannelsPending(ctxt, bob) if err != nil { return fmt.Errorf("error fetching bob's node (%v) "+ "pending channels %v", bob.NodeID, err) } aliceStateCorrect := aliceNumChans == expected if !aliceStateCorrect { return fmt.Errorf("number of pending channels for "+ "alice incorrect. expected %v, got %v", expected, aliceNumChans) } bobStateCorrect := bobNumChans == expected if !bobStateCorrect { return fmt.Errorf("number of pending channels for bob "+ "incorrect. expected %v, got %v", expected, bobNumChans) } return nil }, defaultTimeout) require.NoError(t.t, err) } // assertNumConnections asserts number current connections between two peers. // TODO(yy): refactor to use wait. func assertNumConnections(t *harnessTest, alice, bob *lntest.HarnessNode, expected int) { ctxb := context.Background() const nPolls = 10 tick := time.NewTicker(300 * time.Millisecond) defer tick.Stop() for i := nPolls - 1; i >= 0; i-- { select { case <-tick.C: ctxt, _ := context.WithTimeout(ctxb, defaultTimeout) aNumPeers, err := alice.ListPeers(ctxt, &lnrpc.ListPeersRequest{}) if err != nil { t.Fatalf("unable to fetch alice's node (%v) list peers %v", alice.NodeID, err) } ctxt, _ = context.WithTimeout(ctxb, defaultTimeout) bNumPeers, err := bob.ListPeers(ctxt, &lnrpc.ListPeersRequest{}) if err != nil { t.Fatalf("unable to fetch bob's node (%v) list peers %v", bob.NodeID, err) } if len(aNumPeers.Peers) != expected { // Continue polling if this is not the final // loop. if i > 0 { continue } t.Fatalf("number of peers connected to alice is incorrect: "+ "expected %v, got %v", expected, len(aNumPeers.Peers)) } if len(bNumPeers.Peers) != expected { // Continue polling if this is not the final // loop. if i > 0 { continue } t.Fatalf("number of peers connected to bob is incorrect: "+ "expected %v, got %v", expected, len(bNumPeers.Peers)) } // Alice and Bob both have the required number of // peers, stop polling and return to caller. return } } } // shutdownAndAssert shuts down the given node and asserts that no errors // occur. func shutdownAndAssert(net *lntest.NetworkHarness, t *harnessTest, node *lntest.HarnessNode) { // The process may not be in a state to always shutdown immediately, so // we'll retry up to a hard limit to ensure we eventually shutdown. err := wait.NoError(func() error { return net.ShutdownNode(node) }, defaultTimeout) require.NoErrorf(t.t, err, "unable to shutdown %v", node.Name()) } // assertChannelBalanceResp makes a ChannelBalance request and checks the // returned response matches the expected. func assertChannelBalanceResp(t *harnessTest, node *lntest.HarnessNode, expected *lnrpc.ChannelBalanceResponse) { // nolint:interfacer resp := getChannelBalance(t, node) require.True(t.t, proto.Equal(expected, resp), "balance is incorrect") } // getChannelBalance gets the channel balance. func getChannelBalance(t *harnessTest, node *lntest.HarnessNode) *lnrpc.ChannelBalanceResponse { t.t.Helper() ctxt, _ := context.WithTimeout(context.Background(), defaultTimeout) req := &lnrpc.ChannelBalanceRequest{} resp, err := node.ChannelBalance(ctxt, req) require.NoError(t.t, err, "unable to get node's balance") return resp } // expectedChanUpdate houses params we expect a ChannelUpdate to advertise. type expectedChanUpdate struct { advertisingNode string expectedPolicy *lnrpc.RoutingPolicy chanPoint *lnrpc.ChannelPoint } // txStr returns the string representation of the channel's funding transaction. func txStr(chanPoint *lnrpc.ChannelPoint) string { fundingTxID, err := lnrpc.GetChanPointFundingTxid(chanPoint) if err != nil { return "" } cp := wire.OutPoint{ Hash: *fundingTxID, Index: chanPoint.OutputIndex, } return cp.String() } // waitForChannelUpdate waits for a node to receive the expected channel // updates. func waitForChannelUpdate(t *harnessTest, subscription graphSubscription, expUpdates []expectedChanUpdate) { // Create an array indicating which expected channel updates we have // received. found := make([]bool, len(expUpdates)) out: for { select { case graphUpdate := <-subscription.updateChan: for _, update := range graphUpdate.ChannelUpdates { require.NotZerof( t.t, len(expUpdates), "received unexpected channel "+ "update from %v for channel %v", update.AdvertisingNode, update.ChanId, ) // For each expected update, check if it matches // the update we just received. for i, exp := range expUpdates { fundingTxStr := txStr(update.ChanPoint) if fundingTxStr != txStr(exp.chanPoint) { continue } if update.AdvertisingNode != exp.advertisingNode { continue } err := checkChannelPolicy( update.RoutingPolicy, exp.expectedPolicy, ) if err != nil { continue } // We got a policy update that matched // the values and channel point of what // we expected, mark it as found. found[i] = true // If we have no more channel updates // we are waiting for, break out of the // loop. rem := 0 for _, f := range found { if !f { rem++ } } if rem == 0 { break out } // Since we found a match among the // expected updates, break out of the // inner loop. break } } case err := <-subscription.errChan: t.Fatalf("unable to recv graph update: %v", err) case <-time.After(defaultTimeout): if len(expUpdates) == 0 { return } t.Fatalf("did not receive channel update") } } } // assertNoChannelUpdates ensures that no ChannelUpdates are sent via the // graphSubscription. This method will block for the provided duration before // returning to the caller if successful. func assertNoChannelUpdates(t *harnessTest, subscription graphSubscription, duration time.Duration) { timeout := time.After(duration) for { select { case graphUpdate := <-subscription.updateChan: require.Zero( t.t, len(graphUpdate.ChannelUpdates), "no channel updates were expected", ) case err := <-subscription.errChan: t.Fatalf("graph subscription failure: %v", err) case <-timeout: // No updates received, success. return } } } // getChannelPolicies queries the channel graph and retrieves the current edge // policies for the provided channel points. func getChannelPolicies(t *harnessTest, node *lntest.HarnessNode, advertisingNode string, chanPoints ...*lnrpc.ChannelPoint) []*lnrpc.RoutingPolicy { ctxb := context.Background() descReq := &lnrpc.ChannelGraphRequest{ IncludeUnannounced: true, } ctxt, _ := context.WithTimeout(ctxb, defaultTimeout) chanGraph, err := node.DescribeGraph(ctxt, descReq) require.NoError(t.t, err, "unable to query for alice's graph") var policies []*lnrpc.RoutingPolicy err = wait.NoError(func() error { out: for _, chanPoint := range chanPoints { for _, e := range chanGraph.Edges { if e.ChanPoint != txStr(chanPoint) { continue } if e.Node1Pub == advertisingNode { policies = append(policies, e.Node1Policy) } else { policies = append(policies, e.Node2Policy) } continue out } // If we've iterated over all the known edges and we weren't // able to find this specific one, then we'll fail. return fmt.Errorf("did not find edge %v", txStr(chanPoint)) } return nil }, defaultTimeout) require.NoError(t.t, err) return policies } // assertChannelPolicy asserts that the passed node's known channel policy for // the passed chanPoint is consistent with the expected policy values. func assertChannelPolicy(t *harnessTest, node *lntest.HarnessNode, advertisingNode string, expectedPolicy *lnrpc.RoutingPolicy, chanPoints ...*lnrpc.ChannelPoint) { policies := getChannelPolicies(t, node, advertisingNode, chanPoints...) for _, policy := range policies { err := checkChannelPolicy(policy, expectedPolicy) if err != nil { t.Fatalf(err.Error()) } } } // checkChannelPolicy checks that the policy matches the expected one. func checkChannelPolicy(policy, expectedPolicy *lnrpc.RoutingPolicy) error { if policy.FeeBaseMsat != expectedPolicy.FeeBaseMsat { return fmt.Errorf("expected base fee %v, got %v", expectedPolicy.FeeBaseMsat, policy.FeeBaseMsat) } if policy.FeeRateMilliMsat != expectedPolicy.FeeRateMilliMsat { return fmt.Errorf("expected fee rate %v, got %v", expectedPolicy.FeeRateMilliMsat, policy.FeeRateMilliMsat) } if policy.TimeLockDelta != expectedPolicy.TimeLockDelta { return fmt.Errorf("expected time lock delta %v, got %v", expectedPolicy.TimeLockDelta, policy.TimeLockDelta) } if policy.MinHtlc != expectedPolicy.MinHtlc { return fmt.Errorf("expected min htlc %v, got %v", expectedPolicy.MinHtlc, policy.MinHtlc) } if policy.MaxHtlcMsat != expectedPolicy.MaxHtlcMsat { return fmt.Errorf("expected max htlc %v, got %v", expectedPolicy.MaxHtlcMsat, policy.MaxHtlcMsat) } if policy.Disabled != expectedPolicy.Disabled { return errors.New("edge should be disabled but isn't") } return nil } // assertMinerBlockHeightDelta ensures that tempMiner is 'delta' blocks ahead // of miner. func assertMinerBlockHeightDelta(t *harnessTest, miner, tempMiner *rpctest.Harness, delta int32) { // Ensure the chain lengths are what we expect. var predErr error err := wait.Predicate(func() bool { _, tempMinerHeight, err := tempMiner.Client.GetBestBlock() if err != nil { predErr = fmt.Errorf("unable to get current "+ "blockheight %v", err) return false } _, minerHeight, err := miner.Client.GetBestBlock() if err != nil { predErr = fmt.Errorf("unable to get current "+ "blockheight %v", err) return false } if tempMinerHeight != minerHeight+delta { predErr = fmt.Errorf("expected new miner(%d) to be %d "+ "blocks ahead of original miner(%d)", tempMinerHeight, delta, minerHeight) return false } return true }, defaultTimeout) if err != nil { t.Fatalf(predErr.Error()) } } func checkCommitmentMaturity( forceClose *lnrpc.PendingChannelsResponse_ForceClosedChannel, maturityHeight uint32, blocksTilMaturity int32) error { if forceClose.MaturityHeight != maturityHeight { return fmt.Errorf("expected commitment maturity height to be "+ "%d, found %d instead", maturityHeight, forceClose.MaturityHeight) } if forceClose.BlocksTilMaturity != blocksTilMaturity { return fmt.Errorf("expected commitment blocks til maturity to "+ "be %d, found %d instead", blocksTilMaturity, forceClose.BlocksTilMaturity) } return nil } // checkForceClosedChannelNumHtlcs verifies that a force closed channel has the // proper number of htlcs. func checkPendingChannelNumHtlcs( forceClose *lnrpc.PendingChannelsResponse_ForceClosedChannel, expectedNumHtlcs int) error { if len(forceClose.PendingHtlcs) != expectedNumHtlcs { return fmt.Errorf("expected force closed channel to have %d "+ "pending htlcs, found %d instead", expectedNumHtlcs, len(forceClose.PendingHtlcs)) } return nil } // checkNumForceClosedChannels checks that a pending channel response has the // expected number of force closed channels. func checkNumForceClosedChannels(pendingChanResp *lnrpc.PendingChannelsResponse, expectedNumChans int) error { if len(pendingChanResp.PendingForceClosingChannels) != expectedNumChans { return fmt.Errorf("expected to find %d force closed channels, "+ "got %d", expectedNumChans, len(pendingChanResp.PendingForceClosingChannels)) } return nil } // checkNumWaitingCloseChannels checks that a pending channel response has the // expected number of channels waiting for closing tx to confirm. func checkNumWaitingCloseChannels(pendingChanResp *lnrpc.PendingChannelsResponse, expectedNumChans int) error { if len(pendingChanResp.WaitingCloseChannels) != expectedNumChans { return fmt.Errorf("expected to find %d channels waiting "+ "closure, got %d", expectedNumChans, len(pendingChanResp.WaitingCloseChannels)) } return nil } // checkPendingHtlcStageAndMaturity uniformly tests all pending htlc's belonging // to a force closed channel, testing for the expected stage number, blocks till // maturity, and the maturity height. func checkPendingHtlcStageAndMaturity( forceClose *lnrpc.PendingChannelsResponse_ForceClosedChannel, stage, maturityHeight uint32, blocksTillMaturity int32) error { for _, pendingHtlc := range forceClose.PendingHtlcs { if pendingHtlc.Stage != stage { return fmt.Errorf("expected pending htlc to be stage "+ "%d, found %d", stage, pendingHtlc.Stage) } if pendingHtlc.MaturityHeight != maturityHeight { return fmt.Errorf("expected pending htlc maturity "+ "height to be %d, instead has %d", maturityHeight, pendingHtlc.MaturityHeight) } if pendingHtlc.BlocksTilMaturity != blocksTillMaturity { return fmt.Errorf("expected pending htlc blocks til "+ "maturity to be %d, instead has %d", blocksTillMaturity, pendingHtlc.BlocksTilMaturity) } } return nil } // assertReports checks that the count of resolutions we have present per // type matches a set of expected resolutions. func assertReports(ctxb context.Context, t *harnessTest, node *lntest.HarnessNode, channelPoint wire.OutPoint, expected map[string]*lnrpc.Resolution) { // Get our node's closed channels. ctxt, cancel := context.WithTimeout(ctxb, defaultTimeout) defer cancel() closed, err := node.ClosedChannels( ctxt, &lnrpc.ClosedChannelsRequest{}, ) require.NoError(t.t, err) var resolutions []*lnrpc.Resolution for _, close := range closed.Channels { if close.ChannelPoint == channelPoint.String() { resolutions = close.Resolutions break } } require.NotNil(t.t, resolutions) require.Equal(t.t, len(expected), len(resolutions)) for _, res := range resolutions { outPointStr := fmt.Sprintf("%v:%v", res.Outpoint.TxidStr, res.Outpoint.OutputIndex) expected, ok := expected[outPointStr] require.True(t.t, ok) require.Equal(t.t, expected, res) } } // assertSweepFound looks up a sweep in a nodes list of broadcast sweeps. func assertSweepFound(ctx context.Context, t *testing.T, node *lntest.HarnessNode, sweep string, verbose bool) { // List all sweeps that alice's node had broadcast. ctx, _ = context.WithTimeout(ctx, defaultTimeout) sweepResp, err := node.WalletKitClient.ListSweeps( ctx, &walletrpc.ListSweepsRequest{ Verbose: verbose, }, ) require.NoError(t, err) var found bool if verbose { found = findSweepInDetails(t, sweep, sweepResp) } else { found = findSweepInTxids(t, sweep, sweepResp) } require.True(t, found, "sweep: %v not found", sweep) } func findSweepInTxids(t *testing.T, sweepTxid string, sweepResp *walletrpc.ListSweepsResponse) bool { sweepTxIDs := sweepResp.GetTransactionIds() require.NotNil(t, sweepTxIDs, "expected transaction ids") require.Nil(t, sweepResp.GetTransactionDetails()) // Check that the sweep tx we have just produced is present. for _, tx := range sweepTxIDs.TransactionIds { if tx == sweepTxid { return true } } return false } func findSweepInDetails(t *testing.T, sweepTxid string, sweepResp *walletrpc.ListSweepsResponse) bool { sweepDetails := sweepResp.GetTransactionDetails() require.NotNil(t, sweepDetails, "expected transaction details") require.Nil(t, sweepResp.GetTransactionIds()) for _, tx := range sweepDetails.Transactions { if tx.TxHash == sweepTxid { return true } } return false } // assertAmountSent generates a closure which queries listchannels for sndr and // rcvr, and asserts that sndr sent amt satoshis, and that rcvr received amt // satoshis. // // NOTE: This method assumes that each node only has one channel, and it is the // channel used to send the payment. func assertAmountSent(amt btcutil.Amount, sndr, rcvr *lntest.HarnessNode) func() error { return func() error { // Both channels should also have properly accounted from the // amount that has been sent/received over the channel. listReq := &lnrpc.ListChannelsRequest{} ctxb := context.Background() ctxt, _ := context.WithTimeout(ctxb, defaultTimeout) sndrListChannels, err := sndr.ListChannels(ctxt, listReq) if err != nil { return fmt.Errorf("unable to query for %s's channel "+ "list: %v", sndr.Name(), err) } sndrSatoshisSent := sndrListChannels.Channels[0].TotalSatoshisSent if sndrSatoshisSent != int64(amt) { return fmt.Errorf("%s's satoshis sent is incorrect "+ "got %v, expected %v", sndr.Name(), sndrSatoshisSent, amt) } ctxt, _ = context.WithTimeout(ctxb, defaultTimeout) rcvrListChannels, err := rcvr.ListChannels(ctxt, listReq) if err != nil { return fmt.Errorf("unable to query for %s's channel "+ "list: %v", rcvr.Name(), err) } rcvrSatoshisReceived := rcvrListChannels.Channels[0].TotalSatoshisReceived if rcvrSatoshisReceived != int64(amt) { return fmt.Errorf("%s's satoshis received is "+ "incorrect got %v, expected %v", rcvr.Name(), rcvrSatoshisReceived, amt) } return nil } } // assertLastHTLCError checks that the last sent HTLC of the last payment sent // by the given node failed with the expected failure code. func assertLastHTLCError(t *harnessTest, node *lntest.HarnessNode, code lnrpc.Failure_FailureCode) { req := &lnrpc.ListPaymentsRequest{ IncludeIncomplete: true, } ctxt, _ := context.WithTimeout(context.Background(), defaultTimeout) paymentsResp, err := node.ListPayments(ctxt, req) require.NoError(t.t, err, "error when obtaining payments") payments := paymentsResp.Payments require.NotZero(t.t, len(payments), "no payments found") payment := payments[len(payments)-1] htlcs := payment.Htlcs require.NotZero(t.t, len(htlcs), "no htlcs") htlc := htlcs[len(htlcs)-1] require.NotNil(t.t, htlc.Failure, "expected failure") require.Equal(t.t, code, htlc.Failure.Code, "unexpected failure code") } func assertChannelConstraintsEqual( t *harnessTest, want, got *lnrpc.ChannelConstraints) { t.t.Helper() require.Equal(t.t, want.CsvDelay, got.CsvDelay, "CsvDelay mismatched") require.Equal( t.t, want.ChanReserveSat, got.ChanReserveSat, "ChanReserveSat mismatched", ) require.Equal( t.t, want.DustLimitSat, got.DustLimitSat, "DustLimitSat mismatched", ) require.Equal( t.t, want.MaxPendingAmtMsat, got.MaxPendingAmtMsat, "MaxPendingAmtMsat mismatched", ) require.Equal( t.t, want.MinHtlcMsat, got.MinHtlcMsat, "MinHtlcMsat mismatched", ) require.Equal( t.t, want.MaxAcceptedHtlcs, got.MaxAcceptedHtlcs, "MaxAcceptedHtlcs mismatched", ) } // assertAmountPaid checks that the ListChannels command of the provided // node list the total amount sent and received as expected for the // provided channel. func assertAmountPaid(t *harnessTest, channelName string, node *lntest.HarnessNode, chanPoint wire.OutPoint, amountSent, amountReceived int64) { ctxb := context.Background() checkAmountPaid := func() error { listReq := &lnrpc.ListChannelsRequest{} ctxt, _ := context.WithTimeout(ctxb, defaultTimeout) resp, err := node.ListChannels(ctxt, listReq) if err != nil { return fmt.Errorf("unable to for node's "+ "channels: %v", err) } for _, channel := range resp.Channels { if channel.ChannelPoint != chanPoint.String() { continue } if channel.TotalSatoshisSent != amountSent { return fmt.Errorf("%v: incorrect amount"+ " sent: %v != %v", channelName, channel.TotalSatoshisSent, amountSent) } if channel.TotalSatoshisReceived != amountReceived { return fmt.Errorf("%v: incorrect amount"+ " received: %v != %v", channelName, channel.TotalSatoshisReceived, amountReceived) } return nil } return fmt.Errorf("channel not found") } // As far as HTLC inclusion in commitment transaction might be // postponed we will try to check the balance couple of times, // and then if after some period of time we receive wrong // balance return the error. // TODO(roasbeef): remove sleep after invoice notification hooks // are in place var timeover uint32 go func() { <-time.After(defaultTimeout) atomic.StoreUint32(&timeover, 1) }() for { isTimeover := atomic.LoadUint32(&timeover) == 1 if err := checkAmountPaid(); err != nil { require.Falsef( t.t, isTimeover, "Check amount Paid failed: %v", err, ) } else { break } } } // assertNumPendingChannels checks that a PendingChannels response from the // node reports the expected number of pending channels. func assertNumPendingChannels(t *harnessTest, node *lntest.HarnessNode, expWaitingClose, expPendingForceClose int) { ctxb := context.Background() var predErr error err := wait.Predicate(func() bool { pendingChansRequest := &lnrpc.PendingChannelsRequest{} ctxt, _ := context.WithTimeout(ctxb, defaultTimeout) pendingChanResp, err := node.PendingChannels(ctxt, pendingChansRequest) if err != nil { predErr = fmt.Errorf("unable to query for pending "+ "channels: %v", err) return false } n := len(pendingChanResp.WaitingCloseChannels) if n != expWaitingClose { predErr = fmt.Errorf("Expected to find %d channels "+ "waiting close, found %d", expWaitingClose, n) return false } n = len(pendingChanResp.PendingForceClosingChannels) if n != expPendingForceClose { predErr = fmt.Errorf("expected to find %d channel "+ "pending force close, found %d", expPendingForceClose, n) return false } return true }, defaultTimeout) require.NoErrorf(t.t, err, "got err: %v", predErr) } // assertDLPExecuted asserts that Dave is a node that has recovered their state // form scratch. Carol should then force close on chain, with Dave sweeping his // funds immediately, and Carol sweeping her fund after her CSV delay is up. If // the blankSlate value is true, then this means that Dave won't need to sweep // on chain as he has no funds in the channel. func assertDLPExecuted(net *lntest.NetworkHarness, t *harnessTest, carol *lntest.HarnessNode, carolStartingBalance int64, dave *lntest.HarnessNode, daveStartingBalance int64, anchors bool) { // Increase the fee estimate so that the following force close tx will // be cpfp'ed. net.SetFeeEstimate(30000) // We disabled auto-reconnect for some tests to avoid timing issues. // To make sure the nodes are initiating DLP now, we have to manually // re-connect them. ctxb := context.Background() net.EnsureConnected(ctxb, t.t, carol, dave) // Upon reconnection, the nodes should detect that Dave is out of sync. // Carol should force close the channel using her latest commitment. expectedTxes := 1 if anchors { expectedTxes = 2 } _, err := waitForNTxsInMempool( net.Miner.Client, expectedTxes, minerMempoolTimeout, ) require.NoError( t.t, err, "unable to find Carol's force close tx in mempool", ) // Channel should be in the state "waiting close" for Carol since she // broadcasted the force close tx. assertNumPendingChannels(t, carol, 1, 0) // Dave should also consider the channel "waiting close", as he noticed // the channel was out of sync, and is now waiting for a force close to // hit the chain. assertNumPendingChannels(t, dave, 1, 0) // Restart Dave to make sure he is able to sweep the funds after // shutdown. require.NoError(t.t, net.RestartNode(dave, nil), "Node restart failed") // Generate a single block, which should confirm the closing tx. _ = mineBlocks(t, net, 1, expectedTxes)[0] // Dave should sweep his funds immediately, as they are not timelocked. // We also expect Dave to sweep his anchor, if present. _, err = waitForNTxsInMempool( net.Miner.Client, expectedTxes, minerMempoolTimeout, ) require.NoError(t.t, err, "unable to find Dave's sweep tx in mempool") // Dave should consider the channel pending force close (since he is // waiting for his sweep to confirm). assertNumPendingChannels(t, dave, 0, 1) // Carol is considering it "pending force close", as we must wait // before she can sweep her outputs. assertNumPendingChannels(t, carol, 0, 1) // Mine the sweep tx. _ = mineBlocks(t, net, 1, expectedTxes)[0] // Now Dave should consider the channel fully closed. assertNumPendingChannels(t, dave, 0, 0) // We query Dave's balance to make sure it increased after the channel // closed. This checks that he was able to sweep the funds he had in // the channel. ctxt, _ := context.WithTimeout(ctxb, defaultTimeout) balReq := &lnrpc.WalletBalanceRequest{} daveBalResp, err := dave.WalletBalance(ctxt, balReq) require.NoError(t.t, err, "unable to get dave's balance") daveBalance := daveBalResp.ConfirmedBalance require.Greater( t.t, daveBalance, daveStartingBalance, "balance not increased", ) // After the Carol's output matures, she should also reclaim her funds. // // The commit sweep resolver publishes the sweep tx at defaultCSV-1 and // we already mined one block after the commitmment was published, so // take that into account. mineBlocks(t, net, defaultCSV-1-1, 0) carolSweep, err := waitForTxInMempool( net.Miner.Client, minerMempoolTimeout, ) require.NoError(t.t, err, "unable to find Carol's sweep tx in mempool") block := mineBlocks(t, net, 1, 1)[0] assertTxInBlock(t, block, carolSweep) // Now the channel should be fully closed also from Carol's POV. assertNumPendingChannels(t, carol, 0, 0) // Make sure Carol got her balance back. err = wait.NoError(func() error { ctxt, _ = context.WithTimeout(ctxb, defaultTimeout) carolBalResp, err := carol.WalletBalance(ctxt, balReq) if err != nil { return fmt.Errorf("unable to get carol's balance: %v", err) } carolBalance := carolBalResp.ConfirmedBalance if carolBalance <= carolStartingBalance { return fmt.Errorf("expected carol to have balance "+ "above %d, instead had %v", carolStartingBalance, carolBalance) } return nil }, defaultTimeout) require.NoError(t.t, err) assertNodeNumChannels(t, dave, 0) assertNodeNumChannels(t, carol, 0) } // verifyCloseUpdate is used to verify that a closed channel update is of the // expected type. func verifyCloseUpdate(chanUpdate *lnrpc.ChannelEventUpdate, closeType lnrpc.ChannelCloseSummary_ClosureType, closeInitiator lnrpc.Initiator) error { // We should receive one inactive and one closed notification // for each channel. switch update := chanUpdate.Channel.(type) { case *lnrpc.ChannelEventUpdate_InactiveChannel: if chanUpdate.Type != lnrpc.ChannelEventUpdate_INACTIVE_CHANNEL { return fmt.Errorf("update type mismatch: expected %v, got %v", lnrpc.ChannelEventUpdate_INACTIVE_CHANNEL, chanUpdate.Type) } case *lnrpc.ChannelEventUpdate_ClosedChannel: if chanUpdate.Type != lnrpc.ChannelEventUpdate_CLOSED_CHANNEL { return fmt.Errorf("update type mismatch: expected %v, got %v", lnrpc.ChannelEventUpdate_CLOSED_CHANNEL, chanUpdate.Type) } if update.ClosedChannel.CloseType != closeType { return fmt.Errorf("channel closure type "+ "mismatch: expected %v, got %v", closeType, update.ClosedChannel.CloseType) } if update.ClosedChannel.CloseInitiator != closeInitiator { return fmt.Errorf("expected close intiator: %v, got: %v", closeInitiator, update.ClosedChannel.CloseInitiator) } default: return fmt.Errorf("channel update channel of wrong type, "+ "expected closed channel, got %T", update) } return nil } // assertNodeNumChannels polls the provided node's list channels rpc until it // reaches the desired number of total channels. func assertNodeNumChannels(t *harnessTest, node *lntest.HarnessNode, numChannels int) { ctxb := context.Background() // Poll node for its list of channels. req := &lnrpc.ListChannelsRequest{} var predErr error pred := func() bool { ctxt, _ := context.WithTimeout(ctxb, defaultTimeout) chanInfo, err := node.ListChannels(ctxt, req) if err != nil { predErr = fmt.Errorf("unable to query for node's "+ "channels: %v", err) return false } // Return true if the query returned the expected number of // channels. num := len(chanInfo.Channels) if num != numChannels { predErr = fmt.Errorf("expected %v channels, got %v", numChannels, num) return false } return true } require.NoErrorf( t.t, wait.Predicate(pred, defaultTimeout), "node has incorrect number of channels: %v", predErr, ) } func assertSyncType(t *harnessTest, node *lntest.HarnessNode, peer string, syncType lnrpc.Peer_SyncType) { t.t.Helper() ctxb := context.Background() ctxt, _ := context.WithTimeout(ctxb, defaultTimeout) resp, err := node.ListPeers(ctxt, &lnrpc.ListPeersRequest{}) require.NoError(t.t, err) for _, rpcPeer := range resp.Peers { if rpcPeer.PubKey != peer { continue } require.Equal(t.t, syncType, rpcPeer.SyncType) return } t.t.Fatalf("unable to find peer: %s", peer) } // assertActiveHtlcs makes sure all the passed nodes have the _exact_ HTLCs // matching payHashes on _all_ their channels. func assertActiveHtlcs(nodes []*lntest.HarnessNode, payHashes ...[]byte) error { ctxb := context.Background() req := &lnrpc.ListChannelsRequest{} for _, node := range nodes { ctxt, _ := context.WithTimeout(ctxb, defaultTimeout) nodeChans, err := node.ListChannels(ctxt, req) if err != nil { return fmt.Errorf("unable to get node chans: %v", err) } for _, channel := range nodeChans.Channels { // Record all payment hashes active for this channel. htlcHashes := make(map[string]struct{}) for _, htlc := range channel.PendingHtlcs { h := hex.EncodeToString(htlc.HashLock) _, ok := htlcHashes[h] if ok { return fmt.Errorf("duplicate HashLock") } htlcHashes[h] = struct{}{} } // Channel should have exactly the payHashes active. if len(payHashes) != len(htlcHashes) { return fmt.Errorf("node %x had %v htlcs active, "+ "expected %v", node.PubKey[:], len(htlcHashes), len(payHashes)) } // Make sure all the payHashes are active. for _, payHash := range payHashes { h := hex.EncodeToString(payHash) if _, ok := htlcHashes[h]; ok { continue } return fmt.Errorf("node %x didn't have the "+ "payHash %v active", node.PubKey[:], h) } } } return nil } func assertNumActiveHtlcsChanPoint(node *lntest.HarnessNode, chanPoint wire.OutPoint, numHtlcs int) error { ctxb := context.Background() req := &lnrpc.ListChannelsRequest{} ctxt, _ := context.WithTimeout(ctxb, defaultTimeout) nodeChans, err := node.ListChannels(ctxt, req) if err != nil { return err } for _, channel := range nodeChans.Channels { if channel.ChannelPoint != chanPoint.String() { continue } if len(channel.PendingHtlcs) != numHtlcs { return fmt.Errorf("expected %v active HTLCs, got %v", numHtlcs, len(channel.PendingHtlcs)) } return nil } return fmt.Errorf("channel point %v not found", chanPoint) } func assertNumActiveHtlcs(nodes []*lntest.HarnessNode, numHtlcs int) error { ctxb := context.Background() req := &lnrpc.ListChannelsRequest{} for _, node := range nodes { ctxt, _ := context.WithTimeout(ctxb, defaultTimeout) nodeChans, err := node.ListChannels(ctxt, req) if err != nil { return err } for _, channel := range nodeChans.Channels { if len(channel.PendingHtlcs) != numHtlcs { return fmt.Errorf("expected %v HTLCs, got %v", numHtlcs, len(channel.PendingHtlcs)) } } } return nil } func assertSpendingTxInMempool(t *harnessTest, miner *rpcclient.Client, timeout time.Duration, chanPoint wire.OutPoint) chainhash.Hash { tx := getSpendingTxInMempool(t, miner, timeout, chanPoint) return tx.TxHash() } // getSpendingTxInMempool waits for a transaction spending the given outpoint to // appear in the mempool and returns that tx in full. func getSpendingTxInMempool(t *harnessTest, miner *rpcclient.Client, timeout time.Duration, chanPoint wire.OutPoint) *wire.MsgTx { breakTimeout := time.After(timeout) ticker := time.NewTicker(50 * time.Millisecond) defer ticker.Stop() for { select { case <-breakTimeout: t.Fatalf("didn't find tx in mempool") case <-ticker.C: mempool, err := miner.GetRawMempool() require.NoError(t.t, err, "unable to get mempool") if len(mempool) == 0 { continue } for _, txid := range mempool { tx, err := miner.GetRawTransaction(txid) require.NoError(t.t, err, "unable to fetch tx") msgTx := tx.MsgTx() for _, txIn := range msgTx.TxIn { if txIn.PreviousOutPoint == chanPoint { return msgTx } } } } } } // assertTxLabel is a helper function which finds a target tx in our set // of transactions and checks that it has the desired label. func assertTxLabel(ctx context.Context, t *harnessTest, node *lntest.HarnessNode, targetTx, label string) { // List all transactions relevant to our wallet, and find the tx so that // we can check the correct label has been set. ctxt, cancel := context.WithTimeout(ctx, defaultTimeout) defer cancel() txResp, err := node.GetTransactions( ctxt, &lnrpc.GetTransactionsRequest{}, ) require.NoError(t.t, err, "could not get transactions") // Find our transaction in the set of transactions returned and check // its label. for _, txn := range txResp.Transactions { if txn.TxHash == targetTx { require.Equal(t.t, label, txn.Label, "labels not match") } } } // sendAndAssertSuccess sends the given payment requests and asserts that the // payment completes successfully. func sendAndAssertSuccess(ctx context.Context, t *harnessTest, node *lntest.HarnessNode, req *routerrpc.SendPaymentRequest) *lnrpc.Payment { var result *lnrpc.Payment err := wait.NoError(func() error { stream, err := node.RouterClient.SendPaymentV2(ctx, req) if err != nil { return fmt.Errorf("unable to send payment: %v", err) } result, err = getPaymentResult(stream) if err != nil { return fmt.Errorf("unable to get payment result: %v", err) } if result.Status != lnrpc.Payment_SUCCEEDED { return fmt.Errorf("payment failed: %v", result.Status) } return nil }, defaultTimeout) require.NoError(t.t, err) return result } // sendAndAssertFailure sends the given payment requests and asserts that the // payment fails with the expected reason. func sendAndAssertFailure(t *harnessTest, node *lntest.HarnessNode, req *routerrpc.SendPaymentRequest, failureReason lnrpc.PaymentFailureReason) *lnrpc.Payment { ctx, cancel := context.WithTimeout(context.Background(), defaultTimeout) defer cancel() stream, err := node.RouterClient.SendPaymentV2(ctx, req) require.NoError(t.t, err, "unable to send payment") result, err := getPaymentResult(stream) require.NoError(t.t, err, "unable to get payment result") require.Equal( t.t, lnrpc.Payment_FAILED, result.Status, "payment was expected to fail, but succeeded", ) require.Equal( t.t, failureReason, result.FailureReason, "payment failureReason not matched", ) return result } // getPaymentResult reads a final result from the stream and returns it. func getPaymentResult(stream routerrpc.Router_SendPaymentV2Client) ( *lnrpc.Payment, error) { for { payment, err := stream.Recv() if err != nil { return nil, err } if payment.Status != lnrpc.Payment_IN_FLIGHT { return payment, nil } } }