lnd.xprv/invoices/test_utils_test.go
2021-04-07 12:08:34 -07:00

366 lines
8.6 KiB
Go

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
}