@@ -3,6 +3,7 @@ package accounts
33import (
44 "context"
55 "database/sql"
6+ "fmt"
67 "testing"
78 "time"
89
@@ -11,8 +12,11 @@ import (
1112 "github.com/lightningnetwork/lnd/fn"
1213 "github.com/lightningnetwork/lnd/lnrpc"
1314 "github.com/lightningnetwork/lnd/lntypes"
15+ "github.com/lightningnetwork/lnd/lnwire"
1416 "github.com/lightningnetwork/lnd/sqldb"
1517 "github.com/stretchr/testify/require"
18+ "golang.org/x/exp/rand"
19+ "pgregory.net/rapid"
1620)
1721
1822// TestAccountStoreMigration tests the migration of account store from a bolt
@@ -283,6 +287,16 @@ func TestAccountStoreMigration(t *testing.T) {
283287 require .False (t , known )
284288 },
285289 },
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+ },
286300 }
287301
288302 for _ , test := range tests {
@@ -344,3 +358,228 @@ func TestAccountStoreMigration(t *testing.T) {
344358 })
345359 }
346360}
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