package invoices import ( "crypto/rand" "encoding/binary" "encoding/hex" "fmt" "io/ioutil" "os" "runtime/pprof" "testing" "time" "github.com/btcsuite/btcd/btcec" "github.com/btcsuite/btcd/chaincfg" "github.com/lightningnetwork/lnd/channeldb" "github.com/lightningnetwork/lnd/clock" "github.com/lightningnetwork/lnd/lntypes" "github.com/lightningnetwork/lnd/lnwire" "github.com/lightningnetwork/lnd/record" "github.com/lightningnetwork/lnd/zpay32" "github.com/stretchr/testify/require" ) type mockPayload struct { mpp *record.MPP amp *record.AMP customRecords record.CustomSet } func (p *mockPayload) MultiPath() *record.MPP { return p.mpp } func (p *mockPayload) AMPRecord() *record.AMP { return p.amp } func (p *mockPayload) CustomRecords() record.CustomSet { // This function should always return a map instance, but for mock // configuration we do accept nil. if p.customRecords == nil { return make(record.CustomSet) } return p.customRecords } const ( testHtlcExpiry = uint32(5) testInvoiceCltvDelta = uint32(4) testFinalCltvRejectDelta = int32(4) testCurrentHeight = int32(1) ) var ( testTimeout = 5 * time.Second testTime = time.Date(2018, time.February, 2, 14, 0, 0, 0, time.UTC) testInvoicePreimage = lntypes.Preimage{1} testInvoicePaymentHash = testInvoicePreimage.Hash() testPrivKeyBytes, _ = hex.DecodeString( "e126f68f7eafcc8b74f54d269fe206be715000f94dac067d1c04a8ca3b2db734") testPrivKey, _ = btcec.PrivKeyFromBytes( btcec.S256(), testPrivKeyBytes) testInvoiceDescription = "coffee" testInvoiceAmount = lnwire.MilliSatoshi(100000) testNetParams = &chaincfg.MainNetParams testMessageSigner = zpay32.MessageSigner{ SignCompact: func(hash []byte) ([]byte, error) { sig, err := btcec.SignCompact(btcec.S256(), testPrivKey, hash, true) if err != nil { return nil, fmt.Errorf("can't sign the message: %v", err) } return sig, nil }, } testFeatures = lnwire.NewFeatureVector( nil, lnwire.Features, ) testPayload = &mockPayload{} testInvoiceCreationDate = testTime ) var ( testInvoiceAmt = lnwire.MilliSatoshi(100000) testInvoice = &channeldb.Invoice{ Terms: channeldb.ContractTerm{ PaymentPreimage: &testInvoicePreimage, Value: testInvoiceAmt, Expiry: time.Hour, Features: testFeatures, }, CreationDate: testInvoiceCreationDate, } testPayAddrReqInvoice = &channeldb.Invoice{ Terms: channeldb.ContractTerm{ PaymentPreimage: &testInvoicePreimage, Value: testInvoiceAmt, Expiry: time.Hour, Features: lnwire.NewFeatureVector( lnwire.NewRawFeatureVector( lnwire.TLVOnionPayloadOptional, lnwire.PaymentAddrRequired, ), lnwire.Features, ), }, CreationDate: testInvoiceCreationDate, } testPayAddrOptionalInvoice = &channeldb.Invoice{ Terms: channeldb.ContractTerm{ PaymentPreimage: &testInvoicePreimage, Value: testInvoiceAmt, Expiry: time.Hour, Features: lnwire.NewFeatureVector( lnwire.NewRawFeatureVector( lnwire.TLVOnionPayloadOptional, lnwire.PaymentAddrOptional, ), lnwire.Features, ), }, CreationDate: testInvoiceCreationDate, } testHodlInvoice = &channeldb.Invoice{ Terms: channeldb.ContractTerm{ Value: testInvoiceAmt, Expiry: time.Hour, Features: testFeatures, }, CreationDate: testInvoiceCreationDate, HodlInvoice: true, } ) func newTestChannelDB(clock clock.Clock) (*channeldb.DB, func(), error) { // First, create a temporary directory to be used for the duration of // this test. tempDirName, err := ioutil.TempDir("", "channeldb") if err != nil { return nil, nil, err } // Next, create channeldb for the first time. cdb, err := channeldb.Open( tempDirName, channeldb.OptionClock(clock), ) if err != nil { os.RemoveAll(tempDirName) return nil, nil, err } cleanUp := func() { cdb.Close() os.RemoveAll(tempDirName) } return cdb, cleanUp, nil } type testContext struct { cdb *channeldb.DB registry *InvoiceRegistry clock *clock.TestClock cleanup func() t *testing.T } func newTestContext(t *testing.T) *testContext { clock := clock.NewTestClock(testTime) cdb, cleanup, err := newTestChannelDB(clock) if err != nil { t.Fatal(err) } expiryWatcher := NewInvoiceExpiryWatcher(clock) // Instantiate and start the invoice ctx.registry. cfg := RegistryConfig{ FinalCltvRejectDelta: testFinalCltvRejectDelta, HtlcHoldDuration: 30 * time.Second, Clock: clock, } registry := NewRegistry(cdb, expiryWatcher, &cfg) err = registry.Start() if err != nil { cleanup() t.Fatal(err) } ctx := testContext{ cdb: cdb, registry: registry, clock: clock, t: t, cleanup: func() { registry.Stop() cleanup() }, } return &ctx } func getCircuitKey(htlcID uint64) channeldb.CircuitKey { return channeldb.CircuitKey{ ChanID: lnwire.ShortChannelID{ BlockHeight: 1, TxIndex: 2, TxPosition: 3, }, HtlcID: htlcID, } } func newTestInvoice(t *testing.T, preimage lntypes.Preimage, timestamp time.Time, expiry time.Duration) *channeldb.Invoice { if expiry == 0 { expiry = time.Hour } var payAddr [32]byte if _, err := rand.Read(payAddr[:]); err != nil { t.Fatalf("unable to generate payment addr: %v", err) } rawInvoice, err := zpay32.NewInvoice( testNetParams, preimage.Hash(), timestamp, zpay32.Amount(testInvoiceAmount), zpay32.Description(testInvoiceDescription), zpay32.Expiry(expiry), zpay32.PaymentAddr(payAddr), ) if err != nil { t.Fatalf("Error while creating new invoice: %v", err) } paymentRequest, err := rawInvoice.Encode(testMessageSigner) if err != nil { t.Fatalf("Error while encoding payment request: %v", err) } return &channeldb.Invoice{ Terms: channeldb.ContractTerm{ PaymentPreimage: &preimage, PaymentAddr: payAddr, Value: testInvoiceAmount, Expiry: expiry, Features: testFeatures, }, PaymentRequest: []byte(paymentRequest), CreationDate: timestamp, } } // timeout implements a test level timeout. func timeout() func() { done := make(chan struct{}) go func() { select { case <-time.After(5 * time.Second): err := pprof.Lookup("goroutine").WriteTo(os.Stdout, 1) if err != nil { panic(fmt.Sprintf("error writing to std out after timeout: %v", err)) } panic("timeout") case <-done: } }() return func() { close(done) } } // invoiceExpiryTestData simply holds generated expired and pending invoices. type invoiceExpiryTestData struct { expiredInvoices map[lntypes.Hash]*channeldb.Invoice pendingInvoices map[lntypes.Hash]*channeldb.Invoice } // generateInvoiceExpiryTestData generates the specified number of fake expired // and pending invoices anchored to the passed now timestamp. func generateInvoiceExpiryTestData( t *testing.T, now time.Time, offset, numExpired, numPending int) invoiceExpiryTestData { var testData invoiceExpiryTestData testData.expiredInvoices = make(map[lntypes.Hash]*channeldb.Invoice) testData.pendingInvoices = make(map[lntypes.Hash]*channeldb.Invoice) expiredCreationDate := now.Add(-24 * time.Hour) for i := 1; i <= numExpired; i++ { var preimage lntypes.Preimage binary.BigEndian.PutUint32(preimage[:4], uint32(offset+i)) expiry := time.Duration((i+offset)%24) * time.Hour invoice := newTestInvoice(t, preimage, expiredCreationDate, expiry) testData.expiredInvoices[preimage.Hash()] = invoice } for i := 1; i <= numPending; i++ { var preimage lntypes.Preimage binary.BigEndian.PutUint32(preimage[4:], uint32(offset+i)) expiry := time.Duration((i+offset)%24) * time.Hour invoice := newTestInvoice(t, preimage, now, expiry) testData.pendingInvoices[preimage.Hash()] = invoice } return testData } // checkSettleResolution asserts the resolution is a settle with the correct // preimage. If successful, the HtlcSettleResolution is returned in case further // checks are desired. func checkSettleResolution(t *testing.T, res HtlcResolution, expPreimage lntypes.Preimage) *HtlcSettleResolution { t.Helper() settleResolution, ok := res.(*HtlcSettleResolution) require.True(t, ok) require.Equal(t, expPreimage, settleResolution.Preimage) return settleResolution } // checkFailResolution asserts the resolution is a fail with the correct reason. // If successful, the HtlcFailResolutionis returned in case further checks are // desired. func checkFailResolution(t *testing.T, res HtlcResolution, expOutcome FailResolutionResult) *HtlcFailResolution { t.Helper() failResolution, ok := res.(*HtlcFailResolution) require.True(t, ok) require.Equal(t, expOutcome, failResolution.Outcome) return failResolution }