mirror of
https://github.com/lightningnetwork/lnd.git
synced 2024-11-19 01:43:16 +01:00
653a8ac55e
This commit replaces `AssertTopologyChannelOpen` with `AssertChannelInGraph`, which asserts a given channel edge is found. `AssertTopologyChannelOpen` only asserts a given edge has been received via the topology subscription, while we need to make sure the channel is in the graph before continuing our tests.
2789 lines
80 KiB
Go
2789 lines
80 KiB
Go
package lntest
|
|
|
|
import (
|
|
"bytes"
|
|
"context"
|
|
"crypto/rand"
|
|
"encoding/hex"
|
|
"encoding/json"
|
|
"fmt"
|
|
"math"
|
|
"sort"
|
|
"strings"
|
|
"time"
|
|
|
|
"github.com/btcsuite/btcd/btcec/v2"
|
|
"github.com/btcsuite/btcd/btcec/v2/schnorr"
|
|
"github.com/btcsuite/btcd/btcutil"
|
|
"github.com/btcsuite/btcd/chaincfg/chainhash"
|
|
"github.com/btcsuite/btcd/txscript"
|
|
"github.com/btcsuite/btcd/wire"
|
|
"github.com/lightningnetwork/lnd/channeldb"
|
|
"github.com/lightningnetwork/lnd/fn"
|
|
"github.com/lightningnetwork/lnd/lnrpc"
|
|
"github.com/lightningnetwork/lnd/lnrpc/invoicesrpc"
|
|
"github.com/lightningnetwork/lnd/lnrpc/routerrpc"
|
|
"github.com/lightningnetwork/lnd/lnrpc/walletrpc"
|
|
"github.com/lightningnetwork/lnd/lntest/miner"
|
|
"github.com/lightningnetwork/lnd/lntest/node"
|
|
"github.com/lightningnetwork/lnd/lntest/rpc"
|
|
"github.com/lightningnetwork/lnd/lntest/wait"
|
|
"github.com/lightningnetwork/lnd/lntypes"
|
|
"github.com/stretchr/testify/require"
|
|
"google.golang.org/protobuf/proto"
|
|
)
|
|
|
|
// FindChannelOption is a functional type for an option that modifies a
|
|
// ListChannelsRequest.
|
|
type ListChannelOption func(r *lnrpc.ListChannelsRequest)
|
|
|
|
// WithPeerAliasLookup is an option for setting the peer alias lookup flag on a
|
|
// ListChannelsRequest.
|
|
func WithPeerAliasLookup() ListChannelOption {
|
|
return func(r *lnrpc.ListChannelsRequest) {
|
|
r.PeerAliasLookup = true
|
|
}
|
|
}
|
|
|
|
// WaitForBlockchainSync waits until the node is synced to chain.
|
|
func (h *HarnessTest) WaitForBlockchainSync(hn *node.HarnessNode) {
|
|
err := wait.NoError(func() error {
|
|
resp := hn.RPC.GetInfo()
|
|
if resp.SyncedToChain {
|
|
return nil
|
|
}
|
|
|
|
return fmt.Errorf("%s is not synced to chain", hn.Name())
|
|
}, DefaultTimeout)
|
|
|
|
require.NoError(h, err, "timeout waiting for blockchain sync")
|
|
}
|
|
|
|
// WaitForBlockchainSyncTo waits until the node is synced to bestBlock.
|
|
func (h *HarnessTest) WaitForBlockchainSyncTo(hn *node.HarnessNode,
|
|
bestBlock *wire.MsgBlock) {
|
|
|
|
bestBlockHash := bestBlock.BlockHash().String()
|
|
err := wait.NoError(func() error {
|
|
resp := hn.RPC.GetInfo()
|
|
if resp.SyncedToChain {
|
|
if resp.BlockHash == bestBlockHash {
|
|
return nil
|
|
}
|
|
|
|
return fmt.Errorf("%s's backend is synced to the "+
|
|
"wrong block (expected=%s, actual=%s)",
|
|
hn.Name(), bestBlockHash, resp.BlockHash)
|
|
}
|
|
|
|
return fmt.Errorf("%s is not synced to chain", hn.Name())
|
|
}, DefaultTimeout)
|
|
|
|
require.NoError(h, err, "timeout waiting for blockchain sync")
|
|
}
|
|
|
|
// AssertPeerConnected asserts that the given node b is connected to a.
|
|
func (h *HarnessTest) AssertPeerConnected(a, b *node.HarnessNode) {
|
|
err := wait.NoError(func() error {
|
|
// We require the RPC call to be succeeded and won't wait for
|
|
// it as it's an unexpected behavior.
|
|
resp := a.RPC.ListPeers()
|
|
|
|
// If node B is seen in the ListPeers response from node A,
|
|
// then we can return true as the connection has been fully
|
|
// established.
|
|
for _, peer := range resp.Peers {
|
|
if peer.PubKey == b.PubKeyStr {
|
|
return nil
|
|
}
|
|
}
|
|
|
|
return fmt.Errorf("%s not found in %s's ListPeers",
|
|
b.Name(), a.Name())
|
|
}, DefaultTimeout)
|
|
|
|
require.NoError(h, err, "unable to connect %s to %s, got error: "+
|
|
"peers not connected within %v seconds",
|
|
a.Name(), b.Name(), DefaultTimeout)
|
|
}
|
|
|
|
// ConnectNodes creates a connection between the two nodes and asserts the
|
|
// connection is succeeded.
|
|
func (h *HarnessTest) ConnectNodes(a, b *node.HarnessNode) {
|
|
bobInfo := b.RPC.GetInfo()
|
|
|
|
req := &lnrpc.ConnectPeerRequest{
|
|
Addr: &lnrpc.LightningAddress{
|
|
Pubkey: bobInfo.IdentityPubkey,
|
|
Host: b.Cfg.P2PAddr(),
|
|
},
|
|
}
|
|
a.RPC.ConnectPeer(req)
|
|
h.AssertPeerConnected(a, b)
|
|
}
|
|
|
|
// ConnectNodesPerm creates a persistent connection between the two nodes and
|
|
// asserts the connection is succeeded.
|
|
func (h *HarnessTest) ConnectNodesPerm(a, b *node.HarnessNode) {
|
|
bobInfo := b.RPC.GetInfo()
|
|
|
|
req := &lnrpc.ConnectPeerRequest{
|
|
Addr: &lnrpc.LightningAddress{
|
|
Pubkey: bobInfo.IdentityPubkey,
|
|
Host: b.Cfg.P2PAddr(),
|
|
},
|
|
Perm: true,
|
|
}
|
|
a.RPC.ConnectPeer(req)
|
|
h.AssertPeerConnected(a, b)
|
|
}
|
|
|
|
// DisconnectNodes disconnects the given two nodes and asserts the
|
|
// disconnection is succeeded. The request is made from node a and sent to node
|
|
// b.
|
|
func (h *HarnessTest) DisconnectNodes(a, b *node.HarnessNode) {
|
|
bobInfo := b.RPC.GetInfo()
|
|
a.RPC.DisconnectPeer(bobInfo.IdentityPubkey)
|
|
|
|
// Assert disconnected.
|
|
h.AssertPeerNotConnected(a, b)
|
|
}
|
|
|
|
// EnsureConnected will try to connect to two nodes, returning no error if they
|
|
// are already connected. If the nodes were not connected previously, this will
|
|
// behave the same as ConnectNodes. If a pending connection request has already
|
|
// been made, the method will block until the two nodes appear in each other's
|
|
// peers list, or until the DefaultTimeout expires.
|
|
func (h *HarnessTest) EnsureConnected(a, b *node.HarnessNode) {
|
|
// errConnectionRequested is used to signal that a connection was
|
|
// requested successfully, which is distinct from already being
|
|
// connected to the peer.
|
|
errConnectionRequested := "connection request in progress"
|
|
|
|
// windowsErr is an error we've seen from windows build where
|
|
// connecting to an already connected node gives such error from the
|
|
// receiver side.
|
|
windowsErr := "An established connection was aborted by the software " +
|
|
"in your host machine."
|
|
|
|
tryConnect := func(a, b *node.HarnessNode) error {
|
|
bInfo := b.RPC.GetInfo()
|
|
|
|
req := &lnrpc.ConnectPeerRequest{
|
|
Addr: &lnrpc.LightningAddress{
|
|
Pubkey: bInfo.IdentityPubkey,
|
|
Host: b.Cfg.P2PAddr(),
|
|
},
|
|
}
|
|
|
|
ctxt, cancel := context.WithTimeout(h.runCtx, DefaultTimeout)
|
|
defer cancel()
|
|
|
|
_, err := a.RPC.LN.ConnectPeer(ctxt, req)
|
|
|
|
// Request was successful.
|
|
if err == nil {
|
|
return nil
|
|
}
|
|
|
|
// If the two are already connected, we return early with no
|
|
// error.
|
|
if strings.Contains(err.Error(), "already connected to peer") {
|
|
return nil
|
|
}
|
|
|
|
// Otherwise we log the error to console.
|
|
h.Logf("EnsureConnected %s=>%s got err: %v", a.Name(),
|
|
b.Name(), err)
|
|
|
|
// If the connection is in process, we return no error.
|
|
if strings.Contains(err.Error(), errConnectionRequested) {
|
|
return nil
|
|
}
|
|
|
|
// We may get connection refused error if we happens to be in
|
|
// the middle of a previous node disconnection, e.g., a restart
|
|
// from one of the nodes.
|
|
if strings.Contains(err.Error(), "connection refused") {
|
|
return nil
|
|
}
|
|
|
|
// Check for windows error. If Alice connects to Bob, Alice
|
|
// will throw "i/o timeout" and Bob will give windowsErr.
|
|
if strings.Contains(err.Error(), windowsErr) {
|
|
return nil
|
|
}
|
|
|
|
if strings.Contains(err.Error(), "i/o timeout") {
|
|
return nil
|
|
}
|
|
|
|
return err
|
|
}
|
|
|
|
// Return any critical errors returned by either alice or bob.
|
|
require.NoError(h, tryConnect(a, b), "connection failed between %s "+
|
|
"and %s", a.Cfg.Name, b.Cfg.Name)
|
|
|
|
// When Alice and Bob each makes a connection to the other side at the
|
|
// same time, it's likely neither connections could succeed. Bob's
|
|
// connection will be canceled by Alice since she has an outbound
|
|
// connection to Bob already, and same happens to Alice's. Thus the two
|
|
// connections cancel each other out.
|
|
// TODO(yy): move this back when the above issue is fixed.
|
|
// require.NoError(h, tryConnect(b, a), "connection failed between %s "+
|
|
// "and %s", a.Cfg.Name, b.Cfg.Name)
|
|
|
|
// Otherwise one or both requested a connection, so we wait for the
|
|
// peers lists to reflect the connection.
|
|
h.AssertPeerConnected(a, b)
|
|
h.AssertPeerConnected(b, a)
|
|
}
|
|
|
|
// AssertNumActiveEdges checks that an expected number of active edges can be
|
|
// found in the node specified.
|
|
func (h *HarnessTest) AssertNumActiveEdges(hn *node.HarnessNode,
|
|
expected int, includeUnannounced bool) []*lnrpc.ChannelEdge {
|
|
|
|
var edges []*lnrpc.ChannelEdge
|
|
|
|
old := hn.State.Edge.Public
|
|
if includeUnannounced {
|
|
old = hn.State.Edge.Total
|
|
}
|
|
|
|
// filterDisabled is a helper closure that filters out disabled
|
|
// channels.
|
|
filterDisabled := func(edge *lnrpc.ChannelEdge) bool {
|
|
if edge.Node1Policy.Disabled {
|
|
return false
|
|
}
|
|
if edge.Node2Policy.Disabled {
|
|
return false
|
|
}
|
|
|
|
return true
|
|
}
|
|
|
|
err := wait.NoError(func() error {
|
|
req := &lnrpc.ChannelGraphRequest{
|
|
IncludeUnannounced: includeUnannounced,
|
|
}
|
|
resp := hn.RPC.DescribeGraph(req)
|
|
activeEdges := fn.Filter(filterDisabled, resp.Edges)
|
|
total := len(activeEdges)
|
|
|
|
if total-old == expected {
|
|
if expected != 0 {
|
|
// NOTE: assume edges come in ascending order
|
|
// that the old edges are at the front of the
|
|
// slice.
|
|
edges = activeEdges[old:]
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
return errNumNotMatched(hn.Name(), "num of channel edges",
|
|
expected, total-old, total, old)
|
|
}, DefaultTimeout)
|
|
|
|
require.NoError(h, err, "timeout while checking for edges")
|
|
|
|
return edges
|
|
}
|
|
|
|
// AssertNumEdges checks that an expected number of edges can be found in the
|
|
// node specified.
|
|
func (h *HarnessTest) AssertNumEdges(hn *node.HarnessNode,
|
|
expected int, includeUnannounced bool) []*lnrpc.ChannelEdge {
|
|
|
|
var edges []*lnrpc.ChannelEdge
|
|
|
|
old := hn.State.Edge.Public
|
|
if includeUnannounced {
|
|
old = hn.State.Edge.Total
|
|
}
|
|
|
|
err := wait.NoError(func() error {
|
|
req := &lnrpc.ChannelGraphRequest{
|
|
IncludeUnannounced: includeUnannounced,
|
|
}
|
|
resp := hn.RPC.DescribeGraph(req)
|
|
total := len(resp.Edges)
|
|
|
|
if total-old == expected {
|
|
if expected != 0 {
|
|
// NOTE: assume edges come in ascending order
|
|
// that the old edges are at the front of the
|
|
// slice.
|
|
edges = resp.Edges[old:]
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
return errNumNotMatched(hn.Name(), "num of channel edges",
|
|
expected, total-old, total, old)
|
|
}, DefaultTimeout)
|
|
|
|
require.NoError(h, err, "timeout while checking for edges")
|
|
|
|
return edges
|
|
}
|
|
|
|
// ReceiveOpenChannelUpdate waits until a message is received on the stream or
|
|
// the timeout is reached.
|
|
func (h *HarnessTest) ReceiveOpenChannelUpdate(
|
|
stream rpc.OpenChanClient) *lnrpc.OpenStatusUpdate {
|
|
|
|
update, err := h.receiveOpenChannelUpdate(stream)
|
|
require.NoError(h, err, "received err from open channel stream")
|
|
|
|
return update
|
|
}
|
|
|
|
// ReceiveOpenChannelError waits for the expected error during the open channel
|
|
// flow from the peer or times out.
|
|
func (h *HarnessTest) ReceiveOpenChannelError(
|
|
stream rpc.OpenChanClient, expectedErr error) {
|
|
|
|
_, err := h.receiveOpenChannelUpdate(stream)
|
|
require.Contains(h, err.Error(), expectedErr.Error(),
|
|
"error not matched")
|
|
}
|
|
|
|
// receiveOpenChannelUpdate waits until a message or an error is received on
|
|
// the stream or the timeout is reached.
|
|
//
|
|
// TODO(yy): use generics to unify all receiving stream update once go@1.18 is
|
|
// used.
|
|
func (h *HarnessTest) receiveOpenChannelUpdate(
|
|
stream rpc.OpenChanClient) (*lnrpc.OpenStatusUpdate, error) {
|
|
|
|
chanMsg := make(chan *lnrpc.OpenStatusUpdate)
|
|
errChan := make(chan error)
|
|
go func() {
|
|
// Consume one message. This will block until the message is
|
|
// received.
|
|
resp, err := stream.Recv()
|
|
if err != nil {
|
|
errChan <- err
|
|
return
|
|
}
|
|
chanMsg <- resp
|
|
}()
|
|
|
|
select {
|
|
case <-time.After(DefaultTimeout):
|
|
require.Fail(h, "timeout", "timeout waiting for open channel "+
|
|
"update sent")
|
|
return nil, nil
|
|
|
|
case err := <-errChan:
|
|
return nil, err
|
|
|
|
case updateMsg := <-chanMsg:
|
|
return updateMsg, nil
|
|
}
|
|
}
|
|
|
|
// WaitForChannelOpenEvent waits for a notification that a channel is open by
|
|
// consuming a message from the passed open channel stream.
|
|
func (h HarnessTest) WaitForChannelOpenEvent(
|
|
stream rpc.OpenChanClient) *lnrpc.ChannelPoint {
|
|
|
|
// Consume one event.
|
|
event := h.ReceiveOpenChannelUpdate(stream)
|
|
|
|
resp, ok := event.Update.(*lnrpc.OpenStatusUpdate_ChanOpen)
|
|
require.Truef(h, ok, "expected channel open update, instead got %v",
|
|
resp)
|
|
|
|
return resp.ChanOpen.ChannelPoint
|
|
}
|
|
|
|
// AssertChannelExists asserts that an active channel identified by the
|
|
// specified channel point exists from the point-of-view of the node.
|
|
func (h *HarnessTest) AssertChannelExists(hn *node.HarnessNode,
|
|
cp *lnrpc.ChannelPoint) *lnrpc.Channel {
|
|
|
|
return h.assertChannelStatus(hn, cp, true)
|
|
}
|
|
|
|
// AssertChannelActive checks if a channel identified by the specified channel
|
|
// point is active.
|
|
func (h *HarnessTest) AssertChannelActive(hn *node.HarnessNode,
|
|
cp *lnrpc.ChannelPoint) *lnrpc.Channel {
|
|
|
|
return h.assertChannelStatus(hn, cp, true)
|
|
}
|
|
|
|
// AssertChannelInactive checks if a channel identified by the specified channel
|
|
// point is inactive.
|
|
func (h *HarnessTest) AssertChannelInactive(hn *node.HarnessNode,
|
|
cp *lnrpc.ChannelPoint) *lnrpc.Channel {
|
|
|
|
return h.assertChannelStatus(hn, cp, false)
|
|
}
|
|
|
|
// assertChannelStatus asserts that a channel identified by the specified
|
|
// channel point exists from the point-of-view of the node and that it is either
|
|
// active or inactive depending on the value of the active parameter.
|
|
func (h *HarnessTest) assertChannelStatus(hn *node.HarnessNode,
|
|
cp *lnrpc.ChannelPoint, active bool) *lnrpc.Channel {
|
|
|
|
var (
|
|
channel *lnrpc.Channel
|
|
err error
|
|
)
|
|
|
|
err = wait.NoError(func() error {
|
|
channel, err = h.findChannel(hn, cp)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
// Check whether the channel is active, exit early if it is.
|
|
if channel.Active == active {
|
|
return nil
|
|
}
|
|
|
|
return fmt.Errorf("expected channel_active=%v, got %v",
|
|
active, channel.Active)
|
|
}, DefaultTimeout)
|
|
|
|
require.NoErrorf(h, err, "%s: timeout checking for channel point: %v",
|
|
hn.Name(), cp)
|
|
|
|
return channel
|
|
}
|
|
|
|
// AssertOutputScriptClass checks that the specified transaction output has the
|
|
// expected script class.
|
|
func (h *HarnessTest) AssertOutputScriptClass(tx *btcutil.Tx,
|
|
outputIndex uint32, scriptClass txscript.ScriptClass) {
|
|
|
|
require.Greater(h, len(tx.MsgTx().TxOut), int(outputIndex))
|
|
|
|
txOut := tx.MsgTx().TxOut[outputIndex]
|
|
|
|
pkScript, err := txscript.ParsePkScript(txOut.PkScript)
|
|
require.NoError(h, err)
|
|
require.Equal(h, scriptClass, pkScript.Class())
|
|
}
|
|
|
|
// findChannel tries to find a target channel in the node using the given
|
|
// channel point.
|
|
func (h *HarnessTest) findChannel(hn *node.HarnessNode,
|
|
chanPoint *lnrpc.ChannelPoint,
|
|
opts ...ListChannelOption) (*lnrpc.Channel, error) {
|
|
|
|
// Get the funding point.
|
|
fp := h.OutPointFromChannelPoint(chanPoint)
|
|
|
|
req := &lnrpc.ListChannelsRequest{}
|
|
|
|
for _, opt := range opts {
|
|
opt(req)
|
|
}
|
|
|
|
channelInfo := hn.RPC.ListChannels(req)
|
|
|
|
// Find the target channel.
|
|
for _, channel := range channelInfo.Channels {
|
|
if channel.ChannelPoint == fp.String() {
|
|
return channel, nil
|
|
}
|
|
}
|
|
|
|
return nil, fmt.Errorf("%s: channel not found using %s", hn.Name(),
|
|
fp.String())
|
|
}
|
|
|
|
// ReceiveCloseChannelUpdate waits until a message or an error is received on
|
|
// the subscribe channel close stream or the timeout is reached.
|
|
func (h *HarnessTest) ReceiveCloseChannelUpdate(
|
|
stream rpc.CloseChanClient) (*lnrpc.CloseStatusUpdate, error) {
|
|
|
|
chanMsg := make(chan *lnrpc.CloseStatusUpdate)
|
|
errChan := make(chan error)
|
|
go func() {
|
|
// Consume one message. This will block until the message is
|
|
// received.
|
|
resp, err := stream.Recv()
|
|
if err != nil {
|
|
errChan <- err
|
|
return
|
|
}
|
|
chanMsg <- resp
|
|
}()
|
|
|
|
select {
|
|
case <-time.After(DefaultTimeout):
|
|
require.Fail(h, "timeout", "timeout waiting for close channel "+
|
|
"update sent")
|
|
|
|
return nil, nil
|
|
|
|
case err := <-errChan:
|
|
return nil, fmt.Errorf("received err from close channel "+
|
|
"stream: %v", err)
|
|
|
|
case updateMsg := <-chanMsg:
|
|
return updateMsg, nil
|
|
}
|
|
}
|
|
|
|
type WaitingCloseChannel *lnrpc.PendingChannelsResponse_WaitingCloseChannel
|
|
|
|
// AssertChannelWaitingClose asserts that the given channel found in the node
|
|
// is waiting close. Returns the WaitingCloseChannel if found.
|
|
func (h *HarnessTest) AssertChannelWaitingClose(hn *node.HarnessNode,
|
|
chanPoint *lnrpc.ChannelPoint) WaitingCloseChannel {
|
|
|
|
var target WaitingCloseChannel
|
|
|
|
op := h.OutPointFromChannelPoint(chanPoint)
|
|
|
|
err := wait.NoError(func() error {
|
|
resp := hn.RPC.PendingChannels()
|
|
|
|
for _, waitingClose := range resp.WaitingCloseChannels {
|
|
if waitingClose.Channel.ChannelPoint == op.String() {
|
|
target = waitingClose
|
|
return nil
|
|
}
|
|
}
|
|
|
|
return fmt.Errorf("%v: channel %s not found in waiting close",
|
|
hn.Name(), op)
|
|
}, DefaultTimeout)
|
|
require.NoError(h, err, "assert channel waiting close timed out")
|
|
|
|
return target
|
|
}
|
|
|
|
// AssertTopologyChannelClosed asserts a given channel is closed by checking
|
|
// the graph topology subscription of the specified node. Returns the closed
|
|
// channel update if found.
|
|
func (h *HarnessTest) AssertTopologyChannelClosed(hn *node.HarnessNode,
|
|
chanPoint *lnrpc.ChannelPoint) *lnrpc.ClosedChannelUpdate {
|
|
|
|
closedChan, err := hn.Watcher.WaitForChannelClose(chanPoint)
|
|
require.NoError(h, err, "failed to wait for channel close")
|
|
|
|
return closedChan
|
|
}
|
|
|
|
// WaitForChannelCloseEvent waits for a notification that a channel is closed
|
|
// by consuming a message from the passed close channel stream. Returns the
|
|
// closing txid if found.
|
|
func (h HarnessTest) WaitForChannelCloseEvent(
|
|
stream rpc.CloseChanClient) chainhash.Hash {
|
|
|
|
// Consume one event.
|
|
event, err := h.ReceiveCloseChannelUpdate(stream)
|
|
require.NoError(h, err)
|
|
|
|
resp, ok := event.Update.(*lnrpc.CloseStatusUpdate_ChanClose)
|
|
require.Truef(h, ok, "expected channel open update, instead got %v",
|
|
resp)
|
|
|
|
txid, err := chainhash.NewHash(resp.ChanClose.ClosingTxid)
|
|
require.NoErrorf(h, err, "wrong format found in closing txid: %v",
|
|
resp.ChanClose.ClosingTxid)
|
|
|
|
return *txid
|
|
}
|
|
|
|
// AssertNumWaitingClose checks that a PendingChannels response from the node
|
|
// reports the expected number of waiting close channels.
|
|
func (h *HarnessTest) AssertNumWaitingClose(hn *node.HarnessNode,
|
|
num int) []*lnrpc.PendingChannelsResponse_WaitingCloseChannel {
|
|
|
|
var channels []*lnrpc.PendingChannelsResponse_WaitingCloseChannel
|
|
oldWaiting := hn.State.CloseChannel.WaitingClose
|
|
|
|
err := wait.NoError(func() error {
|
|
resp := hn.RPC.PendingChannels()
|
|
channels = resp.WaitingCloseChannels
|
|
total := len(channels)
|
|
|
|
got := total - oldWaiting
|
|
if got == num {
|
|
return nil
|
|
}
|
|
|
|
return errNumNotMatched(hn.Name(), "waiting close channels",
|
|
num, got, total, oldWaiting)
|
|
}, DefaultTimeout)
|
|
|
|
require.NoErrorf(h, err, "%s: assert waiting close timeout",
|
|
hn.Name())
|
|
|
|
return channels
|
|
}
|
|
|
|
// AssertNumPendingForceClose checks that a PendingChannels response from the
|
|
// node reports the expected number of pending force close channels.
|
|
func (h *HarnessTest) AssertNumPendingForceClose(hn *node.HarnessNode,
|
|
num int) []*lnrpc.PendingChannelsResponse_ForceClosedChannel {
|
|
|
|
var channels []*lnrpc.PendingChannelsResponse_ForceClosedChannel
|
|
oldForce := hn.State.CloseChannel.PendingForceClose
|
|
|
|
err := wait.NoError(func() error {
|
|
// TODO(yy): we should be able to use `hn.RPC.PendingChannels`
|
|
// here to avoid checking the RPC error. However, we may get a
|
|
// `unable to find arbitrator` error from the rpc point, due to
|
|
// a timing issue in rpcserver,
|
|
// 1. `r.server.chanStateDB.FetchClosedChannels` fetches
|
|
// the pending force close channel.
|
|
// 2. `r.arbitratorPopulateForceCloseResp` relies on the
|
|
// channel arbitrator to get the report, and,
|
|
// 3. the arbitrator may be deleted due to the force close
|
|
// channel being resolved.
|
|
// Somewhere along the line is missing a lock to keep the data
|
|
// consistent.
|
|
req := &lnrpc.PendingChannelsRequest{}
|
|
resp, err := hn.RPC.LN.PendingChannels(h.runCtx, req)
|
|
if err != nil {
|
|
return fmt.Errorf("PendingChannels got: %w", err)
|
|
}
|
|
|
|
channels = resp.PendingForceClosingChannels
|
|
total := len(channels)
|
|
|
|
got := total - oldForce
|
|
if got == num {
|
|
return nil
|
|
}
|
|
|
|
return errNumNotMatched(hn.Name(), "pending force close "+
|
|
"channels", num, got, total, oldForce)
|
|
}, DefaultTimeout)
|
|
|
|
require.NoErrorf(h, err, "%s: assert pending force close timeout",
|
|
hn.Name())
|
|
|
|
return channels
|
|
}
|
|
|
|
// AssertStreamChannelCoopClosed reads an update from the close channel client
|
|
// stream and asserts that the mempool state and node's topology match a coop
|
|
// close. In specific,
|
|
// - assert the channel is waiting close and has the expected ChanStatusFlags.
|
|
// - assert the mempool has the closing txes and anchor sweeps.
|
|
// - mine a block and assert the closing txid is mined.
|
|
// - assert the node has zero waiting close channels.
|
|
// - assert the node has seen the channel close update.
|
|
func (h *HarnessTest) AssertStreamChannelCoopClosed(hn *node.HarnessNode,
|
|
cp *lnrpc.ChannelPoint, anchors bool,
|
|
stream rpc.CloseChanClient) chainhash.Hash {
|
|
|
|
// Assert the channel is waiting close.
|
|
resp := h.AssertChannelWaitingClose(hn, cp)
|
|
|
|
// Assert that the channel is in coop broadcasted.
|
|
require.Contains(h, resp.Channel.ChanStatusFlags,
|
|
channeldb.ChanStatusCoopBroadcasted.String(),
|
|
"channel not coop broadcasted")
|
|
|
|
// 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 := h.MineBlocksAndAssertNumTxes(1, expectedTxes)[0]
|
|
|
|
// Consume one close event and assert the closing txid can be found in
|
|
// the block.
|
|
closingTxid := h.WaitForChannelCloseEvent(stream)
|
|
h.AssertTxInBlock(block, closingTxid)
|
|
|
|
// We should see zero waiting close channels now.
|
|
h.AssertNumWaitingClose(hn, 0)
|
|
|
|
// Finally, check that the node's topology graph has seen this channel
|
|
// closed if it's a public channel.
|
|
if !resp.Channel.Private {
|
|
h.AssertTopologyChannelClosed(hn, cp)
|
|
}
|
|
|
|
return closingTxid
|
|
}
|
|
|
|
// AssertStreamChannelForceClosed reads an update from the close channel client
|
|
// stream and asserts that the mempool state and node's topology match a local
|
|
// force close. In specific,
|
|
// - assert the channel is waiting close and has the expected ChanStatusFlags.
|
|
// - assert the mempool has the closing txes.
|
|
// - mine a block and assert the closing txid is mined.
|
|
// - assert the channel is pending force close.
|
|
// - assert the node has seen the channel close update.
|
|
// - assert there's a pending anchor sweep request once the force close tx is
|
|
// confirmed.
|
|
func (h *HarnessTest) AssertStreamChannelForceClosed(hn *node.HarnessNode,
|
|
cp *lnrpc.ChannelPoint, anchorSweep bool,
|
|
stream rpc.CloseChanClient) chainhash.Hash {
|
|
|
|
// Assert the channel is waiting close.
|
|
resp := h.AssertChannelWaitingClose(hn, cp)
|
|
|
|
// Assert that the channel is in local force broadcasted.
|
|
require.Contains(h, resp.Channel.ChanStatusFlags,
|
|
channeldb.ChanStatusLocalCloseInitiator.String(),
|
|
"channel not coop broadcasted")
|
|
|
|
// 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.
|
|
block := h.MineBlocksAndAssertNumTxes(1, 1)[0]
|
|
|
|
// Consume one close event and assert the closing txid can be found in
|
|
// the block.
|
|
closingTxid := h.WaitForChannelCloseEvent(stream)
|
|
h.AssertTxInBlock(block, closingTxid)
|
|
|
|
// We should see zero waiting close channels and 1 pending force close
|
|
// channels now.
|
|
h.AssertNumWaitingClose(hn, 0)
|
|
h.AssertNumPendingForceClose(hn, 1)
|
|
|
|
// Finally, check that the node's topology graph has seen this channel
|
|
// closed if it's a public channel.
|
|
if !resp.Channel.Private {
|
|
h.AssertTopologyChannelClosed(hn, cp)
|
|
}
|
|
|
|
// Assert there's a pending anchor sweep.
|
|
if anchorSweep {
|
|
h.AssertNumPendingSweeps(hn, 1)
|
|
}
|
|
|
|
return closingTxid
|
|
}
|
|
|
|
// AssertChannelPolicyUpdate checks that the required policy update has
|
|
// happened on the given node.
|
|
func (h *HarnessTest) AssertChannelPolicyUpdate(hn *node.HarnessNode,
|
|
advertisingNode *node.HarnessNode, policy *lnrpc.RoutingPolicy,
|
|
chanPoint *lnrpc.ChannelPoint, includeUnannounced bool) {
|
|
|
|
require.NoError(
|
|
h, hn.Watcher.WaitForChannelPolicyUpdate(
|
|
advertisingNode, policy,
|
|
chanPoint, includeUnannounced,
|
|
), "%s: error while waiting for channel update", hn.Name(),
|
|
)
|
|
}
|
|
|
|
// WaitForGraphSync waits until the node is synced to graph or times out.
|
|
func (h *HarnessTest) WaitForGraphSync(hn *node.HarnessNode) {
|
|
err := wait.NoError(func() error {
|
|
resp := hn.RPC.GetInfo()
|
|
if resp.SyncedToGraph {
|
|
return nil
|
|
}
|
|
|
|
return fmt.Errorf("node not synced to graph")
|
|
}, DefaultTimeout)
|
|
require.NoError(h, err, "%s: timeout while sync to graph", hn.Name())
|
|
}
|
|
|
|
// AssertNumUTXOsWithConf waits for the given number of UTXOs with the
|
|
// specified confirmations range to be available or fails if that isn't the
|
|
// case before the default timeout.
|
|
//
|
|
// NOTE: for standby nodes(Alice and Bob), this method takes account of the
|
|
// previous state of the node's UTXOs. The previous state is snapshotted when
|
|
// finishing a previous test case via the cleanup function in `Subtest`. In
|
|
// other words, this assertion only checks the new changes made in the current
|
|
// test.
|
|
func (h *HarnessTest) AssertNumUTXOsWithConf(hn *node.HarnessNode,
|
|
expectedUtxos int, max, min int32) []*lnrpc.Utxo {
|
|
|
|
var unconfirmed bool
|
|
|
|
old := hn.State.UTXO.Confirmed
|
|
if max == 0 {
|
|
old = hn.State.UTXO.Unconfirmed
|
|
unconfirmed = true
|
|
}
|
|
|
|
var utxos []*lnrpc.Utxo
|
|
err := wait.NoError(func() error {
|
|
req := &walletrpc.ListUnspentRequest{
|
|
Account: "",
|
|
MaxConfs: max,
|
|
MinConfs: min,
|
|
UnconfirmedOnly: unconfirmed,
|
|
}
|
|
resp := hn.RPC.ListUnspent(req)
|
|
total := len(resp.Utxos)
|
|
|
|
if total-old == expectedUtxos {
|
|
utxos = resp.Utxos[old:]
|
|
|
|
return nil
|
|
}
|
|
|
|
desc := "has UTXOs:\n"
|
|
for _, utxo := range resp.Utxos {
|
|
desc += fmt.Sprintf("%v\n", utxo)
|
|
}
|
|
|
|
return errNumNotMatched(hn.Name(), "num of UTXOs",
|
|
expectedUtxos, total-old, total, old, desc)
|
|
}, DefaultTimeout)
|
|
require.NoError(h, err, "timeout waiting for UTXOs")
|
|
|
|
return utxos
|
|
}
|
|
|
|
// AssertNumUTXOsUnconfirmed asserts the expected num of unconfirmed utxos are
|
|
// seen.
|
|
//
|
|
// NOTE: for standby nodes(Alice and Bob), this method takes account of the
|
|
// previous state of the node's UTXOs. Check `AssertNumUTXOsWithConf` for
|
|
// details.
|
|
func (h *HarnessTest) AssertNumUTXOsUnconfirmed(hn *node.HarnessNode,
|
|
num int) []*lnrpc.Utxo {
|
|
|
|
return h.AssertNumUTXOsWithConf(hn, num, 0, 0)
|
|
}
|
|
|
|
// AssertNumUTXOsConfirmed asserts the expected num of confirmed utxos are
|
|
// seen, which means the returned utxos have at least one confirmation.
|
|
//
|
|
// NOTE: for standby nodes(Alice and Bob), this method takes account of the
|
|
// previous state of the node's UTXOs. Check `AssertNumUTXOsWithConf` for
|
|
// details.
|
|
func (h *HarnessTest) AssertNumUTXOsConfirmed(hn *node.HarnessNode,
|
|
num int) []*lnrpc.Utxo {
|
|
|
|
return h.AssertNumUTXOsWithConf(hn, num, math.MaxInt32, 1)
|
|
}
|
|
|
|
// AssertNumUTXOs asserts the expected num of utxos are seen, including
|
|
// confirmed and unconfirmed outputs.
|
|
//
|
|
// NOTE: for standby nodes(Alice and Bob), this method takes account of the
|
|
// previous state of the node's UTXOs. Check `AssertNumUTXOsWithConf` for
|
|
// details.
|
|
func (h *HarnessTest) AssertNumUTXOs(hn *node.HarnessNode,
|
|
num int) []*lnrpc.Utxo {
|
|
|
|
return h.AssertNumUTXOsWithConf(hn, num, math.MaxInt32, 0)
|
|
}
|
|
|
|
// getUTXOs gets the number of newly created UTOXs within the current test
|
|
// scope.
|
|
func (h *HarnessTest) getUTXOs(hn *node.HarnessNode, account string,
|
|
max, min int32) []*lnrpc.Utxo {
|
|
|
|
var unconfirmed bool
|
|
|
|
if max == 0 {
|
|
unconfirmed = true
|
|
}
|
|
|
|
req := &walletrpc.ListUnspentRequest{
|
|
Account: account,
|
|
MaxConfs: max,
|
|
MinConfs: min,
|
|
UnconfirmedOnly: unconfirmed,
|
|
}
|
|
resp := hn.RPC.ListUnspent(req)
|
|
|
|
return resp.Utxos
|
|
}
|
|
|
|
// GetUTXOs returns all the UTXOs for the given node's account, including
|
|
// confirmed and unconfirmed.
|
|
func (h *HarnessTest) GetUTXOs(hn *node.HarnessNode,
|
|
account string) []*lnrpc.Utxo {
|
|
|
|
return h.getUTXOs(hn, account, math.MaxInt32, 0)
|
|
}
|
|
|
|
// GetUTXOsConfirmed returns the confirmed UTXOs for the given node's account.
|
|
func (h *HarnessTest) GetUTXOsConfirmed(hn *node.HarnessNode,
|
|
account string) []*lnrpc.Utxo {
|
|
|
|
return h.getUTXOs(hn, account, math.MaxInt32, 1)
|
|
}
|
|
|
|
// GetUTXOsUnconfirmed returns the unconfirmed UTXOs for the given node's
|
|
// account.
|
|
func (h *HarnessTest) GetUTXOsUnconfirmed(hn *node.HarnessNode,
|
|
account string) []*lnrpc.Utxo {
|
|
|
|
return h.getUTXOs(hn, account, 0, 0)
|
|
}
|
|
|
|
// WaitForBalanceConfirmed waits until the node sees the expected confirmed
|
|
// balance in its wallet.
|
|
func (h *HarnessTest) WaitForBalanceConfirmed(hn *node.HarnessNode,
|
|
expected btcutil.Amount) {
|
|
|
|
var lastBalance btcutil.Amount
|
|
err := wait.NoError(func() error {
|
|
resp := hn.RPC.WalletBalance()
|
|
|
|
lastBalance = btcutil.Amount(resp.ConfirmedBalance)
|
|
if lastBalance == expected {
|
|
return nil
|
|
}
|
|
|
|
return fmt.Errorf("expected %v, only have %v", expected,
|
|
lastBalance)
|
|
}, DefaultTimeout)
|
|
|
|
require.NoError(h, err, "timeout waiting for confirmed balances")
|
|
}
|
|
|
|
// WaitForBalanceUnconfirmed waits until the node sees the expected unconfirmed
|
|
// balance in its wallet.
|
|
func (h *HarnessTest) WaitForBalanceUnconfirmed(hn *node.HarnessNode,
|
|
expected btcutil.Amount) {
|
|
|
|
var lastBalance btcutil.Amount
|
|
err := wait.NoError(func() error {
|
|
resp := hn.RPC.WalletBalance()
|
|
|
|
lastBalance = btcutil.Amount(resp.UnconfirmedBalance)
|
|
if lastBalance == expected {
|
|
return nil
|
|
}
|
|
|
|
return fmt.Errorf("expected %v, only have %v", expected,
|
|
lastBalance)
|
|
}, DefaultTimeout)
|
|
|
|
require.NoError(h, err, "timeout waiting for unconfirmed balances")
|
|
}
|
|
|
|
// Random32Bytes generates a random 32 bytes which can be used as a pay hash,
|
|
// preimage, etc.
|
|
func (h *HarnessTest) Random32Bytes() []byte {
|
|
randBuf := make([]byte, lntypes.HashSize)
|
|
|
|
_, err := rand.Read(randBuf)
|
|
require.NoErrorf(h, err, "internal error, cannot generate random bytes")
|
|
|
|
return randBuf
|
|
}
|
|
|
|
// RandomPreimage generates a random preimage which can be used as a payment
|
|
// preimage.
|
|
func (h *HarnessTest) RandomPreimage() lntypes.Preimage {
|
|
var preimage lntypes.Preimage
|
|
copy(preimage[:], h.Random32Bytes())
|
|
|
|
return preimage
|
|
}
|
|
|
|
// DecodeAddress decodes a given address and asserts there's no error.
|
|
func (h *HarnessTest) DecodeAddress(addr string) btcutil.Address {
|
|
resp, err := btcutil.DecodeAddress(addr, miner.HarnessNetParams)
|
|
require.NoError(h, err, "DecodeAddress failed")
|
|
|
|
return resp
|
|
}
|
|
|
|
// PayToAddrScript creates a new script from the given address and asserts
|
|
// there's no error.
|
|
func (h *HarnessTest) PayToAddrScript(addr btcutil.Address) []byte {
|
|
addrScript, err := txscript.PayToAddrScript(addr)
|
|
require.NoError(h, err, "PayToAddrScript failed")
|
|
|
|
return addrScript
|
|
}
|
|
|
|
// AssertChannelBalanceResp makes a ChannelBalance request and checks the
|
|
// returned response matches the expected.
|
|
func (h *HarnessTest) AssertChannelBalanceResp(hn *node.HarnessNode,
|
|
expected *lnrpc.ChannelBalanceResponse) {
|
|
|
|
resp := hn.RPC.ChannelBalance()
|
|
|
|
// Ignore custom channel data of both expected and actual responses.
|
|
expected.CustomChannelData = nil
|
|
resp.CustomChannelData = nil
|
|
|
|
require.True(h, proto.Equal(expected, resp), "balance is incorrect "+
|
|
"got: %v, want: %v", resp, expected)
|
|
}
|
|
|
|
// GetChannelByChanPoint tries to find a channel matching the channel point and
|
|
// asserts. It returns the channel found.
|
|
func (h *HarnessTest) GetChannelByChanPoint(hn *node.HarnessNode,
|
|
chanPoint *lnrpc.ChannelPoint) *lnrpc.Channel {
|
|
|
|
channel, err := h.findChannel(hn, chanPoint)
|
|
require.NoErrorf(h, err, "channel not found using %v", chanPoint)
|
|
|
|
return channel
|
|
}
|
|
|
|
// GetChannelCommitType retrieves the active channel commitment type for the
|
|
// given chan point.
|
|
func (h *HarnessTest) GetChannelCommitType(hn *node.HarnessNode,
|
|
chanPoint *lnrpc.ChannelPoint) lnrpc.CommitmentType {
|
|
|
|
c := h.GetChannelByChanPoint(hn, chanPoint)
|
|
|
|
return c.CommitmentType
|
|
}
|
|
|
|
// AssertNumPendingOpenChannels asserts that a given node have the expected
|
|
// number of pending open channels.
|
|
func (h *HarnessTest) AssertNumPendingOpenChannels(hn *node.HarnessNode,
|
|
expected int) []*lnrpc.PendingChannelsResponse_PendingOpenChannel {
|
|
|
|
var channels []*lnrpc.PendingChannelsResponse_PendingOpenChannel
|
|
|
|
oldNum := hn.State.OpenChannel.Pending
|
|
|
|
err := wait.NoError(func() error {
|
|
resp := hn.RPC.PendingChannels()
|
|
channels = resp.PendingOpenChannels
|
|
total := len(channels)
|
|
|
|
numChans := total - oldNum
|
|
|
|
if numChans != expected {
|
|
return errNumNotMatched(hn.Name(),
|
|
"pending open channels", expected,
|
|
numChans, total, oldNum)
|
|
}
|
|
|
|
return nil
|
|
}, DefaultTimeout)
|
|
|
|
require.NoError(h, err, "num of pending open channels not match")
|
|
|
|
return channels
|
|
}
|
|
|
|
// AssertNodesNumPendingOpenChannels asserts that both of the nodes have the
|
|
// expected number of pending open channels.
|
|
func (h *HarnessTest) AssertNodesNumPendingOpenChannels(a, b *node.HarnessNode,
|
|
expected int) {
|
|
|
|
h.AssertNumPendingOpenChannels(a, expected)
|
|
h.AssertNumPendingOpenChannels(b, expected)
|
|
}
|
|
|
|
// AssertPaymentStatusFromStream takes a client stream and asserts the payment
|
|
// is in desired status before default timeout. The payment found is returned
|
|
// once succeeded.
|
|
func (h *HarnessTest) AssertPaymentStatusFromStream(stream rpc.PaymentClient,
|
|
status lnrpc.Payment_PaymentStatus) *lnrpc.Payment {
|
|
|
|
return h.assertPaymentStatusWithTimeout(
|
|
stream, status, wait.PaymentTimeout,
|
|
)
|
|
}
|
|
|
|
// AssertPaymentSucceedWithTimeout asserts that a payment is succeeded within
|
|
// the specified timeout.
|
|
func (h *HarnessTest) AssertPaymentSucceedWithTimeout(stream rpc.PaymentClient,
|
|
timeout time.Duration) *lnrpc.Payment {
|
|
|
|
return h.assertPaymentStatusWithTimeout(
|
|
stream, lnrpc.Payment_SUCCEEDED, timeout,
|
|
)
|
|
}
|
|
|
|
// assertPaymentStatusWithTimeout takes a client stream and asserts the payment
|
|
// is in desired status before the specified timeout. The payment found is
|
|
// returned once succeeded.
|
|
func (h *HarnessTest) assertPaymentStatusWithTimeout(stream rpc.PaymentClient,
|
|
status lnrpc.Payment_PaymentStatus,
|
|
timeout time.Duration) *lnrpc.Payment {
|
|
|
|
var target *lnrpc.Payment
|
|
err := wait.NoError(func() error {
|
|
// Consume one message. This will raise an error if the message
|
|
// is not received within DefaultTimeout.
|
|
payment, err := h.receivePaymentUpdateWithTimeout(
|
|
stream, timeout,
|
|
)
|
|
if err != nil {
|
|
return fmt.Errorf("received error from payment "+
|
|
"stream: %s", err)
|
|
}
|
|
|
|
// Return if the desired payment state is reached.
|
|
if payment.Status == status {
|
|
target = payment
|
|
|
|
return nil
|
|
}
|
|
|
|
// Return the err so that it can be used for debugging when
|
|
// timeout is reached.
|
|
return fmt.Errorf("payment %v status, got %v, want %v",
|
|
payment.PaymentHash, payment.Status, status)
|
|
}, timeout)
|
|
|
|
require.NoError(h, err, "timeout while waiting payment")
|
|
|
|
return target
|
|
}
|
|
|
|
// ReceivePaymentUpdate waits until a message is received on the payment client
|
|
// stream or the timeout is reached.
|
|
func (h *HarnessTest) ReceivePaymentUpdate(
|
|
stream rpc.PaymentClient) (*lnrpc.Payment, error) {
|
|
|
|
return h.receivePaymentUpdateWithTimeout(stream, DefaultTimeout)
|
|
}
|
|
|
|
// receivePaymentUpdateWithTimeout waits until a message is received on the
|
|
// payment client stream or the timeout is reached.
|
|
func (h *HarnessTest) receivePaymentUpdateWithTimeout(stream rpc.PaymentClient,
|
|
timeout time.Duration) (*lnrpc.Payment, error) {
|
|
|
|
chanMsg := make(chan *lnrpc.Payment, 1)
|
|
errChan := make(chan error, 1)
|
|
|
|
go func() {
|
|
// Consume one message. This will block until the message is
|
|
// received.
|
|
resp, err := stream.Recv()
|
|
if err != nil {
|
|
errChan <- err
|
|
|
|
return
|
|
}
|
|
chanMsg <- resp
|
|
}()
|
|
|
|
select {
|
|
case <-time.After(timeout):
|
|
require.Fail(h, "timeout", "timeout waiting for payment update")
|
|
return nil, nil
|
|
|
|
case err := <-errChan:
|
|
return nil, err
|
|
|
|
case updateMsg := <-chanMsg:
|
|
return updateMsg, nil
|
|
}
|
|
}
|
|
|
|
// AssertInvoiceSettled asserts a given invoice specified by its payment
|
|
// address is settled.
|
|
func (h *HarnessTest) AssertInvoiceSettled(hn *node.HarnessNode, addr []byte) {
|
|
msg := &invoicesrpc.LookupInvoiceMsg{
|
|
InvoiceRef: &invoicesrpc.LookupInvoiceMsg_PaymentAddr{
|
|
PaymentAddr: addr,
|
|
},
|
|
}
|
|
|
|
err := wait.NoError(func() error {
|
|
invoice := hn.RPC.LookupInvoiceV2(msg)
|
|
if invoice.State == lnrpc.Invoice_SETTLED {
|
|
return nil
|
|
}
|
|
|
|
return fmt.Errorf("%s: invoice with payment address %x not "+
|
|
"settled", hn.Name(), addr)
|
|
}, DefaultTimeout)
|
|
require.NoError(h, err, "timeout waiting for invoice settled state")
|
|
}
|
|
|
|
// AssertNodeNumChannels polls the provided node's list channels rpc until it
|
|
// reaches the desired number of total channels.
|
|
func (h *HarnessTest) AssertNodeNumChannels(hn *node.HarnessNode,
|
|
numChannels int) {
|
|
|
|
// Get the total number of channels.
|
|
old := hn.State.OpenChannel.Active + hn.State.OpenChannel.Inactive
|
|
|
|
err := wait.NoError(func() error {
|
|
// We require the RPC call to be succeeded and won't wait for
|
|
// it as it's an unexpected behavior.
|
|
chanInfo := hn.RPC.ListChannels(&lnrpc.ListChannelsRequest{})
|
|
|
|
// Return true if the query returned the expected number of
|
|
// channels.
|
|
num := len(chanInfo.Channels) - old
|
|
if num != numChannels {
|
|
return fmt.Errorf("expected %v channels, got %v",
|
|
numChannels, num)
|
|
}
|
|
|
|
return nil
|
|
}, DefaultTimeout)
|
|
|
|
require.NoError(h, err, "timeout checking node's num of channels")
|
|
}
|
|
|
|
// AssertChannelLocalBalance checks the local balance of the given channel is
|
|
// expected. The channel found using the specified channel point is returned.
|
|
func (h *HarnessTest) AssertChannelLocalBalance(hn *node.HarnessNode,
|
|
cp *lnrpc.ChannelPoint, balance int64) *lnrpc.Channel {
|
|
|
|
var result *lnrpc.Channel
|
|
|
|
// Get the funding point.
|
|
err := wait.NoError(func() error {
|
|
// Find the target channel first.
|
|
target, err := h.findChannel(hn, cp)
|
|
|
|
// Exit early if the channel is not found.
|
|
if err != nil {
|
|
return fmt.Errorf("check balance failed: %w", err)
|
|
}
|
|
|
|
result = target
|
|
|
|
// Check local balance.
|
|
if target.LocalBalance == balance {
|
|
return nil
|
|
}
|
|
|
|
return fmt.Errorf("balance is incorrect, got %v, expected %v",
|
|
target.LocalBalance, balance)
|
|
}, DefaultTimeout)
|
|
|
|
require.NoError(h, err, "timeout while checking for balance")
|
|
|
|
return result
|
|
}
|
|
|
|
// AssertChannelNumUpdates checks the num of updates is expected from the given
|
|
// channel.
|
|
func (h *HarnessTest) AssertChannelNumUpdates(hn *node.HarnessNode,
|
|
num uint64, cp *lnrpc.ChannelPoint) {
|
|
|
|
old := int(hn.State.OpenChannel.NumUpdates)
|
|
|
|
// Find the target channel first.
|
|
target, err := h.findChannel(hn, cp)
|
|
require.NoError(h, err, "unable to find channel")
|
|
|
|
err = wait.NoError(func() error {
|
|
total := int(target.NumUpdates)
|
|
if total-old == int(num) {
|
|
return nil
|
|
}
|
|
|
|
return errNumNotMatched(hn.Name(), "channel updates",
|
|
int(num), total-old, total, old)
|
|
}, DefaultTimeout)
|
|
require.NoError(h, err, "timeout while checking for num of updates")
|
|
}
|
|
|
|
// AssertNumActiveHtlcs asserts that a given number of HTLCs are seen in the
|
|
// node's channels.
|
|
func (h *HarnessTest) AssertNumActiveHtlcs(hn *node.HarnessNode, num int) {
|
|
old := hn.State.HTLC
|
|
|
|
err := wait.NoError(func() error {
|
|
// We require the RPC call to be succeeded and won't wait for
|
|
// it as it's an unexpected behavior.
|
|
req := &lnrpc.ListChannelsRequest{}
|
|
nodeChans := hn.RPC.ListChannels(req)
|
|
|
|
total := 0
|
|
for _, channel := range nodeChans.Channels {
|
|
total += len(channel.PendingHtlcs)
|
|
}
|
|
if total-old != num {
|
|
return errNumNotMatched(hn.Name(), "active HTLCs",
|
|
num, total-old, total, old)
|
|
}
|
|
|
|
return nil
|
|
}, DefaultTimeout)
|
|
|
|
require.NoErrorf(h, err, "%s timeout checking num active htlcs",
|
|
hn.Name())
|
|
}
|
|
|
|
// AssertActiveHtlcs makes sure the node has the _exact_ HTLCs matching
|
|
// payHashes on _all_ their channels.
|
|
func (h *HarnessTest) AssertActiveHtlcs(hn *node.HarnessNode,
|
|
payHashes ...[]byte) {
|
|
|
|
err := wait.NoError(func() error {
|
|
// We require the RPC call to be succeeded and won't wait for
|
|
// it as it's an unexpected behavior.
|
|
req := &lnrpc.ListChannelsRequest{}
|
|
nodeChans := hn.RPC.ListChannels(req)
|
|
|
|
for _, ch := range nodeChans.Channels {
|
|
// Record all payment hashes active for this channel.
|
|
htlcHashes := make(map[string]struct{})
|
|
|
|
for _, htlc := range ch.PendingHtlcs {
|
|
h := hex.EncodeToString(htlc.HashLock)
|
|
_, ok := htlcHashes[h]
|
|
if ok {
|
|
return fmt.Errorf("duplicate HashLock "+
|
|
"in PendingHtlcs: %v",
|
|
ch.PendingHtlcs)
|
|
}
|
|
htlcHashes[h] = struct{}{}
|
|
}
|
|
|
|
// Channel should have exactly the payHashes active.
|
|
if len(payHashes) != len(htlcHashes) {
|
|
return fmt.Errorf("node [%s:%x] had %v "+
|
|
"htlcs active, expected %v",
|
|
hn.Name(), hn.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 [%s:%x] didn't have: "+
|
|
"the payHash %v active", hn.Name(),
|
|
hn.PubKey[:], h)
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}, DefaultTimeout)
|
|
require.NoError(h, err, "timeout checking active HTLCs")
|
|
}
|
|
|
|
// AssertIncomingHTLCActive asserts the node has a pending incoming HTLC in the
|
|
// given channel. Returns the HTLC if found and active.
|
|
func (h *HarnessTest) AssertIncomingHTLCActive(hn *node.HarnessNode,
|
|
cp *lnrpc.ChannelPoint, payHash []byte) *lnrpc.HTLC {
|
|
|
|
return h.assertHTLCActive(hn, cp, payHash, true)
|
|
}
|
|
|
|
// AssertOutgoingHTLCActive asserts the node has a pending outgoing HTLC in the
|
|
// given channel. Returns the HTLC if found and active.
|
|
func (h *HarnessTest) AssertOutgoingHTLCActive(hn *node.HarnessNode,
|
|
cp *lnrpc.ChannelPoint, payHash []byte) *lnrpc.HTLC {
|
|
|
|
return h.assertHTLCActive(hn, cp, payHash, false)
|
|
}
|
|
|
|
// assertHLTCActive asserts the node has a pending HTLC in the given channel.
|
|
// Returns the HTLC if found and active.
|
|
func (h *HarnessTest) assertHTLCActive(hn *node.HarnessNode,
|
|
cp *lnrpc.ChannelPoint, payHash []byte, incoming bool) *lnrpc.HTLC {
|
|
|
|
var result *lnrpc.HTLC
|
|
target := hex.EncodeToString(payHash)
|
|
|
|
err := wait.NoError(func() error {
|
|
// We require the RPC call to be succeeded and won't wait for
|
|
// it as it's an unexpected behavior.
|
|
ch := h.GetChannelByChanPoint(hn, cp)
|
|
|
|
// Check all payment hashes active for this channel.
|
|
for _, htlc := range ch.PendingHtlcs {
|
|
h := hex.EncodeToString(htlc.HashLock)
|
|
if h != target {
|
|
continue
|
|
}
|
|
|
|
// If the payment hash is found, check the incoming
|
|
// field.
|
|
if htlc.Incoming == incoming {
|
|
// Found it and return.
|
|
result = htlc
|
|
return nil
|
|
}
|
|
|
|
// Otherwise we do have the HTLC but its direction is
|
|
// not right.
|
|
have, want := "outgoing", "incoming"
|
|
if htlc.Incoming {
|
|
have, want = "incoming", "outgoing"
|
|
}
|
|
|
|
return fmt.Errorf("node[%s] have htlc(%v), want: %s, "+
|
|
"have: %s", hn.Name(), payHash, want, have)
|
|
}
|
|
|
|
return fmt.Errorf("node [%s:%x] didn't have: the payHash %x",
|
|
hn.Name(), hn.PubKey[:], payHash)
|
|
}, DefaultTimeout)
|
|
require.NoError(h, err, "timeout checking pending HTLC")
|
|
|
|
return result
|
|
}
|
|
|
|
// AssertHLTCNotActive asserts the node doesn't have a pending HTLC in the
|
|
// given channel, which mean either the HTLC never exists, or it was pending
|
|
// and now settled. Returns the HTLC if found and active.
|
|
//
|
|
// NOTE: to check a pending HTLC becoming settled, first use AssertHLTCActive
|
|
// then follow this check.
|
|
func (h *HarnessTest) AssertHTLCNotActive(hn *node.HarnessNode,
|
|
cp *lnrpc.ChannelPoint, payHash []byte) *lnrpc.HTLC {
|
|
|
|
var result *lnrpc.HTLC
|
|
target := hex.EncodeToString(payHash)
|
|
|
|
err := wait.NoError(func() error {
|
|
// We require the RPC call to be succeeded and won't wait for
|
|
// it as it's an unexpected behavior.
|
|
ch := h.GetChannelByChanPoint(hn, cp)
|
|
|
|
// Check all payment hashes active for this channel.
|
|
for _, htlc := range ch.PendingHtlcs {
|
|
h := hex.EncodeToString(htlc.HashLock)
|
|
|
|
// Break if found the htlc.
|
|
if h == target {
|
|
result = htlc
|
|
break
|
|
}
|
|
}
|
|
|
|
// If we've found nothing, we're done.
|
|
if result == nil {
|
|
return nil
|
|
}
|
|
|
|
// Otherwise return an error.
|
|
return fmt.Errorf("node [%s:%x] still has: the payHash %x",
|
|
hn.Name(), hn.PubKey[:], payHash)
|
|
}, DefaultTimeout)
|
|
require.NoError(h, err, "timeout checking pending HTLC")
|
|
|
|
return result
|
|
}
|
|
|
|
// ReceiveSingleInvoice waits until a message is received on the subscribe
|
|
// single invoice stream or the timeout is reached.
|
|
func (h *HarnessTest) ReceiveSingleInvoice(
|
|
stream rpc.SingleInvoiceClient) *lnrpc.Invoice {
|
|
|
|
chanMsg := make(chan *lnrpc.Invoice, 1)
|
|
errChan := make(chan error, 1)
|
|
go func() {
|
|
// Consume one message. This will block until the message is
|
|
// received.
|
|
resp, err := stream.Recv()
|
|
if err != nil {
|
|
errChan <- err
|
|
|
|
return
|
|
}
|
|
chanMsg <- resp
|
|
}()
|
|
|
|
select {
|
|
case <-time.After(DefaultTimeout):
|
|
require.Fail(h, "timeout", "timeout receiving single invoice")
|
|
|
|
case err := <-errChan:
|
|
require.Failf(h, "err from stream",
|
|
"received err from stream: %v", err)
|
|
|
|
case updateMsg := <-chanMsg:
|
|
return updateMsg
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// AssertInvoiceState takes a single invoice subscription stream and asserts
|
|
// that a given invoice has became the desired state before timeout and returns
|
|
// the invoice found.
|
|
func (h *HarnessTest) AssertInvoiceState(stream rpc.SingleInvoiceClient,
|
|
state lnrpc.Invoice_InvoiceState) *lnrpc.Invoice {
|
|
|
|
var invoice *lnrpc.Invoice
|
|
|
|
err := wait.NoError(func() error {
|
|
invoice = h.ReceiveSingleInvoice(stream)
|
|
if invoice.State == state {
|
|
return nil
|
|
}
|
|
|
|
return fmt.Errorf("mismatched invoice state, want %v, got %v",
|
|
state, invoice.State)
|
|
}, DefaultTimeout)
|
|
require.NoError(h, err, "timeout waiting for invoice state: %v", state)
|
|
|
|
return invoice
|
|
}
|
|
|
|
// assertAllTxesSpendFrom asserts that all txes in the list spend from the
|
|
// given tx.
|
|
func (h *HarnessTest) AssertAllTxesSpendFrom(txes []*wire.MsgTx,
|
|
prevTxid chainhash.Hash) {
|
|
|
|
for _, tx := range txes {
|
|
if tx.TxIn[0].PreviousOutPoint.Hash != prevTxid {
|
|
require.Failf(h, "", "tx %v did not spend from %v",
|
|
tx.TxHash(), prevTxid)
|
|
}
|
|
}
|
|
}
|
|
|
|
// AssertTxSpendFrom asserts that a given tx is spent from a previous tx.
|
|
func (h *HarnessTest) AssertTxSpendFrom(tx *wire.MsgTx,
|
|
prevTxid chainhash.Hash) {
|
|
|
|
if tx.TxIn[0].PreviousOutPoint.Hash != prevTxid {
|
|
require.Failf(h, "", "tx %v did not spend from %v",
|
|
tx.TxHash(), prevTxid)
|
|
}
|
|
}
|
|
|
|
type PendingForceClose *lnrpc.PendingChannelsResponse_ForceClosedChannel
|
|
|
|
// AssertChannelPendingForceClose asserts that the given channel found in the
|
|
// node is pending force close. Returns the PendingForceClose if found.
|
|
func (h *HarnessTest) AssertChannelPendingForceClose(hn *node.HarnessNode,
|
|
chanPoint *lnrpc.ChannelPoint) PendingForceClose {
|
|
|
|
var target PendingForceClose
|
|
|
|
op := h.OutPointFromChannelPoint(chanPoint)
|
|
|
|
err := wait.NoError(func() error {
|
|
resp := hn.RPC.PendingChannels()
|
|
|
|
forceCloseChans := resp.PendingForceClosingChannels
|
|
for _, ch := range forceCloseChans {
|
|
if ch.Channel.ChannelPoint == op.String() {
|
|
target = ch
|
|
|
|
return nil
|
|
}
|
|
}
|
|
|
|
return fmt.Errorf("%v: channel %s not found in pending "+
|
|
"force close", hn.Name(), chanPoint)
|
|
}, DefaultTimeout)
|
|
require.NoError(h, err, "assert pending force close timed out")
|
|
|
|
return target
|
|
}
|
|
|
|
// AssertNumHTLCsAndStage takes a pending force close channel's channel point
|
|
// and asserts the expected number of pending HTLCs and HTLC stage are matched.
|
|
func (h *HarnessTest) AssertNumHTLCsAndStage(hn *node.HarnessNode,
|
|
chanPoint *lnrpc.ChannelPoint, num int, stage uint32) {
|
|
|
|
// Get the channel output point.
|
|
cp := h.OutPointFromChannelPoint(chanPoint)
|
|
|
|
var target PendingForceClose
|
|
checkStage := func() error {
|
|
resp := hn.RPC.PendingChannels()
|
|
if len(resp.PendingForceClosingChannels) == 0 {
|
|
return fmt.Errorf("zero pending force closing channels")
|
|
}
|
|
|
|
for _, ch := range resp.PendingForceClosingChannels {
|
|
if ch.Channel.ChannelPoint == cp.String() {
|
|
target = ch
|
|
|
|
break
|
|
}
|
|
}
|
|
|
|
if target == nil {
|
|
return fmt.Errorf("cannot find pending force closing "+
|
|
"channel using %v", cp)
|
|
}
|
|
|
|
if target.LimboBalance == 0 {
|
|
return fmt.Errorf("zero limbo balance")
|
|
}
|
|
|
|
if len(target.PendingHtlcs) != num {
|
|
return fmt.Errorf("got %d pending htlcs, want %d",
|
|
len(target.PendingHtlcs), num)
|
|
}
|
|
|
|
for i, htlc := range target.PendingHtlcs {
|
|
if htlc.Stage == stage {
|
|
continue
|
|
}
|
|
|
|
return fmt.Errorf("HTLC %d got stage: %v, "+
|
|
"want stage: %v", i, htlc.Stage, stage)
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
require.NoErrorf(h, wait.NoError(checkStage, DefaultTimeout),
|
|
"timeout waiting for htlc stage")
|
|
}
|
|
|
|
// findPayment queries the payment from the node's ListPayments which matches
|
|
// the specified preimage hash.
|
|
func (h *HarnessTest) findPayment(hn *node.HarnessNode,
|
|
paymentHash string) (*lnrpc.Payment, error) {
|
|
|
|
req := &lnrpc.ListPaymentsRequest{IncludeIncomplete: true}
|
|
paymentsResp := hn.RPC.ListPayments(req)
|
|
|
|
for _, p := range paymentsResp.Payments {
|
|
if p.PaymentHash == paymentHash {
|
|
return p, nil
|
|
}
|
|
}
|
|
|
|
return nil, fmt.Errorf("payment %v cannot be found", paymentHash)
|
|
}
|
|
|
|
// PaymentCheck is a function that checks a payment for a specific condition.
|
|
type PaymentCheck func(*lnrpc.Payment) error
|
|
|
|
// AssertPaymentStatus asserts that the given node list a payment with the
|
|
// given preimage has the expected status. It also checks that the payment has
|
|
// the expected preimage, which is empty when it's not settled and matches the
|
|
// given preimage when it's succeeded.
|
|
func (h *HarnessTest) AssertPaymentStatus(hn *node.HarnessNode,
|
|
preimage lntypes.Preimage, status lnrpc.Payment_PaymentStatus,
|
|
checks ...PaymentCheck) *lnrpc.Payment {
|
|
|
|
var target *lnrpc.Payment
|
|
payHash := preimage.Hash()
|
|
|
|
err := wait.NoError(func() error {
|
|
p, err := h.findPayment(hn, payHash.String())
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
if status == p.Status {
|
|
target = p
|
|
return nil
|
|
}
|
|
|
|
return fmt.Errorf("payment: %v status not match, want %s "+
|
|
"got %s", payHash, status, p.Status)
|
|
}, DefaultTimeout)
|
|
require.NoError(h, err, "timeout checking payment status")
|
|
|
|
switch status {
|
|
// If this expected status is SUCCEEDED, we expect the final
|
|
// preimage.
|
|
case lnrpc.Payment_SUCCEEDED:
|
|
require.Equal(h, preimage.String(), target.PaymentPreimage,
|
|
"preimage not match")
|
|
|
|
// Otherwise we expect an all-zero preimage.
|
|
default:
|
|
require.Equal(h, (lntypes.Preimage{}).String(),
|
|
target.PaymentPreimage, "expected zero preimage")
|
|
}
|
|
|
|
// Perform any additional checks on the payment.
|
|
for _, check := range checks {
|
|
require.NoError(h, check(target))
|
|
}
|
|
|
|
return target
|
|
}
|
|
|
|
// AssertPaymentFailureReason asserts that the given node lists a payment with
|
|
// the given preimage which has the expected failure reason.
|
|
func (h *HarnessTest) AssertPaymentFailureReason(hn *node.HarnessNode,
|
|
preimage lntypes.Preimage, reason lnrpc.PaymentFailureReason) {
|
|
|
|
payHash := preimage.Hash()
|
|
err := wait.NoError(func() error {
|
|
p, err := h.findPayment(hn, payHash.String())
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
if reason == p.FailureReason {
|
|
return nil
|
|
}
|
|
|
|
return fmt.Errorf("payment: %v failure reason not match, "+
|
|
"want %s got %s", payHash, reason, p.Status)
|
|
}, DefaultTimeout)
|
|
require.NoError(h, err, "timeout checking payment failure reason")
|
|
}
|
|
|
|
// AssertActiveNodesSynced asserts all active nodes have synced to the chain.
|
|
func (h *HarnessTest) AssertActiveNodesSynced() {
|
|
for _, node := range h.manager.activeNodes {
|
|
h.WaitForBlockchainSync(node)
|
|
}
|
|
}
|
|
|
|
// AssertActiveNodesSyncedTo asserts all active nodes have synced to the
|
|
// provided bestBlock.
|
|
func (h *HarnessTest) AssertActiveNodesSyncedTo(bestBlock *wire.MsgBlock) {
|
|
for _, node := range h.manager.activeNodes {
|
|
h.WaitForBlockchainSyncTo(node, bestBlock)
|
|
}
|
|
}
|
|
|
|
// AssertPeerNotConnected asserts that the given node b is not connected to a.
|
|
func (h *HarnessTest) AssertPeerNotConnected(a, b *node.HarnessNode) {
|
|
err := wait.NoError(func() error {
|
|
// We require the RPC call to be succeeded and won't wait for
|
|
// it as it's an unexpected behavior.
|
|
resp := a.RPC.ListPeers()
|
|
|
|
// If node B is seen in the ListPeers response from node A,
|
|
// then we return false as the connection has been fully
|
|
// established.
|
|
for _, peer := range resp.Peers {
|
|
if peer.PubKey == b.PubKeyStr {
|
|
return fmt.Errorf("peers %s and %s still "+
|
|
"connected", a.Name(), b.Name())
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}, DefaultTimeout)
|
|
require.NoError(h, err, "timeout checking peers not connected")
|
|
}
|
|
|
|
// AssertNotConnected asserts that two peers are not connected.
|
|
func (h *HarnessTest) AssertNotConnected(a, b *node.HarnessNode) {
|
|
h.AssertPeerNotConnected(a, b)
|
|
h.AssertPeerNotConnected(b, a)
|
|
}
|
|
|
|
// AssertConnected asserts that two peers are connected.
|
|
func (h *HarnessTest) AssertConnected(a, b *node.HarnessNode) {
|
|
h.AssertPeerConnected(a, b)
|
|
h.AssertPeerConnected(b, a)
|
|
}
|
|
|
|
// AssertAmountPaid checks that the ListChannels command of the provided
|
|
// node list the total amount sent and received as expected for the
|
|
// provided channel.
|
|
func (h *HarnessTest) AssertAmountPaid(channelName string, hn *node.HarnessNode,
|
|
chanPoint *lnrpc.ChannelPoint, amountSent, amountReceived int64) {
|
|
|
|
checkAmountPaid := func() error {
|
|
// Find the targeted channel.
|
|
channel, err := h.findChannel(hn, chanPoint)
|
|
if err != nil {
|
|
return fmt.Errorf("assert amount failed: %w", err)
|
|
}
|
|
|
|
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
|
|
}
|
|
|
|
// 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.
|
|
err := wait.NoError(checkAmountPaid, DefaultTimeout)
|
|
require.NoError(h, err, "timeout while checking amount paid")
|
|
}
|
|
|
|
// AssertLastHTLCError checks that the last sent HTLC of the last payment sent
|
|
// by the given node failed with the expected failure code.
|
|
func (h *HarnessTest) AssertLastHTLCError(hn *node.HarnessNode,
|
|
code lnrpc.Failure_FailureCode) {
|
|
|
|
// Use -1 to specify the last HTLC.
|
|
h.assertHTLCError(hn, code, -1)
|
|
}
|
|
|
|
// AssertFirstHTLCError checks that the first HTLC of the last payment sent
|
|
// by the given node failed with the expected failure code.
|
|
func (h *HarnessTest) AssertFirstHTLCError(hn *node.HarnessNode,
|
|
code lnrpc.Failure_FailureCode) {
|
|
|
|
// Use 0 to specify the first HTLC.
|
|
h.assertHTLCError(hn, code, 0)
|
|
}
|
|
|
|
// assertLastHTLCError checks that the HTLC at the specified index of the last
|
|
// payment sent by the given node failed with the expected failure code.
|
|
func (h *HarnessTest) assertHTLCError(hn *node.HarnessNode,
|
|
code lnrpc.Failure_FailureCode, index int) {
|
|
|
|
req := &lnrpc.ListPaymentsRequest{
|
|
IncludeIncomplete: true,
|
|
}
|
|
|
|
err := wait.NoError(func() error {
|
|
paymentsResp := hn.RPC.ListPayments(req)
|
|
|
|
payments := paymentsResp.Payments
|
|
if len(payments) == 0 {
|
|
return fmt.Errorf("no payments found")
|
|
}
|
|
|
|
payment := payments[len(payments)-1]
|
|
htlcs := payment.Htlcs
|
|
if len(htlcs) == 0 {
|
|
return fmt.Errorf("no htlcs found")
|
|
}
|
|
|
|
// If the index is greater than 0, check we have enough htlcs.
|
|
if index > 0 && len(htlcs) <= index {
|
|
return fmt.Errorf("not enough htlcs")
|
|
}
|
|
|
|
// If index is less than or equal to 0, we will read the last
|
|
// htlc.
|
|
if index <= 0 {
|
|
index = len(htlcs) - 1
|
|
}
|
|
|
|
htlc := htlcs[index]
|
|
|
|
// The htlc must have a status of failed.
|
|
if htlc.Status != lnrpc.HTLCAttempt_FAILED {
|
|
return fmt.Errorf("htlc should be failed")
|
|
}
|
|
// The failure field must not be empty.
|
|
if htlc.Failure == nil {
|
|
return fmt.Errorf("expected htlc failure")
|
|
}
|
|
|
|
// Exit if the expected code is found.
|
|
if htlc.Failure.Code == code {
|
|
return nil
|
|
}
|
|
|
|
return fmt.Errorf("unexpected failure code")
|
|
}, DefaultTimeout)
|
|
|
|
require.NoError(h, err, "timeout checking HTLC error")
|
|
}
|
|
|
|
// AssertZombieChannel asserts that a given channel found using the chanID is
|
|
// marked as zombie.
|
|
func (h *HarnessTest) AssertZombieChannel(hn *node.HarnessNode, chanID uint64) {
|
|
ctxt, cancel := context.WithTimeout(h.runCtx, DefaultTimeout)
|
|
defer cancel()
|
|
|
|
err := wait.NoError(func() error {
|
|
_, err := hn.RPC.LN.GetChanInfo(
|
|
ctxt, &lnrpc.ChanInfoRequest{ChanId: chanID},
|
|
)
|
|
if err == nil {
|
|
return fmt.Errorf("expected error but got nil")
|
|
}
|
|
|
|
if !strings.Contains(err.Error(), "marked as zombie") {
|
|
return fmt.Errorf("expected error to contain '%s' but "+
|
|
"was '%v'", "marked as zombie", err)
|
|
}
|
|
|
|
return nil
|
|
}, DefaultTimeout)
|
|
require.NoError(h, err, "timeout while checking zombie channel")
|
|
}
|
|
|
|
// AssertNotInGraph asserts that a given channel is either not found at all in
|
|
// the graph or that it has been marked as a zombie.
|
|
func (h *HarnessTest) AssertNotInGraph(hn *node.HarnessNode, chanID uint64) {
|
|
ctxt, cancel := context.WithTimeout(h.runCtx, DefaultTimeout)
|
|
defer cancel()
|
|
|
|
err := wait.NoError(func() error {
|
|
_, err := hn.RPC.LN.GetChanInfo(
|
|
ctxt, &lnrpc.ChanInfoRequest{ChanId: chanID},
|
|
)
|
|
if err == nil {
|
|
return fmt.Errorf("expected error but got nil")
|
|
}
|
|
|
|
switch {
|
|
case strings.Contains(err.Error(), "marked as zombie"):
|
|
return nil
|
|
|
|
case strings.Contains(err.Error(), "edge not found"):
|
|
return nil
|
|
|
|
default:
|
|
return fmt.Errorf("expected error to contain either "+
|
|
"'%s' or '%s' but was: '%v'", "marked as i"+
|
|
"zombie", "edge not found", err)
|
|
}
|
|
}, DefaultTimeout)
|
|
require.NoError(h, err, "timeout while checking that channel is not "+
|
|
"found in graph")
|
|
}
|
|
|
|
// AssertChannelInGraph asserts that a given channel is found in the graph.
|
|
func (h *HarnessTest) AssertChannelInGraph(hn *node.HarnessNode,
|
|
chanPoint *lnrpc.ChannelPoint) *lnrpc.ChannelEdge {
|
|
|
|
ctxt, cancel := context.WithCancel(h.runCtx)
|
|
defer cancel()
|
|
|
|
var edge *lnrpc.ChannelEdge
|
|
|
|
op := h.OutPointFromChannelPoint(chanPoint)
|
|
err := wait.NoError(func() error {
|
|
resp, err := hn.RPC.LN.GetChanInfo(
|
|
ctxt, &lnrpc.ChanInfoRequest{
|
|
ChanPoint: op.String(),
|
|
},
|
|
)
|
|
if err != nil {
|
|
return fmt.Errorf("channel %s not found in graph: %w",
|
|
op, err)
|
|
}
|
|
|
|
edge = resp
|
|
|
|
return nil
|
|
}, DefaultTimeout)
|
|
require.NoError(h, err, "%s: timeout finding channel in graph",
|
|
hn.Name())
|
|
|
|
return edge
|
|
}
|
|
|
|
// AssertTxAtHeight gets all of the transactions that a node's wallet has a
|
|
// record of at the target height, and finds and returns the tx with the target
|
|
// txid, failing if it is not found.
|
|
func (h *HarnessTest) AssertTxAtHeight(hn *node.HarnessNode, height int32,
|
|
txid *chainhash.Hash) *lnrpc.Transaction {
|
|
|
|
req := &lnrpc.GetTransactionsRequest{
|
|
StartHeight: height,
|
|
EndHeight: height,
|
|
}
|
|
txns := hn.RPC.GetTransactions(req)
|
|
|
|
for _, tx := range txns.Transactions {
|
|
if tx.TxHash == txid.String() {
|
|
return tx
|
|
}
|
|
}
|
|
|
|
require.Failf(h, "fail to find tx", "tx:%v not found at height:%v",
|
|
txid, height)
|
|
|
|
return nil
|
|
}
|
|
|
|
// getChannelPolicies queries the channel graph and retrieves the current edge
|
|
// policies for the provided channel point.
|
|
func (h *HarnessTest) getChannelPolicies(hn *node.HarnessNode,
|
|
advertisingNode string,
|
|
cp *lnrpc.ChannelPoint) (*lnrpc.RoutingPolicy, error) {
|
|
|
|
req := &lnrpc.ChannelGraphRequest{IncludeUnannounced: true}
|
|
chanGraph := hn.RPC.DescribeGraph(req)
|
|
|
|
cpStr := channelPointStr(cp)
|
|
for _, e := range chanGraph.Edges {
|
|
if e.ChanPoint != cpStr {
|
|
continue
|
|
}
|
|
|
|
if e.Node1Pub == advertisingNode {
|
|
return e.Node1Policy, nil
|
|
}
|
|
|
|
return e.Node2Policy, nil
|
|
}
|
|
|
|
// If we've iterated over all the known edges and we weren't
|
|
// able to find this specific one, then we'll fail.
|
|
return nil, fmt.Errorf("did not find edge with advertisingNode: %s"+
|
|
", channel point: %s", advertisingNode, cpStr)
|
|
}
|
|
|
|
// AssertChannelPolicy asserts that the passed node's known channel policy for
|
|
// the passed chanPoint is consistent with the expected policy values.
|
|
func (h *HarnessTest) AssertChannelPolicy(hn *node.HarnessNode,
|
|
advertisingNode string, expectedPolicy *lnrpc.RoutingPolicy,
|
|
chanPoint *lnrpc.ChannelPoint) {
|
|
|
|
policy, err := h.getChannelPolicies(hn, advertisingNode, chanPoint)
|
|
require.NoErrorf(h, err, "%s: failed to find policy", hn.Name())
|
|
|
|
err = node.CheckChannelPolicy(policy, expectedPolicy)
|
|
require.NoErrorf(h, err, "%s: check policy failed", hn.Name())
|
|
}
|
|
|
|
// AssertNumPolicyUpdates asserts that a given number of channel policy updates
|
|
// has been seen in the specified node.
|
|
func (h *HarnessTest) AssertNumPolicyUpdates(hn *node.HarnessNode,
|
|
chanPoint *lnrpc.ChannelPoint,
|
|
advertisingNode *node.HarnessNode, num int) {
|
|
|
|
op := h.OutPointFromChannelPoint(chanPoint)
|
|
|
|
var policies []*node.PolicyUpdateInfo
|
|
|
|
err := wait.NoError(func() error {
|
|
policyMap := hn.Watcher.GetPolicyUpdates(op)
|
|
nodePolicy, ok := policyMap[advertisingNode.PubKeyStr]
|
|
if ok {
|
|
policies = nodePolicy
|
|
}
|
|
|
|
if len(policies) == num {
|
|
return nil
|
|
}
|
|
|
|
p, err := json.MarshalIndent(policies, "", "\t")
|
|
require.NoError(h, err, "encode policy err")
|
|
|
|
return fmt.Errorf("expected to find %d policy updates, "+
|
|
"instead got: %d, chanPoint: %v, "+
|
|
"advertisingNode: %s:%s, policy: %s", num,
|
|
len(policies), op, advertisingNode.Name(),
|
|
advertisingNode.PubKeyStr, p)
|
|
}, DefaultTimeout)
|
|
|
|
require.NoError(h, err, "%s: timeout waiting for num of policy updates",
|
|
hn.Name())
|
|
}
|
|
|
|
// AssertNumPayments asserts that the number of payments made within the test
|
|
// scope is as expected, including the incomplete ones.
|
|
func (h *HarnessTest) AssertNumPayments(hn *node.HarnessNode,
|
|
num int) []*lnrpc.Payment {
|
|
|
|
// Get the number of payments we already have from the previous test.
|
|
have := hn.State.Payment.Total
|
|
|
|
req := &lnrpc.ListPaymentsRequest{
|
|
IncludeIncomplete: true,
|
|
IndexOffset: hn.State.Payment.LastIndexOffset,
|
|
}
|
|
|
|
var payments []*lnrpc.Payment
|
|
err := wait.NoError(func() error {
|
|
resp := hn.RPC.ListPayments(req)
|
|
|
|
payments = resp.Payments
|
|
if len(payments) == num {
|
|
return nil
|
|
}
|
|
|
|
return errNumNotMatched(hn.Name(), "num of payments",
|
|
num, len(payments), have+len(payments), have)
|
|
}, DefaultTimeout)
|
|
require.NoError(h, err, "%s: timeout checking num of payments",
|
|
hn.Name())
|
|
|
|
return payments
|
|
}
|
|
|
|
// AssertNumNodeAnns asserts that a given number of node announcements has been
|
|
// seen in the specified node.
|
|
func (h *HarnessTest) AssertNumNodeAnns(hn *node.HarnessNode,
|
|
pubkey string, num int) []*lnrpc.NodeUpdate {
|
|
|
|
// We will get the current number of channel updates first and add it
|
|
// to our expected number of newly created channel updates.
|
|
anns, err := hn.Watcher.WaitForNumNodeUpdates(pubkey, num)
|
|
require.NoError(h, err, "%s: failed to assert num of node anns",
|
|
hn.Name())
|
|
|
|
return anns
|
|
}
|
|
|
|
// AssertNumChannelUpdates asserts that a given number of channel updates has
|
|
// been seen in the specified node's network topology.
|
|
func (h *HarnessTest) AssertNumChannelUpdates(hn *node.HarnessNode,
|
|
chanPoint *lnrpc.ChannelPoint, num int) {
|
|
|
|
op := h.OutPointFromChannelPoint(chanPoint)
|
|
err := hn.Watcher.WaitForNumChannelUpdates(op, num)
|
|
require.NoError(h, err, "%s: failed to assert num of channel updates",
|
|
hn.Name())
|
|
}
|
|
|
|
// CreateBurnAddr creates a random burn address of the given type.
|
|
func (h *HarnessTest) CreateBurnAddr(addrType lnrpc.AddressType) ([]byte,
|
|
btcutil.Address) {
|
|
|
|
randomPrivKey, err := btcec.NewPrivateKey()
|
|
require.NoError(h, err)
|
|
|
|
randomKeyBytes := randomPrivKey.PubKey().SerializeCompressed()
|
|
harnessNetParams := miner.HarnessNetParams
|
|
|
|
var addr btcutil.Address
|
|
switch addrType {
|
|
case lnrpc.AddressType_WITNESS_PUBKEY_HASH:
|
|
addr, err = btcutil.NewAddressWitnessPubKeyHash(
|
|
btcutil.Hash160(randomKeyBytes), harnessNetParams,
|
|
)
|
|
|
|
case lnrpc.AddressType_TAPROOT_PUBKEY:
|
|
taprootKey := txscript.ComputeTaprootKeyNoScript(
|
|
randomPrivKey.PubKey(),
|
|
)
|
|
addr, err = btcutil.NewAddressPubKey(
|
|
schnorr.SerializePubKey(taprootKey), harnessNetParams,
|
|
)
|
|
|
|
case lnrpc.AddressType_NESTED_PUBKEY_HASH:
|
|
var witnessAddr btcutil.Address
|
|
witnessAddr, err = btcutil.NewAddressWitnessPubKeyHash(
|
|
btcutil.Hash160(randomKeyBytes), harnessNetParams,
|
|
)
|
|
require.NoError(h, err)
|
|
|
|
addr, err = btcutil.NewAddressScriptHash(
|
|
h.PayToAddrScript(witnessAddr), harnessNetParams,
|
|
)
|
|
|
|
default:
|
|
h.Fatalf("Unsupported burn address type: %v", addrType)
|
|
}
|
|
require.NoError(h, err)
|
|
|
|
return h.PayToAddrScript(addr), addr
|
|
}
|
|
|
|
// ReceiveTrackPayment waits until a message is received on the track payment
|
|
// stream or the timeout is reached.
|
|
func (h *HarnessTest) ReceiveTrackPayment(
|
|
stream rpc.TrackPaymentClient) *lnrpc.Payment {
|
|
|
|
chanMsg := make(chan *lnrpc.Payment)
|
|
errChan := make(chan error)
|
|
go func() {
|
|
// Consume one message. This will block until the message is
|
|
// received.
|
|
resp, err := stream.Recv()
|
|
if err != nil {
|
|
errChan <- err
|
|
return
|
|
}
|
|
chanMsg <- resp
|
|
}()
|
|
|
|
select {
|
|
case <-time.After(DefaultTimeout):
|
|
require.Fail(h, "timeout", "timeout trakcing payment")
|
|
|
|
case err := <-errChan:
|
|
require.Failf(h, "err from stream",
|
|
"received err from stream: %v", err)
|
|
|
|
case updateMsg := <-chanMsg:
|
|
return updateMsg
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// ReceiveHtlcEvent waits until a message is received on the subscribe
|
|
// htlc event stream or the timeout is reached.
|
|
func (h *HarnessTest) ReceiveHtlcEvent(
|
|
stream rpc.HtlcEventsClient) *routerrpc.HtlcEvent {
|
|
|
|
chanMsg := make(chan *routerrpc.HtlcEvent)
|
|
errChan := make(chan error)
|
|
go func() {
|
|
// Consume one message. This will block until the message is
|
|
// received.
|
|
resp, err := stream.Recv()
|
|
if err != nil {
|
|
errChan <- err
|
|
return
|
|
}
|
|
chanMsg <- resp
|
|
}()
|
|
|
|
select {
|
|
case <-time.After(DefaultTimeout):
|
|
require.Fail(h, "timeout", "timeout receiving htlc "+
|
|
"event update")
|
|
|
|
case err := <-errChan:
|
|
require.Failf(h, "err from stream",
|
|
"received err from stream: %v", err)
|
|
|
|
case updateMsg := <-chanMsg:
|
|
return updateMsg
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// AssertHtlcEventType consumes one event from a client and asserts the event
|
|
// type is matched.
|
|
func (h *HarnessTest) AssertHtlcEventType(client rpc.HtlcEventsClient,
|
|
userType routerrpc.HtlcEvent_EventType) *routerrpc.HtlcEvent {
|
|
|
|
event := h.ReceiveHtlcEvent(client)
|
|
require.Equalf(h, userType, event.EventType, "wrong event type, "+
|
|
"want %v got %v", userType, event.EventType)
|
|
|
|
return event
|
|
}
|
|
|
|
// HtlcEvent maps the series of event types used in `*routerrpc.HtlcEvent_*`.
|
|
type HtlcEvent int
|
|
|
|
const (
|
|
HtlcEventForward HtlcEvent = iota
|
|
HtlcEventForwardFail
|
|
HtlcEventSettle
|
|
HtlcEventLinkFail
|
|
HtlcEventFinal
|
|
)
|
|
|
|
// AssertHtlcEventType consumes one event from a client and asserts both the
|
|
// user event type the event.Event type is matched.
|
|
func (h *HarnessTest) AssertHtlcEventTypes(client rpc.HtlcEventsClient,
|
|
userType routerrpc.HtlcEvent_EventType,
|
|
eventType HtlcEvent) *routerrpc.HtlcEvent {
|
|
|
|
event := h.ReceiveHtlcEvent(client)
|
|
require.Equalf(h, userType, event.EventType, "wrong event type, "+
|
|
"want %v got %v", userType, event.EventType)
|
|
|
|
var ok bool
|
|
|
|
switch eventType {
|
|
case HtlcEventForward:
|
|
_, ok = event.Event.(*routerrpc.HtlcEvent_ForwardEvent)
|
|
|
|
case HtlcEventForwardFail:
|
|
_, ok = event.Event.(*routerrpc.HtlcEvent_ForwardFailEvent)
|
|
|
|
case HtlcEventSettle:
|
|
_, ok = event.Event.(*routerrpc.HtlcEvent_SettleEvent)
|
|
|
|
case HtlcEventLinkFail:
|
|
_, ok = event.Event.(*routerrpc.HtlcEvent_LinkFailEvent)
|
|
|
|
case HtlcEventFinal:
|
|
_, ok = event.Event.(*routerrpc.HtlcEvent_FinalHtlcEvent)
|
|
}
|
|
|
|
require.Truef(h, ok, "wrong event type: %T, want %T", event.Event,
|
|
eventType)
|
|
|
|
return event
|
|
}
|
|
|
|
// AssertFeeReport checks that the fee report from the given node has the
|
|
// desired day, week, and month sum values.
|
|
func (h *HarnessTest) AssertFeeReport(hn *node.HarnessNode,
|
|
day, week, month int) {
|
|
|
|
err := wait.NoError(func() error {
|
|
feeReport, err := hn.RPC.LN.FeeReport(
|
|
h.runCtx, &lnrpc.FeeReportRequest{},
|
|
)
|
|
require.NoError(h, err, "unable to query for fee report")
|
|
|
|
if uint64(day) != feeReport.DayFeeSum {
|
|
return fmt.Errorf("day fee mismatch, want %d, got %d",
|
|
day, feeReport.DayFeeSum)
|
|
}
|
|
|
|
if uint64(week) != feeReport.WeekFeeSum {
|
|
return fmt.Errorf("week fee mismatch, want %d, got %d",
|
|
week, feeReport.WeekFeeSum)
|
|
}
|
|
if uint64(month) != feeReport.MonthFeeSum {
|
|
return fmt.Errorf("month fee mismatch, want %d, got %d",
|
|
month, feeReport.MonthFeeSum)
|
|
}
|
|
|
|
return nil
|
|
}, wait.DefaultTimeout)
|
|
require.NoErrorf(h, err, "%s: time out checking fee report", hn.Name())
|
|
}
|
|
|
|
// AssertHtlcEvents consumes events from a client and ensures that they are of
|
|
// the expected type and contain the expected number of forwards, forward
|
|
// failures and settles.
|
|
//
|
|
// TODO(yy): needs refactor to reduce its complexity.
|
|
func (h *HarnessTest) AssertHtlcEvents(client rpc.HtlcEventsClient,
|
|
fwdCount, fwdFailCount, settleCount, linkFailCount int,
|
|
userType routerrpc.HtlcEvent_EventType) []*routerrpc.HtlcEvent {
|
|
|
|
var forwards, forwardFails, settles, linkFails int
|
|
|
|
numEvents := fwdCount + fwdFailCount + settleCount + linkFailCount
|
|
events := make([]*routerrpc.HtlcEvent, 0)
|
|
|
|
// It's either the userType or the unknown type.
|
|
//
|
|
// TODO(yy): maybe the FinalHtlcEvent shouldn't be in UNKNOWN type?
|
|
eventTypes := []routerrpc.HtlcEvent_EventType{
|
|
userType, routerrpc.HtlcEvent_UNKNOWN,
|
|
}
|
|
|
|
for i := 0; i < numEvents; i++ {
|
|
event := h.ReceiveHtlcEvent(client)
|
|
|
|
require.Containsf(h, eventTypes, event.EventType,
|
|
"wrong event type, got %v", userType, event.EventType)
|
|
|
|
events = append(events, event)
|
|
|
|
switch e := event.Event.(type) {
|
|
case *routerrpc.HtlcEvent_ForwardEvent:
|
|
forwards++
|
|
|
|
case *routerrpc.HtlcEvent_ForwardFailEvent:
|
|
forwardFails++
|
|
|
|
case *routerrpc.HtlcEvent_SettleEvent:
|
|
settles++
|
|
|
|
case *routerrpc.HtlcEvent_FinalHtlcEvent:
|
|
if e.FinalHtlcEvent.Settled {
|
|
settles++
|
|
}
|
|
|
|
case *routerrpc.HtlcEvent_LinkFailEvent:
|
|
linkFails++
|
|
|
|
default:
|
|
require.Fail(h, "assert event fail",
|
|
"unexpected event: %T", event.Event)
|
|
}
|
|
}
|
|
|
|
require.Equal(h, fwdCount, forwards, "num of forwards mismatch")
|
|
require.Equal(h, fwdFailCount, forwardFails,
|
|
"num of forward fails mismatch")
|
|
require.Equal(h, settleCount, settles, "num of settles mismatch")
|
|
require.Equal(h, linkFailCount, linkFails, "num of link fails mismatch")
|
|
|
|
return events
|
|
}
|
|
|
|
// AssertTransactionInWallet asserts a given txid can be found in the node's
|
|
// wallet.
|
|
func (h *HarnessTest) AssertTransactionInWallet(hn *node.HarnessNode,
|
|
txid chainhash.Hash) {
|
|
|
|
req := &lnrpc.GetTransactionsRequest{}
|
|
err := wait.NoError(func() error {
|
|
txResp := hn.RPC.GetTransactions(req)
|
|
for _, txn := range txResp.Transactions {
|
|
if txn.TxHash == txid.String() {
|
|
return nil
|
|
}
|
|
}
|
|
|
|
return fmt.Errorf("%s: expected txid=%v not found in wallet",
|
|
hn.Name(), txid)
|
|
}, DefaultTimeout)
|
|
|
|
require.NoError(h, err, "failed to find tx")
|
|
}
|
|
|
|
// AssertTransactionNotInWallet asserts a given txid can NOT be found in the
|
|
// node's wallet.
|
|
func (h *HarnessTest) AssertTransactionNotInWallet(hn *node.HarnessNode,
|
|
txid chainhash.Hash) {
|
|
|
|
req := &lnrpc.GetTransactionsRequest{}
|
|
err := wait.NoError(func() error {
|
|
txResp := hn.RPC.GetTransactions(req)
|
|
for _, txn := range txResp.Transactions {
|
|
if txn.TxHash == txid.String() {
|
|
return fmt.Errorf("expected txid=%v to be "+
|
|
"not found", txid)
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}, DefaultTimeout)
|
|
|
|
require.NoErrorf(h, err, "%s: failed to assert tx not found", hn.Name())
|
|
}
|
|
|
|
// WaitForNodeBlockHeight queries the node for its current block height until
|
|
// it reaches the passed height.
|
|
func (h *HarnessTest) WaitForNodeBlockHeight(hn *node.HarnessNode,
|
|
height int32) {
|
|
|
|
err := wait.NoError(func() error {
|
|
info := hn.RPC.GetInfo()
|
|
if int32(info.BlockHeight) != height {
|
|
return fmt.Errorf("expected block height to "+
|
|
"be %v, was %v", height, info.BlockHeight)
|
|
}
|
|
|
|
return nil
|
|
}, DefaultTimeout)
|
|
|
|
require.NoErrorf(h, err, "%s: timeout while waiting for height",
|
|
hn.Name())
|
|
}
|
|
|
|
// AssertChannelCommitHeight asserts the given channel for the node has the
|
|
// expected commit height(`NumUpdates`).
|
|
func (h *HarnessTest) AssertChannelCommitHeight(hn *node.HarnessNode,
|
|
cp *lnrpc.ChannelPoint, height int) {
|
|
|
|
err := wait.NoError(func() error {
|
|
c, err := h.findChannel(hn, cp)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
if int(c.NumUpdates) == height {
|
|
return nil
|
|
}
|
|
|
|
return fmt.Errorf("expected commit height to be %v, was %v",
|
|
height, c.NumUpdates)
|
|
}, DefaultTimeout)
|
|
|
|
require.NoError(h, err, "timeout while waiting for commit height")
|
|
}
|
|
|
|
// AssertNumInvoices asserts that the number of invoices made within the test
|
|
// scope is as expected.
|
|
func (h *HarnessTest) AssertNumInvoices(hn *node.HarnessNode,
|
|
num int) []*lnrpc.Invoice {
|
|
|
|
have := hn.State.Invoice.Total
|
|
req := &lnrpc.ListInvoiceRequest{
|
|
NumMaxInvoices: math.MaxUint64,
|
|
IndexOffset: hn.State.Invoice.LastIndexOffset,
|
|
}
|
|
|
|
var invoices []*lnrpc.Invoice
|
|
err := wait.NoError(func() error {
|
|
resp := hn.RPC.ListInvoices(req)
|
|
|
|
invoices = resp.Invoices
|
|
if len(invoices) == num {
|
|
return nil
|
|
}
|
|
|
|
return errNumNotMatched(hn.Name(), "num of invoices",
|
|
num, len(invoices), have+len(invoices), have)
|
|
}, DefaultTimeout)
|
|
require.NoError(h, err, "timeout checking num of invoices")
|
|
|
|
return invoices
|
|
}
|
|
|
|
// ReceiveSendToRouteUpdate waits until a message is received on the
|
|
// SendToRoute client stream or the timeout is reached.
|
|
func (h *HarnessTest) ReceiveSendToRouteUpdate(
|
|
stream rpc.SendToRouteClient) (*lnrpc.SendResponse, error) {
|
|
|
|
chanMsg := make(chan *lnrpc.SendResponse, 1)
|
|
errChan := make(chan error, 1)
|
|
go func() {
|
|
// Consume one message. This will block until the message is
|
|
// received.
|
|
resp, err := stream.Recv()
|
|
if err != nil {
|
|
errChan <- err
|
|
|
|
return
|
|
}
|
|
chanMsg <- resp
|
|
}()
|
|
|
|
select {
|
|
case <-time.After(DefaultTimeout):
|
|
require.Fail(h, "timeout", "timeout waiting for send resp")
|
|
return nil, nil
|
|
|
|
case err := <-errChan:
|
|
return nil, err
|
|
|
|
case updateMsg := <-chanMsg:
|
|
return updateMsg, nil
|
|
}
|
|
}
|
|
|
|
// AssertInvoiceEqual asserts that two lnrpc.Invoices are equivalent. A custom
|
|
// comparison function is defined for these tests, since proto message returned
|
|
// from unary and streaming RPCs (as of protobuf 1.23.0 and grpc 1.29.1) aren't
|
|
// consistent with the private fields set on the messages. As a result, we
|
|
// avoid using require.Equal and test only the actual data members.
|
|
func (h *HarnessTest) AssertInvoiceEqual(a, b *lnrpc.Invoice) {
|
|
// Ensure the HTLCs are sorted properly before attempting to compare.
|
|
sort.Slice(a.Htlcs, func(i, j int) bool {
|
|
return a.Htlcs[i].ChanId < a.Htlcs[j].ChanId
|
|
})
|
|
sort.Slice(b.Htlcs, func(i, j int) bool {
|
|
return b.Htlcs[i].ChanId < b.Htlcs[j].ChanId
|
|
})
|
|
|
|
require.Equal(h, a.Memo, b.Memo)
|
|
require.Equal(h, a.RPreimage, b.RPreimage)
|
|
require.Equal(h, a.RHash, b.RHash)
|
|
require.Equal(h, a.Value, b.Value)
|
|
require.Equal(h, a.ValueMsat, b.ValueMsat)
|
|
require.Equal(h, a.CreationDate, b.CreationDate)
|
|
require.Equal(h, a.SettleDate, b.SettleDate)
|
|
require.Equal(h, a.PaymentRequest, b.PaymentRequest)
|
|
require.Equal(h, a.DescriptionHash, b.DescriptionHash)
|
|
require.Equal(h, a.Expiry, b.Expiry)
|
|
require.Equal(h, a.FallbackAddr, b.FallbackAddr)
|
|
require.Equal(h, a.CltvExpiry, b.CltvExpiry)
|
|
require.Equal(h, a.RouteHints, b.RouteHints)
|
|
require.Equal(h, a.Private, b.Private)
|
|
require.Equal(h, a.AddIndex, b.AddIndex)
|
|
require.Equal(h, a.SettleIndex, b.SettleIndex)
|
|
require.Equal(h, a.AmtPaidSat, b.AmtPaidSat)
|
|
require.Equal(h, a.AmtPaidMsat, b.AmtPaidMsat)
|
|
require.Equal(h, a.State, b.State)
|
|
require.Equal(h, a.Features, b.Features)
|
|
require.Equal(h, a.IsKeysend, b.IsKeysend)
|
|
require.Equal(h, a.PaymentAddr, b.PaymentAddr)
|
|
require.Equal(h, a.IsAmp, b.IsAmp)
|
|
|
|
require.Equal(h, len(a.Htlcs), len(b.Htlcs))
|
|
for i := range a.Htlcs {
|
|
htlcA, htlcB := a.Htlcs[i], b.Htlcs[i]
|
|
require.Equal(h, htlcA.ChanId, htlcB.ChanId)
|
|
require.Equal(h, htlcA.HtlcIndex, htlcB.HtlcIndex)
|
|
require.Equal(h, htlcA.AmtMsat, htlcB.AmtMsat)
|
|
require.Equal(h, htlcA.AcceptHeight, htlcB.AcceptHeight)
|
|
require.Equal(h, htlcA.AcceptTime, htlcB.AcceptTime)
|
|
require.Equal(h, htlcA.ResolveTime, htlcB.ResolveTime)
|
|
require.Equal(h, htlcA.ExpiryHeight, htlcB.ExpiryHeight)
|
|
require.Equal(h, htlcA.State, htlcB.State)
|
|
require.Equal(h, htlcA.CustomRecords, htlcB.CustomRecords)
|
|
require.Equal(h, htlcA.MppTotalAmtMsat, htlcB.MppTotalAmtMsat)
|
|
require.Equal(h, htlcA.Amp, htlcB.Amp)
|
|
}
|
|
}
|
|
|
|
// AssertUTXOInWallet asserts that a given UTXO can be found in the node's
|
|
// wallet.
|
|
func (h *HarnessTest) AssertUTXOInWallet(hn *node.HarnessNode,
|
|
op *lnrpc.OutPoint, account string) {
|
|
|
|
err := wait.NoError(func() error {
|
|
utxos := h.GetUTXOs(hn, account)
|
|
|
|
err := fmt.Errorf("tx with hash %x not found", op.TxidBytes)
|
|
for _, utxo := range utxos {
|
|
if !bytes.Equal(utxo.Outpoint.TxidBytes, op.TxidBytes) {
|
|
continue
|
|
}
|
|
|
|
err = fmt.Errorf("tx with output index %v not found",
|
|
op.OutputIndex)
|
|
if utxo.Outpoint.OutputIndex != op.OutputIndex {
|
|
continue
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
return err
|
|
}, DefaultTimeout)
|
|
|
|
require.NoErrorf(h, err, "outpoint %v not found in %s's wallet",
|
|
op, hn.Name())
|
|
}
|
|
|
|
// AssertWalletAccountBalance asserts that the unconfirmed and confirmed
|
|
// balance for the given account is satisfied by the WalletBalance and
|
|
// ListUnspent RPCs. The unconfirmed balance is not checked for neutrino nodes.
|
|
func (h *HarnessTest) AssertWalletAccountBalance(hn *node.HarnessNode,
|
|
account string, confirmedBalance, unconfirmedBalance int64) {
|
|
|
|
err := wait.NoError(func() error {
|
|
balanceResp := hn.RPC.WalletBalance()
|
|
require.Contains(h, balanceResp.AccountBalance, account)
|
|
accountBalance := balanceResp.AccountBalance[account]
|
|
|
|
// Check confirmed balance.
|
|
if accountBalance.ConfirmedBalance != confirmedBalance {
|
|
return fmt.Errorf("expected confirmed balance %v, "+
|
|
"got %v", confirmedBalance,
|
|
accountBalance.ConfirmedBalance)
|
|
}
|
|
|
|
utxos := h.GetUTXOsConfirmed(hn, account)
|
|
var totalConfirmedVal int64
|
|
for _, utxo := range utxos {
|
|
totalConfirmedVal += utxo.AmountSat
|
|
}
|
|
if totalConfirmedVal != confirmedBalance {
|
|
return fmt.Errorf("expected total confirmed utxo "+
|
|
"balance %v, got %v", confirmedBalance,
|
|
totalConfirmedVal)
|
|
}
|
|
|
|
// Skip unconfirmed balance checks for neutrino nodes.
|
|
if h.IsNeutrinoBackend() {
|
|
return nil
|
|
}
|
|
|
|
// Check unconfirmed balance.
|
|
if accountBalance.UnconfirmedBalance != unconfirmedBalance {
|
|
return fmt.Errorf("expected unconfirmed balance %v, "+
|
|
"got %v", unconfirmedBalance,
|
|
accountBalance.UnconfirmedBalance)
|
|
}
|
|
|
|
utxos = h.GetUTXOsUnconfirmed(hn, account)
|
|
var totalUnconfirmedVal int64
|
|
for _, utxo := range utxos {
|
|
totalUnconfirmedVal += utxo.AmountSat
|
|
}
|
|
if totalUnconfirmedVal != unconfirmedBalance {
|
|
return fmt.Errorf("expected total unconfirmed utxo "+
|
|
"balance %v, got %v", unconfirmedBalance,
|
|
totalUnconfirmedVal)
|
|
}
|
|
|
|
return nil
|
|
}, DefaultTimeout)
|
|
require.NoError(h, err, "timeout checking wallet account balance")
|
|
}
|
|
|
|
// AssertClosingTxInMempool assert that the closing transaction of the given
|
|
// channel point can be found in the mempool. If the channel has anchors, it
|
|
// will assert the anchor sweep tx is also in the mempool.
|
|
func (h *HarnessTest) AssertClosingTxInMempool(cp *lnrpc.ChannelPoint,
|
|
c lnrpc.CommitmentType) *wire.MsgTx {
|
|
|
|
// Get expected number of txes to be found in the mempool.
|
|
expectedTxes := 1
|
|
hasAnchors := CommitTypeHasAnchors(c)
|
|
if hasAnchors {
|
|
expectedTxes = 2
|
|
}
|
|
|
|
// Wait for the expected txes to be found in the mempool.
|
|
h.AssertNumTxsInMempool(expectedTxes)
|
|
|
|
// Get the closing tx from the mempool.
|
|
op := h.OutPointFromChannelPoint(cp)
|
|
closeTx := h.AssertOutpointInMempool(op)
|
|
|
|
return closeTx
|
|
}
|
|
|
|
// AssertClosingTxInMempool assert that the closing transaction of the given
|
|
// channel point can be found in the mempool. If the channel has anchors, it
|
|
// will assert the anchor sweep tx is also in the mempool.
|
|
func (h *HarnessTest) MineClosingTx(cp *lnrpc.ChannelPoint) *wire.MsgTx {
|
|
// Wait for the expected txes to be found in the mempool.
|
|
h.AssertNumTxsInMempool(1)
|
|
|
|
// Get the closing tx from the mempool.
|
|
op := h.OutPointFromChannelPoint(cp)
|
|
closeTx := h.AssertOutpointInMempool(op)
|
|
|
|
// Mine a block to confirm the closing transaction and potential anchor
|
|
// sweep.
|
|
h.MineBlocksAndAssertNumTxes(1, 1)
|
|
|
|
return closeTx
|
|
}
|
|
|
|
// AssertWalletLockedBalance asserts the expected amount has been marked as
|
|
// locked in the node's WalletBalance response.
|
|
func (h *HarnessTest) AssertWalletLockedBalance(hn *node.HarnessNode,
|
|
balance int64) {
|
|
|
|
err := wait.NoError(func() error {
|
|
balanceResp := hn.RPC.WalletBalance()
|
|
got := balanceResp.LockedBalance
|
|
|
|
if got != balance {
|
|
return fmt.Errorf("want %d, got %d", balance, got)
|
|
}
|
|
|
|
return nil
|
|
}, wait.DefaultTimeout)
|
|
require.NoError(h, err, "%s: timeout checking locked balance",
|
|
hn.Name())
|
|
}
|
|
|
|
// AssertNumPendingSweeps asserts the number of pending sweeps for the given
|
|
// node.
|
|
func (h *HarnessTest) AssertNumPendingSweeps(hn *node.HarnessNode,
|
|
n int) []*walletrpc.PendingSweep {
|
|
|
|
results := make([]*walletrpc.PendingSweep, 0, n)
|
|
|
|
err := wait.NoError(func() error {
|
|
resp := hn.RPC.PendingSweeps()
|
|
num := len(resp.PendingSweeps)
|
|
|
|
numDesc := "\n"
|
|
for _, s := range resp.PendingSweeps {
|
|
desc := fmt.Sprintf("op=%v:%v, amt=%v, type=%v, "+
|
|
"deadline=%v\n", s.Outpoint.TxidStr,
|
|
s.Outpoint.OutputIndex, s.AmountSat,
|
|
s.WitnessType, s.DeadlineHeight)
|
|
numDesc += desc
|
|
|
|
// The deadline height must be set, otherwise the
|
|
// pending input response is not update-to-date.
|
|
if s.DeadlineHeight == 0 {
|
|
return fmt.Errorf("input not updated: %s", desc)
|
|
}
|
|
}
|
|
|
|
if num == n {
|
|
results = resp.PendingSweeps
|
|
return nil
|
|
}
|
|
|
|
return fmt.Errorf("want %d , got %d, sweeps: %s", n, num,
|
|
numDesc)
|
|
}, DefaultTimeout)
|
|
|
|
require.NoErrorf(h, err, "%s: check pending sweeps timeout", hn.Name())
|
|
|
|
return results
|
|
}
|
|
|
|
// FindSweepingTxns asserts the expected number of sweeping txns are found in
|
|
// the txns specified and return them.
|
|
func (h *HarnessTest) FindSweepingTxns(txns []*wire.MsgTx,
|
|
expectedNumSweeps int, closeTxid chainhash.Hash) []*wire.MsgTx {
|
|
|
|
var sweepTxns []*wire.MsgTx
|
|
|
|
for _, tx := range txns {
|
|
if tx.TxIn[0].PreviousOutPoint.Hash == closeTxid {
|
|
sweepTxns = append(sweepTxns, tx)
|
|
}
|
|
}
|
|
require.Len(h, sweepTxns, expectedNumSweeps, "unexpected num of sweeps")
|
|
|
|
return sweepTxns
|
|
}
|