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 }