mirror of
https://github.com/lightningnetwork/lnd.git
synced 2025-02-23 14:40:30 +01:00
In this commit, we start persisting shutdown info when we send the Shutdown message. When starting up a link, we also first check if we have previously persisted Shutdown info and if we have, we start the link in shutdown mode meaning that it will not accept any new outgoing HTLC additions and it will queue the shutdown message after any pending CommitSig has been sent.
243 lines
8.1 KiB
Go
243 lines
8.1 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,
|
|
})
|
|
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,
|
|
})
|
|
|
|
// 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)
|
|
}
|