mirror of
https://github.com/lightningnetwork/lnd.git
synced 2025-01-19 05:45:21 +01:00
246 lines
8.2 KiB
Go
246 lines
8.2 KiB
Go
package itest
|
|
|
|
import (
|
|
"testing"
|
|
|
|
"github.com/btcsuite/btcd/btcutil"
|
|
"github.com/btcsuite/btcd/chaincfg/chainhash"
|
|
"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"
|
|
"github.com/lightningnetwork/lnd/lntest/wait"
|
|
"github.com/lightningnetwork/lnd/lntypes"
|
|
"github.com/stretchr/testify/require"
|
|
)
|
|
|
|
// testCoopCloseWithHtlcs tests whether we can successfully issue a coop close
|
|
// request while there are still active htlcs on the link. In all the tests, we
|
|
// will set up an HODL invoice to suspend settlement. Then we will attempt to
|
|
// close the channel which should appear as a noop for the time being. Then we
|
|
// will have the receiver settle the invoice and observe that the channel gets
|
|
// torn down after settlement.
|
|
func testCoopCloseWithHtlcs(ht *lntest.HarnessTest) {
|
|
ht.Run("no restart", func(t *testing.T) {
|
|
tt := ht.Subtest(t)
|
|
coopCloseWithHTLCs(tt)
|
|
})
|
|
|
|
ht.Run("with restart", func(t *testing.T) {
|
|
tt := ht.Subtest(t)
|
|
coopCloseWithHTLCsWithRestart(tt)
|
|
})
|
|
}
|
|
|
|
// coopCloseWithHTLCs tests the basic coop close scenario which occurs when one
|
|
// channel party initiates a channel shutdown while an HTLC is still pending on
|
|
// the channel.
|
|
func coopCloseWithHTLCs(ht *lntest.HarnessTest) {
|
|
alice, bob := ht.Alice, ht.Bob
|
|
ht.ConnectNodes(alice, bob)
|
|
|
|
// Here we set up a channel between Alice and Bob, beginning with a
|
|
// balance on Bob's side.
|
|
chanPoint := ht.OpenChannel(bob, alice, lntest.OpenChannelParams{
|
|
Amt: btcutil.Amount(1000000),
|
|
})
|
|
|
|
// Wait for Bob to understand that the channel is ready to use.
|
|
ht.AssertTopologyChannelOpen(bob, chanPoint)
|
|
|
|
// Here we set things up so that Alice generates a HODL invoice so we
|
|
// can test whether the shutdown is deferred until the settlement of
|
|
// that invoice.
|
|
payAmt := btcutil.Amount(4)
|
|
var preimage lntypes.Preimage
|
|
copy(preimage[:], ht.Random32Bytes())
|
|
payHash := preimage.Hash()
|
|
|
|
invoiceReq := &invoicesrpc.AddHoldInvoiceRequest{
|
|
Memo: "testing close",
|
|
Value: int64(payAmt),
|
|
Hash: payHash[:],
|
|
}
|
|
resp := alice.RPC.AddHoldInvoice(invoiceReq)
|
|
invoiceStream := alice.RPC.SubscribeSingleInvoice(payHash[:])
|
|
|
|
// Here we wait for the invoice to be open and payable.
|
|
ht.AssertInvoiceState(invoiceStream, lnrpc.Invoice_OPEN)
|
|
|
|
// Now that the invoice is ready to be paid, let's have Bob open an
|
|
// HTLC for it.
|
|
req := &routerrpc.SendPaymentRequest{
|
|
PaymentRequest: resp.PaymentRequest,
|
|
TimeoutSeconds: 60,
|
|
FeeLimitSat: 1000000,
|
|
}
|
|
ht.SendPaymentAndAssertStatus(bob, req, lnrpc.Payment_IN_FLIGHT)
|
|
ht.AssertNumActiveHtlcs(bob, 1)
|
|
|
|
// Assert at this point that the HTLC is open but not yet settled.
|
|
ht.AssertInvoiceState(invoiceStream, lnrpc.Invoice_ACCEPTED)
|
|
|
|
// Have alice attempt to close the channel.
|
|
closeClient := alice.RPC.CloseChannel(&lnrpc.CloseChannelRequest{
|
|
ChannelPoint: chanPoint,
|
|
NoWait: true,
|
|
TargetConf: 6,
|
|
})
|
|
ht.AssertChannelInactive(bob, chanPoint)
|
|
|
|
// Now that the channel is inactive we can be certain that the deferred
|
|
// closure is set up. Let's settle the invoice.
|
|
alice.RPC.SettleInvoice(preimage[:])
|
|
|
|
// Pull the instant update off the wire to clear the path for the
|
|
// close pending update.
|
|
_, err := closeClient.Recv()
|
|
require.NoError(ht, err)
|
|
|
|
// Wait for the next channel closure update. Now that we have settled
|
|
// the only HTLC this should be imminent.
|
|
update, err := closeClient.Recv()
|
|
require.NoError(ht, err)
|
|
|
|
// This next update should be a GetClosePending as it should be the
|
|
// negotiation of the coop close tx.
|
|
closePending := update.GetClosePending()
|
|
require.NotNil(ht, closePending)
|
|
|
|
// Convert the txid we get from the PendingUpdate to a Hash so we can
|
|
// wait for it to be mined.
|
|
var closeTxid chainhash.Hash
|
|
require.NoError(
|
|
ht, closeTxid.SetBytes(closePending.Txid),
|
|
"invalid closing txid",
|
|
)
|
|
|
|
// Wait for the close tx to be in the Mempool.
|
|
ht.Miner.AssertTxInMempool(&closeTxid)
|
|
|
|
// Wait for it to get mined and finish tearing down.
|
|
ht.AssertStreamChannelCoopClosed(alice, chanPoint, false, closeClient)
|
|
}
|
|
|
|
// coopCloseWithHTLCsWithRestart also tests the coop close flow when an HTLC
|
|
// is still pending on the channel but this time it ensures that the shutdown
|
|
// process continues as expected even if a channel re-establish happens after
|
|
// one party has already initiated the shutdown.
|
|
func coopCloseWithHTLCsWithRestart(ht *lntest.HarnessTest) {
|
|
alice, bob := ht.Alice, ht.Bob
|
|
ht.ConnectNodes(alice, bob)
|
|
|
|
// Open a channel between Alice and Bob with the balance split equally.
|
|
// We do this to ensure that the close transaction will have 2 outputs
|
|
// so that we can assert that the correct delivery address gets used by
|
|
// the channel close initiator.
|
|
chanPoint := ht.OpenChannel(bob, alice, lntest.OpenChannelParams{
|
|
Amt: btcutil.Amount(1000000),
|
|
PushAmt: btcutil.Amount(1000000 / 2),
|
|
})
|
|
|
|
// Wait for Bob to understand that the channel is ready to use.
|
|
ht.AssertTopologyChannelOpen(bob, chanPoint)
|
|
|
|
// Set up a HODL invoice so that we can be sure that an HTLC is pending
|
|
// on the channel at the time that shutdown is requested.
|
|
var preimage lntypes.Preimage
|
|
copy(preimage[:], ht.Random32Bytes())
|
|
payHash := preimage.Hash()
|
|
|
|
invoiceReq := &invoicesrpc.AddHoldInvoiceRequest{
|
|
Memo: "testing close",
|
|
Value: 400,
|
|
Hash: payHash[:],
|
|
}
|
|
resp := alice.RPC.AddHoldInvoice(invoiceReq)
|
|
invoiceStream := alice.RPC.SubscribeSingleInvoice(payHash[:])
|
|
|
|
// Wait for the invoice to be ready and payable.
|
|
ht.AssertInvoiceState(invoiceStream, lnrpc.Invoice_OPEN)
|
|
|
|
// Now that the invoice is ready to be paid, let's have Bob open an HTLC
|
|
// for it.
|
|
req := &routerrpc.SendPaymentRequest{
|
|
PaymentRequest: resp.PaymentRequest,
|
|
TimeoutSeconds: 60,
|
|
FeeLimitSat: 1000000,
|
|
}
|
|
ht.SendPaymentAndAssertStatus(bob, req, lnrpc.Payment_IN_FLIGHT)
|
|
ht.AssertNumActiveHtlcs(bob, 1)
|
|
|
|
// Assert at this point that the HTLC is open but not yet settled.
|
|
ht.AssertInvoiceState(invoiceStream, lnrpc.Invoice_ACCEPTED)
|
|
|
|
// We will now let Alice initiate the closure of the channel. We will
|
|
// also let her specify a specific delivery address to be used since we
|
|
// want to test that this same address is used in the Shutdown message
|
|
// on reconnection.
|
|
newAddr := alice.RPC.NewAddress(&lnrpc.NewAddressRequest{
|
|
Type: AddrTypeWitnessPubkeyHash,
|
|
})
|
|
|
|
_ = alice.RPC.CloseChannel(&lnrpc.CloseChannelRequest{
|
|
ChannelPoint: chanPoint,
|
|
NoWait: true,
|
|
DeliveryAddress: newAddr.Address,
|
|
TargetConf: 6,
|
|
})
|
|
|
|
// Assert that both nodes see the channel as waiting for close.
|
|
ht.AssertChannelInactive(bob, chanPoint)
|
|
ht.AssertChannelInactive(alice, chanPoint)
|
|
|
|
// Now restart Alice and Bob.
|
|
ht.RestartNode(alice)
|
|
ht.RestartNode(bob)
|
|
|
|
ht.AssertConnected(alice, bob)
|
|
|
|
// Show that both nodes still see the channel as waiting for close after
|
|
// the restart.
|
|
ht.AssertChannelInactive(bob, chanPoint)
|
|
ht.AssertChannelInactive(alice, chanPoint)
|
|
|
|
// Settle the invoice.
|
|
alice.RPC.SettleInvoice(preimage[:])
|
|
|
|
// Wait for the channel to appear in the waiting closed list.
|
|
err := wait.Predicate(func() bool {
|
|
pendingChansResp := alice.RPC.PendingChannels()
|
|
waitingClosed := pendingChansResp.WaitingCloseChannels
|
|
|
|
return len(waitingClosed) == 1
|
|
}, defaultTimeout)
|
|
require.NoError(ht, err)
|
|
|
|
// Wait for the close tx to be in the Mempool and then mine 6 blocks
|
|
// to confirm the close.
|
|
closingTx := ht.AssertClosingTxInMempool(
|
|
chanPoint, lnrpc.CommitmentType_LEGACY,
|
|
)
|
|
ht.MineBlocksAndAssertNumTxes(6, 1)
|
|
|
|
// Finally, we inspect the closing transaction here to show that the
|
|
// delivery address that Alice specified in her original close request
|
|
// is the one that ended up being used in the final closing transaction.
|
|
tx := alice.RPC.GetTransaction(&walletrpc.GetTransactionRequest{
|
|
Txid: closingTx.TxHash().String(),
|
|
})
|
|
require.Len(ht, tx.OutputDetails, 2)
|
|
|
|
// Find Alice's output in the coop-close transaction.
|
|
var outputDetail *lnrpc.OutputDetail
|
|
for _, output := range tx.OutputDetails {
|
|
if output.IsOurAddress {
|
|
outputDetail = output
|
|
break
|
|
}
|
|
}
|
|
require.NotNil(ht, outputDetail)
|
|
|
|
// Show that the address used is the one she requested.
|
|
require.Equal(ht, outputDetail.Address, newAddr.Address)
|
|
}
|