Skip to content

Commit

Permalink
invoices: release mpp htlcs after timeout
Browse files Browse the repository at this point in the history
  • Loading branch information
joostjager committed Oct 11, 2019
1 parent ec9c202 commit a4b9fa2
Showing 1 changed file with 112 additions and 0 deletions.
112 changes: 112 additions & 0 deletions invoices/invoiceregistry.go
Expand Up @@ -5,6 +5,7 @@ import (
"fmt"
"sync"
"sync/atomic"
"time"

"github.com/davecgh/go-spew/spew"
"github.com/lightningnetwork/lnd/channeldb"
Expand All @@ -30,6 +31,12 @@ var (
errNoUpdate = errors.New("no update needed")
)

const (
// htlcHoldDuration defines how long mpp htlcs are held while waiting
// for the other set members to arrive.
htlcHoldDuration = 30 * time.Second
)

// 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.
Expand Down Expand Up @@ -80,6 +87,9 @@ type InvoiceRegistry struct {
// not be hit.
finalCltvRejectDelta int32

// now returns the current time.
now func() time.Time

wg sync.WaitGroup
quit chan struct{}
}
Expand All @@ -100,6 +110,7 @@ func NewRegistry(cdb *channeldb.DB, finalCltvRejectDelta int32) *InvoiceRegistry
hodlSubscriptions: make(map[channeldb.CircuitKey]map[chan<- interface{}]struct{}),
hodlReverseSubscriptions: make(map[chan<- interface{}]map[channeldb.CircuitKey]struct{}),
finalCltvRejectDelta: finalCltvRejectDelta,
now: time.Now,
quit: make(chan struct{}),
}
}
Expand Down Expand Up @@ -416,6 +427,98 @@ func (i *InvoiceRegistry) LookupInvoice(rHash lntypes.Hash) (channeldb.Invoice,
return i.cdb.LookupInvoice(rHash)
}

// startHtlcTimer starts a new goroutine that cancels a single htlc on an
// invoice when the htlc hold duration has passed.
func (i *InvoiceRegistry) startHtlcTimer(hash lntypes.Hash,
key channeldb.CircuitKey, acceptTime time.Time) bool {

now := i.now()
releaseTime := acceptTime.Add(htlcHoldDuration)
if releaseTime.Before(now) {
return true
}

i.wg.Add(1)
go func() {
defer i.wg.Done()

select {
case <-i.quit:
case <-time.After(releaseTime.Sub(now)):
err := i.cancelSingleHtlc(hash, key)
if err != nil {
log.Errorf("HTLC timer: %v", err)
}
}
}()

return false
}

// cancelSingleHtlc cancels a single accepted htlc on an invoice.
func (i *InvoiceRegistry) cancelSingleHtlc(hash lntypes.Hash,
key channeldb.CircuitKey) error {

i.Lock()
defer i.Unlock()

updateInvoice := func(invoice *channeldb.Invoice) (
*channeldb.InvoiceUpdateDesc, error) {

if invoice.Terms.State != channeldb.ContractOpen {
log.Debugf("cancelSingleHtlc: invoice %v no longer "+
"open", hash)

return nil, errNoUpdate
}

htlc, ok := invoice.Htlcs[key]
if !ok {
return nil, fmt.Errorf("htlc %v not found", key)
}

if htlc.State != channeldb.HtlcStateAccepted {
log.Debugf("cancelSingleHtlc: htlc %v on invoice %v "+
"is already resolved", key, hash)

return nil, errNoUpdate
}

log.Debugf("cancelSingleHtlc: cancelling htlc %v on invoice %v",
key, hash)

canceledHtlcs := map[channeldb.CircuitKey]*channeldb.HtlcAcceptDesc{
key: nil,
}

// Cancel htlc and keep invoice open.
return &channeldb.InvoiceUpdateDesc{
Htlcs: canceledHtlcs,
State: channeldb.ContractOpen,
}, nil
}

invoice, err := i.cdb.UpdateInvoice(hash, updateInvoice)
if err == errNoUpdate {
return nil
}
if err != nil {
return err
}

htlc, ok := invoice.Htlcs[key]
if !ok {
return fmt.Errorf("htlc %v not found", key)
}
if htlc.State == channeldb.HtlcStateCanceled {
i.notifyHodlSubscribers(HodlEvent{
CircuitKey: key,
AcceptHeight: int32(htlc.AcceptHeight),
})
}
return nil
}

// 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. The return value describes how the htlc should be resolved.
Expand Down Expand Up @@ -635,6 +738,15 @@ func (i *InvoiceRegistry) NotifyExitHopHtlc(rHash lntypes.Hash,
}, nil

case channeldb.HtlcStateAccepted:
// (Re)start the htlc timer if the invoice is
// still open.
if invoice.Terms.State == channeldb.ContractOpen {
i.startHtlcTimer(
rHash, circuitKey,
invoiceHtlc.AcceptTime,
)
}

i.hodlSubscribe(hodlChan, circuitKey)
return nil, nil

Expand Down

0 comments on commit a4b9fa2

Please sign in to comment.