@@ -3,6 +3,7 @@ package accounts
3
3
import (
4
4
"context"
5
5
"database/sql"
6
+ "fmt"
6
7
"testing"
7
8
"time"
8
9
@@ -11,8 +12,11 @@ import (
11
12
"github.com/lightningnetwork/lnd/fn"
12
13
"github.com/lightningnetwork/lnd/lnrpc"
13
14
"github.com/lightningnetwork/lnd/lntypes"
15
+ "github.com/lightningnetwork/lnd/lnwire"
14
16
"github.com/lightningnetwork/lnd/sqldb"
15
17
"github.com/stretchr/testify/require"
18
+ "golang.org/x/exp/rand"
19
+ "pgregory.net/rapid"
16
20
)
17
21
18
22
// TestAccountStoreMigration tests the migration of account store from a bolt
@@ -283,6 +287,16 @@ func TestAccountStoreMigration(t *testing.T) {
283
287
require .False (t , known )
284
288
},
285
289
},
290
+ {
291
+ name : "randomized accounts" ,
292
+ expectLastIndex : true ,
293
+ populateDB : randomizeAccounts ,
294
+ },
295
+ {
296
+ name : "rapid randomized accounts" ,
297
+ expectLastIndex : true ,
298
+ populateDB : rapidRandomizeAccounts ,
299
+ },
286
300
}
287
301
288
302
for _ , test := range tests {
@@ -346,3 +360,228 @@ func TestAccountStoreMigration(t *testing.T) {
346
360
})
347
361
}
348
362
}
363
+
364
+ // randomizeAccounts adds 10 randomized accounts to the kvStore, each with
365
+ // 50-1000 invoices and payments. The accounts are randomized in terms of
366
+ // balance, expiry, number of invoices and payments, and payment status.
367
+ func randomizeAccounts (t * testing.T , kvStore * BoltStore ) {
368
+ ctx := context .Background ()
369
+
370
+ var (
371
+ // numberOfAccounts is set to 10 to add enough accounts to get
372
+ // enough variation between number of invoices and payments, but
373
+ // kept low enough for the test not take too long to run, as the
374
+ // test time increases drastically by the number of accounts we
375
+ // migrate.
376
+ numberOfAccounts = 10
377
+ invoiceCounter uint64 = 0
378
+ )
379
+
380
+ for i := 0 ; i < numberOfAccounts ; i ++ {
381
+ label := fmt .Sprintf ("account%d" , i )
382
+
383
+ // Generate a random balance between 1,000 and 100,000,000.
384
+ balance := lnwire .MilliSatoshi (
385
+ rand .Int63n (100000000 - 1000 ) + 1000 ,
386
+ )
387
+
388
+ // Generate a random expiry between 10 and 10,000 minutes.
389
+ expiry := time .Now ().Add (
390
+ time .Minute * time .Duration (rand .Intn (10000 - 10 )+ 10 ),
391
+ )
392
+
393
+ acct , err := kvStore .NewAccount (ctx , balance , expiry , label )
394
+ require .NoError (t , err )
395
+
396
+ // Add between 50 and 1000 invoices for the account.
397
+ numberOfInvoices := rand .Intn (1000 - 50 ) + 50
398
+ for j := 0 ; j < numberOfInvoices ; j ++ {
399
+ invoiceCounter ++
400
+
401
+ var rHash lntypes.Hash
402
+ _ , err := rand .Read (rHash [:])
403
+ require .NoError (t , err )
404
+
405
+ err = kvStore .AddAccountInvoice (ctx , acct .ID , rHash )
406
+ require .NoError (t , err )
407
+
408
+ err = kvStore .StoreLastIndexes (ctx , invoiceCounter , 0 )
409
+ require .NoError (t , err )
410
+ }
411
+
412
+ // Add between 50 and 1000 payments for the account.
413
+ numberOfPayments := rand .Intn (1000 - 50 ) + 50
414
+ for j := 0 ; j < numberOfPayments ; j ++ {
415
+ var rHash lntypes.Hash
416
+ _ , err := rand .Read (rHash [:])
417
+ require .NoError (t , err )
418
+
419
+ // Generate a random payment amount from 1,000 to
420
+ // 100,000,000.
421
+ amt := lnwire .MilliSatoshi (
422
+ rand .Int63n (100000000 - 1000 ) + 1000 ,
423
+ )
424
+
425
+ // Ensure that we get an almost equal amount of
426
+ // different payment statuses for the payments.
427
+ status := paymentStatus (j )
428
+
429
+ known , err := kvStore .UpsertAccountPayment (
430
+ ctx , acct .ID , rHash , amt , status ,
431
+ )
432
+ require .NoError (t , err )
433
+ require .False (t , known )
434
+ }
435
+ }
436
+ }
437
+
438
+ // rapidRandomizeAccounts is a rapid test that generates randomized
439
+ // accounts using rapid, invoices and payments, and inserts them into the
440
+ // kvStore. Each account is generated with a random balance, expiry, label,
441
+ // and a random number of 20-100 invoices and payments. The invoices and
442
+ // payments are also generated with random hashes and amounts.
443
+ func rapidRandomizeAccounts (t * testing.T , kvStore * BoltStore ) {
444
+ invoiceCounter := uint64 (0 )
445
+
446
+ rapid .Check (t , func (t * rapid.T ) {
447
+ ctx := context .Background ()
448
+
449
+ // Generate the randomized account for this check run.
450
+ acct := makeAccountGen ().Draw (t , "account" )
451
+
452
+ // Then proceed to insert the account with its invoices and
453
+ // payments into the db
454
+ newAcct , err := kvStore .NewAccount (
455
+ ctx , acct .balance , acct .expiry , acct .label ,
456
+ )
457
+ require .NoError (t , err )
458
+
459
+ for _ , invoiceHash := range acct .invoices {
460
+ invoiceCounter ++
461
+
462
+ err := kvStore .AddAccountInvoice (
463
+ ctx , newAcct .ID , invoiceHash ,
464
+ )
465
+ require .NoError (t , err )
466
+
467
+ err = kvStore .StoreLastIndexes (ctx , invoiceCounter , 0 )
468
+ require .NoError (t , err )
469
+ }
470
+
471
+ for _ , pmt := range acct .payments {
472
+ // Note that as rapid can generate multiple payments
473
+ // of the same values, we cannot be sure that the
474
+ // payment is unknown.
475
+ _ , err := kvStore .UpsertAccountPayment (
476
+ ctx , newAcct .ID , pmt .hash , pmt .amt , pmt .status ,
477
+ )
478
+ require .NoError (t , err )
479
+ }
480
+ })
481
+ }
482
+
483
+ // makeAccountGen returns a rapid generator that generates accounts, with
484
+ // random labels, balances, expiry times, and between 20-100 randomly generated
485
+ // invoices and payments. The invoices and payments are also generated with
486
+ // random hashes and amounts.
487
+ func makeAccountGen () * rapid.Generator [account ] {
488
+ return rapid .Custom (func (t * rapid.T ) account {
489
+ // As the store has a unique constraint for inserting labels,
490
+ // we don't use rapid to generate it, and instead use
491
+ // sufficiently large random number as the account suffix to
492
+ // avoid collisions.
493
+ label := fmt .Sprintf ("account:%d" , rand .Int63 ())
494
+
495
+ balance := lnwire .MilliSatoshi (
496
+ rapid .Int64Range (1000 , 100000000 ).Draw (
497
+ t , fmt .Sprintf ("balance_%s" , label ),
498
+ ),
499
+ )
500
+
501
+ expiry := time .Now ().Add (
502
+ time .Duration (
503
+ rapid .IntRange (10 , 10000 ).Draw (
504
+ t , fmt .Sprintf ("expiry_%s" , label ),
505
+ ),
506
+ ) * time .Minute ,
507
+ )
508
+
509
+ // Generate the random invoices
510
+ numInvoices := rapid .IntRange (20 , 100 ).Draw (
511
+ t , fmt .Sprintf ("numInvoices_%s" , label ),
512
+ )
513
+ invoices := make ([]lntypes.Hash , numInvoices )
514
+ for i := range invoices {
515
+ invoices [i ] = randomHash (
516
+ t , fmt .Sprintf ("invoiceHash_%s_%d" , label , i ),
517
+ )
518
+ }
519
+
520
+ // Generate the random payments
521
+ numPayments := rapid .IntRange (20 , 100 ).Draw (
522
+ t , fmt .Sprintf ("numPayments_%s" , label ),
523
+ )
524
+ payments := make ([]payment , numPayments )
525
+ for i := range payments {
526
+ hashName := fmt .Sprintf ("paymentHash_%s_%d" , label , i )
527
+ amtName := fmt .Sprintf ("amt_%s_%d" , label , i )
528
+
529
+ payments [i ] = payment {
530
+ hash : randomHash (t , hashName ),
531
+ amt : lnwire .MilliSatoshi (
532
+ rapid .Int64Range (1000 , 100000000 ).Draw (
533
+ t , amtName ,
534
+ ),
535
+ ),
536
+ status : paymentStatus (i ),
537
+ }
538
+ }
539
+
540
+ return account {
541
+ label : label ,
542
+ balance : balance ,
543
+ expiry : expiry ,
544
+ invoices : invoices ,
545
+ payments : payments ,
546
+ }
547
+ })
548
+ }
549
+
550
+ // randomHash generates a random hash of 32 bytes. It uses rapid to generate
551
+ // the random bytes, and then copies them into a lntypes.Hash struct.
552
+ func randomHash (t * rapid.T , name string ) lntypes.Hash {
553
+ hashBytes := rapid .SliceOfN (rapid .Byte (), 32 , 32 ).Draw (t , name )
554
+ var hash lntypes.Hash
555
+ copy (hash [:], hashBytes )
556
+ return hash
557
+ }
558
+
559
+ // paymentStatus returns a payment status based on the given index by taking
560
+ // the index modulo 4. This ensures an approximately equal distribution of
561
+ // different payment statuses across payments.
562
+ func paymentStatus (i int ) lnrpc.Payment_PaymentStatus {
563
+ switch i % 4 {
564
+ case 0 :
565
+ return lnrpc .Payment_SUCCEEDED
566
+ case 1 :
567
+ return lnrpc .Payment_IN_FLIGHT
568
+ case 2 :
569
+ return lnrpc .Payment_UNKNOWN
570
+ default :
571
+ return lnrpc .Payment_FAILED
572
+ }
573
+ }
574
+
575
+ type account struct {
576
+ label string
577
+ balance lnwire.MilliSatoshi
578
+ expiry time.Time
579
+ invoices []lntypes.Hash
580
+ payments []payment
581
+ }
582
+
583
+ type payment struct {
584
+ hash lntypes.Hash
585
+ amt lnwire.MilliSatoshi
586
+ status lnrpc.Payment_PaymentStatus
587
+ }
0 commit comments