lnd.xprv/lntest/itest/assertions.go
2021-07-13 14:52:08 +08:00

1724 lines
51 KiB
Go

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
}
}
}