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