invoices: adding InvoiceExpryWatcher to cancel expired invoices

This commit adds InvoiceExpryWatcher which is a separate class that
receives new invoices (and existing ones upon restart) from InvoiceRegistry
and actively watches their expiry. When an invoice is expired
InvoiceExpiryWatcher will call into InvoiceRegistry to cancel the
invoice and by that notify all subscribers about the state change.
This commit is contained in:
Andras Banki-Horvath committed 2019-12-13 17:03:08 +01:00
1 parent 7024f36a76
commit 44f13d1d60
9 files changed
+643 -13

No files matched your search

+110 -4
View File
@@ -5,6 +5,8 @@ import (
"time"
"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"
)
@@ -301,8 +303,9 @@ func TestSettleHoldInvoice(t *testing.T) {
// Instantiate and start the invoice ctx.registry.
cfg := RegistryConfig{
FinalCltvRejectDelta: testFinalCltvRejectDelta,
Clock: clock.NewTestClock(testTime),
}
registry := NewRegistry(cdb, &cfg)
registry := NewRegistry(cdb, NewInvoiceExpiryWatcher(cfg.Clock), &cfg)
err = registry.Start()
if err != nil {
@@ -461,7 +464,7 @@ func TestSettleHoldInvoice(t *testing.T) {
// TestCancelHoldInvoice tests canceling of a hold invoice and related
// notifications.
func TestCancelHoldInvoice(t *testing.T) {
defer timeout()
defer timeout()()
cdb, cleanup, err := newTestChannelDB()
if err != nil {
@@ -472,8 +475,9 @@ func TestCancelHoldInvoice(t *testing.T) {
// Instantiate and start the invoice ctx.registry.
cfg := RegistryConfig{
FinalCltvRejectDelta: testFinalCltvRejectDelta,
Clock: clock.NewTestClock(testTime),
}
registry := NewRegistry(cdb, &cfg)
registry := NewRegistry(cdb, NewInvoiceExpiryWatcher(cfg.Clock), &cfg)
err = registry.Start()
if err != nil {
@@ -557,7 +561,7 @@ func TestUnknownInvoice(t *testing.T) {
// TestSettleMpp tests settling of an invoice with multiple partial payments.
func TestSettleMpp(t *testing.T) {
defer timeout()
defer timeout()()
ctx := newTestContext(t)
defer ctx.cleanup()
@@ -636,3 +640,105 @@ func TestSettleMpp(t *testing.T) {
testInvoice.Terms.Value, inv.AmtPaid)
}
}
// Tests that invoices are canceled after expiration.
func TestInvoiceExpiryWithRegistry(t *testing.T) {
t.Parallel()
cdb, cleanup, err := newTestChannelDB()
defer cleanup()
if err != nil {
t.Fatal(err)
}
testClock := clock.NewTestClock(testTime)
cfg := RegistryConfig{
FinalCltvRejectDelta: testFinalCltvRejectDelta,
Clock: testClock,
}
expiryWatcher := NewInvoiceExpiryWatcher(cfg.Clock)
registry := NewRegistry(cdb, expiryWatcher, &cfg)
// First prefill the Channel DB with some pre-existing invoices,
// half of them still pending, half of them expired.
const numExpired = 5
const numPending = 5
existingInvoices := generateInvoiceExpiryTestData(
t, testTime, 0, numExpired, numPending,
)
var expectedCancellations []lntypes.Hash
for paymentHash, expiredInvoice := range existingInvoices.expiredInvoices {
if _, err := cdb.AddInvoice(expiredInvoice, paymentHash); err != nil {
t.Fatalf("cannot add invoice to channel db: %v", err)
}
expectedCancellations = append(expectedCancellations, paymentHash)
}
for paymentHash, pendingInvoice := range existingInvoices.pendingInvoices {
if _, err := cdb.AddInvoice(pendingInvoice, paymentHash); err != nil {
t.Fatalf("cannot add invoice to channel db: %v", err)
}
}
if err = registry.Start(); err != nil {
t.Fatalf("cannot start registry: %v", err)
}
// Now generate pending and invoices and add them to the registry while
// it is up and running. We'll manipulate the clock to let them expire.
newInvoices := generateInvoiceExpiryTestData(
t, testTime, numExpired+numPending, 0, numPending,
)
var invoicesThatWillCancel []lntypes.Hash
for paymentHash, pendingInvoice := range newInvoices.pendingInvoices {
_, err := registry.AddInvoice(pendingInvoice, paymentHash)
invoicesThatWillCancel = append(invoicesThatWillCancel, paymentHash)
if err != nil {
t.Fatal(err)
}
}
// Check that they are really not canceled until before the clock is
// advanced.
for i := range invoicesThatWillCancel {
invoice, err := registry.LookupInvoice(invoicesThatWillCancel[i])
if err != nil {
t.Fatalf("cannot find invoice: %v", err)
}
if invoice.State == channeldb.ContractCanceled {
t.Fatalf("expected pending invoice, got canceled")
}
}
// Fwd time 1 day.
testClock.SetTime(testTime.Add(24 * time.Hour))
// Give some time to the watcher to cancel everything.
time.Sleep(testTimeout)
registry.Stop()
// Create the expected cancellation set before the final check.
expectedCancellations = append(
expectedCancellations, invoicesThatWillCancel...,
)
// Retrospectively check that all invoices that were expected to be canceled
// are indeed canceled.
for i := range expectedCancellations {
invoice, err := registry.LookupInvoice(expectedCancellations[i])
if err != nil {
t.Fatalf("cannot find invoice: %v", err)
}
if invoice.State != channeldb.ContractCanceled {
t.Fatalf("expected canceled invoice, got: %v", invoice.State)
}
}
}