mirror of
https://github.com/lightninglabs/lightning-terminal.git
synced 2026-08-13 12:33:36 +02:00
Co-authored-by: Olaoluwa Osuntokun <laolu32@gmail.com> Co-authored-by: Gijs van Dam <gijs@lightning.engineering> Co-authored-by: George Tsagkarelis <george.tsagkarelis@gmail.com>
241 lines
7.9 KiB
Go
241 lines
7.9 KiB
Go
package itest
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"testing"
|
|
|
|
"github.com/btcsuite/btcd/chaincfg/chainhash"
|
|
"github.com/btcsuite/btcd/wire"
|
|
"github.com/lightninglabs/taproot-assets/taprpc"
|
|
"github.com/lightningnetwork/lnd/channeldb"
|
|
"github.com/lightningnetwork/lnd/lnrpc"
|
|
"github.com/lightningnetwork/lnd/lnrpc/walletrpc"
|
|
"github.com/lightningnetwork/lnd/lntest"
|
|
"github.com/lightningnetwork/lnd/lntest/wait"
|
|
"github.com/stretchr/testify/require"
|
|
"google.golang.org/protobuf/proto"
|
|
)
|
|
|
|
// shutdownAndAssert shuts down the given node and asserts that no errors
|
|
// occur.
|
|
func shutdownAndAssert(net *NetworkHarness, t *harnessTest,
|
|
node *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())
|
|
}
|
|
|
|
// 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(t *harnessTest, net *NetworkHarness, alice,
|
|
bob *HarnessNode, p lntest.OpenChannelParams) *lnrpc.ChannelPoint {
|
|
|
|
t.t.Helper()
|
|
|
|
chanOpenUpdate := openChannelStream(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(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(alice, &chanPoint),
|
|
"unable to assert channel existence",
|
|
)
|
|
require.NoError(
|
|
t.t, net.AssertChannelExists(bob, &chanPoint),
|
|
"unable to assert channel existence",
|
|
)
|
|
|
|
return fundingChanPoint
|
|
}
|
|
|
|
// 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(t *harnessTest, net *NetworkHarness, alice,
|
|
bob *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(alice, bob, p)
|
|
return err
|
|
}, defaultTimeout)
|
|
require.NoError(t.t, err, "unable to open channel")
|
|
|
|
return chanOpenUpdate
|
|
}
|
|
|
|
// 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(t *harnessTest, net *NetworkHarness,
|
|
node *HarnessNode, fundingChanPoint *lnrpc.ChannelPoint,
|
|
force bool) *chainhash.Hash {
|
|
|
|
ctxb := context.Background()
|
|
ctxt, cancel := context.WithTimeout(ctxb, defaultTimeout)
|
|
defer cancel()
|
|
|
|
closeUpdates, _, err := net.CloseChannel(node, fundingChanPoint, force)
|
|
require.NoError(t.t, err, "unable to close channel")
|
|
|
|
return assertChannelClosed(
|
|
ctxt, t, net, node, fundingChanPoint, 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 *NetworkHarness, node *HarnessNode,
|
|
fundingChanPoint *lnrpc.ChannelPoint,
|
|
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.
|
|
listChansRequest := &lnrpc.ListChannelsRequest{}
|
|
listChansResp, err := node.ListChannels(ctx, 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".
|
|
pendingChansRequest := &lnrpc.PendingChannelsRequest{}
|
|
pendingChanResp, err := node.PendingChannels(ctx, 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.
|
|
block := mineBlocks(t, net, 1, 1)[0]
|
|
|
|
closingUpdate, err := net.WaitForChannelClose(closeUpdates)
|
|
require.NoError(t.t, err, "error while waiting for channel close")
|
|
|
|
closingTxid, err := chainhash.NewHash(closingUpdate.ClosingTxid)
|
|
require.NoError(t.t, err)
|
|
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 {
|
|
resp, err := node.PendingChannels(
|
|
ctx, &lnrpc.PendingChannelsRequest{},
|
|
)
|
|
if err != nil {
|
|
return false
|
|
}
|
|
|
|
for _, pendingClose := range resp.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
|
|
}
|
|
|
|
func assertSweepExists(t *testing.T, node *HarnessNode,
|
|
witnessType walletrpc.WitnessType) {
|
|
|
|
ctxb := context.Background()
|
|
err := wait.NoError(func() error {
|
|
pendingSweeps, err := node.WalletKitClient.PendingSweeps(
|
|
ctxb, &walletrpc.PendingSweepsRequest{},
|
|
)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
for _, sweep := range pendingSweeps.PendingSweeps {
|
|
if sweep.WitnessType == witnessType {
|
|
return nil
|
|
}
|
|
}
|
|
|
|
return fmt.Errorf("failed to find second level sweep: %v",
|
|
toProtoJSON(t, pendingSweeps))
|
|
}, defaultTimeout)
|
|
require.NoError(t, err)
|
|
}
|
|
|
|
func toProtoJSON(t *testing.T, resp proto.Message) string {
|
|
jsonBytes, err := taprpc.ProtoJSONMarshalOpts.Marshal(resp)
|
|
require.NoError(t, err)
|
|
|
|
return string(jsonBytes)
|
|
}
|