package channeldb import ( "crypto/rand" "fmt" "math" "testing" "time" "github.com/lightningnetwork/lnd/lntypes" "github.com/lightningnetwork/lnd/lnwire" "github.com/lightningnetwork/lnd/record" "github.com/stretchr/testify/require" ) var ( emptyFeatures = lnwire.NewFeatureVector(nil, lnwire.Features) testNow = time.Unix(1, 0) ) func randInvoice(value lnwire.MilliSatoshi) (*Invoice, error) { var ( pre lntypes.Preimage payAddr [32]byte ) if _, err := rand.Read(pre[:]); err != nil { return nil, err } if _, err := rand.Read(payAddr[:]); err != nil { return nil, err } i := &Invoice{ CreationDate: testNow, Terms: ContractTerm{ Expiry: 4000, PaymentPreimage: &pre, PaymentAddr: payAddr, Value: value, Features: emptyFeatures, }, Htlcs: map[CircuitKey]*InvoiceHTLC{}, } i.Memo = []byte("memo") // Create a random byte slice of MaxPaymentRequestSize bytes to be used // as a dummy paymentrequest, and determine if it should be set based // on one of the random bytes. var r [MaxPaymentRequestSize]byte if _, err := rand.Read(r[:]); err != nil { return nil, err } if r[0]&1 == 0 { i.PaymentRequest = r[:] } else { i.PaymentRequest = []byte("") } return i, nil } // settleTestInvoice settles a test invoice. func settleTestInvoice(invoice *Invoice, settleIndex uint64) { invoice.SettleDate = testNow invoice.AmtPaid = invoice.Terms.Value invoice.State = ContractSettled invoice.Htlcs[CircuitKey{}] = &InvoiceHTLC{ Amt: invoice.Terms.Value, AcceptTime: testNow, ResolveTime: testNow, State: HtlcStateSettled, CustomRecords: make(record.CustomSet), } invoice.SettleIndex = settleIndex } // Tests that pending invoices are those which are either in ContractOpen or // in ContractAccepted state. func TestInvoiceIsPending(t *testing.T) { contractStates := []ContractState{ ContractOpen, ContractSettled, ContractCanceled, ContractAccepted, } for _, state := range contractStates { invoice := Invoice{ State: state, } // We expect that an invoice is pending if it's either in ContractOpen // or ContractAccepted state. pending := (state == ContractOpen || state == ContractAccepted) if invoice.IsPending() != pending { t.Fatalf("expected pending: %v, got: %v, invoice: %v", pending, invoice.IsPending(), invoice) } } } type invWorkflowTest struct { name string queryPayHash bool queryPayAddr bool } var invWorkflowTests = []invWorkflowTest{ { name: "unknown", queryPayHash: false, queryPayAddr: false, }, { name: "only payhash known", queryPayHash: true, queryPayAddr: false, }, { name: "payaddr and payhash known", queryPayHash: true, queryPayAddr: true, }, } // TestInvoiceWorkflow asserts the basic process of inserting, fetching, and // updating an invoice. We assert that the flow is successful using when // querying with various combinations of payment hash and payment address. func TestInvoiceWorkflow(t *testing.T) { t.Parallel() for _, test := range invWorkflowTests { test := test t.Run(test.name, func(t *testing.T) { testInvoiceWorkflow(t, test) }) } } func testInvoiceWorkflow(t *testing.T, test invWorkflowTest) { db, cleanUp, err := MakeTestDB() defer cleanUp() if err != nil { t.Fatalf("unable to make test db: %v", err) } // Create a fake invoice which we'll use several times in the tests // below. fakeInvoice, err := randInvoice(10000) if err != nil { t.Fatalf("unable to create invoice: %v", err) } invPayHash := fakeInvoice.Terms.PaymentPreimage.Hash() // Select the payment hash and payment address we will use to lookup or // update the invoice for the remainder of the test. var ( payHash lntypes.Hash payAddr *[32]byte ref InvoiceRef ) switch { case test.queryPayHash && test.queryPayAddr: payHash = invPayHash payAddr = &fakeInvoice.Terms.PaymentAddr ref = InvoiceRefByHashAndAddr(payHash, *payAddr) case test.queryPayHash: payHash = invPayHash ref = InvoiceRefByHash(payHash) } // Add the invoice to the database, this should succeed as there aren't // any existing invoices within the database with the same payment // hash. if _, err := db.AddInvoice(fakeInvoice, invPayHash); err != nil { t.Fatalf("unable to find invoice: %v", err) } // Attempt to retrieve the invoice which was just added to the // database. It should be found, and the invoice returned should be // identical to the one created above. dbInvoice, err := db.LookupInvoice(ref) if !test.queryPayAddr && !test.queryPayHash { if err != ErrInvoiceNotFound { t.Fatalf("invoice should not exist: %v", err) } return } require.Equal(t, *fakeInvoice, dbInvoice, "invoice fetched from db doesn't match original", ) // The add index of the invoice retrieved from the database should now // be fully populated. As this is the first index written to the DB, // the addIndex should be 1. if dbInvoice.AddIndex != 1 { t.Fatalf("wrong add index: expected %v, got %v", 1, dbInvoice.AddIndex) } // Settle the invoice, the version retrieved from the database should // now have the settled bit toggle to true and a non-default // SettledDate payAmt := fakeInvoice.Terms.Value * 2 _, err = db.UpdateInvoice(ref, getUpdateInvoice(payAmt)) if err != nil { t.Fatalf("unable to settle invoice: %v", err) } dbInvoice2, err := db.LookupInvoice(ref) if err != nil { t.Fatalf("unable to fetch invoice: %v", err) } if dbInvoice2.State != ContractSettled { t.Fatalf("invoice should now be settled but isn't") } if dbInvoice2.SettleDate.IsZero() { t.Fatalf("invoice should have non-zero SettledDate but isn't") } // Our 2x payment should be reflected, and also the settle index of 1 // should also have been committed for this index. if dbInvoice2.AmtPaid != payAmt { t.Fatalf("wrong amt paid: expected %v, got %v", payAmt, dbInvoice2.AmtPaid) } if dbInvoice2.SettleIndex != 1 { t.Fatalf("wrong settle index: expected %v, got %v", 1, dbInvoice2.SettleIndex) } // Attempt to insert generated above again, this should fail as // duplicates are rejected by the processing logic. if _, err := db.AddInvoice(fakeInvoice, payHash); err != ErrDuplicateInvoice { t.Fatalf("invoice insertion should fail due to duplication, "+ "instead %v", err) } // Attempt to look up a non-existent invoice, this should also fail but // with a "not found" error. var fakeHash [32]byte fakeRef := InvoiceRefByHash(fakeHash) _, err = db.LookupInvoice(fakeRef) if err != ErrInvoiceNotFound { t.Fatalf("lookup should have failed, instead %v", err) } // Add 10 random invoices. const numInvoices = 10 amt := lnwire.NewMSatFromSatoshis(1000) invoices := make([]*Invoice, numInvoices+1) invoices[0] = &dbInvoice2 for i := 1; i < len(invoices); i++ { invoice, err := randInvoice(amt) if err != nil { t.Fatalf("unable to create invoice: %v", err) } hash := invoice.Terms.PaymentPreimage.Hash() if _, err := db.AddInvoice(invoice, hash); err != nil { t.Fatalf("unable to add invoice %v", err) } invoices[i] = invoice } // Perform a scan to collect all the active invoices. query := InvoiceQuery{ IndexOffset: 0, NumMaxInvoices: math.MaxUint64, PendingOnly: false, } response, err := db.QueryInvoices(query) if err != nil { t.Fatalf("invoice query failed: %v", err) } // The retrieve list of invoices should be identical as since we're // using big endian, the invoices should be retrieved in ascending // order (and the primary key should be incremented with each // insertion). for i := 0; i < len(invoices); i++ { require.Equal(t, *invoices[i], response.Invoices[i], "retrieved invoice doesn't match", ) } } // TestAddDuplicatePayAddr asserts that the payment addresses of inserted // invoices are unique. func TestAddDuplicatePayAddr(t *testing.T) { db, cleanUp, err := MakeTestDB() defer cleanUp() require.NoError(t, err) // Create two invoices with the same payment addr. invoice1, err := randInvoice(1000) require.NoError(t, err) invoice2, err := randInvoice(20000) require.NoError(t, err) invoice2.Terms.PaymentAddr = invoice1.Terms.PaymentAddr // First insert should succeed. inv1Hash := invoice1.Terms.PaymentPreimage.Hash() _, err = db.AddInvoice(invoice1, inv1Hash) require.NoError(t, err) // Second insert should fail with duplicate payment addr. inv2Hash := invoice2.Terms.PaymentPreimage.Hash() _, err = db.AddInvoice(invoice2, inv2Hash) require.Error(t, err, ErrDuplicatePayAddr) } // TestAddDuplicateKeysendPayAddr asserts that we permit duplicate payment // addresses to be inserted if they are blank to support JIT legacy keysend // invoices. func TestAddDuplicateKeysendPayAddr(t *testing.T) { db, cleanUp, err := MakeTestDB() defer cleanUp() require.NoError(t, err) // Create two invoices with the same _blank_ payment addr. invoice1, err := randInvoice(1000) require.NoError(t, err) invoice1.Terms.PaymentAddr = BlankPayAddr invoice2, err := randInvoice(20000) require.NoError(t, err) invoice2.Terms.PaymentAddr = BlankPayAddr // Inserting both should succeed without a duplicate payment address // failure. inv1Hash := invoice1.Terms.PaymentPreimage.Hash() _, err = db.AddInvoice(invoice1, inv1Hash) require.NoError(t, err) inv2Hash := invoice2.Terms.PaymentPreimage.Hash() _, err = db.AddInvoice(invoice2, inv2Hash) require.NoError(t, err) // Querying for each should succeed. Here we use hash+addr refs since // the lookup will fail if the hash and addr point to different // invoices, so if both succeed we can be assured they aren't included // in the payment address index. ref1 := InvoiceRefByHashAndAddr(inv1Hash, BlankPayAddr) dbInv1, err := db.LookupInvoice(ref1) require.NoError(t, err) require.Equal(t, invoice1, &dbInv1) ref2 := InvoiceRefByHashAndAddr(inv2Hash, BlankPayAddr) dbInv2, err := db.LookupInvoice(ref2) require.NoError(t, err) require.Equal(t, invoice2, &dbInv2) } // TestInvRefEquivocation asserts that retrieving or updating an invoice using // an equivocating InvoiceRef results in ErrInvRefEquivocation. func TestInvRefEquivocation(t *testing.T) { db, cleanUp, err := MakeTestDB() defer cleanUp() require.NoError(t, err) // Add two random invoices. invoice1, err := randInvoice(1000) require.NoError(t, err) inv1Hash := invoice1.Terms.PaymentPreimage.Hash() _, err = db.AddInvoice(invoice1, inv1Hash) require.NoError(t, err) invoice2, err := randInvoice(2000) require.NoError(t, err) inv2Hash := invoice2.Terms.PaymentPreimage.Hash() _, err = db.AddInvoice(invoice2, inv2Hash) require.NoError(t, err) // Now, query using invoice 1's payment address, but invoice 2's payment // hash. We expect an error since the invref points to multiple // invoices. ref := InvoiceRefByHashAndAddr(inv2Hash, invoice1.Terms.PaymentAddr) _, err = db.LookupInvoice(ref) require.Error(t, err, ErrInvRefEquivocation) // The same error should be returned when updating an equivocating // reference. nop := func(_ *Invoice) (*InvoiceUpdateDesc, error) { return nil, nil } _, err = db.UpdateInvoice(ref, nop) require.Error(t, err, ErrInvRefEquivocation) } // TestInvoiceCancelSingleHtlc tests that a single htlc can be canceled on the // invoice. func TestInvoiceCancelSingleHtlc(t *testing.T) { t.Parallel() db, cleanUp, err := MakeTestDB() defer cleanUp() if err != nil { t.Fatalf("unable to make test db: %v", err) } preimage := lntypes.Preimage{1} paymentHash := preimage.Hash() testInvoice := &Invoice{ Htlcs: map[CircuitKey]*InvoiceHTLC{}, Terms: ContractTerm{ Value: lnwire.NewMSatFromSatoshis(10000), Features: emptyFeatures, PaymentPreimage: &preimage, }, } if _, err := db.AddInvoice(testInvoice, paymentHash); err != nil { t.Fatalf("unable to find invoice: %v", err) } // Accept an htlc on this invoice. key := CircuitKey{ChanID: lnwire.NewShortChanIDFromInt(1), HtlcID: 4} htlc := HtlcAcceptDesc{ Amt: 500, CustomRecords: make(record.CustomSet), } ref := InvoiceRefByHash(paymentHash) invoice, err := db.UpdateInvoice(ref, func(invoice *Invoice) (*InvoiceUpdateDesc, error) { return &InvoiceUpdateDesc{ AddHtlcs: map[CircuitKey]*HtlcAcceptDesc{ key: &htlc, }, }, nil }) if err != nil { t.Fatalf("unable to add invoice htlc: %v", err) } if len(invoice.Htlcs) != 1 { t.Fatalf("expected the htlc to be added") } if invoice.Htlcs[key].State != HtlcStateAccepted { t.Fatalf("expected htlc in state accepted") } // Cancel the htlc again. invoice, err = db.UpdateInvoice(ref, func(invoice *Invoice) (*InvoiceUpdateDesc, error) { return &InvoiceUpdateDesc{ CancelHtlcs: map[CircuitKey]struct{}{ key: {}, }, }, nil }) if err != nil { t.Fatalf("unable to cancel htlc: %v", err) } if len(invoice.Htlcs) != 1 { t.Fatalf("expected the htlc to be present") } if invoice.Htlcs[key].State != HtlcStateCanceled { t.Fatalf("expected htlc in state canceled") } } // TestInvoiceTimeSeries tests that newly added invoices invoices, as well as // settled invoices are added to the database are properly placed in the add // add or settle index which serves as an event time series. func TestInvoiceAddTimeSeries(t *testing.T) { t.Parallel() db, cleanUp, err := MakeTestDB(OptionClock(testClock)) defer cleanUp() if err != nil { t.Fatalf("unable to make test db: %v", err) } _, err = db.InvoicesAddedSince(0) require.NoError(t, err) // We'll start off by creating 20 random invoices, and inserting them // into the database. const numInvoices = 20 amt := lnwire.NewMSatFromSatoshis(1000) invoices := make([]Invoice, numInvoices) for i := 0; i < len(invoices); i++ { invoice, err := randInvoice(amt) if err != nil { t.Fatalf("unable to create invoice: %v", err) } paymentHash := invoice.Terms.PaymentPreimage.Hash() if _, err := db.AddInvoice(invoice, paymentHash); err != nil { t.Fatalf("unable to add invoice %v", err) } invoices[i] = *invoice } // With the invoices constructed, we'll now create a series of queries // that we'll use to assert expected return values of // InvoicesAddedSince. addQueries := []struct { sinceAddIndex uint64 resp []Invoice }{ // If we specify a value of zero, we shouldn't get any invoices // back. { sinceAddIndex: 0, }, // If we specify a value well beyond the number of inserted // invoices, we shouldn't get any invoices back. { sinceAddIndex: 99999999, }, // Using an index of 1 should result in all values, but the // first one being returned. { sinceAddIndex: 1, resp: invoices[1:], }, // If we use an index of 10, then we should retrieve the // reaming 10 invoices. { sinceAddIndex: 10, resp: invoices[10:], }, } for i, query := range addQueries { resp, err := db.InvoicesAddedSince(query.sinceAddIndex) if err != nil { t.Fatalf("unable to query: %v", err) } require.Equal(t, len(query.resp), len(resp)) for j := 0; j < len(query.resp); j++ { require.Equal(t, query.resp[j], resp[j], fmt.Sprintf("test: #%v, item: #%v", i, j), ) } } _, err = db.InvoicesSettledSince(0) require.NoError(t, err) var settledInvoices []Invoice var settleIndex uint64 = 1 // We'll now only settle the latter half of each of those invoices. for i := 10; i < len(invoices); i++ { invoice := &invoices[i] paymentHash := invoice.Terms.PaymentPreimage.Hash() ref := InvoiceRefByHash(paymentHash) _, err := db.UpdateInvoice( ref, getUpdateInvoice(invoice.Terms.Value), ) if err != nil { t.Fatalf("unable to settle invoice: %v", err) } // Create the settled invoice for the expectation set. settleTestInvoice(invoice, settleIndex) settleIndex++ settledInvoices = append(settledInvoices, *invoice) } // We'll now prepare an additional set of queries to ensure the settle // time series has properly been maintained in the database. settleQueries := []struct { sinceSettleIndex uint64 resp []Invoice }{ // If we specify a value of zero, we shouldn't get any settled // invoices back. { sinceSettleIndex: 0, }, // If we specify a value well beyond the number of settled // invoices, we shouldn't get any invoices back. { sinceSettleIndex: 99999999, }, // Using an index of 1 should result in the final 10 invoices // being returned, as we only settled those. { sinceSettleIndex: 1, resp: settledInvoices[1:], }, } for i, query := range settleQueries { resp, err := db.InvoicesSettledSince(query.sinceSettleIndex) if err != nil { t.Fatalf("unable to query: %v", err) } require.Equal(t, len(query.resp), len(resp)) for j := 0; j < len(query.resp); j++ { require.Equal(t, query.resp[j], resp[j], fmt.Sprintf("test: #%v, item: #%v", i, j), ) } } } // TestScanInvoices tests that ScanInvoices scans trough all stored invoices // correctly. func TestScanInvoices(t *testing.T) { t.Parallel() db, cleanup, err := MakeTestDB() defer cleanup() if err != nil { t.Fatalf("unable to make test db: %v", err) } var invoices map[lntypes.Hash]*Invoice callCount := 0 resetCount := 0 // reset is used to reset/initialize results and is called once // upon calling ScanInvoices and when the underlying transaction is // retried. reset := func() { invoices = make(map[lntypes.Hash]*Invoice) callCount = 0 resetCount++ } scanFunc := func(paymentHash lntypes.Hash, invoice *Invoice) error { invoices[paymentHash] = invoice callCount++ return nil } // With an empty DB we expect to not scan any invoices. require.NoError(t, db.ScanInvoices(scanFunc, reset)) require.Equal(t, 0, len(invoices)) require.Equal(t, 0, callCount) require.Equal(t, 1, resetCount) numInvoices := 5 testInvoices := make(map[lntypes.Hash]*Invoice) // Now populate the DB and check if we can get all invoices with their // payment hashes as expected. for i := 1; i <= numInvoices; i++ { invoice, err := randInvoice(lnwire.MilliSatoshi(i)) require.NoError(t, err) paymentHash := invoice.Terms.PaymentPreimage.Hash() testInvoices[paymentHash] = invoice _, err = db.AddInvoice(invoice, paymentHash) require.NoError(t, err) } resetCount = 0 require.NoError(t, db.ScanInvoices(scanFunc, reset)) require.Equal(t, numInvoices, callCount) require.Equal(t, testInvoices, invoices) require.Equal(t, 1, resetCount) } // TestDuplicateSettleInvoice tests that if we add a new invoice and settle it // twice, then the second time we also receive the invoice that we settled as a // return argument. func TestDuplicateSettleInvoice(t *testing.T) { t.Parallel() db, cleanUp, err := MakeTestDB(OptionClock(testClock)) defer cleanUp() if err != nil { t.Fatalf("unable to make test db: %v", err) } // We'll start out by creating an invoice and writing it to the DB. amt := lnwire.NewMSatFromSatoshis(1000) invoice, err := randInvoice(amt) if err != nil { t.Fatalf("unable to create invoice: %v", err) } payHash := invoice.Terms.PaymentPreimage.Hash() if _, err := db.AddInvoice(invoice, payHash); err != nil { t.Fatalf("unable to add invoice %v", err) } // With the invoice in the DB, we'll now attempt to settle the invoice. ref := InvoiceRefByHash(payHash) dbInvoice, err := db.UpdateInvoice(ref, getUpdateInvoice(amt)) if err != nil { t.Fatalf("unable to settle invoice: %v", err) } // We'll update what we expect the settle invoice to be so that our // comparison below has the correct assumption. invoice.SettleIndex = 1 invoice.State = ContractSettled invoice.AmtPaid = amt invoice.SettleDate = dbInvoice.SettleDate invoice.Htlcs = map[CircuitKey]*InvoiceHTLC{ {}: { Amt: amt, AcceptTime: time.Unix(1, 0), ResolveTime: time.Unix(1, 0), State: HtlcStateSettled, CustomRecords: make(record.CustomSet), }, } // We should get back the exact same invoice that we just inserted. require.Equal(t, invoice, dbInvoice, "wrong invoice after settle") // If we try to settle the invoice again, then we should get the very // same invoice back, but with an error this time. dbInvoice, err = db.UpdateInvoice(ref, getUpdateInvoice(amt)) if err != ErrInvoiceAlreadySettled { t.Fatalf("expected ErrInvoiceAlreadySettled") } if dbInvoice == nil { t.Fatalf("invoice from db is nil after settle!") } invoice.SettleDate = dbInvoice.SettleDate require.Equal(t, invoice, dbInvoice, "wrong invoice after second settle") } // TestQueryInvoices ensures that we can properly query the invoice database for // invoices using different types of queries. func TestQueryInvoices(t *testing.T) { t.Parallel() db, cleanUp, err := MakeTestDB(OptionClock(testClock)) defer cleanUp() if err != nil { t.Fatalf("unable to make test db: %v", err) } // To begin the test, we'll add 50 invoices to the database. We'll // assume that the index of the invoice within the database is the same // as the amount of the invoice itself. const numInvoices = 50 var settleIndex uint64 = 1 var invoices []Invoice var pendingInvoices []Invoice for i := 1; i <= numInvoices; i++ { amt := lnwire.MilliSatoshi(i) invoice, err := randInvoice(amt) if err != nil { t.Fatalf("unable to create invoice: %v", err) } paymentHash := invoice.Terms.PaymentPreimage.Hash() if _, err := db.AddInvoice(invoice, paymentHash); err != nil { t.Fatalf("unable to add invoice: %v", err) } // We'll only settle half of all invoices created. if i%2 == 0 { ref := InvoiceRefByHash(paymentHash) _, err := db.UpdateInvoice(ref, getUpdateInvoice(amt)) if err != nil { t.Fatalf("unable to settle invoice: %v", err) } // Create the settled invoice for the expectation set. settleTestInvoice(invoice, settleIndex) settleIndex++ } else { pendingInvoices = append(pendingInvoices, *invoice) } invoices = append(invoices, *invoice) } // The test will consist of several queries along with their respective // expected response. Each query response should match its expected one. testCases := []struct { query InvoiceQuery expected []Invoice }{ // Fetch all invoices with a single query. { query: InvoiceQuery{ NumMaxInvoices: numInvoices, }, expected: invoices, }, // Fetch all invoices with a single query, reversed. { query: InvoiceQuery{ Reversed: true, NumMaxInvoices: numInvoices, }, expected: invoices, }, // Fetch the first 25 invoices. { query: InvoiceQuery{ NumMaxInvoices: numInvoices / 2, }, expected: invoices[:numInvoices/2], }, // Fetch the first 10 invoices, but this time iterating // backwards. { query: InvoiceQuery{ IndexOffset: 11, Reversed: true, NumMaxInvoices: numInvoices, }, expected: invoices[:10], }, // Fetch the last 40 invoices. { query: InvoiceQuery{ IndexOffset: 10, NumMaxInvoices: numInvoices, }, expected: invoices[10:], }, // Fetch all but the first invoice. { query: InvoiceQuery{ IndexOffset: 1, NumMaxInvoices: numInvoices, }, expected: invoices[1:], }, // Fetch one invoice, reversed, with index offset 3. This // should give us the second invoice in the array. { query: InvoiceQuery{ IndexOffset: 3, Reversed: true, NumMaxInvoices: 1, }, expected: invoices[1:2], }, // Same as above, at index 2. { query: InvoiceQuery{ IndexOffset: 2, Reversed: true, NumMaxInvoices: 1, }, expected: invoices[0:1], }, // Fetch one invoice, at index 1, reversed. Since invoice#1 is // the very first, there won't be any left in a reverse search, // so we expect no invoices to be returned. { query: InvoiceQuery{ IndexOffset: 1, Reversed: true, NumMaxInvoices: 1, }, expected: nil, }, // Same as above, but don't restrict the number of invoices to // 1. { query: InvoiceQuery{ IndexOffset: 1, Reversed: true, NumMaxInvoices: numInvoices, }, expected: nil, }, // Fetch one invoice, reversed, with no offset set. We expect // the last invoice in the response. { query: InvoiceQuery{ Reversed: true, NumMaxInvoices: 1, }, expected: invoices[numInvoices-1:], }, // Fetch one invoice, reversed, the offset set at numInvoices+1. // We expect this to return the last invoice. { query: InvoiceQuery{ IndexOffset: numInvoices + 1, Reversed: true, NumMaxInvoices: 1, }, expected: invoices[numInvoices-1:], }, // Same as above, at offset numInvoices. { query: InvoiceQuery{ IndexOffset: numInvoices, Reversed: true, NumMaxInvoices: 1, }, expected: invoices[numInvoices-2 : numInvoices-1], }, // Fetch one invoice, at no offset (same as offset 0). We // expect the first invoice only in the response. { query: InvoiceQuery{ NumMaxInvoices: 1, }, expected: invoices[:1], }, // Same as above, at offset 1. { query: InvoiceQuery{ IndexOffset: 1, NumMaxInvoices: 1, }, expected: invoices[1:2], }, // Same as above, at offset 2. { query: InvoiceQuery{ IndexOffset: 2, NumMaxInvoices: 1, }, expected: invoices[2:3], }, // Same as above, at offset numInvoices-1. Expect the last // invoice to be returned. { query: InvoiceQuery{ IndexOffset: numInvoices - 1, NumMaxInvoices: 1, }, expected: invoices[numInvoices-1:], }, // Same as above, at offset numInvoices. No invoices should be // returned, as there are no invoices after this offset. { query: InvoiceQuery{ IndexOffset: numInvoices, NumMaxInvoices: 1, }, expected: nil, }, // Fetch all pending invoices with a single query. { query: InvoiceQuery{ PendingOnly: true, NumMaxInvoices: numInvoices, }, expected: pendingInvoices, }, // Fetch the first 12 pending invoices. { query: InvoiceQuery{ PendingOnly: true, NumMaxInvoices: numInvoices / 4, }, expected: pendingInvoices[:len(pendingInvoices)/2], }, // Fetch the first 5 pending invoices, but this time iterating // backwards. { query: InvoiceQuery{ IndexOffset: 10, PendingOnly: true, Reversed: true, NumMaxInvoices: numInvoices, }, // Since we seek to the invoice with index 10 and // iterate backwards, there should only be 5 pending // invoices before it as every other invoice within the // index is settled. expected: pendingInvoices[:5], }, // Fetch the last 15 invoices. { query: InvoiceQuery{ IndexOffset: 20, PendingOnly: true, NumMaxInvoices: numInvoices, }, // Since we seek to the invoice with index 20, there are // 30 invoices left. From these 30, only 15 of them are // still pending. expected: pendingInvoices[len(pendingInvoices)-15:], }, // Fetch all invoices paginating backwards, with an index offset // that is beyond our last offset. We expect all invoices to be // returned. { query: InvoiceQuery{ IndexOffset: numInvoices * 2, PendingOnly: false, Reversed: true, NumMaxInvoices: numInvoices, }, expected: invoices, }, } for i, testCase := range testCases { response, err := db.QueryInvoices(testCase.query) if err != nil { t.Fatalf("unable to query invoice database: %v", err) } require.Equal(t, len(testCase.expected), len(response.Invoices)) for j, expected := range testCase.expected { require.Equal(t, expected, response.Invoices[j], fmt.Sprintf("test: #%v, item: #%v", i, j), ) } } } // getUpdateInvoice returns an invoice update callback that, when called, // settles the invoice with the given amount. func getUpdateInvoice(amt lnwire.MilliSatoshi) InvoiceUpdateCallback { return func(invoice *Invoice) (*InvoiceUpdateDesc, error) { if invoice.State == ContractSettled { return nil, ErrInvoiceAlreadySettled } noRecords := make(record.CustomSet) update := &InvoiceUpdateDesc{ State: &InvoiceStateUpdateDesc{ Preimage: invoice.Terms.PaymentPreimage, NewState: ContractSettled, }, AddHtlcs: map[CircuitKey]*HtlcAcceptDesc{ {}: { Amt: amt, CustomRecords: noRecords, }, }, } return update, nil } } // TestCustomRecords tests that custom records are properly recorded in the // invoice database. func TestCustomRecords(t *testing.T) { t.Parallel() db, cleanUp, err := MakeTestDB() defer cleanUp() if err != nil { t.Fatalf("unable to make test db: %v", err) } preimage := lntypes.Preimage{1} paymentHash := preimage.Hash() testInvoice := &Invoice{ Htlcs: map[CircuitKey]*InvoiceHTLC{}, Terms: ContractTerm{ Value: lnwire.NewMSatFromSatoshis(10000), Features: emptyFeatures, PaymentPreimage: &preimage, }, } if _, err := db.AddInvoice(testInvoice, paymentHash); err != nil { t.Fatalf("unable to add invoice: %v", err) } // Accept an htlc with custom records on this invoice. key := CircuitKey{ChanID: lnwire.NewShortChanIDFromInt(1), HtlcID: 4} records := record.CustomSet{ 100000: []byte{}, 100001: []byte{1, 2}, } ref := InvoiceRefByHash(paymentHash) _, err = db.UpdateInvoice(ref, func(invoice *Invoice) (*InvoiceUpdateDesc, error) { return &InvoiceUpdateDesc{ AddHtlcs: map[CircuitKey]*HtlcAcceptDesc{ key: { Amt: 500, CustomRecords: records, }, }, }, nil }, ) if err != nil { t.Fatalf("unable to add invoice htlc: %v", err) } // Retrieve the invoice from that database and verify that the custom // records are present. dbInvoice, err := db.LookupInvoice(ref) if err != nil { t.Fatalf("unable to lookup invoice: %v", err) } if len(dbInvoice.Htlcs) != 1 { t.Fatalf("expected the htlc to be added") } require.Equal(t, records, dbInvoice.Htlcs[key].CustomRecords, "invalid custom records", ) } // TestInvoiceRef asserts that the proper identifiers are returned from an // InvoiceRef depending on the constructor used. func TestInvoiceRef(t *testing.T) { payHash := lntypes.Hash{0x01} payAddr := [32]byte{0x02} // An InvoiceRef by hash should return the provided hash and a nil // payment addr. refByHash := InvoiceRefByHash(payHash) require.Equal(t, payHash, refByHash.PayHash()) require.Equal(t, (*[32]byte)(nil), refByHash.PayAddr()) // An InvoiceRef by hash and addr should return the payment hash and // payment addr passed to the constructor. refByHashAndAddr := InvoiceRefByHashAndAddr(payHash, payAddr) require.Equal(t, payHash, refByHashAndAddr.PayHash()) require.Equal(t, &payAddr, refByHashAndAddr.PayAddr()) } // TestDeleteInvoices tests that deleting a list of invoices will succeed // if all delete references are valid, or will fail otherwise. func TestDeleteInvoices(t *testing.T) { t.Parallel() db, cleanup, err := MakeTestDB() defer cleanup() require.NoError(t, err, "unable to make test db") // Add some invoices to the test db. numInvoices := 3 invoicesToDelete := make([]InvoiceDeleteRef, numInvoices) for i := 0; i < numInvoices; i++ { invoice, err := randInvoice(lnwire.MilliSatoshi(i + 1)) require.NoError(t, err) paymentHash := invoice.Terms.PaymentPreimage.Hash() addIndex, err := db.AddInvoice(invoice, paymentHash) require.NoError(t, err) // Settle the second invoice. if i == 1 { invoice, err = db.UpdateInvoice( InvoiceRefByHash(paymentHash), getUpdateInvoice(invoice.Terms.Value), ) require.NoError(t, err, "unable to settle invoice") } // store the delete ref for later. invoicesToDelete[i] = InvoiceDeleteRef{ PayHash: paymentHash, PayAddr: &invoice.Terms.PaymentAddr, AddIndex: addIndex, SettleIndex: invoice.SettleIndex, } } // assertInvoiceCount asserts that the number of invoices equals // to the passed count. assertInvoiceCount := func(count int) { // Query to collect all invoices. query := InvoiceQuery{ IndexOffset: 0, NumMaxInvoices: math.MaxUint64, } // Check that we really have 3 invoices. response, err := db.QueryInvoices(query) require.NoError(t, err) require.Equal(t, count, len(response.Invoices)) } // XOR one byte of one of the references' hash and attempt to delete. invoicesToDelete[0].PayHash[2] ^= 3 require.Error(t, db.DeleteInvoice(invoicesToDelete)) assertInvoiceCount(3) // Restore the hash. invoicesToDelete[0].PayHash[2] ^= 3 // XOR one byte of one of the references' payment address and attempt // to delete. invoicesToDelete[1].PayAddr[5] ^= 7 require.Error(t, db.DeleteInvoice(invoicesToDelete)) assertInvoiceCount(3) // Restore the payment address. invoicesToDelete[1].PayAddr[5] ^= 7 // XOR the second invoice's payment settle index as it is settled, and // attempt to delete. invoicesToDelete[1].SettleIndex ^= 11 require.Error(t, db.DeleteInvoice(invoicesToDelete)) assertInvoiceCount(3) // Restore the settle index. invoicesToDelete[1].SettleIndex ^= 11 // XOR the add index for one of the references and attempt to delete. invoicesToDelete[2].AddIndex ^= 13 require.Error(t, db.DeleteInvoice(invoicesToDelete)) assertInvoiceCount(3) // Restore the add index. invoicesToDelete[2].AddIndex ^= 13 // Delete should succeed with all the valid references. require.NoError(t, db.DeleteInvoice(invoicesToDelete)) assertInvoiceCount(0) }