mirror of
https://github.com/lightningnetwork/lnd.git
synced 2025-09-21 06:06:20 +02:00
htlcswitch: abtract invoice from link
This commit detaches signaling the invoice registry that an htlc was locked in from the actually settling of the htlc. It is a preparation for hodl invoices.
This commit is contained in:
@@ -27,6 +27,14 @@ var (
|
||||
DebugHash = DebugPre.Hash()
|
||||
)
|
||||
|
||||
// HodlEvent describes how an htlc should be resolved. If HodlEvent.Preimage is
|
||||
// set, the event indicates a settle event. If Preimage is nil, it is a cancel
|
||||
// event.
|
||||
type HodlEvent struct {
|
||||
Preimage *lntypes.Preimage
|
||||
Hash lntypes.Hash
|
||||
}
|
||||
|
||||
// InvoiceRegistry is a central registry of all the outstanding invoices
|
||||
// created by the daemon. The registry is a thin wrapper around a map in order
|
||||
// to ensure that all updates/reads are thread safe.
|
||||
@@ -163,7 +171,7 @@ func (i *InvoiceRegistry) invoiceEventNotifier() {
|
||||
// dispatch notifications to all registered clients.
|
||||
case event := <-i.invoiceEvents:
|
||||
// For backwards compatibility, do not notify all
|
||||
// invoice subscribers of cancel events
|
||||
// invoice subscribers of cancel events.
|
||||
if event.state != channeldb.ContractCanceled {
|
||||
i.dispatchToClients(event)
|
||||
}
|
||||
@@ -438,45 +446,54 @@ func (i *InvoiceRegistry) LookupInvoice(rHash lntypes.Hash) (channeldb.Invoice,
|
||||
return invoice, expiry, nil
|
||||
}
|
||||
|
||||
// SettleInvoice attempts to mark an invoice as settled. If the invoice is a
|
||||
// NotifyExitHopHtlc attempts to mark an invoice as settled. If the invoice is a
|
||||
// debug invoice, then this method is a noop as debug invoices are never fully
|
||||
// settled.
|
||||
func (i *InvoiceRegistry) SettleInvoice(rHash lntypes.Hash,
|
||||
amtPaid lnwire.MilliSatoshi) error {
|
||||
// settled. The return value describes how the htlc should be resolved.
|
||||
func (i *InvoiceRegistry) NotifyExitHopHtlc(rHash lntypes.Hash,
|
||||
amtPaid lnwire.MilliSatoshi) (*HodlEvent, error) {
|
||||
|
||||
i.Lock()
|
||||
defer i.Unlock()
|
||||
|
||||
log.Debugf("Settling invoice %x", rHash[:])
|
||||
|
||||
createEvent := func(preimage *lntypes.Preimage) *HodlEvent {
|
||||
return &HodlEvent{
|
||||
Hash: rHash,
|
||||
Preimage: preimage,
|
||||
}
|
||||
}
|
||||
|
||||
// First check the in-memory debug invoice index to see if this is an
|
||||
// existing invoice added for debugging.
|
||||
if _, ok := i.debugInvoices[rHash]; ok {
|
||||
// Debug invoices are never fully settled, so we simply return
|
||||
// immediately in this case.
|
||||
return nil
|
||||
if invoice, ok := i.debugInvoices[rHash]; ok {
|
||||
// Debug invoices are never fully settled, so we just settle the
|
||||
// htlc in this case.
|
||||
return createEvent(&invoice.Terms.PaymentPreimage), nil
|
||||
}
|
||||
|
||||
// If this isn't a debug invoice, then we'll attempt to settle an
|
||||
// invoice matching this rHash on disk (if one exists).
|
||||
invoice, err := i.cdb.SettleInvoice(rHash, amtPaid)
|
||||
switch err {
|
||||
|
||||
// Implement idempotency by returning success if the invoice was already
|
||||
// settled.
|
||||
if err == channeldb.ErrInvoiceAlreadySettled {
|
||||
log.Debugf("Invoice %v already settled", rHash)
|
||||
return nil
|
||||
// If invoice is already settled, settle htlc. This means we accept more
|
||||
// payments to the same invoice hash.
|
||||
case channeldb.ErrInvoiceAlreadySettled:
|
||||
return createEvent(&invoice.Terms.PaymentPreimage), nil
|
||||
|
||||
// If invoice is already canceled, cancel htlc.
|
||||
case channeldb.ErrInvoiceAlreadyCanceled:
|
||||
return createEvent(nil), nil
|
||||
|
||||
// If this call settled the invoice, settle the htlc.
|
||||
case nil:
|
||||
i.notifyClients(rHash, invoice, invoice.Terms.State)
|
||||
return createEvent(&invoice.Terms.PaymentPreimage), nil
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
log.Infof("Payment received: %v", spew.Sdump(invoice))
|
||||
|
||||
i.notifyClients(rHash, invoice, channeldb.ContractSettled)
|
||||
|
||||
return nil
|
||||
// If another error occurred, return that.
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// CancelInvoice attempts to cancel the invoice corresponding to the passed
|
||||
|
@@ -37,22 +37,41 @@ func decodeExpiry(payReq string) (uint32, error) {
|
||||
return uint32(invoice.MinFinalCLTVExpiry()), nil
|
||||
}
|
||||
|
||||
// TestSettleInvoice tests settling of an invoice and related notifications.
|
||||
func TestSettleInvoice(t *testing.T) {
|
||||
var (
|
||||
testInvoice = &channeldb.Invoice{
|
||||
Terms: channeldb.ContractTerm{
|
||||
PaymentPreimage: preimage,
|
||||
Value: lnwire.MilliSatoshi(100000),
|
||||
},
|
||||
PaymentRequest: []byte(testPayReq),
|
||||
}
|
||||
)
|
||||
|
||||
func newTestContext(t *testing.T) (*InvoiceRegistry, func()) {
|
||||
cdb, cleanup, err := newDB()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer cleanup()
|
||||
|
||||
// Instantiate and start the invoice registry.
|
||||
registry := NewRegistry(cdb, decodeExpiry)
|
||||
|
||||
err = registry.Start()
|
||||
if err != nil {
|
||||
cleanup()
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer registry.Stop()
|
||||
|
||||
return registry, func() {
|
||||
registry.Stop()
|
||||
cleanup()
|
||||
}
|
||||
}
|
||||
|
||||
// TestSettleInvoice tests settling of an invoice and related notifications.
|
||||
func TestSettleInvoice(t *testing.T) {
|
||||
registry, cleanup := newTestContext(t)
|
||||
defer cleanup()
|
||||
|
||||
allSubscriptions := registry.SubscribeNotifications(0, 0)
|
||||
defer allSubscriptions.Cancel()
|
||||
@@ -66,15 +85,7 @@ func TestSettleInvoice(t *testing.T) {
|
||||
}
|
||||
|
||||
// Add the invoice.
|
||||
invoice := &channeldb.Invoice{
|
||||
Terms: channeldb.ContractTerm{
|
||||
PaymentPreimage: preimage,
|
||||
Value: lnwire.MilliSatoshi(100000),
|
||||
},
|
||||
PaymentRequest: []byte(testPayReq),
|
||||
}
|
||||
|
||||
addIdx, err := registry.AddInvoice(invoice, hash)
|
||||
addIdx, err := registry.AddInvoice(testInvoice, hash)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
@@ -108,7 +119,7 @@ func TestSettleInvoice(t *testing.T) {
|
||||
|
||||
// Settle invoice with a slightly higher amount.
|
||||
amtPaid := lnwire.MilliSatoshi(100500)
|
||||
err = registry.SettleInvoice(hash, amtPaid)
|
||||
_, err = registry.NotifyExitHopHtlc(hash, amtPaid)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
@@ -140,13 +151,13 @@ func TestSettleInvoice(t *testing.T) {
|
||||
}
|
||||
|
||||
// Try to settle again.
|
||||
err = registry.SettleInvoice(hash, amtPaid)
|
||||
_, err = registry.NotifyExitHopHtlc(hash, amtPaid)
|
||||
if err != nil {
|
||||
t.Fatal("expected duplicate settle to succeed")
|
||||
}
|
||||
|
||||
// Try to settle again with a different amount.
|
||||
err = registry.SettleInvoice(hash, amtPaid+600)
|
||||
_, err = registry.NotifyExitHopHtlc(hash, amtPaid+600)
|
||||
if err != nil {
|
||||
t.Fatal("expected duplicate settle to succeed")
|
||||
}
|
||||
@@ -169,26 +180,14 @@ func TestSettleInvoice(t *testing.T) {
|
||||
|
||||
// TestCancelInvoice tests cancelation of an invoice and related notifications.
|
||||
func TestCancelInvoice(t *testing.T) {
|
||||
cdb, cleanup, err := newDB()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
registry, cleanup := newTestContext(t)
|
||||
defer cleanup()
|
||||
|
||||
// Instantiate and start the invoice registry.
|
||||
registry := NewRegistry(cdb, decodeExpiry)
|
||||
|
||||
err = registry.Start()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer registry.Stop()
|
||||
|
||||
allSubscriptions := registry.SubscribeNotifications(0, 0)
|
||||
defer allSubscriptions.Cancel()
|
||||
|
||||
// Try to cancel the not yet existing invoice. This should fail.
|
||||
err = registry.CancelInvoice(hash)
|
||||
err := registry.CancelInvoice(hash)
|
||||
if err != channeldb.ErrInvoiceNotFound {
|
||||
t.Fatalf("expected ErrInvoiceNotFound, but got %v", err)
|
||||
}
|
||||
@@ -203,14 +202,7 @@ func TestCancelInvoice(t *testing.T) {
|
||||
|
||||
// Add the invoice.
|
||||
amt := lnwire.MilliSatoshi(100000)
|
||||
invoice := &channeldb.Invoice{
|
||||
Terms: channeldb.ContractTerm{
|
||||
PaymentPreimage: preimage,
|
||||
Value: amt,
|
||||
},
|
||||
}
|
||||
|
||||
_, err = registry.AddInvoice(invoice, hash)
|
||||
_, err = registry.AddInvoice(testInvoice, hash)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
@@ -270,10 +262,15 @@ func TestCancelInvoice(t *testing.T) {
|
||||
t.Fatal("expected cancelation of a canceled invoice to succeed")
|
||||
}
|
||||
|
||||
// Try to settle. This should not be possible.
|
||||
err = registry.SettleInvoice(hash, amt)
|
||||
if err != channeldb.ErrInvoiceAlreadyCanceled {
|
||||
t.Fatal("expected settlement of a canceled invoice to fail")
|
||||
// Notify arrival of a new htlc paying to this invoice. This should
|
||||
// succeed.
|
||||
event, err := registry.NotifyExitHopHtlc(hash, amt)
|
||||
if err != nil {
|
||||
t.Fatal("expected settlement of a canceled invoice to succeed")
|
||||
}
|
||||
|
||||
if event.Preimage != nil {
|
||||
t.Fatal("expected cancel hodl event")
|
||||
}
|
||||
}
|
||||
|
||||
|
Reference in New Issue
Block a user