payments_test.go raw
1 package transactions
2
3 import (
4 "context"
5 "encoding/json"
6 "errors"
7 "fmt"
8 "strings"
9 "sync"
10 "testing"
11 "time"
12
13 "github.com/stretchr/testify/assert"
14 "github.com/stretchr/testify/require"
15
16 "github.com/getAlby/hub/constants"
17 "github.com/getAlby/hub/db"
18 "github.com/getAlby/hub/events"
19 "github.com/getAlby/hub/lnclient"
20 "github.com/getAlby/hub/tests"
21 )
22
23 func TestSendPaymentSync_NoApp(t *testing.T) {
24 svc, err := tests.CreateTestService(t)
25 require.NoError(t, err)
26 defer svc.Remove()
27
28 metadata := map[string]interface{}{
29 "a": 123,
30 }
31
32 transactionsService := NewTransactionsService(svc.DB, svc.EventPublisher)
33 transaction, err := transactionsService.SendPaymentSync(tests.MockLNClientTransaction.Invoice, nil, metadata, svc.LNClient, nil, nil)
34
35 assert.NoError(t, err)
36 assert.Equal(t, uint64(123000), transaction.AmountMsat)
37 assert.Equal(t, constants.TRANSACTION_STATE_SETTLED, transaction.State)
38 assert.Zero(t, transaction.FeeReserveMsat)
39 assert.Equal(t, "123preimage", *transaction.Preimage)
40
41 type dummyMetadata struct {
42 A int `json:"a"`
43 }
44 var decodedMetadata dummyMetadata
45 err = json.Unmarshal(transaction.Metadata, &decodedMetadata)
46 assert.NoError(t, err)
47 assert.Equal(t, 123, decodedMetadata.A)
48 }
49
50 func TestSendPaymentSync_ZeroAmount(t *testing.T) {
51 svc, err := tests.CreateTestService(t)
52 require.NoError(t, err)
53 defer svc.Remove()
54
55 metadata := map[string]interface{}{
56 "a": 123,
57 }
58
59 transactionsService := NewTransactionsService(svc.DB, svc.EventPublisher)
60 amount := uint64(1234)
61 transaction, err := transactionsService.SendPaymentSync(tests.MockZeroAmountInvoice, &amount, metadata, svc.LNClient, nil, nil)
62
63 assert.NoError(t, err)
64 assert.Equal(t, amount, transaction.AmountMsat)
65 assert.Equal(t, constants.TRANSACTION_STATE_SETTLED, transaction.State)
66 assert.Zero(t, transaction.FeeReserveMsat)
67 assert.Equal(t, "123preimage", *transaction.Preimage)
68 }
69
70 func TestSendPaymentSync_AmountOnNonZeroAmountInvoice(t *testing.T) {
71 svc, err := tests.CreateTestService(t)
72 require.NoError(t, err)
73 defer svc.Remove()
74
75 metadata := map[string]interface{}{
76 "a": 123,
77 }
78
79 transactionsService := NewTransactionsService(svc.DB, svc.EventPublisher)
80 amount := uint64(1234)
81 transaction, err := transactionsService.SendPaymentSync(tests.MockInvoice, &amount, metadata, svc.LNClient, nil, nil)
82
83 assert.NoError(t, err)
84 // amount is from the invoice, not what was specified
85 assert.Equal(t, uint64(123_000), transaction.AmountMsat)
86 assert.Equal(t, constants.TRANSACTION_STATE_SETTLED, transaction.State)
87 assert.Zero(t, transaction.FeeReserveMsat)
88 assert.Equal(t, "123preimage", *transaction.Preimage)
89 }
90
91 func TestSendPaymentSync_MetadataTooLarge(t *testing.T) {
92 svc, err := tests.CreateTestService(t)
93 require.NoError(t, err)
94 defer svc.Remove()
95
96 metadata := make(map[string]interface{})
97 metadata["randomkey"] = strings.Repeat("a", constants.INVOICE_METADATA_MAX_LENGTH-15) // json encoding adds 16 characters
98
99 transactionsService := NewTransactionsService(svc.DB, svc.EventPublisher)
100 transaction, err := transactionsService.SendPaymentSync(tests.MockLNClientTransaction.Invoice, nil, metadata, svc.LNClient, nil, nil)
101
102 assert.Error(t, err)
103 assert.Equal(t, fmt.Sprintf("encoded payment metadata provided is too large. Limit: %d Received: %d", constants.INVOICE_METADATA_MAX_LENGTH, constants.INVOICE_METADATA_MAX_LENGTH+1), err.Error())
104 assert.Nil(t, transaction)
105 }
106
107 func TestSendPaymentSync_Duplicate_AlreadyPaid(t *testing.T) {
108 svc, err := tests.CreateTestService(t)
109 require.NoError(t, err)
110 defer svc.Remove()
111
112 svc.DB.Create(&db.Transaction{
113 State: constants.TRANSACTION_STATE_SETTLED,
114 Type: constants.TRANSACTION_TYPE_OUTGOING,
115 PaymentRequest: tests.MockLNClientTransaction.Invoice,
116 PaymentHash: tests.MockLNClientTransaction.PaymentHash,
117 AmountMsat: 123000,
118 })
119
120 transactionsService := NewTransactionsService(svc.DB, svc.EventPublisher)
121 transaction, err := transactionsService.SendPaymentSync(tests.MockLNClientTransaction.Invoice, nil, nil, svc.LNClient, nil, nil)
122
123 assert.Error(t, err)
124 assert.Equal(t, "this invoice has already been paid", err.Error())
125 assert.Nil(t, transaction)
126 }
127
128 func TestSendPaymentSync_Duplicate_Pending(t *testing.T) {
129 svc, err := tests.CreateTestService(t)
130 require.NoError(t, err)
131 defer svc.Remove()
132
133 svc.DB.Create(&db.Transaction{
134 State: constants.TRANSACTION_STATE_PENDING,
135 Type: constants.TRANSACTION_TYPE_OUTGOING,
136 PaymentHash: tests.MockLNClientTransaction.PaymentHash,
137 PaymentRequest: tests.MockLNClientTransaction.Invoice,
138 AmountMsat: 123000,
139 })
140
141 transactionsService := NewTransactionsService(svc.DB, svc.EventPublisher)
142 transaction, err := transactionsService.SendPaymentSync(tests.MockLNClientTransaction.Invoice, nil, nil, svc.LNClient, nil, nil)
143
144 assert.Error(t, err)
145 assert.Equal(t, "there is already a payment pending for this invoice", err.Error())
146 assert.Nil(t, transaction)
147 }
148
149 func TestSendPaymentSync_Duplicate_Failed(t *testing.T) {
150 svc, err := tests.CreateTestService(t)
151 require.NoError(t, err)
152 defer svc.Remove()
153
154 svc.DB.Create(&db.Transaction{
155 State: constants.TRANSACTION_STATE_FAILED,
156 Type: constants.TRANSACTION_TYPE_OUTGOING,
157 PaymentHash: tests.MockLNClientTransaction.PaymentHash,
158 AmountMsat: 123000,
159 })
160
161 transactionsService := NewTransactionsService(svc.DB, svc.EventPublisher)
162 _, err = transactionsService.SendPaymentSync(tests.MockLNClientTransaction.Invoice, nil, nil, svc.LNClient, nil, nil)
163
164 assert.NoError(t, err)
165 }
166
167 func TestMarkSettled_Sent(t *testing.T) {
168 svc, err := tests.CreateTestService(t)
169 require.NoError(t, err)
170 defer svc.Remove()
171
172 dbTransaction := db.Transaction{
173 State: constants.TRANSACTION_STATE_PENDING,
174 Type: constants.TRANSACTION_TYPE_OUTGOING,
175 PaymentHash: tests.MockLNClientTransaction.PaymentHash,
176 AmountMsat: 123000,
177 }
178 svc.DB.Create(&dbTransaction)
179
180 mockEventConsumer := tests.NewMockEventConsumer()
181 svc.EventPublisher.RegisterSubscriber(mockEventConsumer)
182 transactionsService := NewTransactionsService(svc.DB, svc.EventPublisher)
183 _, err = transactionsService.markTransactionSettled(&dbTransaction, "test", 0, false)
184
185 assert.NoError(t, err)
186 assert.Equal(t, constants.TRANSACTION_STATE_SETTLED, dbTransaction.State)
187 consumedEvents := mockEventConsumer.WaitForConsumedEvents(1)
188 assert.Equal(t, 1, len(consumedEvents))
189 assert.Equal(t, "nwc_payment_sent", consumedEvents[0].Event)
190 settledTransaction := consumedEvents[0].Properties.(*db.Transaction)
191 assert.Equal(t, &dbTransaction, settledTransaction)
192 }
193
194 func TestMarkSettled_Twice(t *testing.T) {
195 svc, err := tests.CreateTestService(t)
196 require.NoError(t, err)
197 defer svc.Remove()
198
199 dbTransaction := db.Transaction{
200 State: constants.TRANSACTION_STATE_PENDING,
201 Type: constants.TRANSACTION_TYPE_OUTGOING,
202 PaymentHash: tests.MockLNClientTransaction.PaymentHash,
203 AmountMsat: 123000,
204 }
205 svc.DB.Create(&dbTransaction)
206
207 mockEventConsumer := tests.NewMockEventConsumer()
208 svc.EventPublisher.RegisterSubscriber(mockEventConsumer)
209 transactionsService := NewTransactionsService(svc.DB, svc.EventPublisher)
210 var wg sync.WaitGroup
211 n := 10
212 markErrors := make([]error, n)
213 wg.Add(n)
214 for i := range n {
215 go func() {
216 defer wg.Done()
217 // load an independent copy so goroutines don't share the struct
218 var transactionCopy db.Transaction
219 if err := svc.DB.First(&transactionCopy, dbTransaction.ID).Error; err != nil {
220 markErrors[i] = err
221 return
222 }
223 _, markErrors[i] = transactionsService.markTransactionSettled(&transactionCopy, "test", 0, false)
224 }()
225 }
226 wg.Wait()
227
228 for _, markError := range markErrors {
229 assert.NoError(t, markError)
230 }
231
232 // ensure we only mark transaction settled once and only fire
233 // settled notifications once
234 var reloadedTransaction db.Transaction
235 require.NoError(t, svc.DB.First(&reloadedTransaction, dbTransaction.ID).Error)
236 assert.Equal(t, constants.TRANSACTION_STATE_SETTLED, reloadedTransaction.State)
237 consumedEvents := mockEventConsumer.WaitForConsumedEvents(1)
238 assert.Equal(t, 1, len(consumedEvents))
239 assert.Equal(t, "nwc_payment_sent", consumedEvents[0].Event)
240 settledTransaction := consumedEvents[0].Properties.(*db.Transaction)
241 assert.Equal(t, constants.TRANSACTION_STATE_SETTLED, settledTransaction.State)
242 assert.Equal(t, dbTransaction.PaymentHash, settledTransaction.PaymentHash)
243 }
244
245 func TestMarkSettled_Received(t *testing.T) {
246 svc, err := tests.CreateTestService(t)
247 require.NoError(t, err)
248 defer svc.Remove()
249
250 dbTransaction := db.Transaction{
251 State: constants.TRANSACTION_STATE_PENDING,
252 Type: constants.TRANSACTION_TYPE_INCOMING,
253 PaymentHash: tests.MockLNClientTransaction.PaymentHash,
254 AmountMsat: 123000,
255 }
256 svc.DB.Create(&dbTransaction)
257
258 mockEventConsumer := tests.NewMockEventConsumer()
259 svc.EventPublisher.RegisterSubscriber(mockEventConsumer)
260 transactionsService := NewTransactionsService(svc.DB, svc.EventPublisher)
261 _, err = transactionsService.markTransactionSettled(&dbTransaction, "test", 0, false)
262
263 assert.NoError(t, err)
264 assert.Equal(t, constants.TRANSACTION_STATE_SETTLED, dbTransaction.State)
265 consumedEvents := mockEventConsumer.WaitForConsumedEvents(1)
266 assert.Equal(t, 1, len(consumedEvents))
267 assert.Equal(t, "nwc_payment_received", consumedEvents[0].Event)
268 settledTransaction := consumedEvents[0].Properties.(*db.Transaction)
269 assert.Equal(t, &dbTransaction, settledTransaction)
270 }
271
272 func TestDoNotMarkSettledTwice(t *testing.T) {
273 svc, err := tests.CreateTestService(t)
274 require.NoError(t, err)
275 defer svc.Remove()
276
277 settledAt := time.Now().Add(time.Duration(-1) * time.Minute)
278 dbTransaction := db.Transaction{
279 State: constants.TRANSACTION_STATE_SETTLED,
280 Type: constants.TRANSACTION_TYPE_OUTGOING,
281 PaymentHash: tests.MockLNClientTransaction.PaymentHash,
282 AmountMsat: 123000,
283 SettledAt: &settledAt,
284 }
285 svc.DB.Create(&dbTransaction)
286
287 mockEventConsumer := tests.NewMockEventConsumer()
288 svc.EventPublisher.RegisterSubscriber(mockEventConsumer)
289 transactionsService := NewTransactionsService(svc.DB, svc.EventPublisher)
290 _, err = transactionsService.markTransactionSettled(&dbTransaction, "test", 0, false)
291
292 assert.NoError(t, err)
293 assert.Zero(t, len(mockEventConsumer.GetConsumedEvents()))
294 }
295
296 func TestMarkFailed(t *testing.T) {
297 svc, err := tests.CreateTestService(t)
298 require.NoError(t, err)
299 defer svc.Remove()
300
301 dbTransaction := db.Transaction{
302 State: constants.TRANSACTION_STATE_PENDING,
303 Type: constants.TRANSACTION_TYPE_OUTGOING,
304 PaymentHash: tests.MockLNClientTransaction.PaymentHash,
305 AmountMsat: 123000,
306 }
307 svc.DB.Create(&dbTransaction)
308
309 mockEventConsumer := tests.NewMockEventConsumer()
310 svc.EventPublisher.RegisterSubscriber(mockEventConsumer)
311 transactionsService := NewTransactionsService(svc.DB, svc.EventPublisher)
312 markedFailed, err := transactionsService.markPaymentFailed(&dbTransaction, "some routing error")
313
314 assert.NoError(t, err)
315 assert.True(t, markedFailed)
316 assert.Equal(t, constants.TRANSACTION_STATE_FAILED, dbTransaction.State)
317 consumedEvents := mockEventConsumer.WaitForConsumedEvents(1)
318 assert.Equal(t, 1, len(consumedEvents))
319 assert.Equal(t, "nwc_payment_failed", consumedEvents[0].Event)
320 settledTransaction := consumedEvents[0].Properties.(*db.Transaction)
321 assert.Equal(t, &dbTransaction, settledTransaction)
322 assert.Equal(t, "some routing error", settledTransaction.FailureReason)
323 }
324
325 func TestDoNotMarkFailedTwice(t *testing.T) {
326 svc, err := tests.CreateTestService(t)
327 require.NoError(t, err)
328 defer svc.Remove()
329
330 updatedAt := time.Now().Add(time.Duration(-1) * time.Minute)
331 dbTransaction := db.Transaction{
332 State: constants.TRANSACTION_STATE_FAILED,
333 Type: constants.TRANSACTION_TYPE_OUTGOING,
334 PaymentHash: tests.MockLNClientTransaction.PaymentHash,
335 AmountMsat: 123000,
336 UpdatedAt: updatedAt,
337 }
338 svc.DB.Create(&dbTransaction)
339
340 mockEventConsumer := tests.NewMockEventConsumer()
341 svc.EventPublisher.RegisterSubscriber(mockEventConsumer)
342 transactionsService := NewTransactionsService(svc.DB, svc.EventPublisher)
343 markedFailed, err := transactionsService.markPaymentFailed(&dbTransaction, "some routing error")
344
345 assert.NoError(t, err)
346 assert.False(t, markedFailed)
347 assert.Equal(t, updatedAt, dbTransaction.UpdatedAt)
348 assert.Zero(t, len(mockEventConsumer.GetConsumedEvents()))
349 }
350
351 func TestDoNotMarkSettledPaymentFailed(t *testing.T) {
352 svc, err := tests.CreateTestService(t)
353 require.NoError(t, err)
354 defer svc.Remove()
355
356 settledAt := time.Now()
357 dbTransaction := db.Transaction{
358 State: constants.TRANSACTION_STATE_SETTLED,
359 Type: constants.TRANSACTION_TYPE_OUTGOING,
360 PaymentHash: tests.MockLNClientTransaction.PaymentHash,
361 AmountMsat: 123000,
362 SettledAt: &settledAt,
363 }
364 svc.DB.Create(&dbTransaction)
365
366 mockEventConsumer := tests.NewMockEventConsumer()
367 svc.EventPublisher.RegisterSubscriber(mockEventConsumer)
368 transactionsService := NewTransactionsService(svc.DB, svc.EventPublisher)
369 markedFailed, err := transactionsService.markPaymentFailed(&dbTransaction, "some routing error")
370
371 assert.Error(t, err)
372 assert.False(t, markedFailed)
373
374 var reloadedTransaction db.Transaction
375 require.NoError(t, svc.DB.First(&reloadedTransaction, dbTransaction.ID).Error)
376 assert.Equal(t, constants.TRANSACTION_STATE_SETTLED, reloadedTransaction.State)
377 assert.Zero(t, len(mockEventConsumer.GetConsumedEvents()))
378 }
379
380 func TestSendPaymentSync_FailedRemovesFeeReserve(t *testing.T) {
381 svc, err := tests.CreateTestService(t)
382 require.NoError(t, err)
383 defer svc.Remove()
384
385 svc.LNClient.(*tests.MockLn).PayInvoiceErrors = append(svc.LNClient.(*tests.MockLn).PayInvoiceErrors, errors.New("Some error"))
386 svc.LNClient.(*tests.MockLn).PayInvoiceResponses = append(svc.LNClient.(*tests.MockLn).PayInvoiceResponses, nil)
387
388 mockEventConsumer := tests.NewMockEventConsumer()
389 svc.EventPublisher.RegisterSubscriber(mockEventConsumer)
390
391 transactionsService := NewTransactionsService(svc.DB, svc.EventPublisher)
392 transaction, err := transactionsService.SendPaymentSync(tests.MockLNClientTransaction.Invoice, nil, nil, svc.LNClient, nil, nil)
393
394 assert.Error(t, err)
395 assert.Nil(t, transaction)
396
397 transactionType := constants.TRANSACTION_TYPE_OUTGOING
398 transaction, err = transactionsService.LookupTransaction(context.TODO(), tests.MockLNClientTransaction.PaymentHash, &transactionType, svc.LNClient, nil)
399 assert.NoError(t, err)
400
401 assert.Equal(t, uint64(123000), transaction.AmountMsat)
402 assert.Equal(t, constants.TRANSACTION_STATE_FAILED, transaction.State)
403 assert.Zero(t, transaction.FeeReserveMsat)
404 assert.Nil(t, transaction.Preimage)
405
406 consumedEvents := mockEventConsumer.WaitForConsumedEvents(1)
407 assert.Equal(t, 1, len(consumedEvents))
408 assert.Equal(t, "nwc_payment_failed", consumedEvents[0].Event)
409 }
410
411 func TestSendPaymentSync_PendingHasFeeReserve(t *testing.T) {
412 svc, err := tests.CreateTestService(t)
413 require.NoError(t, err)
414 defer svc.Remove()
415
416 // fake a delay to ensure the payment is still pending
417 delay := 10 * time.Second
418 svc.LNClient.(*tests.MockLn).PaymentDelay = &delay
419
420 transactionsService := NewTransactionsService(svc.DB, svc.EventPublisher)
421 go func() {
422 transactionsService.SendPaymentSync(tests.MockLNClientTransaction.Invoice, nil, nil, svc.LNClient, nil, nil)
423 }()
424 // ensure the goroutine above runs first
425 time.Sleep(10 * time.Millisecond)
426
427 transactionType := constants.TRANSACTION_TYPE_OUTGOING
428 transaction, err := transactionsService.LookupTransaction(context.TODO(), tests.MockLNClientTransaction.PaymentHash, &transactionType, svc.LNClient, nil)
429 assert.NoError(t, err)
430
431 assert.Equal(t, uint64(123000), transaction.AmountMsat)
432 assert.Equal(t, constants.TRANSACTION_STATE_PENDING, transaction.State)
433 assert.Equal(t, uint64(10000), transaction.FeeReserveMsat)
434 assert.Nil(t, transaction.Preimage)
435 }
436
437 func TestConsumeEvent_FailedMarkedAsSuccessful(t *testing.T) {
438 svc, err := tests.CreateTestService(t)
439 require.NoError(t, err)
440 defer svc.Remove()
441
442 transactionsService := NewTransactionsService(svc.DB, svc.EventPublisher)
443
444 svc.LNClient.(*tests.MockLn).PayInvoiceErrors = append(svc.LNClient.(*tests.MockLn).PayInvoiceErrors, errors.New("some error"))
445 svc.LNClient.(*tests.MockLn).PayInvoiceResponses = append(svc.LNClient.(*tests.MockLn).PayInvoiceResponses, nil)
446
447 transaction, err := transactionsService.SendPaymentSync(tests.MockLNClientTransaction.Invoice, nil, nil, svc.LNClient, nil, nil)
448
449 assert.Error(t, err)
450 assert.Nil(t, transaction)
451
452 var transactions []db.Transaction
453 result := svc.DB.Find(&transactions, &db.Transaction{
454 Type: constants.TRANSACTION_TYPE_OUTGOING,
455 PaymentHash: tests.MockLNClientTransaction.PaymentHash,
456 })
457 assert.NoError(t, result.Error)
458 assert.Equal(t, 1, len(transactions))
459
460 transaction = &transactions[0]
461 assert.Equal(t, constants.TRANSACTION_STATE_FAILED, transaction.State)
462
463 // Now that we have a failed transaction, we submit a "nwc_lnclient_payment_sent" event.
464 // This should be marked as successful as long as there are no pending payments for the
465 // same payment hash
466
467 transactionsService.ConsumeEvent(context.TODO(), &events.Event{
468 Event: "nwc_lnclient_payment_sent",
469 Properties: &lnclient.Transaction{
470 Type: tests.MockLNClientTransaction.Type,
471 Invoice: tests.MockLNClientTransaction.Invoice,
472 Description: tests.MockLNClientTransaction.Description,
473 DescriptionHash: tests.MockLNClientTransaction.DescriptionHash,
474 Preimage: tests.MockLNClientTransaction.Preimage,
475 PaymentHash: tests.MockLNClientTransaction.PaymentHash,
476 AmountMsat: tests.MockLNClientTransaction.AmountMsat,
477 FeesPaidMsat: tests.MockLNClientTransaction.FeesPaidMsat,
478 },
479 }, nil)
480
481 // Re-read transactions and ensure that the single returned transaction
482 // is now settled.
483 result = svc.DB.Find(&transactions, &db.Transaction{
484 Type: constants.TRANSACTION_TYPE_OUTGOING,
485 PaymentHash: tests.MockLNClientTransaction.PaymentHash,
486 })
487 assert.NoError(t, result.Error)
488 assert.Equal(t, 1, len(transactions))
489
490 transaction = &transactions[0]
491 assert.Equal(t, constants.TRANSACTION_STATE_SETTLED, transaction.State)
492 }
493