2019-11-20 18:06:51 +01:00
|
|
|
package invoices
|
|
|
|
|
|
|
|
import (
|
2019-12-09 17:50:11 +01:00
|
|
|
"encoding/binary"
|
2019-11-20 18:06:51 +01:00
|
|
|
"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"
|
2019-11-25 11:46:29 +01:00
|
|
|
"github.com/lightningnetwork/lnd/clock"
|
2019-11-20 18:06:51 +01:00
|
|
|
"github.com/lightningnetwork/lnd/lntypes"
|
|
|
|
"github.com/lightningnetwork/lnd/lnwire"
|
|
|
|
"github.com/lightningnetwork/lnd/record"
|
|
|
|
"github.com/lightningnetwork/lnd/zpay32"
|
|
|
|
)
|
|
|
|
|
|
|
|
type mockPayload struct {
|
2019-12-05 12:55:17 +01:00
|
|
|
mpp *record.MPP
|
|
|
|
customRecords record.CustomSet
|
2019-11-20 18:06:51 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
func (p *mockPayload) MultiPath() *record.MPP {
|
|
|
|
return p.mpp
|
|
|
|
}
|
|
|
|
|
|
|
|
func (p *mockPayload) CustomRecords() record.CustomSet {
|
2019-12-05 12:55:17 +01:00
|
|
|
// 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
|
2019-11-20 18:06:51 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
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()
|
|
|
|
|
|
|
|
testHtlcExpiry = uint32(5)
|
|
|
|
|
|
|
|
testInvoiceCltvDelta = uint32(4)
|
|
|
|
|
|
|
|
testFinalCltvRejectDelta = int32(4)
|
|
|
|
|
|
|
|
testCurrentHeight = int32(1)
|
|
|
|
|
|
|
|
testPrivKeyBytes, _ = hex.DecodeString(
|
|
|
|
"e126f68f7eafcc8b74f54d269fe206be715000f94dac067d1c04a8ca3b2db734")
|
|
|
|
|
2019-12-09 17:50:11 +01:00
|
|
|
testPrivKey, _ = btcec.PrivKeyFromBytes(
|
2019-11-20 18:06:51 +01:00
|
|
|
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{}
|
2019-12-09 17:50:11 +01:00
|
|
|
|
|
|
|
testInvoiceCreationDate = testTime
|
2019-11-20 18:06:51 +01:00
|
|
|
)
|
|
|
|
|
|
|
|
var (
|
|
|
|
testInvoiceAmt = lnwire.MilliSatoshi(100000)
|
|
|
|
testInvoice = &channeldb.Invoice{
|
|
|
|
Terms: channeldb.ContractTerm{
|
|
|
|
PaymentPreimage: testInvoicePreimage,
|
|
|
|
Value: testInvoiceAmt,
|
2019-12-09 17:50:11 +01:00
|
|
|
Expiry: time.Hour,
|
2019-11-20 18:06:51 +01:00
|
|
|
Features: testFeatures,
|
|
|
|
},
|
2019-12-09 17:50:11 +01:00
|
|
|
CreationDate: testInvoiceCreationDate,
|
2019-11-20 18:06:51 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
testHodlInvoice = &channeldb.Invoice{
|
|
|
|
Terms: channeldb.ContractTerm{
|
|
|
|
PaymentPreimage: channeldb.UnknownPreimage,
|
|
|
|
Value: testInvoiceAmt,
|
2019-12-09 17:50:11 +01:00
|
|
|
Expiry: time.Hour,
|
2019-11-20 18:06:51 +01:00
|
|
|
Features: testFeatures,
|
|
|
|
},
|
2019-12-09 17:50:11 +01:00
|
|
|
CreationDate: testInvoiceCreationDate,
|
2019-11-20 18:06:51 +01:00
|
|
|
}
|
|
|
|
)
|
|
|
|
|
2020-01-20 10:57:34 +01:00
|
|
|
func newTestChannelDB(clock clock.Clock) (*channeldb.DB, func(), error) {
|
2019-11-20 18:06:51 +01:00
|
|
|
// 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.
|
2020-01-20 10:57:34 +01:00
|
|
|
cdb, err := channeldb.Open(
|
|
|
|
tempDirName, channeldb.OptionClock(clock),
|
|
|
|
)
|
2019-11-20 18:06:51 +01:00
|
|
|
if err != nil {
|
|
|
|
os.RemoveAll(tempDirName)
|
|
|
|
return nil, nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
cleanUp := func() {
|
|
|
|
cdb.Close()
|
|
|
|
os.RemoveAll(tempDirName)
|
|
|
|
}
|
|
|
|
|
|
|
|
return cdb, cleanUp, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
type testContext struct {
|
2019-12-09 17:50:11 +01:00
|
|
|
cdb *channeldb.DB
|
2019-11-20 18:06:51 +01:00
|
|
|
registry *InvoiceRegistry
|
2019-11-25 11:46:29 +01:00
|
|
|
clock *clock.TestClock
|
2019-11-20 18:06:51 +01:00
|
|
|
|
|
|
|
cleanup func()
|
|
|
|
t *testing.T
|
|
|
|
}
|
|
|
|
|
|
|
|
func newTestContext(t *testing.T) *testContext {
|
2019-11-25 11:46:29 +01:00
|
|
|
clock := clock.NewTestClock(testTime)
|
2019-11-20 18:06:51 +01:00
|
|
|
|
2020-01-20 10:57:34 +01:00
|
|
|
cdb, cleanup, err := newTestChannelDB(clock)
|
2019-11-20 18:06:51 +01:00
|
|
|
if err != nil {
|
|
|
|
t.Fatal(err)
|
|
|
|
}
|
|
|
|
|
2019-12-09 17:50:11 +01:00
|
|
|
expiryWatcher := NewInvoiceExpiryWatcher(clock)
|
|
|
|
|
2019-11-20 18:06:51 +01:00
|
|
|
// Instantiate and start the invoice ctx.registry.
|
|
|
|
cfg := RegistryConfig{
|
|
|
|
FinalCltvRejectDelta: testFinalCltvRejectDelta,
|
|
|
|
HtlcHoldDuration: 30 * time.Second,
|
2019-11-25 11:46:29 +01:00
|
|
|
Clock: clock,
|
2019-11-20 18:06:51 +01:00
|
|
|
}
|
2019-12-09 17:50:11 +01:00
|
|
|
registry := NewRegistry(cdb, expiryWatcher, &cfg)
|
2019-11-20 18:06:51 +01:00
|
|
|
|
|
|
|
err = registry.Start()
|
|
|
|
if err != nil {
|
|
|
|
cleanup()
|
|
|
|
t.Fatal(err)
|
|
|
|
}
|
|
|
|
|
|
|
|
ctx := testContext{
|
2019-12-09 17:50:11 +01:00
|
|
|
cdb: cdb,
|
2019-11-20 18:06:51 +01:00
|
|
|
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,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-12-09 17:50:11 +01:00
|
|
|
func newTestInvoice(t *testing.T, preimage lntypes.Preimage,
|
2019-11-20 18:06:51 +01:00
|
|
|
timestamp time.Time, expiry time.Duration) *channeldb.Invoice {
|
|
|
|
|
|
|
|
if expiry == 0 {
|
|
|
|
expiry = time.Hour
|
|
|
|
}
|
|
|
|
|
|
|
|
rawInvoice, err := zpay32.NewInvoice(
|
|
|
|
testNetParams,
|
2019-12-09 17:50:11 +01:00
|
|
|
preimage.Hash(),
|
2019-11-20 18:06:51 +01:00
|
|
|
timestamp,
|
|
|
|
zpay32.Amount(testInvoiceAmount),
|
|
|
|
zpay32.Description(testInvoiceDescription),
|
|
|
|
zpay32.Expiry(expiry))
|
|
|
|
|
|
|
|
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{
|
2019-12-09 17:50:11 +01:00
|
|
|
PaymentPreimage: preimage,
|
2019-11-20 18:06:51 +01:00
|
|
|
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)
|
|
|
|
}
|
|
|
|
}
|
2019-12-09 17:50:11 +01:00
|
|
|
|
|
|
|
// 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
|
|
|
|
}
|