44 "context"
55 "database/sql"
66 "errors"
7+ "fmt"
78 "testing"
89 "time"
910
@@ -12,8 +13,11 @@ import (
1213 "github.com/lightningnetwork/lnd/fn"
1314 "github.com/lightningnetwork/lnd/lnrpc"
1415 "github.com/lightningnetwork/lnd/lntypes"
16+ "github.com/lightningnetwork/lnd/lnwire"
1517 "github.com/lightningnetwork/lnd/sqldb"
1618 "github.com/stretchr/testify/require"
19+ "golang.org/x/exp/rand"
20+ "pgregory.net/rapid"
1721)
1822
1923// TestAccountStoreMigration tests the migration of account store from a bolt
@@ -320,6 +324,16 @@ func TestAccountStoreMigration(t *testing.T) {
320324 require .False (t , known )
321325 },
322326 },
327+ {
328+ "randomized accounts" ,
329+ true ,
330+ randomizeAccounts ,
331+ },
332+ {
333+ "rapid randomized accounts" ,
334+ true ,
335+ rapidRandomizeAccounts ,
336+ },
323337 }
324338
325339 for _ , test := range tests {
@@ -346,3 +360,227 @@ 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+ // 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