lnd.xprv/invoiceregistry.go
Olaoluwa Osuntokun cfd9f8f6f0
invoices: add subscription to invoice updates
This commit adds the ability for clients within the daemon to register
for notifications that are dispatched once invoices are settled, or new
invoices are added. Such notifications can prove useful when
synchronizing higher level primitives, or implementing workflow within
desktop/mobile UI’s.
2016-10-14 19:49:04 -07:00

218 lines
6.7 KiB
Go

package main
import (
"bytes"
"sync"
"time"
"github.com/btcsuite/fastsha256"
"github.com/davecgh/go-spew/spew"
"github.com/lightningnetwork/lnd/channeldb"
"github.com/roasbeef/btcd/wire"
"github.com/roasbeef/btcutil"
)
var (
// debugPre is the default debug preimage which is inserted into the
// invoice registry if the --debughtlc flag is activated on start up.
// All nodes initialize with the flag active will immediately settle
// any incoming HTLC whose rHash is corresponds with the debug
// preimage.
debugPre, _ = wire.NewShaHash(bytes.Repeat([]byte{1}, 32))
debugHash = wire.ShaHash(fastsha256.Sum256(debugPre[:]))
)
// 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.
type invoiceRegistry struct {
sync.RWMutex
cdb *channeldb.DB
clientMtx sync.Mutex
nextClientID uint32
notificationClients map[uint32]*invoiceSubscription
// debugInvoices is a mp which stores special "debug" invoices which
// should be only created/used when manual tests require an invoice
// that *all* nodes are able to fully settle.
debugInvoices map[wire.ShaHash]*channeldb.Invoice
}
// newInvoiceRegistry creates a new invoice registry. The invoice registry
// wraps the persistent on-disk invoice storage with an additional in-memory
// layer. The in-memory layer is in pace such that debug invoices can be added
// which are volatile yet available system wide within the daemon.
func newInvoiceRegistry(cdb *channeldb.DB) *invoiceRegistry {
return &invoiceRegistry{
cdb: cdb,
debugInvoices: make(map[wire.ShaHash]*channeldb.Invoice),
notificationClients: make(map[uint32]*invoiceSubscription),
}
}
// addDebugInvoice adds a debug invoice for the specified amount, identified
// by the passed preimage. Once this invoice is added, sub-systems within the
// daemon add/forward HTLC's are able to obtain the proper preimage required
// for redemption in the case that we're the final destination.
func (i *invoiceRegistry) AddDebugInvoice(amt btcutil.Amount, preimage wire.ShaHash) {
paymentHash := wire.ShaHash(fastsha256.Sum256(preimage[:]))
invoice := &channeldb.Invoice{
CreationDate: time.Now(),
Terms: channeldb.ContractTerm{
Value: amt,
PaymentPreimage: preimage,
},
}
i.Lock()
i.debugInvoices[paymentHash] = invoice
i.Unlock()
ltndLog.Debugf("Adding debug invoice %v", newLogClosure(func() string {
return spew.Sdump(invoice)
}))
}
// AddInvoice adds a regular invoice for the specified amount, identified by
// the passed preimage. Additionally, any memo or recipt data provided will
// also be stored on-disk. Once this invoice is added, sub-systems within the
// daemon add/forward HTLC's are able to obtain the proper preimage required
// for redemption in the case that we're the final destination.
func (i *invoiceRegistry) AddInvoice(invoice *channeldb.Invoice) error {
ltndLog.Debugf("Adding invoice %v", newLogClosure(func() string {
return spew.Sdump(invoice)
}))
// TODO(roasbeef): also check in memory for quick lookups/settles?
if err := i.cdb.AddInvoice(invoice); err != nil {
return err
}
// TODO(roasbeef): re-enable?
//go i.notifyClients(invoice, false)
return nil
}
// lookupInvoice looks up an invoice by it's payment hash (R-Hash), if found
// then we're able to pull the funds pending within an HTLC.
// TODO(roasbeef): ignore if settled?
func (i *invoiceRegistry) LookupInvoice(rHash wire.ShaHash) (*channeldb.Invoice, error) {
// First check the in-memory debug invoice index to see if this is an
// existing invoice added for debugging.
i.RLock()
invoice, ok := i.debugInvoices[rHash]
i.RUnlock()
// If found, then simply return the invoice directly.
if ok {
return invoice, nil
}
// Otherwise, we'll check the database to see if there's an existing
// matching invoice.
return i.cdb.LookupInvoice(rHash)
}
// SettleInvoice attempts to mark an invoice as settled. If the invoice is a
// dbueg invoice, then this method is a nooop as debug invoices are never fully
// settled.
func (i *invoiceRegistry) SettleInvoice(rHash wire.ShaHash) error {
ltndLog.Debugf("Settling invoice %x", rHash[:])
// First check the in-memory debug invoice index to see if this is an
// existing invoice added for debugging.
i.RLock()
if _, ok := i.debugInvoices[rHash]; ok {
// Debug invoices are never fully settled, so we simply return
// immediately in this case.
i.RUnlock()
return nil
}
i.RUnlock()
// If this isn't a debug invoice, then we'll attempt to settle an
// invoice matching this rHash on disk (if one exists).
if err := i.cdb.SettleInvoice(rHash); err != nil {
return err
}
// Launch a new goroutine to notify any/all registered invoice
// notification clients.
go func() {
invoice, err := i.cdb.LookupInvoice(rHash)
if err != nil {
ltndLog.Errorf("unable to find invoice: %v", err)
return
}
i.notifyClients(invoice, true)
}()
return nil
}
// notifyClients notifies all currently registered invoice notificaiton clients
// of a newly added/settled invoice.
func (i *invoiceRegistry) notifyClients(invoice *channeldb.Invoice, settle bool) {
i.clientMtx.Lock()
defer i.clientMtx.Unlock()
for _, client := range i.notificationClients {
var eventChan chan *channeldb.Invoice
if settle {
eventChan = client.SettledInvoices
} else {
eventChan = client.NewInvoices
}
go func() {
eventChan <- invoice
}()
}
}
// invoiceSubscription represents an intent to receive updates for newly added
// or settled invoices. For each newly added invoice, a copy of the invoice
// will be sent over the NewInvoices channel. Similarly, for each newly settled
// invoice, a copy of the invoice will be sent over the SettledInvoices
// channel.
type invoiceSubscription struct {
NewInvoices chan *channeldb.Invoice
SettledInvoices chan *channeldb.Invoice
inv *invoiceRegistry
id uint32
}
// Cancel unregisteres the invoiceSubscription, freeing any previoulsy allocate
// resources.
func (i *invoiceSubscription) Cancel() {
i.inv.clientMtx.Lock()
delete(i.inv.notificationClients, i.id)
i.inv.clientMtx.Unlock()
}
// SubscribeNotifications returns an invoiceSubscription which allows the
// caller to receive async notifications when any invoices are settled or
// added.
func (i *invoiceRegistry) SubscribeNotifications() *invoiceSubscription {
client := &invoiceSubscription{
NewInvoices: make(chan *channeldb.Invoice),
SettledInvoices: make(chan *channeldb.Invoice),
}
i.clientMtx.Lock()
i.notificationClients[i.nextClientID] = client
client.id = i.nextClientID
i.nextClientID++
i.clientMtx.Unlock()
return client
}