2021-04-14 09:19:21 +02:00
|
|
|
package itest
|
|
|
|
|
|
|
|
import (
|
|
|
|
"fmt"
|
|
|
|
|
2022-02-23 14:48:00 +01:00
|
|
|
"github.com/btcsuite/btcd/btcutil"
|
2021-04-14 09:19:21 +02:00
|
|
|
"github.com/lightningnetwork/lnd/lnrpc"
|
|
|
|
"github.com/lightningnetwork/lnd/lnrpc/invoicesrpc"
|
|
|
|
"github.com/lightningnetwork/lnd/lnrpc/routerrpc"
|
2022-08-12 11:03:44 +02:00
|
|
|
"github.com/lightningnetwork/lnd/lntest"
|
|
|
|
"github.com/lightningnetwork/lnd/lntest/rpc"
|
2021-04-14 09:19:21 +02:00
|
|
|
"github.com/lightningnetwork/lnd/lntest/wait"
|
|
|
|
"github.com/lightningnetwork/lnd/lntypes"
|
2021-04-14 09:19:22 +02:00
|
|
|
"github.com/stretchr/testify/require"
|
2021-04-14 09:19:21 +02:00
|
|
|
)
|
|
|
|
|
|
|
|
// testHoldInvoicePersistence tests that a sender to a hold-invoice, can be
|
|
|
|
// restarted before the payment gets settled, and still be able to receive the
|
|
|
|
// preimage.
|
2022-08-12 11:03:44 +02:00
|
|
|
func testHoldInvoicePersistence(ht *lntest.HarnessTest) {
|
2021-04-14 09:19:21 +02:00
|
|
|
const (
|
|
|
|
chanAmt = btcutil.Amount(1000000)
|
|
|
|
numPayments = 10
|
2022-08-05 11:59:00 +02:00
|
|
|
reason = lnrpc.PaymentFailureReason_FAILURE_REASON_INCORRECT_PAYMENT_DETAILS //nolint:lll
|
2021-04-14 09:19:21 +02:00
|
|
|
)
|
|
|
|
|
|
|
|
// Create carol, and clean up when the test finishes.
|
2022-08-05 11:59:00 +02:00
|
|
|
carol := ht.NewNode("Carol", nil)
|
2021-04-14 09:19:21 +02:00
|
|
|
|
|
|
|
// Connect Alice to Carol.
|
2022-08-05 11:59:00 +02:00
|
|
|
alice, bob := ht.Alice, ht.Bob
|
|
|
|
ht.ConnectNodes(alice, carol)
|
2021-04-14 09:19:21 +02:00
|
|
|
|
|
|
|
// Open a channel between Alice and Carol which is private so that we
|
|
|
|
// cover the addition of hop hints for hold invoices.
|
2022-08-05 11:59:00 +02:00
|
|
|
chanPointAlice := ht.OpenChannel(
|
2022-08-12 11:03:44 +02:00
|
|
|
alice, carol, lntest.OpenChannelParams{
|
2021-04-14 09:19:21 +02:00
|
|
|
Amt: chanAmt,
|
|
|
|
Private: true,
|
|
|
|
},
|
|
|
|
)
|
|
|
|
|
2022-02-07 08:03:09 +01:00
|
|
|
// For Carol to include her private channel with Alice as a hop hint,
|
|
|
|
// we need Alice to be perceived as a "public" node, meaning that she
|
|
|
|
// has at least one public channel in the graph. We open a public
|
|
|
|
// channel from Alice -> Bob and wait for Carol to see it.
|
2022-08-05 11:59:00 +02:00
|
|
|
chanPointBob := ht.OpenChannel(
|
2022-08-12 11:03:44 +02:00
|
|
|
alice, bob, lntest.OpenChannelParams{
|
2022-02-07 08:03:09 +01:00
|
|
|
Amt: chanAmt,
|
|
|
|
},
|
|
|
|
)
|
|
|
|
|
2022-08-05 11:59:00 +02:00
|
|
|
// Wait for Carol to see the open channel Alice-Bob.
|
|
|
|
ht.AssertTopologyChannelOpen(carol, chanPointBob)
|
2022-02-07 08:03:09 +01:00
|
|
|
|
2021-04-14 09:19:21 +02:00
|
|
|
// Create preimages for all payments we are going to initiate.
|
|
|
|
var preimages []lntypes.Preimage
|
|
|
|
for i := 0; i < numPayments; i++ {
|
|
|
|
var preimage lntypes.Preimage
|
2022-08-05 11:59:00 +02:00
|
|
|
copy(preimage[:], ht.Random32Bytes())
|
2021-04-14 09:19:21 +02:00
|
|
|
preimages = append(preimages, preimage)
|
|
|
|
}
|
|
|
|
|
|
|
|
// Let Carol create hold-invoices for all the payments.
|
|
|
|
var (
|
|
|
|
payAmt = btcutil.Amount(4)
|
|
|
|
payReqs []string
|
2022-08-05 11:59:00 +02:00
|
|
|
invoiceStreams []rpc.SingleInvoiceClient
|
2021-04-14 09:19:21 +02:00
|
|
|
)
|
|
|
|
|
2022-08-05 11:59:00 +02:00
|
|
|
assertInvoiceState := func(state lnrpc.Invoice_InvoiceState) {
|
|
|
|
for _, client := range invoiceStreams {
|
|
|
|
ht.AssertInvoiceState(client, state)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-04-14 09:19:21 +02:00
|
|
|
for _, preimage := range preimages {
|
|
|
|
payHash := preimage.Hash()
|
|
|
|
|
|
|
|
// Make our invoices private so that we get coverage for adding
|
|
|
|
// hop hints.
|
|
|
|
invoiceReq := &invoicesrpc.AddHoldInvoiceRequest{
|
|
|
|
Memo: "testing",
|
|
|
|
Value: int64(payAmt),
|
|
|
|
Hash: payHash[:],
|
|
|
|
Private: true,
|
|
|
|
}
|
2022-08-05 11:59:00 +02:00
|
|
|
resp := carol.RPC.AddHoldInvoice(invoiceReq)
|
|
|
|
payReqs = append(payReqs, resp.PaymentRequest)
|
2021-04-14 09:19:21 +02:00
|
|
|
|
2022-02-07 08:03:09 +01:00
|
|
|
// We expect all of our invoices to have hop hints attached,
|
|
|
|
// since Carol and Alice are connected with a private channel.
|
|
|
|
// We assert that we have one hop hint present to ensure that
|
|
|
|
// we've got coverage for hop hints.
|
2022-08-05 11:59:00 +02:00
|
|
|
invoice := alice.RPC.DecodePayReq(resp.PaymentRequest)
|
|
|
|
require.Len(ht, invoice.RouteHints, 1)
|
2022-02-07 08:03:09 +01:00
|
|
|
|
2022-08-05 11:59:00 +02:00
|
|
|
stream := carol.RPC.SubscribeSingleInvoice(payHash[:])
|
2021-04-14 09:19:21 +02:00
|
|
|
invoiceStreams = append(invoiceStreams, stream)
|
|
|
|
}
|
|
|
|
|
|
|
|
// Wait for all the invoices to reach the OPEN state.
|
2022-08-05 11:59:00 +02:00
|
|
|
assertInvoiceState(lnrpc.Invoice_OPEN)
|
2021-04-14 09:19:21 +02:00
|
|
|
|
|
|
|
// Let Alice initiate payments for all the created invoices.
|
|
|
|
for _, payReq := range payReqs {
|
2022-08-05 11:59:00 +02:00
|
|
|
req := &routerrpc.SendPaymentRequest{
|
|
|
|
PaymentRequest: payReq,
|
|
|
|
TimeoutSeconds: 60,
|
|
|
|
FeeLimitSat: 1000000,
|
2021-04-14 09:19:21 +02:00
|
|
|
}
|
|
|
|
|
2022-08-05 11:59:00 +02:00
|
|
|
// Wait for inflight status update.
|
|
|
|
ht.SendPaymentAndAssertStatus(
|
|
|
|
alice, req, lnrpc.Payment_IN_FLIGHT,
|
|
|
|
)
|
2021-04-14 09:19:21 +02:00
|
|
|
}
|
|
|
|
|
2022-08-05 11:59:00 +02:00
|
|
|
// The payments should now show up in Alice's ListPayments, with a zero
|
2021-04-14 09:19:21 +02:00
|
|
|
// preimage, indicating they are not yet settled.
|
2022-08-05 11:59:00 +02:00
|
|
|
var zeroPreimg lntypes.Preimage
|
|
|
|
err := wait.NoError(func() error {
|
|
|
|
payments := ht.AssertNumPayments(alice, numPayments)
|
2021-04-14 09:19:21 +02:00
|
|
|
|
|
|
|
// Gather the payment hashes we are looking for in the
|
|
|
|
// response.
|
|
|
|
payHashes := make(map[string]struct{})
|
|
|
|
for _, preimg := range preimages {
|
|
|
|
payHashes[preimg.Hash().String()] = struct{}{}
|
|
|
|
}
|
|
|
|
|
2022-08-05 11:59:00 +02:00
|
|
|
for _, payment := range payments {
|
2021-04-14 09:19:21 +02:00
|
|
|
_, ok := payHashes[payment.PaymentHash]
|
|
|
|
if !ok {
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
|
|
|
|
// The preimage should NEVER be non-zero at this point.
|
2022-08-05 11:59:00 +02:00
|
|
|
require.Equal(ht, zeroPreimg.String(),
|
|
|
|
payment.PaymentPreimage,
|
|
|
|
"expected zero preimage")
|
2021-04-14 09:19:21 +02:00
|
|
|
|
|
|
|
// We wait for the payment attempt to have been
|
|
|
|
// properly recorded in the DB.
|
|
|
|
if len(payment.Htlcs) == 0 {
|
|
|
|
return fmt.Errorf("no attempt recorded")
|
|
|
|
}
|
|
|
|
|
|
|
|
delete(payHashes, payment.PaymentHash)
|
|
|
|
}
|
|
|
|
|
|
|
|
if len(payHashes) != 0 {
|
|
|
|
return fmt.Errorf("payhash not found in response")
|
|
|
|
}
|
|
|
|
|
|
|
|
return nil
|
|
|
|
}, defaultTimeout)
|
2022-08-05 11:59:00 +02:00
|
|
|
require.NoError(ht, err, "timeout checking alice's payments")
|
2021-04-14 09:19:21 +02:00
|
|
|
|
|
|
|
// Wait for all invoices to be accepted.
|
2022-08-05 11:59:00 +02:00
|
|
|
assertInvoiceState(lnrpc.Invoice_ACCEPTED)
|
2021-04-14 09:19:21 +02:00
|
|
|
|
|
|
|
// Restart alice. This to ensure she will still be able to handle
|
|
|
|
// settling the invoices after a restart.
|
2022-08-05 11:59:00 +02:00
|
|
|
ht.RestartNode(alice)
|
|
|
|
|
|
|
|
// Ensure the connections are made.
|
|
|
|
//
|
|
|
|
// TODO(yy): we shouldn't need these two lines since the connections
|
|
|
|
// are permanent, they'd reconnect automatically upon Alice's restart.
|
|
|
|
// However, we'd sometimes see the error `unable to gracefully close
|
|
|
|
// channel while peer is offline (try force closing it instead):
|
|
|
|
// channel link not found` from closing the channels in the end,
|
|
|
|
// indicating there's something wrong with the peer conn. We need to
|
|
|
|
// investigate and fix it in peer conn management.
|
|
|
|
ht.EnsureConnected(alice, bob)
|
|
|
|
ht.EnsureConnected(alice, carol)
|
2021-04-14 09:19:21 +02:00
|
|
|
|
|
|
|
// Now after a restart, we must re-track the payments. We set up a
|
2022-02-07 08:03:09 +01:00
|
|
|
// goroutine for each to track their status updates.
|
2021-04-14 09:19:21 +02:00
|
|
|
for _, preimg := range preimages {
|
|
|
|
hash := preimg.Hash()
|
|
|
|
|
2022-08-05 11:59:00 +02:00
|
|
|
payStream := alice.RPC.TrackPaymentV2(hash[:])
|
|
|
|
ht.ReceiveTrackPayment(payStream)
|
2021-04-14 09:19:21 +02:00
|
|
|
|
2022-08-05 11:59:00 +02:00
|
|
|
ht.AssertPaymentStatus(
|
|
|
|
alice, preimg, lnrpc.Payment_IN_FLIGHT,
|
2021-04-14 09:19:21 +02:00
|
|
|
)
|
|
|
|
}
|
|
|
|
|
|
|
|
// Settle invoices half the invoices, cancel the rest.
|
|
|
|
for i, preimage := range preimages {
|
|
|
|
if i%2 == 0 {
|
2022-08-05 11:59:00 +02:00
|
|
|
carol.RPC.SettleInvoice(preimage[:])
|
|
|
|
ht.AssertInvoiceState(
|
|
|
|
invoiceStreams[i], lnrpc.Invoice_SETTLED,
|
|
|
|
)
|
2021-04-14 09:19:21 +02:00
|
|
|
} else {
|
|
|
|
hash := preimage.Hash()
|
2022-08-05 11:59:00 +02:00
|
|
|
carol.RPC.CancelInvoice(hash[:])
|
|
|
|
ht.AssertInvoiceState(
|
|
|
|
invoiceStreams[i], lnrpc.Invoice_CANCELED,
|
|
|
|
)
|
2021-04-14 09:19:21 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Check that Alice's invoices to be shown as settled and failed
|
|
|
|
// accordingly, and preimages matching up.
|
2022-08-05 11:59:00 +02:00
|
|
|
for i, preimg := range preimages {
|
2021-04-14 09:19:21 +02:00
|
|
|
if i%2 == 0 {
|
2022-08-05 11:59:00 +02:00
|
|
|
ht.AssertPaymentStatus(
|
|
|
|
alice, preimg, lnrpc.Payment_SUCCEEDED,
|
|
|
|
)
|
2021-04-14 09:19:21 +02:00
|
|
|
} else {
|
2022-08-05 11:59:00 +02:00
|
|
|
payment := ht.AssertPaymentStatus(
|
|
|
|
alice, preimg, lnrpc.Payment_FAILED,
|
|
|
|
)
|
|
|
|
require.Equal(ht, reason, payment.FailureReason,
|
|
|
|
"wrong failure reason")
|
2021-04-14 09:19:21 +02:00
|
|
|
}
|
|
|
|
}
|
2021-04-14 09:19:23 +02:00
|
|
|
|
2022-08-05 11:59:00 +02:00
|
|
|
// Finally, close all channels.
|
|
|
|
ht.CloseChannel(alice, chanPointBob)
|
|
|
|
ht.CloseChannel(alice, chanPointAlice)
|
2021-04-14 09:19:21 +02:00
|
|
|
}
|