4
4
"context"
5
5
"database/sql"
6
6
"errors"
7
+ "fmt"
7
8
"testing"
8
9
"time"
9
10
@@ -12,8 +13,11 @@ import (
12
13
"github.com/lightningnetwork/lnd/fn"
13
14
"github.com/lightningnetwork/lnd/lnrpc"
14
15
"github.com/lightningnetwork/lnd/lntypes"
16
+ "github.com/lightningnetwork/lnd/lnwire"
15
17
"github.com/lightningnetwork/lnd/sqldb"
16
18
"github.com/stretchr/testify/require"
19
+ "golang.org/x/exp/rand"
20
+ "pgregory.net/rapid"
17
21
)
18
22
19
23
// TestAccountStoreMigration tests the migration of account store from a bolt
@@ -320,6 +324,16 @@ func TestAccountStoreMigration(t *testing.T) {
320
324
require .False (t , known )
321
325
},
322
326
},
327
+ {
328
+ "randomized accounts" ,
329
+ true ,
330
+ randomizeAccounts ,
331
+ },
332
+ {
333
+ "rapid randomized accounts" ,
334
+ true ,
335
+ rapidRandomizeAccounts ,
336
+ },
323
337
}
324
338
325
339
for _ , test := range tests {
@@ -346,3 +360,227 @@ 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
+ // randomize balance from 1,000 to 100,000,000
384
+ balance := lnwire .MilliSatoshi (
385
+ rand .Int63n (100000000 - 1000 ) + 1000 ,
386
+ )
387
+
388
+ // randomize expiry from 10 to 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 from 50 to 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 from 50 to 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
+ // randomize amt from 1,000 to 100,000,000
420
+ amt := lnwire .MilliSatoshi (
421
+ rand .Int63n (100000000 - 1000 ) + 1000 ,
422
+ )
423
+
424
+ // Ensure that we get an almost equal amount of
425
+ // different payment statuses for the payments
426
+ status := paymentStatus (j )
427
+
428
+ known , err := kvStore .UpsertAccountPayment (
429
+ ctx , acct .ID , rHash , amt , status ,
430
+ )
431
+ require .NoError (t , err )
432
+ require .False (t , known )
433
+ }
434
+ }
435
+ }
436
+
437
+ // RapidRandomizeAccounts is a rapid test that generates randomized
438
+ // accounts using rapid, invoices and payments, and inserts them into the
439
+ // kvStore. Each account is generated with a random balance, expiry, label,
440
+ // and a random number of 20-100 invoices and payments. The invoices and
441
+ // payments are also generated with random hashes and amounts.
442
+ func rapidRandomizeAccounts (t * testing.T , kvStore * BoltStore ) {
443
+ invoiceCounter := uint64 (0 )
444
+
445
+ rapid .Check (t , func (t * rapid.T ) {
446
+ ctx := context .Background ()
447
+
448
+ // Generate the randomized account for this check run.
449
+ acct := makeAccountGen ().Draw (t , "account" )
450
+
451
+ // Then proceed to insert the account with its invoices and
452
+ // payments into the db
453
+ newAcct , err := kvStore .NewAccount (
454
+ ctx , acct .balance , acct .expiry , acct .label ,
455
+ )
456
+ require .NoError (t , err )
457
+
458
+ for _ , invoiceHash := range acct .invoices {
459
+ invoiceCounter ++
460
+
461
+ err := kvStore .AddAccountInvoice (
462
+ ctx , newAcct .ID , invoiceHash ,
463
+ )
464
+ require .NoError (t , err )
465
+
466
+ err = kvStore .StoreLastIndexes (ctx , invoiceCounter , 0 )
467
+ require .NoError (t , err )
468
+ }
469
+
470
+ for _ , pmt := range acct .payments {
471
+ // Note that as rapid can generate multiple payments
472
+ // of the same values, we cannot be sure that the
473
+ // payment is unknown.
474
+ _ , err := kvStore .UpsertAccountPayment (
475
+ ctx , newAcct .ID , pmt .hash , pmt .amt , pmt .status ,
476
+ )
477
+ require .NoError (t , err )
478
+ }
479
+ })
480
+ }
481
+
482
+ // makeAccountGen returns a rapid generator that generates accounts, with
483
+ // random labels, balances, expiry times, and between 20-100 randomly generated
484
+ // invoices and payments. The invoices and payments are also generated with
485
+ // random hashes and amounts.
486
+ func makeAccountGen () * rapid.Generator [account ] {
487
+ return rapid.Custom [account ](func (t * rapid.T ) account {
488
+ // As the store has a unique constraint for inserting labels,
489
+ // we don't use rapid to generate it, and instead use
490
+ // sufficiently large random number as the account suffix to
491
+ // avoid collisions.
492
+ label := fmt .Sprintf ("account:%d" , rand .Int63 ())
493
+
494
+ balance := lnwire .MilliSatoshi (
495
+ rapid .Int64Range (1000 , 100000000 ).Draw (
496
+ t , fmt .Sprintf ("balance_%s" , label ),
497
+ ),
498
+ )
499
+
500
+ expiry := time .Now ().Add (
501
+ time .Duration (
502
+ rapid .IntRange (10 , 10000 ).Draw (
503
+ t , fmt .Sprintf ("expiry_%s" , label ),
504
+ ),
505
+ ) * time .Minute ,
506
+ )
507
+
508
+ // Generate the random invoices
509
+ numInvoices := rapid .IntRange (20 , 100 ).Draw (
510
+ t , fmt .Sprintf ("numInvoices_%s" , label ),
511
+ )
512
+ invoices := make ([]lntypes.Hash , numInvoices )
513
+ for i := range invoices {
514
+ invoices [i ] = randomHash (
515
+ t , fmt .Sprintf ("invoiceHash_%s_%d" , label , i ),
516
+ )
517
+ }
518
+
519
+ // Generate the random payments
520
+ numPayments := rapid .IntRange (20 , 100 ).Draw (
521
+ t , fmt .Sprintf ("numPayments_%s" , label ),
522
+ )
523
+ payments := make ([]payment , numPayments )
524
+ for i := range payments {
525
+ hashName := fmt .Sprintf ("paymentHash_%s_%d" , label , i )
526
+ amtName := fmt .Sprintf ("amt_%s_%d" , label , i )
527
+
528
+ payments [i ] = payment {
529
+ hash : randomHash (t , hashName ),
530
+ amt : lnwire .MilliSatoshi (
531
+ rapid .Int64Range (1000 , 100000000 ).Draw (
532
+ t , amtName ,
533
+ ),
534
+ ),
535
+ status : paymentStatus (i ),
536
+ }
537
+ }
538
+
539
+ return account {
540
+ label : label ,
541
+ balance : balance ,
542
+ expiry : expiry ,
543
+ invoices : invoices ,
544
+ payments : payments ,
545
+ }
546
+ })
547
+ }
548
+
549
+ // randomHash generates a random hash of 32 bytes. It uses rapid to generate
550
+ // the random bytes, and then copies them into a lntypes.Hash struct.
551
+ func randomHash (t * rapid.T , name string ) lntypes.Hash {
552
+ hashBytes := rapid .SliceOfN [byte ](rapid .Byte (), 32 , 32 ).Draw (t , name )
553
+ var hash lntypes.Hash
554
+ copy (hash [:], hashBytes )
555
+ return hash
556
+ }
557
+
558
+ // paymentStatus returns a payment status based on the given index by taking
559
+ // the index modulo 4. This ensures an approximately equal distribution of
560
+ // different payment statuses across payments.
561
+ func paymentStatus (i int ) lnrpc.Payment_PaymentStatus {
562
+ switch i % 4 {
563
+ case 0 :
564
+ return lnrpc .Payment_SUCCEEDED
565
+ case 1 :
566
+ return lnrpc .Payment_IN_FLIGHT
567
+ case 2 :
568
+ return lnrpc .Payment_UNKNOWN
569
+ default :
570
+ return lnrpc .Payment_FAILED
571
+ }
572
+ }
573
+
574
+ type account struct {
575
+ label string
576
+ balance lnwire.MilliSatoshi
577
+ expiry time.Time
578
+ invoices []lntypes.Hash
579
+ payments []payment
580
+ }
581
+
582
+ type payment struct {
583
+ hash lntypes.Hash
584
+ amt lnwire.MilliSatoshi
585
+ status lnrpc.Payment_PaymentStatus
586
+ }
0 commit comments