github.com/decred/dcrlnd@v0.7.6/lntest/itest/lnd_hold_persistence_test.go (about)

     1  package itest
     2  
     3  import (
     4  	"context"
     5  	"crypto/rand"
     6  	"fmt"
     7  	"io"
     8  	"sync"
     9  	"time"
    10  
    11  	"github.com/decred/dcrd/dcrutil/v4"
    12  	"github.com/decred/dcrlnd/lnrpc"
    13  	"github.com/decred/dcrlnd/lnrpc/invoicesrpc"
    14  	"github.com/decred/dcrlnd/lnrpc/routerrpc"
    15  	"github.com/decred/dcrlnd/lntest"
    16  	"github.com/decred/dcrlnd/lntest/wait"
    17  	"github.com/decred/dcrlnd/lntypes"
    18  	"github.com/stretchr/testify/require"
    19  )
    20  
    21  // testHoldInvoicePersistence tests that a sender to a hold-invoice, can be
    22  // restarted before the payment gets settled, and still be able to receive the
    23  // preimage.
    24  func testHoldInvoicePersistence(net *lntest.NetworkHarness, t *harnessTest) {
    25  	ctxb := context.Background()
    26  
    27  	const (
    28  		chanAmt     = dcrutil.Amount(1000000)
    29  		numPayments = 10
    30  	)
    31  
    32  	// Create carol, and clean up when the test finishes.
    33  	carol := net.NewNode(t.t, "Carol", nil)
    34  	defer shutdownAndAssert(net, t, carol)
    35  
    36  	// Connect Alice to Carol.
    37  	net.ConnectNodes(t.t, net.Alice, carol)
    38  
    39  	// Open a channel between Alice and Carol which is private so that we
    40  	// cover the addition of hop hints for hold invoices.
    41  	chanPointAlice := openChannelAndAssert(
    42  		t, net, net.Alice, carol,
    43  		lntest.OpenChannelParams{
    44  			Amt:     chanAmt,
    45  			Private: true,
    46  		},
    47  	)
    48  
    49  	// Wait for Alice and Carol to receive the channel edge from the
    50  	// funding manager.
    51  	err := net.Alice.WaitForNetworkChannelOpen(chanPointAlice)
    52  	if err != nil {
    53  		t.Fatalf("alice didn't see the alice->carol channel before "+
    54  			"timeout: %v", err)
    55  	}
    56  
    57  	err = carol.WaitForNetworkChannelOpen(chanPointAlice)
    58  	if err != nil {
    59  		t.Fatalf("carol didn't see the carol->alice channel before "+
    60  			"timeout: %v", err)
    61  	}
    62  
    63  	// Create preimages for all payments we are going to initiate.
    64  	var preimages []lntypes.Preimage
    65  	for i := 0; i < numPayments; i++ {
    66  		var preimage lntypes.Preimage
    67  		_, err = rand.Read(preimage[:])
    68  		if err != nil {
    69  			t.Fatalf("unable to generate preimage: %v", err)
    70  		}
    71  
    72  		preimages = append(preimages, preimage)
    73  	}
    74  
    75  	// Let Carol create hold-invoices for all the payments.
    76  	var (
    77  		payAmt         = dcrutil.Amount(4)
    78  		payReqs        []string
    79  		invoiceStreams []invoicesrpc.Invoices_SubscribeSingleInvoiceClient
    80  	)
    81  
    82  	for _, preimage := range preimages {
    83  		payHash := preimage.Hash()
    84  
    85  		// Make our invoices private so that we get coverage for adding
    86  		// hop hints.
    87  		invoiceReq := &invoicesrpc.AddHoldInvoiceRequest{
    88  			Memo:    "testing",
    89  			Value:   int64(payAmt),
    90  			Hash:    payHash[:],
    91  			Private: true,
    92  		}
    93  		ctxt, _ := context.WithTimeout(ctxb, defaultTimeout)
    94  		resp, err := carol.AddHoldInvoice(ctxt, invoiceReq)
    95  		if err != nil {
    96  			t.Fatalf("unable to add invoice: %v", err)
    97  		}
    98  
    99  		ctx, cancel := context.WithCancel(ctxb)
   100  		defer cancel()
   101  
   102  		stream, err := carol.SubscribeSingleInvoice(
   103  			ctx,
   104  			&invoicesrpc.SubscribeSingleInvoiceRequest{
   105  				RHash: payHash[:],
   106  			},
   107  		)
   108  		if err != nil {
   109  			t.Fatalf("unable to subscribe to invoice: %v", err)
   110  		}
   111  
   112  		invoiceStreams = append(invoiceStreams, stream)
   113  		payReqs = append(payReqs, resp.PaymentRequest)
   114  	}
   115  
   116  	// Wait for all the invoices to reach the OPEN state.
   117  	for _, stream := range invoiceStreams {
   118  		invoice, err := stream.Recv()
   119  		if err != nil {
   120  			t.Fatalf("err: %v", err)
   121  		}
   122  
   123  		if invoice.State != lnrpc.Invoice_OPEN {
   124  			t.Fatalf("expected OPEN, got state: %v", invoice.State)
   125  		}
   126  	}
   127  
   128  	// Let Alice initiate payments for all the created invoices.
   129  	var paymentStreams []routerrpc.Router_SendPaymentV2Client
   130  	for _, payReq := range payReqs {
   131  		ctx, cancel := context.WithCancel(ctxb)
   132  		defer cancel()
   133  
   134  		payStream, err := net.Alice.RouterClient.SendPaymentV2(
   135  			ctx, &routerrpc.SendPaymentRequest{
   136  				PaymentRequest: payReq,
   137  				TimeoutSeconds: 60,
   138  				FeeLimitAtoms:  1000000,
   139  			},
   140  		)
   141  		if err != nil {
   142  			t.Fatalf("unable to send alice htlc: %v", err)
   143  		}
   144  
   145  		paymentStreams = append(paymentStreams, payStream)
   146  	}
   147  
   148  	// Wait for inlight status update.
   149  	for _, payStream := range paymentStreams {
   150  		payment, err := payStream.Recv()
   151  		if err != nil {
   152  			t.Fatalf("Failed receiving status update: %v", err)
   153  		}
   154  
   155  		if payment.Status != lnrpc.Payment_IN_FLIGHT {
   156  			t.Fatalf("state not in flight: %v", payment.Status)
   157  		}
   158  	}
   159  
   160  	// The payments should now show up in Alice's ListInvoices, with a zero
   161  	// preimage, indicating they are not yet settled.
   162  	err = wait.NoError(func() error {
   163  		req := &lnrpc.ListPaymentsRequest{
   164  			IncludeIncomplete: true,
   165  		}
   166  		ctxt, _ := context.WithTimeout(ctxb, defaultTimeout)
   167  		paymentsResp, err := net.Alice.ListPayments(ctxt, req)
   168  		if err != nil {
   169  			return fmt.Errorf("error when obtaining payments: %v",
   170  				err)
   171  		}
   172  
   173  		// Gather the payment hashes we are looking for in the
   174  		// response.
   175  		payHashes := make(map[string]struct{})
   176  		for _, preimg := range preimages {
   177  			payHashes[preimg.Hash().String()] = struct{}{}
   178  		}
   179  
   180  		var zeroPreimg lntypes.Preimage
   181  		for _, payment := range paymentsResp.Payments {
   182  			_, ok := payHashes[payment.PaymentHash]
   183  			if !ok {
   184  				continue
   185  			}
   186  
   187  			// The preimage should NEVER be non-zero at this point.
   188  			if payment.PaymentPreimage != zeroPreimg.String() {
   189  				t.Fatalf("expected zero preimage, got %v",
   190  					payment.PaymentPreimage)
   191  			}
   192  
   193  			// We wait for the payment attempt to have been
   194  			// properly recorded in the DB.
   195  			if len(payment.Htlcs) == 0 {
   196  				return fmt.Errorf("no attempt recorded")
   197  			}
   198  
   199  			delete(payHashes, payment.PaymentHash)
   200  		}
   201  
   202  		if len(payHashes) != 0 {
   203  			return fmt.Errorf("payhash not found in response")
   204  		}
   205  
   206  		return nil
   207  	}, defaultTimeout)
   208  	if err != nil {
   209  		t.Fatalf("predicate not satisfied: %v", err)
   210  	}
   211  
   212  	// Wait for all invoices to be accepted.
   213  	for _, stream := range invoiceStreams {
   214  		invoice, err := stream.Recv()
   215  		if err != nil {
   216  			t.Fatalf("err: %v", err)
   217  		}
   218  
   219  		if invoice.State != lnrpc.Invoice_ACCEPTED {
   220  			t.Fatalf("expected ACCEPTED, got state: %v",
   221  				invoice.State)
   222  		}
   223  	}
   224  
   225  	// Restart alice. This to ensure she will still be able to handle
   226  	// settling the invoices after a restart.
   227  	if err := net.RestartNode(net.Alice, nil); err != nil {
   228  		t.Fatalf("Node restart failed: %v", err)
   229  	}
   230  
   231  	// Now after a restart, we must re-track the payments. We set up a
   232  	// goroutine for each to track thir status updates.
   233  	var (
   234  		statusUpdates []chan *lnrpc.Payment
   235  		wg            sync.WaitGroup
   236  		quit          = make(chan struct{})
   237  	)
   238  
   239  	defer close(quit)
   240  	for _, preimg := range preimages {
   241  		hash := preimg.Hash()
   242  
   243  		ctx, cancel := context.WithCancel(ctxb)
   244  		defer cancel()
   245  
   246  		payStream, err := net.Alice.RouterClient.TrackPaymentV2(
   247  			ctx, &routerrpc.TrackPaymentRequest{
   248  				PaymentHash: hash[:],
   249  			},
   250  		)
   251  		if err != nil {
   252  			t.Fatalf("unable to send track payment: %v", err)
   253  		}
   254  
   255  		// We set up a channel where we'll forward any status update.
   256  		upd := make(chan *lnrpc.Payment)
   257  		wg.Add(1)
   258  		go func() {
   259  			defer wg.Done()
   260  
   261  			for {
   262  				payment, err := payStream.Recv()
   263  				if err != nil {
   264  					close(upd)
   265  					return
   266  				}
   267  
   268  				select {
   269  				case upd <- payment:
   270  				case <-quit:
   271  					return
   272  				}
   273  			}
   274  		}()
   275  
   276  		statusUpdates = append(statusUpdates, upd)
   277  	}
   278  
   279  	// Wait for the in-flight status update.
   280  	for _, upd := range statusUpdates {
   281  		select {
   282  		case payment, ok := <-upd:
   283  			if !ok {
   284  				t.Fatalf("failed getting payment update")
   285  			}
   286  
   287  			if payment.Status != lnrpc.Payment_IN_FLIGHT {
   288  				t.Fatalf("state not in in flight: %v",
   289  					payment.Status)
   290  			}
   291  		case <-time.After(5 * time.Second):
   292  			t.Fatalf("in flight status not recevied")
   293  		}
   294  	}
   295  
   296  	// Settle invoices half the invoices, cancel the rest.
   297  	for i, preimage := range preimages {
   298  		var expectedState lnrpc.Invoice_InvoiceState
   299  
   300  		ctxt, _ := context.WithTimeout(ctxb, defaultTimeout)
   301  		if i%2 == 0 {
   302  			settle := &invoicesrpc.SettleInvoiceMsg{
   303  				Preimage: preimage[:],
   304  			}
   305  			_, err = carol.SettleInvoice(ctxt, settle)
   306  
   307  			expectedState = lnrpc.Invoice_SETTLED
   308  		} else {
   309  			hash := preimage.Hash()
   310  			settle := &invoicesrpc.CancelInvoiceMsg{
   311  				PaymentHash: hash[:],
   312  			}
   313  			_, err = carol.CancelInvoice(ctxt, settle)
   314  
   315  			expectedState = lnrpc.Invoice_CANCELED
   316  		}
   317  		if err != nil {
   318  			t.Fatalf("unable to cancel/settle invoice: %v", err)
   319  		}
   320  
   321  		stream := invoiceStreams[i]
   322  		invoice, err := stream.Recv()
   323  		require.NoError(t.t, err)
   324  		require.Equal(t.t, expectedState, invoice.State)
   325  	}
   326  
   327  	// Make sure we get the expected status update.
   328  	for i, upd := range statusUpdates {
   329  		// Read until the payment is in a terminal state.
   330  		var payment *lnrpc.Payment
   331  		for payment == nil {
   332  			select {
   333  			case p, ok := <-upd:
   334  				if !ok {
   335  					t.Fatalf("failed getting payment update")
   336  				}
   337  
   338  				if p.Status == lnrpc.Payment_IN_FLIGHT {
   339  					continue
   340  				}
   341  
   342  				payment = p
   343  			case <-time.After(5 * time.Second):
   344  				t.Fatalf("in flight status not recevied")
   345  			}
   346  		}
   347  
   348  		// Assert terminal payment state.
   349  		if i%2 == 0 {
   350  			if payment.Status != lnrpc.Payment_SUCCEEDED {
   351  				t.Fatalf("state not succeeded : %v",
   352  					payment.Status)
   353  			}
   354  		} else {
   355  			if payment.FailureReason !=
   356  				lnrpc.PaymentFailureReason_FAILURE_REASON_INCORRECT_PAYMENT_DETAILS {
   357  
   358  				t.Fatalf("state not failed: %v",
   359  					payment.FailureReason)
   360  			}
   361  		}
   362  	}
   363  
   364  	// Check that Alice's invoices to be shown as settled and failed
   365  	// accordingly, and preimages matching up.
   366  	req := &lnrpc.ListPaymentsRequest{
   367  		IncludeIncomplete: true,
   368  	}
   369  	ctxt, _ := context.WithTimeout(ctxb, defaultTimeout)
   370  	paymentsResp, err := net.Alice.ListPayments(ctxt, req)
   371  	if err != nil {
   372  		t.Fatalf("error when obtaining Alice payments: %v", err)
   373  	}
   374  	for i, preimage := range preimages {
   375  		paymentHash := preimage.Hash()
   376  		var p string
   377  		for _, resp := range paymentsResp.Payments {
   378  			if resp.PaymentHash == paymentHash.String() {
   379  				p = resp.PaymentPreimage
   380  				break
   381  			}
   382  		}
   383  		if p == "" {
   384  			t.Fatalf("payment not found")
   385  		}
   386  
   387  		if i%2 == 0 {
   388  			if p != preimage.String() {
   389  				t.Fatalf("preimage doesn't match: %v vs %v",
   390  					p, preimage.String())
   391  			}
   392  		} else {
   393  			if p != lntypes.ZeroHash.String() {
   394  				t.Fatalf("preimage not zero: %v", p)
   395  			}
   396  		}
   397  	}
   398  
   399  	// Check that all of our invoice streams are terminated by the server
   400  	// since the invoices have completed.
   401  	for _, stream := range invoiceStreams {
   402  		_, err = stream.Recv()
   403  		require.Equal(t.t, io.EOF, err)
   404  	}
   405  
   406  	closeChannelAndAssert(t, net, carol, chanPointAlice, false)
   407  }