@@ -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 {
@@ -346,3 +360,228 @@ func TestAccountStoreMigration(t *testing.T) {
346360 })
347361 }
348362}
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