Merge pull request #3831 from carlaKC/invoices-surfaceupdateresult

Add MPPTimeout ResolutionResult and expose to caller
This commit is contained in:
Joost Jager 2019-12-20 14:41:37 +01:00 committed by GitHub
commit 8d0205e9e4
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
18 changed files with 676 additions and 408 deletions

@ -167,10 +167,10 @@ func (h *htlcIncomingContestResolver) Resolve() (ContractResolver, error) {
preimageSubscription := h.PreimageDB.SubscribeUpdates() preimageSubscription := h.PreimageDB.SubscribeUpdates()
defer preimageSubscription.CancelSubscription() defer preimageSubscription.CancelSubscription()
// Define closure to process hodl events either direct or triggered by // Define closure to process htlc resolutions either direct or triggered by
// later notifcation. // later notification.
processHodlEvent := func(e invoices.HodlEvent) (ContractResolver, processHtlcResolution := func(e invoices.HtlcResolution) (
error) { ContractResolver, error) {
if e.Preimage == nil { if e.Preimage == nil {
log.Infof("%T(%v): Exit hop HTLC canceled "+ log.Infof("%T(%v): Exit hop HTLC canceled "+
@ -201,23 +201,26 @@ func (h *htlcIncomingContestResolver) Resolve() (ContractResolver, error) {
HtlcID: h.htlc.HtlcIndex, HtlcID: h.htlc.HtlcIndex,
} }
event, err := h.Registry.NotifyExitHopHtlc( resolution, err := h.Registry.NotifyExitHopHtlc(
h.htlc.RHash, h.htlc.Amt, h.htlcExpiry, currentHeight, h.htlc.RHash, h.htlc.Amt, h.htlcExpiry, currentHeight,
circuitKey, hodlChan, payload, circuitKey, hodlChan, payload,
) )
switch err { if err != nil {
case channeldb.ErrInvoiceNotFound:
case nil:
defer h.Registry.HodlUnsubscribeAll(hodlChan)
// Resolve the htlc directly if possible.
if event != nil {
return processHodlEvent(*event)
}
default:
return nil, err return nil, err
} }
defer h.Registry.HodlUnsubscribeAll(hodlChan)
// If the resolution is non-nil (indicating that a settle or cancel has
// occurred), and the invoice is known to the registry (indicating that
// the htlc is paying one of our invoices and is not a forward), try to
// resolve it directly.
if resolution != nil &&
resolution.Outcome != invoices.ResultInvoiceNotFound {
return processHtlcResolution(*resolution)
}
// With the epochs and preimage subscriptions initialized, we'll query // With the epochs and preimage subscriptions initialized, we'll query
// to see if we already know the preimage. // to see if we already know the preimage.
preimage, ok := h.PreimageDB.LookupPreimage(h.htlc.RHash) preimage, ok := h.PreimageDB.LookupPreimage(h.htlc.RHash)
@ -252,9 +255,9 @@ func (h *htlcIncomingContestResolver) Resolve() (ContractResolver, error) {
return &h.htlcSuccessResolver, nil return &h.htlcSuccessResolver, nil
case hodlItem := <-hodlChan: case hodlItem := <-hodlChan:
hodlEvent := hodlItem.(invoices.HodlEvent) htlcResolution := hodlItem.(invoices.HtlcResolution)
return processHodlEvent(hodlEvent) return processHtlcResolution(htlcResolution)
case newBlock, ok := <-blockEpochs.Epochs: case newBlock, ok := <-blockEpochs.Epochs:
if !ok { if !ok {

@ -21,10 +21,11 @@ const (
) )
var ( var (
testResPreimage = lntypes.Preimage{1, 2, 3} testResPreimage = lntypes.Preimage{1, 2, 3}
testResHash = testResPreimage.Hash() testResHash = testResPreimage.Hash()
testResCircuitKey = channeldb.CircuitKey{} testResCircuitKey = channeldb.CircuitKey{}
testOnionBlob = []byte{4, 5, 6} testOnionBlob = []byte{4, 5, 6}
testAcceptHeight int32 = 1234
) )
// TestHtlcIncomingResolverFwdPreimageKnown tests resolution of a forwarded htlc // TestHtlcIncomingResolverFwdPreimageKnown tests resolution of a forwarded htlc
@ -34,7 +35,10 @@ func TestHtlcIncomingResolverFwdPreimageKnown(t *testing.T) {
defer timeout(t)() defer timeout(t)()
ctx := newIncomingResolverTestContext(t) ctx := newIncomingResolverTestContext(t)
ctx.registry.notifyErr = channeldb.ErrInvoiceNotFound ctx.registry.notifyResolution = invoices.NewFailureResolution(
testResCircuitKey, testHtlcExpiry,
invoices.ResultInvoiceNotFound,
)
ctx.witnessBeacon.lookupPreimage[testResHash] = testResPreimage ctx.witnessBeacon.lookupPreimage[testResHash] = testResPreimage
ctx.resolve() ctx.resolve()
ctx.waitForResult(true) ctx.waitForResult(true)
@ -48,7 +52,10 @@ func TestHtlcIncomingResolverFwdContestedSuccess(t *testing.T) {
defer timeout(t)() defer timeout(t)()
ctx := newIncomingResolverTestContext(t) ctx := newIncomingResolverTestContext(t)
ctx.registry.notifyErr = channeldb.ErrInvoiceNotFound ctx.registry.notifyResolution = invoices.NewFailureResolution(
testResCircuitKey, testHtlcExpiry,
invoices.ResultInvoiceNotFound,
)
ctx.resolve() ctx.resolve()
// Simulate a new block coming in. HTLC is not yet expired. // Simulate a new block coming in. HTLC is not yet expired.
@ -65,7 +72,10 @@ func TestHtlcIncomingResolverFwdContestedTimeout(t *testing.T) {
defer timeout(t)() defer timeout(t)()
ctx := newIncomingResolverTestContext(t) ctx := newIncomingResolverTestContext(t)
ctx.registry.notifyErr = channeldb.ErrInvoiceNotFound ctx.registry.notifyResolution = invoices.NewFailureResolution(
testResCircuitKey, testHtlcExpiry,
invoices.ResultInvoiceNotFound,
)
ctx.resolve() ctx.resolve()
// Simulate a new block coming in. HTLC expires. // Simulate a new block coming in. HTLC expires.
@ -81,8 +91,10 @@ func TestHtlcIncomingResolverFwdTimeout(t *testing.T) {
defer timeout(t)() defer timeout(t)()
ctx := newIncomingResolverTestContext(t) ctx := newIncomingResolverTestContext(t)
ctx.registry.notifyResolution = invoices.NewFailureResolution(
ctx.registry.notifyErr = channeldb.ErrInvoiceNotFound testResCircuitKey, testHtlcExpiry,
invoices.ResultInvoiceNotFound,
)
ctx.witnessBeacon.lookupPreimage[testResHash] = testResPreimage ctx.witnessBeacon.lookupPreimage[testResHash] = testResPreimage
ctx.resolver.htlcExpiry = 90 ctx.resolver.htlcExpiry = 90
ctx.resolve() ctx.resolve()
@ -96,10 +108,11 @@ func TestHtlcIncomingResolverExitSettle(t *testing.T) {
defer timeout(t)() defer timeout(t)()
ctx := newIncomingResolverTestContext(t) ctx := newIncomingResolverTestContext(t)
ctx.registry.notifyEvent = &invoices.HodlEvent{ ctx.registry.notifyResolution = invoices.NewSettleResolution(
CircuitKey: testResCircuitKey, testResPreimage, testResCircuitKey, testAcceptHeight,
Preimage: &testResPreimage, invoices.ResultReplayToSettled,
} )
ctx.resolve() ctx.resolve()
data := <-ctx.registry.notifyChan data := <-ctx.registry.notifyChan
@ -126,9 +139,11 @@ func TestHtlcIncomingResolverExitCancel(t *testing.T) {
defer timeout(t)() defer timeout(t)()
ctx := newIncomingResolverTestContext(t) ctx := newIncomingResolverTestContext(t)
ctx.registry.notifyEvent = &invoices.HodlEvent{ ctx.registry.notifyResolution = invoices.NewFailureResolution(
CircuitKey: testResCircuitKey, testResCircuitKey, testAcceptHeight,
} invoices.ResultInvoiceAlreadyCanceled,
)
ctx.resolve() ctx.resolve()
ctx.waitForResult(false) ctx.waitForResult(false)
} }
@ -143,10 +158,10 @@ func TestHtlcIncomingResolverExitSettleHodl(t *testing.T) {
ctx.resolve() ctx.resolve()
notifyData := <-ctx.registry.notifyChan notifyData := <-ctx.registry.notifyChan
notifyData.hodlChan <- invoices.HodlEvent{ notifyData.hodlChan <- *invoices.NewSettleResolution(
CircuitKey: testResCircuitKey, testResPreimage, testResCircuitKey, testAcceptHeight,
Preimage: &testResPreimage, invoices.ResultSettled,
} )
ctx.waitForResult(true) ctx.waitForResult(true)
} }
@ -172,9 +187,10 @@ func TestHtlcIncomingResolverExitCancelHodl(t *testing.T) {
ctx := newIncomingResolverTestContext(t) ctx := newIncomingResolverTestContext(t)
ctx.resolve() ctx.resolve()
notifyData := <-ctx.registry.notifyChan notifyData := <-ctx.registry.notifyChan
notifyData.hodlChan <- invoices.HodlEvent{ notifyData.hodlChan <- *invoices.NewFailureResolution(
CircuitKey: testResCircuitKey, testResCircuitKey, testAcceptHeight, invoices.ResultCanceled,
} )
ctx.waitForResult(false) ctx.waitForResult(false)
} }

@ -27,9 +27,9 @@ type Registry interface {
NotifyExitHopHtlc(payHash lntypes.Hash, paidAmount lnwire.MilliSatoshi, NotifyExitHopHtlc(payHash lntypes.Hash, paidAmount lnwire.MilliSatoshi,
expiry uint32, currentHeight int32, expiry uint32, currentHeight int32,
circuitKey channeldb.CircuitKey, hodlChan chan<- interface{}, circuitKey channeldb.CircuitKey, hodlChan chan<- interface{},
payload invoices.Payload) (*invoices.HodlEvent, error) payload invoices.Payload) (*invoices.HtlcResolution, error)
// HodlUnsubscribeAll unsubscribes from all hodl events. // HodlUnsubscribeAll unsubscribes from all htlc resolutions.
HodlUnsubscribeAll(subscriber chan<- interface{}) HodlUnsubscribeAll(subscriber chan<- interface{})
} }

@ -16,15 +16,15 @@ type notifyExitHopData struct {
} }
type mockRegistry struct { type mockRegistry struct {
notifyChan chan notifyExitHopData notifyChan chan notifyExitHopData
notifyErr error notifyErr error
notifyEvent *invoices.HodlEvent notifyResolution *invoices.HtlcResolution
} }
func (r *mockRegistry) NotifyExitHopHtlc(payHash lntypes.Hash, func (r *mockRegistry) NotifyExitHopHtlc(payHash lntypes.Hash,
paidAmount lnwire.MilliSatoshi, expiry uint32, currentHeight int32, paidAmount lnwire.MilliSatoshi, expiry uint32, currentHeight int32,
circuitKey channeldb.CircuitKey, hodlChan chan<- interface{}, circuitKey channeldb.CircuitKey, hodlChan chan<- interface{},
payload invoices.Payload) (*invoices.HodlEvent, error) { payload invoices.Payload) (*invoices.HtlcResolution, error) {
r.notifyChan <- notifyExitHopData{ r.notifyChan <- notifyExitHopData{
hodlChan: hodlChan, hodlChan: hodlChan,
@ -34,7 +34,7 @@ func (r *mockRegistry) NotifyExitHopHtlc(payHash lntypes.Hash,
currentHeight: currentHeight, currentHeight: currentHeight,
} }
return r.notifyEvent, r.notifyErr return r.notifyResolution, r.notifyErr
} }
func (r *mockRegistry) HodlUnsubscribeAll(subscriber chan<- interface{}) {} func (r *mockRegistry) HodlUnsubscribeAll(subscriber chan<- interface{}) {}

@ -27,7 +27,7 @@ type InvoiceDatabase interface {
NotifyExitHopHtlc(payHash lntypes.Hash, paidAmount lnwire.MilliSatoshi, NotifyExitHopHtlc(payHash lntypes.Hash, paidAmount lnwire.MilliSatoshi,
expiry uint32, currentHeight int32, expiry uint32, currentHeight int32,
circuitKey channeldb.CircuitKey, hodlChan chan<- interface{}, circuitKey channeldb.CircuitKey, hodlChan chan<- interface{},
payload invoices.Payload) (*invoices.HodlEvent, error) payload invoices.Payload) (*invoices.HtlcResolution, error)
// CancelInvoice attempts to cancel the invoice corresponding to the // CancelInvoice attempts to cancel the invoice corresponding to the
// passed payment hash. // passed payment hash.
@ -36,7 +36,7 @@ type InvoiceDatabase interface {
// SettleHodlInvoice settles a hold invoice. // SettleHodlInvoice settles a hold invoice.
SettleHodlInvoice(preimage lntypes.Preimage) error SettleHodlInvoice(preimage lntypes.Preimage) error
// HodlUnsubscribeAll unsubscribes from all hodl events. // HodlUnsubscribeAll unsubscribes from all htlc resolutions.
HodlUnsubscribeAll(subscriber chan<- interface{}) HodlUnsubscribeAll(subscriber chan<- interface{})
} }

@ -489,8 +489,8 @@ func (l *channelLink) Stop() {
l.log.Info("stopping") l.log.Info("stopping")
// As the link is stopping, we are no longer interested in hodl events // As the link is stopping, we are no longer interested in htlc
// coming from the invoice registry. // resolutions coming from the invoice registry.
l.cfg.Registry.HodlUnsubscribeAll(l.hodlQueue.ChanIn()) l.cfg.Registry.HodlUnsubscribeAll(l.hodlQueue.ChanIn())
if l.cfg.ChainEvents.Cancel != nil { if l.cfg.ChainEvents.Cancel != nil {
@ -1126,11 +1126,11 @@ out:
case msg := <-l.upstream: case msg := <-l.upstream:
l.handleUpstreamMsg(msg) l.handleUpstreamMsg(msg)
// A hodl event is received. This means that we now have a // A htlc resolution is received. This means that we now have a
// resolution for a previously accepted htlc. // resolution for a previously accepted htlc.
case hodlItem := <-l.hodlQueue.ChanOut(): case hodlItem := <-l.hodlQueue.ChanOut():
hodlEvent := hodlItem.(invoices.HodlEvent) htlcResolution := hodlItem.(invoices.HtlcResolution)
err := l.processHodlQueue(hodlEvent) err := l.processHodlQueue(htlcResolution)
if err != nil { if err != nil {
l.fail(LinkFailureError{code: ErrInternalError}, l.fail(LinkFailureError{code: ErrInternalError},
fmt.Sprintf("process hodl queue: %v", fmt.Sprintf("process hodl queue: %v",
@ -1145,24 +1145,26 @@ out:
} }
} }
// processHodlQueue processes a received hodl event and continues reading from // processHodlQueue processes a received htlc resolution and continues reading
// the hodl queue until no more events remain. When this function returns // from the hodl queue until no more resolutions remain. When this function
// without an error, the commit tx should be updated. // returns without an error, the commit tx should be updated.
func (l *channelLink) processHodlQueue(firstHodlEvent invoices.HodlEvent) error { func (l *channelLink) processHodlQueue(
firstResolution invoices.HtlcResolution) error {
// Try to read all waiting resolution messages, so that they can all be // Try to read all waiting resolution messages, so that they can all be
// processed in a single commitment tx update. // processed in a single commitment tx update.
hodlEvent := firstHodlEvent htlcResolution := firstResolution
loop: loop:
for { for {
// Lookup all hodl htlcs that can be failed or settled with this event. // Lookup all hodl htlcs that can be failed or settled with this event.
// The hodl htlc must be present in the map. // The hodl htlc must be present in the map.
circuitKey := hodlEvent.CircuitKey circuitKey := htlcResolution.CircuitKey
hodlHtlc, ok := l.hodlMap[circuitKey] hodlHtlc, ok := l.hodlMap[circuitKey]
if !ok { if !ok {
return fmt.Errorf("hodl htlc not found: %v", circuitKey) return fmt.Errorf("hodl htlc not found: %v", circuitKey)
} }
if err := l.processHodlEvent(hodlEvent, hodlHtlc); err != nil { if err := l.processHtlcResolution(htlcResolution, hodlHtlc); err != nil {
return err return err
} }
@ -1171,7 +1173,7 @@ loop:
select { select {
case item := <-l.hodlQueue.ChanOut(): case item := <-l.hodlQueue.ChanOut():
hodlEvent = item.(invoices.HodlEvent) htlcResolution = item.(invoices.HtlcResolution)
default: default:
break loop break loop
} }
@ -1185,30 +1187,30 @@ loop:
return nil return nil
} }
// processHodlEvent applies a received hodl event to the provided htlc. When // processHtlcResolution applies a received htlc resolution to the provided
// this function returns without an error, the commit tx should be updated. // htlc. When this function returns without an error, the commit tx should be
func (l *channelLink) processHodlEvent(hodlEvent invoices.HodlEvent, // updated.
func (l *channelLink) processHtlcResolution(resolution invoices.HtlcResolution,
htlc hodlHtlc) error { htlc hodlHtlc) error {
circuitKey := hodlEvent.CircuitKey circuitKey := resolution.CircuitKey
// Determine required action for the resolution. // Determine required action for the resolution. If the event's preimage is
if hodlEvent.Preimage != nil { // non-nil, the htlc must be settled. Otherwise, it should be canceled.
l.log.Debugf("received hodl settle event for %v", circuitKey) if resolution.Preimage != nil {
l.log.Debugf("received settle resolution for %v", circuitKey)
return l.settleHTLC( return l.settleHTLC(
*hodlEvent.Preimage, htlc.pd.HtlcIndex, *resolution.Preimage, htlc.pd.HtlcIndex,
htlc.pd.SourceRef, htlc.pd.SourceRef,
) )
} }
l.log.Debugf("received hodl cancel event for %v", circuitKey) l.log.Debugf("received cancel resolution for %v with outcome: %v",
circuitKey, resolution.Outcome)
// In case of a cancel, always return // Get the lnwire failure message based on the resolution result.
// incorrect_or_unknown_payment_details in order to avoid leaking info. failure := getResolutionFailure(resolution, htlc.pd.Amount)
failure := lnwire.NewFailIncorrectDetails(
htlc.pd.Amount, uint32(hodlEvent.AcceptHeight),
)
l.sendHTLCError( l.sendHTLCError(
htlc.pd.HtlcIndex, failure, htlc.obfuscator, htlc.pd.HtlcIndex, failure, htlc.obfuscator,
@ -1217,6 +1219,25 @@ func (l *channelLink) processHodlEvent(hodlEvent invoices.HodlEvent,
return nil return nil
} }
// getResolutionFailure returns the wire message that a htlc resolution should
// be failed with.
func getResolutionFailure(resolution invoices.HtlcResolution,
amount lnwire.MilliSatoshi) lnwire.FailureMessage {
// If the resolution has been resolved as part of a MPP timeout, we need
// to fail the htlc with lnwire.FailMppTimeout.
if resolution.Outcome == invoices.ResultMppTimeout {
return &lnwire.FailMPPTimeout{}
}
// If the htlc is not a MPP timeout, we fail it with FailIncorrectDetails
// This covers hodl cancels (which return it to avoid leaking information
// and other invoice failures such as underpayment or expiry too soon.
return lnwire.NewFailIncorrectDetails(
amount, uint32(resolution.AcceptHeight),
)
}
// randomFeeUpdateTimeout returns a random timeout between the bounds defined // randomFeeUpdateTimeout returns a random timeout between the bounds defined
// within the link's configuration that will be used to determine when the link // within the link's configuration that will be used to determine when the link
// should propose an update to its commitment fee rate. // should propose an update to its commitment fee rate.
@ -2817,21 +2838,7 @@ func (l *channelLink) processExitHop(pd *lnwallet.PaymentDescriptor,
invoiceHash, pd.Amount, pd.Timeout, int32(heightNow), invoiceHash, pd.Amount, pd.Timeout, int32(heightNow),
circuitKey, l.hodlQueue.ChanIn(), payload, circuitKey, l.hodlQueue.ChanIn(), payload,
) )
if err != nil {
switch err {
// Cancel htlc if we don't have an invoice for it.
case channeldb.ErrInvoiceNotFound:
failure := lnwire.NewFailIncorrectDetails(pd.Amount, heightNow)
l.sendHTLCError(pd.HtlcIndex, failure, obfuscator, pd.SourceRef)
return nil
// No error.
case nil:
// Pass error to caller.
default:
return err return err
} }
@ -2841,15 +2848,15 @@ func (l *channelLink) processExitHop(pd *lnwallet.PaymentDescriptor,
obfuscator: obfuscator, obfuscator: obfuscator,
} }
// If the event is nil, the invoice is being held, so we save payment
// descriptor for future reference.
if event == nil { if event == nil {
// Save payment descriptor for future reference.
l.hodlMap[circuitKey] = htlc l.hodlMap[circuitKey] = htlc
return nil return nil
} }
// Process the received resolution. // Process the received resolution.
return l.processHodlEvent(*event, htlc) return l.processHtlcResolution(*event, htlc)
} }
// settleHTLC settles the HTLC on the channel. // settleHTLC settles the HTLC on the channel.

@ -819,7 +819,7 @@ func (i *mockInvoiceRegistry) SettleHodlInvoice(preimage lntypes.Preimage) error
func (i *mockInvoiceRegistry) NotifyExitHopHtlc(rhash lntypes.Hash, func (i *mockInvoiceRegistry) NotifyExitHopHtlc(rhash lntypes.Hash,
amt lnwire.MilliSatoshi, expiry uint32, currentHeight int32, amt lnwire.MilliSatoshi, expiry uint32, currentHeight int32,
circuitKey channeldb.CircuitKey, hodlChan chan<- interface{}, circuitKey channeldb.CircuitKey, hodlChan chan<- interface{},
payload invoices.Payload) (*invoices.HodlEvent, error) { payload invoices.Payload) (*invoices.HtlcResolution, error) {
event, err := i.registry.NotifyExitHopHtlc( event, err := i.registry.NotifyExitHopHtlc(
rhash, amt, expiry, currentHeight, circuitKey, hodlChan, rhash, amt, expiry, currentHeight, circuitKey, hodlChan,

@ -35,10 +35,10 @@ const (
DefaultHtlcHoldDuration = 120 * time.Second DefaultHtlcHoldDuration = 120 * time.Second
) )
// HodlEvent describes how an htlc should be resolved. If HodlEvent.Preimage is // HtlcResolution describes how an htlc should be resolved. If the preimage
// set, the event indicates a settle event. If Preimage is nil, it is a cancel // field is set, the event indicates a settle event. If Preimage is nil, it is
// event. // a cancel event.
type HodlEvent struct { type HtlcResolution struct {
// Preimage is the htlc preimage. Its value is nil in case of a cancel. // Preimage is the htlc preimage. Its value is nil in case of a cancel.
Preimage *lntypes.Preimage Preimage *lntypes.Preimage
@ -48,6 +48,33 @@ type HodlEvent struct {
// AcceptHeight is the original height at which the htlc was accepted. // AcceptHeight is the original height at which the htlc was accepted.
AcceptHeight int32 AcceptHeight int32
// Outcome indicates the outcome of the invoice registry update.
Outcome ResolutionResult
}
// NewFailureResolution returns a htlc failure resolution.
func NewFailureResolution(key channeldb.CircuitKey,
acceptHeight int32, outcome ResolutionResult) *HtlcResolution {
return &HtlcResolution{
CircuitKey: key,
AcceptHeight: acceptHeight,
Outcome: outcome,
}
}
// NewSettleResolution returns a htlc resolution which is associated with a
// settle.
func NewSettleResolution(preimage lntypes.Preimage, key channeldb.CircuitKey,
acceptHeight int32, outcome ResolutionResult) *HtlcResolution {
return &HtlcResolution{
Preimage: &preimage,
CircuitKey: key,
AcceptHeight: acceptHeight,
Outcome: outcome,
}
} }
// RegistryConfig contains the configuration parameters for invoice registry. // RegistryConfig contains the configuration parameters for invoice registry.
@ -325,7 +352,7 @@ func (i *InvoiceRegistry) invoiceEventLoop() {
case <-nextReleaseTick: case <-nextReleaseTick:
event := autoReleaseHeap.Pop().(*htlcReleaseEvent) event := autoReleaseHeap.Pop().(*htlcReleaseEvent)
err := i.cancelSingleHtlc( err := i.cancelSingleHtlc(
event.hash, event.key, event.hash, event.key, ResultMppTimeout,
) )
if err != nil { if err != nil {
log.Errorf("HTLC timer: %v", err) log.Errorf("HTLC timer: %v", err)
@ -574,9 +601,11 @@ func (i *InvoiceRegistry) startHtlcTimer(hash lntypes.Hash,
} }
} }
// cancelSingleHtlc cancels a single accepted htlc on an invoice. // cancelSingleHtlc cancels a single accepted htlc on an invoice. It takes
// a resolution result which will be used to notify subscribed links and
// resolvers of the details of the htlc cancellation.
func (i *InvoiceRegistry) cancelSingleHtlc(hash lntypes.Hash, func (i *InvoiceRegistry) cancelSingleHtlc(hash lntypes.Hash,
key channeldb.CircuitKey) error { key channeldb.CircuitKey, result ResolutionResult) error {
i.Lock() i.Lock()
defer i.Unlock() defer i.Unlock()
@ -652,11 +681,11 @@ func (i *InvoiceRegistry) cancelSingleHtlc(hash lntypes.Hash,
return fmt.Errorf("htlc %v not found", key) return fmt.Errorf("htlc %v not found", key)
} }
if htlc.State == channeldb.HtlcStateCanceled { if htlc.State == channeldb.HtlcStateCanceled {
i.notifyHodlSubscribers(HodlEvent{ resolution := *NewFailureResolution(
CircuitKey: key, key, int32(htlc.AcceptHeight), result,
AcceptHeight: int32(htlc.AcceptHeight), )
Preimage: nil,
}) i.notifyHodlSubscribers(resolution)
} }
return nil return nil
} }
@ -679,7 +708,7 @@ func (i *InvoiceRegistry) cancelSingleHtlc(hash lntypes.Hash,
func (i *InvoiceRegistry) NotifyExitHopHtlc(rHash lntypes.Hash, func (i *InvoiceRegistry) NotifyExitHopHtlc(rHash lntypes.Hash,
amtPaid lnwire.MilliSatoshi, expiry uint32, currentHeight int32, amtPaid lnwire.MilliSatoshi, expiry uint32, currentHeight int32,
circuitKey channeldb.CircuitKey, hodlChan chan<- interface{}, circuitKey channeldb.CircuitKey, hodlChan chan<- interface{},
payload Payload) (*HodlEvent, error) { payload Payload) (*HtlcResolution, error) {
i.Lock() i.Lock()
defer i.Unlock() defer i.Unlock()
@ -706,7 +735,7 @@ func (i *InvoiceRegistry) NotifyExitHopHtlc(rHash lntypes.Hash,
// We'll attempt to settle an invoice matching this rHash on disk (if // We'll attempt to settle an invoice matching this rHash on disk (if
// one exists). The callback will update the invoice state and/or htlcs. // one exists). The callback will update the invoice state and/or htlcs.
var ( var (
result updateResult result ResolutionResult
updateSubscribers bool updateSubscribers bool
) )
invoice, err := i.cdb.UpdateInvoice( invoice, err := i.cdb.UpdateInvoice(
@ -729,11 +758,21 @@ func (i *InvoiceRegistry) NotifyExitHopHtlc(rHash lntypes.Hash,
return updateDesc, nil return updateDesc, nil
}, },
) )
if err != nil { switch err {
debugLog(err.Error()) case channeldb.ErrInvoiceNotFound:
// If the invoice was not found, return a failure resolution
// with an invoice not found result.
return NewFailureResolution(
circuitKey, currentHeight, ResultInvoiceNotFound,
), nil
case nil:
default:
debugLog(err.Error())
return nil, err return nil, err
} }
debugLog(result.String()) debugLog(result.String())
if updateSubscribers { if updateSubscribers {
@ -745,10 +784,9 @@ func (i *InvoiceRegistry) NotifyExitHopHtlc(rHash lntypes.Hash,
// If it isn't recorded, cancel htlc. // If it isn't recorded, cancel htlc.
if !ok { if !ok {
return &HodlEvent{ return NewFailureResolution(
CircuitKey: circuitKey, circuitKey, currentHeight, result,
AcceptHeight: currentHeight, ), nil
}, nil
} }
// Determine accepted height of this htlc. If the htlc reached the // Determine accepted height of this htlc. If the htlc reached the
@ -759,10 +797,9 @@ func (i *InvoiceRegistry) NotifyExitHopHtlc(rHash lntypes.Hash,
switch invoiceHtlc.State { switch invoiceHtlc.State {
case channeldb.HtlcStateCanceled: case channeldb.HtlcStateCanceled:
return &HodlEvent{ return NewFailureResolution(
CircuitKey: circuitKey, circuitKey, acceptHeight, result,
AcceptHeight: acceptHeight, ), nil
}, nil
case channeldb.HtlcStateSettled: case channeldb.HtlcStateSettled:
// Also settle any previously accepted htlcs. The invoice state // Also settle any previously accepted htlcs. The invoice state
@ -773,18 +810,24 @@ func (i *InvoiceRegistry) NotifyExitHopHtlc(rHash lntypes.Hash,
continue continue
} }
i.notifyHodlSubscribers(HodlEvent{ // Notify subscribers that the htlcs should be settled
CircuitKey: key, // with our peer. Note that the outcome of the
Preimage: &invoice.Terms.PaymentPreimage, // resolution is set based on the outcome of the single
AcceptHeight: int32(htlc.AcceptHeight), // htlc that we just settled, so may not be accurate
}) // for all htlcs.
resolution := *NewSettleResolution(
invoice.Terms.PaymentPreimage, key,
acceptHeight, result,
)
i.notifyHodlSubscribers(resolution)
} }
return &HodlEvent{ resolution := NewSettleResolution(
CircuitKey: circuitKey, invoice.Terms.PaymentPreimage, circuitKey,
Preimage: &invoice.Terms.PaymentPreimage, acceptHeight, result,
AcceptHeight: acceptHeight, )
}, nil return resolution, nil
case channeldb.HtlcStateAccepted: case channeldb.HtlcStateAccepted:
// (Re)start the htlc timer if the invoice is still open. It can // (Re)start the htlc timer if the invoice is still open. It can
@ -836,7 +879,9 @@ func (i *InvoiceRegistry) SettleHodlInvoice(preimage lntypes.Preimage) error {
hash := preimage.Hash() hash := preimage.Hash()
invoice, err := i.cdb.UpdateInvoice(hash, updateInvoice) invoice, err := i.cdb.UpdateInvoice(hash, updateInvoice)
if err != nil { if err != nil {
log.Errorf("SettleHodlInvoice with preimage %v: %v", preimage, err) log.Errorf("SettleHodlInvoice with preimage %v: %v",
preimage, err)
return err return err
} }
@ -854,11 +899,11 @@ func (i *InvoiceRegistry) SettleHodlInvoice(preimage lntypes.Preimage) error {
continue continue
} }
i.notifyHodlSubscribers(HodlEvent{ resolution := *NewSettleResolution(
CircuitKey: key, preimage, key, int32(htlc.AcceptHeight), ResultSettled,
Preimage: &preimage, )
AcceptHeight: int32(htlc.AcceptHeight),
}) i.notifyHodlSubscribers(resolution)
} }
i.notifyClients(hash, invoice, invoice.State) i.notifyClients(hash, invoice, invoice.State)
@ -873,7 +918,8 @@ func (i *InvoiceRegistry) CancelInvoice(payHash lntypes.Hash) error {
// cancelInvoice attempts to cancel the invoice corresponding to the passed // cancelInvoice attempts to cancel the invoice corresponding to the passed
// payment hash. Accepted invoices will only be canceled if explicitly // payment hash. Accepted invoices will only be canceled if explicitly
// requested to do so. // requested to do so. It notifies subscribing links and resolvers that
// the associated htlcs were canceled if they change state.
func (i *InvoiceRegistry) cancelInvoiceImpl(payHash lntypes.Hash, func (i *InvoiceRegistry) cancelInvoiceImpl(payHash lntypes.Hash,
cancelAccepted bool) error { cancelAccepted bool) error {
@ -932,10 +978,11 @@ func (i *InvoiceRegistry) cancelInvoiceImpl(payHash lntypes.Hash,
continue continue
} }
i.notifyHodlSubscribers(HodlEvent{ i.notifyHodlSubscribers(
CircuitKey: key, *NewFailureResolution(
AcceptHeight: int32(htlc.AcceptHeight), key, int32(htlc.AcceptHeight), ResultCanceled,
}) ),
)
} }
i.notifyClients(payHash, invoice, channeldb.ContractCanceled) i.notifyClients(payHash, invoice, channeldb.ContractCanceled)
@ -1201,9 +1248,10 @@ func (i *InvoiceRegistry) SubscribeSingleInvoice(
return client, nil return client, nil
} }
// notifyHodlSubscribers sends out the hodl event to all current subscribers. // notifyHodlSubscribers sends out the htlc resolution to all current
func (i *InvoiceRegistry) notifyHodlSubscribers(hodlEvent HodlEvent) { // subscribers.
subscribers, ok := i.hodlSubscriptions[hodlEvent.CircuitKey] func (i *InvoiceRegistry) notifyHodlSubscribers(htlcResolution HtlcResolution) {
subscribers, ok := i.hodlSubscriptions[htlcResolution.CircuitKey]
if !ok { if !ok {
return return
} }
@ -1213,18 +1261,18 @@ func (i *InvoiceRegistry) notifyHodlSubscribers(hodlEvent HodlEvent) {
// single resolution for each hash. // single resolution for each hash.
for subscriber := range subscribers { for subscriber := range subscribers {
select { select {
case subscriber <- hodlEvent: case subscriber <- htlcResolution:
case <-i.quit: case <-i.quit:
return return
} }
delete( delete(
i.hodlReverseSubscriptions[subscriber], i.hodlReverseSubscriptions[subscriber],
hodlEvent.CircuitKey, htlcResolution.CircuitKey,
) )
} }
delete(i.hodlSubscriptions, hodlEvent.CircuitKey) delete(i.hodlSubscriptions, htlcResolution.CircuitKey)
} }
// hodlSubscribe adds a new invoice subscription. // hodlSubscribe adds a new invoice subscription.

@ -66,7 +66,7 @@ func TestSettleInvoice(t *testing.T) {
hodlChan := make(chan interface{}, 1) hodlChan := make(chan interface{}, 1)
// Try to settle invoice with an htlc that expires too soon. // Try to settle invoice with an htlc that expires too soon.
event, err := ctx.registry.NotifyExitHopHtlc( resolution, err := ctx.registry.NotifyExitHopHtlc(
testInvoicePaymentHash, testInvoice.Terms.Value, testInvoicePaymentHash, testInvoice.Terms.Value,
uint32(testCurrentHeight)+testInvoiceCltvDelta-1, uint32(testCurrentHeight)+testInvoiceCltvDelta-1,
testCurrentHeight, getCircuitKey(10), hodlChan, testPayload, testCurrentHeight, getCircuitKey(10), hodlChan, testPayload,
@ -74,23 +74,30 @@ func TestSettleInvoice(t *testing.T) {
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
if event.Preimage != nil { if resolution.Preimage != nil {
t.Fatal("expected cancel event") t.Fatal("expected cancel resolution")
} }
if event.AcceptHeight != testCurrentHeight { if resolution.AcceptHeight != testCurrentHeight {
t.Fatalf("expected acceptHeight %v, but got %v", t.Fatalf("expected acceptHeight %v, but got %v",
testCurrentHeight, event.AcceptHeight) testCurrentHeight, resolution.AcceptHeight)
}
if resolution.Outcome != ResultExpiryTooSoon {
t.Fatalf("expected expiry too soon, got: %v",
resolution.Outcome)
} }
// Settle invoice with a slightly higher amount. // Settle invoice with a slightly higher amount.
amtPaid := lnwire.MilliSatoshi(100500) amtPaid := lnwire.MilliSatoshi(100500)
_, err = ctx.registry.NotifyExitHopHtlc( resolution, err = ctx.registry.NotifyExitHopHtlc(
testInvoicePaymentHash, amtPaid, testHtlcExpiry, testCurrentHeight, testInvoicePaymentHash, amtPaid, testHtlcExpiry, testCurrentHeight,
getCircuitKey(0), hodlChan, testPayload, getCircuitKey(0), hodlChan, testPayload,
) )
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
if resolution.Outcome != ResultSettled {
t.Fatalf("expected settled, got: %v", resolution.Outcome)
}
// We expect the settled state to be sent to the single invoice // We expect the settled state to be sent to the single invoice
// subscriber. // subscriber.
@ -120,42 +127,54 @@ func TestSettleInvoice(t *testing.T) {
// Try to settle again with the same htlc id. We need this idempotent // Try to settle again with the same htlc id. We need this idempotent
// behaviour after a restart. // behaviour after a restart.
event, err = ctx.registry.NotifyExitHopHtlc( resolution, err = ctx.registry.NotifyExitHopHtlc(
testInvoicePaymentHash, amtPaid, testHtlcExpiry, testCurrentHeight, testInvoicePaymentHash, amtPaid, testHtlcExpiry, testCurrentHeight,
getCircuitKey(0), hodlChan, testPayload, getCircuitKey(0), hodlChan, testPayload,
) )
if err != nil { if err != nil {
t.Fatalf("unexpected NotifyExitHopHtlc error: %v", err) t.Fatalf("unexpected NotifyExitHopHtlc error: %v", err)
} }
if event.Preimage == nil { if resolution.Preimage == nil {
t.Fatal("expected settle event") t.Fatal("expected settle resolution")
}
if resolution.Outcome != ResultReplayToSettled {
t.Fatalf("expected replay settled, got: %v",
resolution.Outcome)
} }
// Try to settle again with a new higher-valued htlc. This payment // Try to settle again with a new higher-valued htlc. This payment
// should also be accepted, to prevent any change in behaviour for a // should also be accepted, to prevent any change in behaviour for a
// paid invoice that may open up a probe vector. // paid invoice that may open up a probe vector.
event, err = ctx.registry.NotifyExitHopHtlc( resolution, err = ctx.registry.NotifyExitHopHtlc(
testInvoicePaymentHash, amtPaid+600, testHtlcExpiry, testCurrentHeight, testInvoicePaymentHash, amtPaid+600, testHtlcExpiry, testCurrentHeight,
getCircuitKey(1), hodlChan, testPayload, getCircuitKey(1), hodlChan, testPayload,
) )
if err != nil { if err != nil {
t.Fatalf("unexpected NotifyExitHopHtlc error: %v", err) t.Fatalf("unexpected NotifyExitHopHtlc error: %v", err)
} }
if event.Preimage == nil { if resolution.Preimage == nil {
t.Fatal("expected settle event") t.Fatal("expected settle resolution")
}
if resolution.Outcome != ResultDuplicateToSettled {
t.Fatalf("expected duplicate settled, got: %v",
resolution.Outcome)
} }
// Try to settle again with a lower amount. This should fail just as it // Try to settle again with a lower amount. This should fail just as it
// would have failed if it were the first payment. // would have failed if it were the first payment.
event, err = ctx.registry.NotifyExitHopHtlc( resolution, err = ctx.registry.NotifyExitHopHtlc(
testInvoicePaymentHash, amtPaid-600, testHtlcExpiry, testCurrentHeight, testInvoicePaymentHash, amtPaid-600, testHtlcExpiry, testCurrentHeight,
getCircuitKey(2), hodlChan, testPayload, getCircuitKey(2), hodlChan, testPayload,
) )
if err != nil { if err != nil {
t.Fatalf("unexpected NotifyExitHopHtlc error: %v", err) t.Fatalf("unexpected NotifyExitHopHtlc error: %v", err)
} }
if event.Preimage != nil { if resolution.Preimage != nil {
t.Fatal("expected cancel event") t.Fatal("expected cancel resolution")
}
if resolution.Outcome != ResultAmountTooLow {
t.Fatalf("expected amount too low, got: %v",
resolution.Outcome)
} }
// Check that settled amount is equal to the sum of values of the htlcs // Check that settled amount is equal to the sum of values of the htlcs
@ -177,7 +196,7 @@ func TestSettleInvoice(t *testing.T) {
// As this is a direct sette, we expect nothing on the hodl chan. // As this is a direct sette, we expect nothing on the hodl chan.
select { select {
case <-hodlChan: case <-hodlChan:
t.Fatal("unexpected event") t.Fatal("unexpected resolution")
default: default:
} }
} }
@ -270,9 +289,9 @@ func TestCancelInvoice(t *testing.T) {
} }
// Notify arrival of a new htlc paying to this invoice. This should // Notify arrival of a new htlc paying to this invoice. This should
// result in a cancel event. // result in a cancel resolution.
hodlChan := make(chan interface{}) hodlChan := make(chan interface{})
event, err := ctx.registry.NotifyExitHopHtlc( resolution, err := ctx.registry.NotifyExitHopHtlc(
testInvoicePaymentHash, amt, testHtlcExpiry, testCurrentHeight, testInvoicePaymentHash, amt, testHtlcExpiry, testCurrentHeight,
getCircuitKey(0), hodlChan, testPayload, getCircuitKey(0), hodlChan, testPayload,
) )
@ -280,12 +299,16 @@ func TestCancelInvoice(t *testing.T) {
t.Fatal("expected settlement of a canceled invoice to succeed") t.Fatal("expected settlement of a canceled invoice to succeed")
} }
if event.Preimage != nil { if resolution.Preimage != nil {
t.Fatal("expected cancel hodl event") t.Fatal("expected cancel htlc resolution")
} }
if event.AcceptHeight != testCurrentHeight { if resolution.AcceptHeight != testCurrentHeight {
t.Fatalf("expected acceptHeight %v, but got %v", t.Fatalf("expected acceptHeight %v, but got %v",
testCurrentHeight, event.AcceptHeight) testCurrentHeight, resolution.AcceptHeight)
}
if resolution.Outcome != ResultInvoiceAlreadyCanceled {
t.Fatalf("expected invoice already canceled, got: %v",
resolution.Outcome)
} }
} }
@ -354,54 +377,58 @@ func TestSettleHoldInvoice(t *testing.T) {
// NotifyExitHopHtlc without a preimage present in the invoice registry // NotifyExitHopHtlc without a preimage present in the invoice registry
// should be possible. // should be possible.
event, err := registry.NotifyExitHopHtlc( resolution, err := registry.NotifyExitHopHtlc(
testInvoicePaymentHash, amtPaid, testHtlcExpiry, testCurrentHeight, testInvoicePaymentHash, amtPaid, testHtlcExpiry, testCurrentHeight,
getCircuitKey(0), hodlChan, testPayload, getCircuitKey(0), hodlChan, testPayload,
) )
if err != nil { if err != nil {
t.Fatalf("expected settle to succeed but got %v", err) t.Fatalf("expected settle to succeed but got %v", err)
} }
if event != nil { if resolution != nil {
t.Fatalf("expected htlc to be held") t.Fatalf("expected htlc to be held")
} }
// Test idempotency. // Test idempotency.
event, err = registry.NotifyExitHopHtlc( resolution, err = registry.NotifyExitHopHtlc(
testInvoicePaymentHash, amtPaid, testHtlcExpiry, testCurrentHeight, testInvoicePaymentHash, amtPaid, testHtlcExpiry, testCurrentHeight,
getCircuitKey(0), hodlChan, testPayload, getCircuitKey(0), hodlChan, testPayload,
) )
if err != nil { if err != nil {
t.Fatalf("expected settle to succeed but got %v", err) t.Fatalf("expected settle to succeed but got %v", err)
} }
if event != nil { if resolution != nil {
t.Fatalf("expected htlc to be held") t.Fatalf("expected htlc to be held")
} }
// Test replay at a higher height. We expect the same result because it // Test replay at a higher height. We expect the same result because it
// is a replay. // is a replay.
event, err = registry.NotifyExitHopHtlc( resolution, err = registry.NotifyExitHopHtlc(
testInvoicePaymentHash, amtPaid, testHtlcExpiry, testCurrentHeight+10, testInvoicePaymentHash, amtPaid, testHtlcExpiry, testCurrentHeight+10,
getCircuitKey(0), hodlChan, testPayload, getCircuitKey(0), hodlChan, testPayload,
) )
if err != nil { if err != nil {
t.Fatalf("expected settle to succeed but got %v", err) t.Fatalf("expected settle to succeed but got %v", err)
} }
if event != nil { if resolution != nil {
t.Fatalf("expected htlc to be held") t.Fatalf("expected htlc to be held")
} }
// Test a new htlc coming in that doesn't meet the final cltv delta // Test a new htlc coming in that doesn't meet the final cltv delta
// requirement. It should be rejected. // requirement. It should be rejected.
event, err = registry.NotifyExitHopHtlc( resolution, err = registry.NotifyExitHopHtlc(
testInvoicePaymentHash, amtPaid, 1, testCurrentHeight, testInvoicePaymentHash, amtPaid, 1, testCurrentHeight,
getCircuitKey(1), hodlChan, testPayload, getCircuitKey(1), hodlChan, testPayload,
) )
if err != nil { if err != nil {
t.Fatalf("expected settle to succeed but got %v", err) t.Fatalf("expected settle to succeed but got %v", err)
} }
if event == nil || event.Preimage != nil { if resolution == nil || resolution.Preimage != nil {
t.Fatalf("expected htlc to be canceled") t.Fatalf("expected htlc to be canceled")
} }
if resolution.Outcome != ResultExpiryTooSoon {
t.Fatalf("expected expiry too soon, got: %v",
resolution.Outcome)
}
// We expect the accepted state to be sent to the single invoice // We expect the accepted state to be sent to the single invoice
// subscriber. For all invoice subscribers, we don't expect an update. // subscriber. For all invoice subscribers, we don't expect an update.
@ -421,13 +448,17 @@ func TestSettleHoldInvoice(t *testing.T) {
t.Fatal("expected set preimage to succeed") t.Fatal("expected set preimage to succeed")
} }
hodlEvent := (<-hodlChan).(HodlEvent) htlcResolution := (<-hodlChan).(HtlcResolution)
if *hodlEvent.Preimage != testInvoicePreimage { if *htlcResolution.Preimage != testInvoicePreimage {
t.Fatal("unexpected preimage in hodl event") t.Fatal("unexpected preimage in hodl resolution")
} }
if hodlEvent.AcceptHeight != testCurrentHeight { if htlcResolution.AcceptHeight != testCurrentHeight {
t.Fatalf("expected acceptHeight %v, but got %v", t.Fatalf("expected acceptHeight %v, but got %v",
testCurrentHeight, event.AcceptHeight) testCurrentHeight, resolution.AcceptHeight)
}
if htlcResolution.Outcome != ResultSettled {
t.Fatalf("expected result settled, got: %v",
htlcResolution.Outcome)
} }
// We expect a settled notification to be sent out for both all and // We expect a settled notification to be sent out for both all and
@ -496,14 +527,14 @@ func TestCancelHoldInvoice(t *testing.T) {
// NotifyExitHopHtlc without a preimage present in the invoice registry // NotifyExitHopHtlc without a preimage present in the invoice registry
// should be possible. // should be possible.
event, err := registry.NotifyExitHopHtlc( resolution, err := registry.NotifyExitHopHtlc(
testInvoicePaymentHash, amtPaid, testHtlcExpiry, testCurrentHeight, testInvoicePaymentHash, amtPaid, testHtlcExpiry, testCurrentHeight,
getCircuitKey(0), hodlChan, testPayload, getCircuitKey(0), hodlChan, testPayload,
) )
if err != nil { if err != nil {
t.Fatalf("expected settle to succeed but got %v", err) t.Fatalf("expected settle to succeed but got %v", err)
} }
if event != nil { if resolution != nil {
t.Fatalf("expected htlc to be held") t.Fatalf("expected htlc to be held")
} }
@ -513,35 +544,39 @@ func TestCancelHoldInvoice(t *testing.T) {
t.Fatal("cancel invoice failed") t.Fatal("cancel invoice failed")
} }
hodlEvent := (<-hodlChan).(HodlEvent) htlcResolution := (<-hodlChan).(HtlcResolution)
if hodlEvent.Preimage != nil { if htlcResolution.Preimage != nil {
t.Fatal("expected cancel hodl event") t.Fatal("expected cancel htlc resolution")
} }
// Offering the same htlc again at a higher height should still result // Offering the same htlc again at a higher height should still result
// in a rejection. The accept height is expected to be the original // in a rejection. The accept height is expected to be the original
// accept height. // accept height.
event, err = registry.NotifyExitHopHtlc( resolution, err = registry.NotifyExitHopHtlc(
testInvoicePaymentHash, amtPaid, testHtlcExpiry, testCurrentHeight+1, testInvoicePaymentHash, amtPaid, testHtlcExpiry, testCurrentHeight+1,
getCircuitKey(0), hodlChan, testPayload, getCircuitKey(0), hodlChan, testPayload,
) )
if err != nil { if err != nil {
t.Fatalf("expected settle to succeed but got %v", err) t.Fatalf("expected settle to succeed but got %v", err)
} }
if event.Preimage != nil { if resolution.Preimage != nil {
t.Fatalf("expected htlc to be canceled") t.Fatalf("expected htlc to be canceled")
} }
if event.AcceptHeight != testCurrentHeight { if resolution.AcceptHeight != testCurrentHeight {
t.Fatalf("expected acceptHeight %v, but got %v", t.Fatalf("expected acceptHeight %v, but got %v",
testCurrentHeight, event.AcceptHeight) testCurrentHeight, resolution.AcceptHeight)
}
if resolution.Outcome != ResultReplayToCanceled {
t.Fatalf("expected replay to canceled, got %v",
resolution.Outcome)
} }
} }
// TestUnknownInvoice tests that invoice registry returns an error when the // TestUnknownInvoice tests that invoice registry returns an error when the
// invoice is unknown. This is to guard against returning a cancel hodl event // invoice is unknown. This is to guard against returning a cancel htlc
// for forwarded htlcs. In the link, NotifyExitHopHtlc is only called if we are // resolution for forwarded htlcs. In the link, NotifyExitHopHtlc is only called
// the exit hop, but in htlcIncomingContestResolver it is called with forwarded // if we are the exit hop, but in htlcIncomingContestResolver it is called with
// htlc hashes as well. // forwarded htlc hashes as well.
func TestUnknownInvoice(t *testing.T) { func TestUnknownInvoice(t *testing.T) {
ctx := newTestContext(t) ctx := newTestContext(t)
defer ctx.cleanup() defer ctx.cleanup()
@ -550,17 +585,23 @@ func TestUnknownInvoice(t *testing.T) {
// succeed. // succeed.
hodlChan := make(chan interface{}) hodlChan := make(chan interface{})
amt := lnwire.MilliSatoshi(100000) amt := lnwire.MilliSatoshi(100000)
_, err := ctx.registry.NotifyExitHopHtlc( result, err := ctx.registry.NotifyExitHopHtlc(
testInvoicePaymentHash, amt, testHtlcExpiry, testCurrentHeight, testInvoicePaymentHash, amt, testHtlcExpiry, testCurrentHeight,
getCircuitKey(0), hodlChan, testPayload, getCircuitKey(0), hodlChan, testPayload,
) )
if err != channeldb.ErrInvoiceNotFound { if err != nil {
t.Fatal("expected invoice not found error") t.Fatal("unexpected error")
}
if result.Outcome != ResultInvoiceNotFound {
t.Fatalf("expected ResultInvoiceNotFound, got: %v",
result.Outcome)
} }
} }
// TestSettleMpp tests settling of an invoice with multiple partial payments. // TestMppPayment tests settling of an invoice with multiple partial payments.
func TestSettleMpp(t *testing.T) { // It covers the case where there is a mpp timeout before the whole invoice is
// paid and the case where the invoice is settled in time.
func TestMppPayment(t *testing.T) {
defer timeout()() defer timeout()()
ctx := newTestContext(t) ctx := newTestContext(t)
@ -578,7 +619,7 @@ func TestSettleMpp(t *testing.T) {
// Send htlc 1. // Send htlc 1.
hodlChan1 := make(chan interface{}, 1) hodlChan1 := make(chan interface{}, 1)
event, err := ctx.registry.NotifyExitHopHtlc( resolution, err := ctx.registry.NotifyExitHopHtlc(
testInvoicePaymentHash, testInvoice.Terms.Value/2, testInvoicePaymentHash, testInvoice.Terms.Value/2,
testHtlcExpiry, testHtlcExpiry,
testCurrentHeight, getCircuitKey(10), hodlChan1, mppPayload, testCurrentHeight, getCircuitKey(10), hodlChan1, mppPayload,
@ -586,21 +627,25 @@ func TestSettleMpp(t *testing.T) {
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
if event != nil { if resolution != nil {
t.Fatal("expected no direct resolution") t.Fatal("expected no direct resolution")
} }
// Simulate mpp timeout releasing htlc 1. // Simulate mpp timeout releasing htlc 1.
ctx.clock.SetTime(testTime.Add(30 * time.Second)) ctx.clock.SetTime(testTime.Add(30 * time.Second))
hodlEvent := (<-hodlChan1).(HodlEvent) htlcResolution := (<-hodlChan1).(HtlcResolution)
if hodlEvent.Preimage != nil { if htlcResolution.Preimage != nil {
t.Fatal("expected cancel event") t.Fatal("expected cancel resolution")
}
if htlcResolution.Outcome != ResultMppTimeout {
t.Fatalf("expected mpp timeout, got: %v",
htlcResolution.Outcome)
} }
// Send htlc 2. // Send htlc 2.
hodlChan2 := make(chan interface{}, 1) hodlChan2 := make(chan interface{}, 1)
event, err = ctx.registry.NotifyExitHopHtlc( resolution, err = ctx.registry.NotifyExitHopHtlc(
testInvoicePaymentHash, testInvoice.Terms.Value/2, testInvoicePaymentHash, testInvoice.Terms.Value/2,
testHtlcExpiry, testHtlcExpiry,
testCurrentHeight, getCircuitKey(11), hodlChan2, mppPayload, testCurrentHeight, getCircuitKey(11), hodlChan2, mppPayload,
@ -608,13 +653,13 @@ func TestSettleMpp(t *testing.T) {
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
if event != nil { if resolution != nil {
t.Fatal("expected no direct resolution") t.Fatal("expected no direct resolution")
} }
// Send htlc 3. // Send htlc 3.
hodlChan3 := make(chan interface{}, 1) hodlChan3 := make(chan interface{}, 1)
event, err = ctx.registry.NotifyExitHopHtlc( resolution, err = ctx.registry.NotifyExitHopHtlc(
testInvoicePaymentHash, testInvoice.Terms.Value/2, testInvoicePaymentHash, testInvoice.Terms.Value/2,
testHtlcExpiry, testHtlcExpiry,
testCurrentHeight, getCircuitKey(12), hodlChan3, mppPayload, testCurrentHeight, getCircuitKey(12), hodlChan3, mppPayload,
@ -622,12 +667,16 @@ func TestSettleMpp(t *testing.T) {
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
if event == nil { if resolution == nil {
t.Fatal("expected a settle event") t.Fatal("expected a settle resolution")
}
if resolution.Outcome != ResultSettled {
t.Fatalf("expected result settled, got: %v",
resolution.Outcome)
} }
// Check that settled amount is equal to the sum of values of the htlcs // Check that settled amount is equal to the sum of values of the htlcs
// 0 and 1. // 2 and 3.
inv, err := ctx.registry.LookupInvoice(testInvoicePaymentHash) inv, err := ctx.registry.LookupInvoice(testInvoicePaymentHash)
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)

@ -8,86 +8,141 @@ import (
"github.com/lightningnetwork/lnd/record" "github.com/lightningnetwork/lnd/record"
) )
// updateResult is the result of the invoice update call. // ResolutionResult provides metadata which about an invoice update which can
type updateResult uint8 // be used to take custom actions on resolution of the htlc. Only results which
// are actionable by the link are exported.
type ResolutionResult uint8
const ( const (
resultInvalid updateResult = iota resultInvalid ResolutionResult = iota
resultReplayToCanceled
resultReplayToAccepted // ResultReplayToCanceled is returned when we replay a canceled invoice.
resultReplayToSettled ResultReplayToCanceled
resultInvoiceAlreadyCanceled
resultAmountTooLow // ResultReplayToAccepted is returned when we replay an accepted invoice.
resultExpiryTooSoon ResultReplayToAccepted
resultDuplicateToAccepted
resultDuplicateToSettled // ResultReplayToSettled is returned when we replay a settled invoice.
resultAccepted ResultReplayToSettled
resultSettled
resultInvoiceNotOpen // ResultInvoiceAlreadyCanceled is returned when trying to pay an invoice
resultPartialAccepted // that is already canceled.
resultMppInProgress ResultInvoiceAlreadyCanceled
resultAddressMismatch
resultHtlcSetTotalMismatch // ResultAmountTooLow is returned when an invoice is underpaid.
resultHtlcSetTotalTooLow ResultAmountTooLow
resultHtlcSetOverpayment
// ResultExpiryTooSoon is returned when we do not accept an invoice payment
// because it expires too soon.
ResultExpiryTooSoon
// ResultDuplicateToAccepted is returned when we accept a duplicate htlc.
ResultDuplicateToAccepted
// ResultDuplicateToSettled is returned when we settle an invoice which has
// already been settled at least once.
ResultDuplicateToSettled
// ResultAccepted is returned when we accept a hodl invoice.
ResultAccepted
// ResultSettled is returned when we settle an invoice.
ResultSettled
// ResultCanceled is returned when we cancel an invoice and its associated
// htlcs.
ResultCanceled
// ResultInvoiceNotOpen is returned when a mpp invoice is not open.
ResultInvoiceNotOpen
// ResultPartialAccepted is returned when we have partially received
// payment.
ResultPartialAccepted
// ResultMppInProgress is returned when we are busy receiving a mpp payment.
ResultMppInProgress
// ResultMppTimeout is returned when an invoice paid with multiple partial
// payments times out before it is fully paid.
ResultMppTimeout
// ResultAddressMismatch is returned when the payment address for a mpp
// invoice does not match.
ResultAddressMismatch
// ResultHtlcSetTotalMismatch is returned when the amount paid by a htlc
// does not match its set total.
ResultHtlcSetTotalMismatch
// ResultHtlcSetTotalTooLow is returned when a mpp set total is too low for
// an invoice.
ResultHtlcSetTotalTooLow
// ResultHtlcSetOverpayment is returned when a mpp set is overpaid.
ResultHtlcSetOverpayment
// ResultInvoiceNotFound is returned when an attempt is made to pay an
// invoice that is unknown to us.
ResultInvoiceNotFound
) )
// String returns a human-readable representation of the invoice update result. // String returns a human-readable representation of the invoice update result.
func (u updateResult) String() string { func (u ResolutionResult) String() string {
switch u { switch u {
case resultInvalid: case resultInvalid:
return "invalid" return "invalid"
case resultReplayToCanceled: case ResultReplayToCanceled:
return "replayed htlc to canceled invoice" return "replayed htlc to canceled invoice"
case resultReplayToAccepted: case ResultReplayToAccepted:
return "replayed htlc to accepted invoice" return "replayed htlc to accepted invoice"
case resultReplayToSettled: case ResultReplayToSettled:
return "replayed htlc to settled invoice" return "replayed htlc to settled invoice"
case resultInvoiceAlreadyCanceled: case ResultInvoiceAlreadyCanceled:
return "invoice already canceled" return "invoice already canceled"
case resultAmountTooLow: case ResultAmountTooLow:
return "amount too low" return "amount too low"
case resultExpiryTooSoon: case ResultExpiryTooSoon:
return "expiry too soon" return "expiry too soon"
case resultDuplicateToAccepted: case ResultDuplicateToAccepted:
return "accepting duplicate payment to accepted invoice" return "accepting duplicate payment to accepted invoice"
case resultDuplicateToSettled: case ResultDuplicateToSettled:
return "accepting duplicate payment to settled invoice" return "accepting duplicate payment to settled invoice"
case resultAccepted: case ResultAccepted:
return "accepted" return "accepted"
case resultSettled: case ResultSettled:
return "settled" return "settled"
case resultInvoiceNotOpen: case ResultInvoiceNotOpen:
return "invoice no longer open" return "invoice no longer open"
case resultPartialAccepted: case ResultPartialAccepted:
return "partial payment accepted" return "partial payment accepted"
case resultMppInProgress: case ResultMppInProgress:
return "mpp reception in progress" return "mpp reception in progress"
case resultAddressMismatch: case ResultAddressMismatch:
return "payment address mismatch" return "payment address mismatch"
case resultHtlcSetTotalMismatch: case ResultHtlcSetTotalMismatch:
return "htlc total amt doesn't match set total" return "htlc total amt doesn't match set total"
case resultHtlcSetTotalTooLow: case ResultHtlcSetTotalTooLow:
return "set total too low for invoice" return "set total too low for invoice"
case resultHtlcSetOverpayment: case ResultHtlcSetOverpayment:
return "mpp is overpaying set total" return "mpp is overpaying set total"
default: default:
@ -110,20 +165,20 @@ type invoiceUpdateCtx struct {
// updateInvoice is a callback for DB.UpdateInvoice that contains the invoice // updateInvoice is a callback for DB.UpdateInvoice that contains the invoice
// settlement logic. // settlement logic.
func updateInvoice(ctx *invoiceUpdateCtx, inv *channeldb.Invoice) ( func updateInvoice(ctx *invoiceUpdateCtx, inv *channeldb.Invoice) (
*channeldb.InvoiceUpdateDesc, updateResult, error) { *channeldb.InvoiceUpdateDesc, ResolutionResult, error) {
// Don't update the invoice when this is a replayed htlc. // Don't update the invoice when this is a replayed htlc.
htlc, ok := inv.Htlcs[ctx.circuitKey] htlc, ok := inv.Htlcs[ctx.circuitKey]
if ok { if ok {
switch htlc.State { switch htlc.State {
case channeldb.HtlcStateCanceled: case channeldb.HtlcStateCanceled:
return nil, resultReplayToCanceled, nil return nil, ResultReplayToCanceled, nil
case channeldb.HtlcStateAccepted: case channeldb.HtlcStateAccepted:
return nil, resultReplayToAccepted, nil return nil, ResultReplayToAccepted, nil
case channeldb.HtlcStateSettled: case channeldb.HtlcStateSettled:
return nil, resultReplayToSettled, nil return nil, ResultReplayToSettled, nil
default: default:
return nil, 0, errors.New("unknown htlc state") return nil, 0, errors.New("unknown htlc state")
@ -140,7 +195,7 @@ func updateInvoice(ctx *invoiceUpdateCtx, inv *channeldb.Invoice) (
// updateMpp is a callback for DB.UpdateInvoice that contains the invoice // updateMpp is a callback for DB.UpdateInvoice that contains the invoice
// settlement logic for mpp payments. // settlement logic for mpp payments.
func updateMpp(ctx *invoiceUpdateCtx, inv *channeldb.Invoice) ( func updateMpp(ctx *invoiceUpdateCtx, inv *channeldb.Invoice) (
*channeldb.InvoiceUpdateDesc, updateResult, error) { *channeldb.InvoiceUpdateDesc, ResolutionResult, error) {
// Start building the accept descriptor. // Start building the accept descriptor.
acceptDesc := &channeldb.HtlcAcceptDesc{ acceptDesc := &channeldb.HtlcAcceptDesc{
@ -156,23 +211,23 @@ func updateMpp(ctx *invoiceUpdateCtx, inv *channeldb.Invoice) (
// Because non-mpp payments don't have a payment address, this is needed // Because non-mpp payments don't have a payment address, this is needed
// to thwart probing. // to thwart probing.
if inv.State != channeldb.ContractOpen { if inv.State != channeldb.ContractOpen {
return nil, resultInvoiceNotOpen, nil return nil, ResultInvoiceNotOpen, nil
} }
// Check the payment address that authorizes the payment. // Check the payment address that authorizes the payment.
if ctx.mpp.PaymentAddr() != inv.Terms.PaymentAddr { if ctx.mpp.PaymentAddr() != inv.Terms.PaymentAddr {
return nil, resultAddressMismatch, nil return nil, ResultAddressMismatch, nil
} }
// Don't accept zero-valued sets. // Don't accept zero-valued sets.
if ctx.mpp.TotalMsat() == 0 { if ctx.mpp.TotalMsat() == 0 {
return nil, resultHtlcSetTotalTooLow, nil return nil, ResultHtlcSetTotalTooLow, nil
} }
// Check that the total amt of the htlc set is high enough. In case this // Check that the total amt of the htlc set is high enough. In case this
// is a zero-valued invoice, it will always be enough. // is a zero-valued invoice, it will always be enough.
if ctx.mpp.TotalMsat() < inv.Terms.Value { if ctx.mpp.TotalMsat() < inv.Terms.Value {
return nil, resultHtlcSetTotalTooLow, nil return nil, ResultHtlcSetTotalTooLow, nil
} }
// Check whether total amt matches other htlcs in the set. // Check whether total amt matches other htlcs in the set.
@ -186,7 +241,7 @@ func updateMpp(ctx *invoiceUpdateCtx, inv *channeldb.Invoice) (
} }
if ctx.mpp.TotalMsat() != htlc.MppTotalAmt { if ctx.mpp.TotalMsat() != htlc.MppTotalAmt {
return nil, resultHtlcSetTotalMismatch, nil return nil, ResultHtlcSetTotalMismatch, nil
} }
newSetTotal += htlc.Amt newSetTotal += htlc.Amt
@ -197,16 +252,16 @@ func updateMpp(ctx *invoiceUpdateCtx, inv *channeldb.Invoice) (
// Make sure the communicated set total isn't overpaid. // Make sure the communicated set total isn't overpaid.
if newSetTotal > ctx.mpp.TotalMsat() { if newSetTotal > ctx.mpp.TotalMsat() {
return nil, resultHtlcSetOverpayment, nil return nil, ResultHtlcSetOverpayment, nil
} }
// The invoice is still open. Check the expiry. // The invoice is still open. Check the expiry.
if ctx.expiry < uint32(ctx.currentHeight+ctx.finalCltvRejectDelta) { if ctx.expiry < uint32(ctx.currentHeight+ctx.finalCltvRejectDelta) {
return nil, resultExpiryTooSoon, nil return nil, ResultExpiryTooSoon, nil
} }
if ctx.expiry < uint32(ctx.currentHeight+inv.Terms.FinalCltvDelta) { if ctx.expiry < uint32(ctx.currentHeight+inv.Terms.FinalCltvDelta) {
return nil, resultExpiryTooSoon, nil return nil, ResultExpiryTooSoon, nil
} }
// Record HTLC in the invoice database. // Record HTLC in the invoice database.
@ -221,7 +276,7 @@ func updateMpp(ctx *invoiceUpdateCtx, inv *channeldb.Invoice) (
// If the invoice cannot be settled yet, only record the htlc. // If the invoice cannot be settled yet, only record the htlc.
setComplete := newSetTotal == ctx.mpp.TotalMsat() setComplete := newSetTotal == ctx.mpp.TotalMsat()
if !setComplete { if !setComplete {
return &update, resultPartialAccepted, nil return &update, ResultPartialAccepted, nil
} }
// Check to see if we can settle or this is an hold invoice and // Check to see if we can settle or this is an hold invoice and
@ -231,7 +286,7 @@ func updateMpp(ctx *invoiceUpdateCtx, inv *channeldb.Invoice) (
update.State = &channeldb.InvoiceStateUpdateDesc{ update.State = &channeldb.InvoiceStateUpdateDesc{
NewState: channeldb.ContractAccepted, NewState: channeldb.ContractAccepted,
} }
return &update, resultAccepted, nil return &update, ResultAccepted, nil
} }
update.State = &channeldb.InvoiceStateUpdateDesc{ update.State = &channeldb.InvoiceStateUpdateDesc{
@ -239,18 +294,18 @@ func updateMpp(ctx *invoiceUpdateCtx, inv *channeldb.Invoice) (
Preimage: inv.Terms.PaymentPreimage, Preimage: inv.Terms.PaymentPreimage,
} }
return &update, resultSettled, nil return &update, ResultSettled, nil
} }
// updateLegacy is a callback for DB.UpdateInvoice that contains the invoice // updateLegacy is a callback for DB.UpdateInvoice that contains the invoice
// settlement logic for legacy payments. // settlement logic for legacy payments.
func updateLegacy(ctx *invoiceUpdateCtx, inv *channeldb.Invoice) ( func updateLegacy(ctx *invoiceUpdateCtx, inv *channeldb.Invoice) (
*channeldb.InvoiceUpdateDesc, updateResult, error) { *channeldb.InvoiceUpdateDesc, ResolutionResult, error) {
// If the invoice is already canceled, there is no further // If the invoice is already canceled, there is no further
// checking to do. // checking to do.
if inv.State == channeldb.ContractCanceled { if inv.State == channeldb.ContractCanceled {
return nil, resultInvoiceAlreadyCanceled, nil return nil, ResultInvoiceAlreadyCanceled, nil
} }
// If an invoice amount is specified, check that enough is paid. Also // If an invoice amount is specified, check that enough is paid. Also
@ -258,7 +313,7 @@ func updateLegacy(ctx *invoiceUpdateCtx, inv *channeldb.Invoice) (
// or accepted. In case this is a zero-valued invoice, it will always be // or accepted. In case this is a zero-valued invoice, it will always be
// enough. // enough.
if ctx.amtPaid < inv.Terms.Value { if ctx.amtPaid < inv.Terms.Value {
return nil, resultAmountTooLow, nil return nil, ResultAmountTooLow, nil
} }
// TODO(joostjager): Check invoice mpp required feature // TODO(joostjager): Check invoice mpp required feature
@ -271,17 +326,17 @@ func updateLegacy(ctx *invoiceUpdateCtx, inv *channeldb.Invoice) (
if htlc.State == channeldb.HtlcStateAccepted && if htlc.State == channeldb.HtlcStateAccepted &&
htlc.MppTotalAmt > 0 { htlc.MppTotalAmt > 0 {
return nil, resultMppInProgress, nil return nil, ResultMppInProgress, nil
} }
} }
// The invoice is still open. Check the expiry. // The invoice is still open. Check the expiry.
if ctx.expiry < uint32(ctx.currentHeight+ctx.finalCltvRejectDelta) { if ctx.expiry < uint32(ctx.currentHeight+ctx.finalCltvRejectDelta) {
return nil, resultExpiryTooSoon, nil return nil, ResultExpiryTooSoon, nil
} }
if ctx.expiry < uint32(ctx.currentHeight+inv.Terms.FinalCltvDelta) { if ctx.expiry < uint32(ctx.currentHeight+inv.Terms.FinalCltvDelta) {
return nil, resultExpiryTooSoon, nil return nil, ResultExpiryTooSoon, nil
} }
// Record HTLC in the invoice database. // Record HTLC in the invoice database.
@ -302,10 +357,10 @@ func updateLegacy(ctx *invoiceUpdateCtx, inv *channeldb.Invoice) (
// We do accept or settle the HTLC. // We do accept or settle the HTLC.
switch inv.State { switch inv.State {
case channeldb.ContractAccepted: case channeldb.ContractAccepted:
return &update, resultDuplicateToAccepted, nil return &update, ResultDuplicateToAccepted, nil
case channeldb.ContractSettled: case channeldb.ContractSettled:
return &update, resultDuplicateToSettled, nil return &update, ResultDuplicateToSettled, nil
} }
// Check to see if we can settle or this is an hold invoice and we need // Check to see if we can settle or this is an hold invoice and we need
@ -315,7 +370,7 @@ func updateLegacy(ctx *invoiceUpdateCtx, inv *channeldb.Invoice) (
update.State = &channeldb.InvoiceStateUpdateDesc{ update.State = &channeldb.InvoiceStateUpdateDesc{
NewState: channeldb.ContractAccepted, NewState: channeldb.ContractAccepted,
} }
return &update, resultAccepted, nil return &update, ResultAccepted, nil
} }
update.State = &channeldb.InvoiceStateUpdateDesc{ update.State = &channeldb.InvoiceStateUpdateDesc{
@ -323,5 +378,5 @@ func updateLegacy(ctx *invoiceUpdateCtx, inv *channeldb.Invoice) (
Preimage: inv.Terms.PaymentPreimage, Preimage: inv.Terms.PaymentPreimage,
} }
return &update, resultSettled, nil return &update, ResultSettled, nil
} }

@ -109,6 +109,7 @@ const (
Failure_PERMANENT_NODE_FAILURE Failure_FailureCode = 20 Failure_PERMANENT_NODE_FAILURE Failure_FailureCode = 20
Failure_PERMANENT_CHANNEL_FAILURE Failure_FailureCode = 21 Failure_PERMANENT_CHANNEL_FAILURE Failure_FailureCode = 21
Failure_EXPIRY_TOO_FAR Failure_FailureCode = 22 Failure_EXPIRY_TOO_FAR Failure_FailureCode = 22
Failure_MPP_TIMEOUT Failure_FailureCode = 23
//* //*
//The error source is known, but the failure itself couldn't be decoded. //The error source is known, but the failure itself couldn't be decoded.
Failure_UNKNOWN_FAILURE Failure_FailureCode = 998 Failure_UNKNOWN_FAILURE Failure_FailureCode = 998
@ -142,6 +143,7 @@ var Failure_FailureCode_name = map[int32]string{
20: "PERMANENT_NODE_FAILURE", 20: "PERMANENT_NODE_FAILURE",
21: "PERMANENT_CHANNEL_FAILURE", 21: "PERMANENT_CHANNEL_FAILURE",
22: "EXPIRY_TOO_FAR", 22: "EXPIRY_TOO_FAR",
23: "MPP_TIMEOUT",
998: "UNKNOWN_FAILURE", 998: "UNKNOWN_FAILURE",
999: "UNREADABLE_FAILURE", 999: "UNREADABLE_FAILURE",
} }
@ -170,6 +172,7 @@ var Failure_FailureCode_value = map[string]int32{
"PERMANENT_NODE_FAILURE": 20, "PERMANENT_NODE_FAILURE": 20,
"PERMANENT_CHANNEL_FAILURE": 21, "PERMANENT_CHANNEL_FAILURE": 21,
"EXPIRY_TOO_FAR": 22, "EXPIRY_TOO_FAR": 22,
"MPP_TIMEOUT": 23,
"UNKNOWN_FAILURE": 998, "UNKNOWN_FAILURE": 998,
"UNREADABLE_FAILURE": 999, "UNREADABLE_FAILURE": 999,
} }
@ -1526,138 +1529,139 @@ func init() {
func init() { proto.RegisterFile("routerrpc/router.proto", fileDescriptor_7a0613f69d37b0a5) } func init() { proto.RegisterFile("routerrpc/router.proto", fileDescriptor_7a0613f69d37b0a5) }
var fileDescriptor_7a0613f69d37b0a5 = []byte{ var fileDescriptor_7a0613f69d37b0a5 = []byte{
// 2093 bytes of a gzipped FileDescriptorProto // 2102 bytes of a gzipped FileDescriptorProto
0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x8c, 0x58, 0x4f, 0x73, 0xdb, 0xb8, 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x8c, 0x58, 0xcb, 0x73, 0xdb, 0xc6,
0x15, 0x5f, 0x5a, 0xff, 0x9f, 0xfe, 0xd1, 0xb0, 0xe3, 0x30, 0x72, 0xbc, 0xf1, 0x32, 0x69, 0x56, 0x19, 0x0f, 0xc4, 0xf7, 0xc7, 0x17, 0xb4, 0x92, 0x65, 0x98, 0xb2, 0x62, 0x05, 0x76, 0x1d, 0x8e,
0x93, 0xc9, 0xda, 0xa9, 0xdb, 0xcd, 0x64, 0x7a, 0x68, 0x47, 0x96, 0xa8, 0x35, 0x1d, 0x89, 0x72, 0xc7, 0x91, 0x5c, 0xb5, 0xf1, 0x78, 0x7a, 0x68, 0x87, 0x22, 0xc1, 0x08, 0x32, 0x09, 0xca, 0x4b,
0x20, 0x29, 0xbb, 0xe9, 0x1e, 0x30, 0xb4, 0x04, 0x5b, 0x1c, 0x53, 0xa4, 0x96, 0x84, 0xb2, 0xf1, 0xd2, 0x89, 0x9b, 0xc3, 0x0e, 0x44, 0xae, 0x44, 0x8c, 0x40, 0x80, 0x01, 0x96, 0x8e, 0xf5, 0x0f,
0x77, 0xe8, 0xf7, 0x68, 0x0f, 0x6d, 0x2f, 0xfd, 0x4e, 0xed, 0xbd, 0x33, 0x3d, 0xf4, 0xd6, 0x01, 0xf4, 0xd4, 0xff, 0xa3, 0xbd, 0xb4, 0x97, 0x9e, 0xfb, 0xef, 0xb4, 0xf7, 0xde, 0x7a, 0xeb, 0xec,
0x40, 0x4a, 0x94, 0x2c, 0x67, 0x7b, 0xb2, 0xf0, 0x7b, 0x3f, 0xbc, 0x07, 0xe2, 0xe1, 0xfd, 0xf0, 0x2e, 0x40, 0x82, 0x14, 0xe5, 0xf4, 0x44, 0xec, 0xef, 0x7b, 0xed, 0xee, 0xb7, 0xdf, 0x8b, 0xb0,
0x60, 0xd8, 0x0b, 0xfc, 0x39, 0xa3, 0x41, 0x30, 0x1b, 0x1d, 0xcb, 0x5f, 0x47, 0xb3, 0xc0, 0x67, 0x17, 0xf8, 0x73, 0x46, 0x83, 0x60, 0x36, 0x3a, 0x96, 0x5f, 0x47, 0xb3, 0xc0, 0x67, 0x3e, 0x2a,
0x3e, 0x2a, 0x2c, 0xf0, 0x5a, 0x21, 0x98, 0x8d, 0x24, 0xaa, 0xff, 0x37, 0x03, 0xa8, 0x4f, 0xbd, 0x2c, 0xf0, 0x5a, 0x21, 0x98, 0x8d, 0x24, 0xaa, 0xff, 0x37, 0x03, 0xa8, 0x4f, 0xbd, 0xf1, 0x85,
0xf1, 0x85, 0x7d, 0x3b, 0xa5, 0x1e, 0xc3, 0xf4, 0xa7, 0x39, 0x0d, 0x19, 0x42, 0x90, 0x1e, 0xd3, 0x7d, 0x3b, 0xa5, 0x1e, 0xc3, 0xf4, 0xa7, 0x39, 0x0d, 0x19, 0x42, 0x90, 0x1e, 0xd3, 0x90, 0x69,
0x90, 0x69, 0xca, 0xa1, 0x52, 0x2f, 0x61, 0xf1, 0x1b, 0xa9, 0x90, 0xb2, 0xa7, 0x4c, 0xdb, 0x3a, 0xca, 0xa1, 0x52, 0x2f, 0x61, 0xf1, 0x8d, 0x54, 0x48, 0xd9, 0x53, 0xa6, 0x6d, 0x1d, 0x2a, 0xf5,
0x54, 0xea, 0x29, 0xcc, 0x7f, 0xa2, 0x47, 0x90, 0xb7, 0xa7, 0x8c, 0x4c, 0x43, 0x9b, 0x69, 0x25, 0x14, 0xe6, 0x9f, 0xe8, 0x11, 0xe4, 0xed, 0x29, 0x23, 0xd3, 0xd0, 0x66, 0x5a, 0x49, 0xc0, 0x39,
0x01, 0xe7, 0xec, 0x29, 0xeb, 0x86, 0x36, 0x43, 0x5f, 0x41, 0x69, 0x26, 0x5d, 0x92, 0x89, 0x1d, 0x7b, 0xca, 0xba, 0xa1, 0xcd, 0xd0, 0x57, 0x50, 0x9a, 0x49, 0x95, 0x64, 0x62, 0x87, 0x13, 0x2d,
0x4e, 0xb4, 0x94, 0x70, 0x54, 0x8c, 0xb0, 0x33, 0x3b, 0x9c, 0xa0, 0x3a, 0xa8, 0x57, 0x8e, 0x67, 0x25, 0x14, 0x15, 0x23, 0xec, 0xcc, 0x0e, 0x27, 0xa8, 0x0e, 0xea, 0x95, 0xe3, 0xd9, 0x2e, 0x19,
0xbb, 0x64, 0xe4, 0xb2, 0x8f, 0x64, 0x4c, 0x5d, 0x66, 0x6b, 0xe9, 0x43, 0xa5, 0x9e, 0xc1, 0x15, 0xb9, 0xec, 0x23, 0x19, 0x53, 0x97, 0xd9, 0x5a, 0xfa, 0x50, 0xa9, 0x67, 0x70, 0x45, 0xe0, 0x4d,
0x81, 0x37, 0x5d, 0xf6, 0xb1, 0xc5, 0x51, 0xf4, 0x35, 0x54, 0x63, 0x67, 0x81, 0x5c, 0xa0, 0x96, 0x97, 0x7d, 0x6c, 0x71, 0x14, 0x7d, 0x0d, 0xd5, 0x58, 0x59, 0x20, 0x37, 0xa8, 0x65, 0x0e, 0x95,
0x39, 0x54, 0xea, 0x05, 0x5c, 0x99, 0xad, 0x2e, 0xfb, 0x6b, 0xa8, 0x32, 0x67, 0x4a, 0xfd, 0x39, 0x7a, 0x01, 0x57, 0x66, 0xab, 0xdb, 0xfe, 0x1a, 0xaa, 0xcc, 0x99, 0x52, 0x7f, 0xce, 0x48, 0x48,
0x23, 0x21, 0x1d, 0xf9, 0xde, 0x38, 0xd4, 0xb2, 0xd2, 0x63, 0x04, 0xf7, 0x25, 0x8a, 0x74, 0x28, 0x47, 0xbe, 0x37, 0x0e, 0xb5, 0xac, 0xd4, 0x18, 0xc1, 0x7d, 0x89, 0x22, 0x1d, 0xca, 0x57, 0x94,
0x5f, 0x51, 0x4a, 0x5c, 0x67, 0xea, 0x30, 0xc2, 0x97, 0x9f, 0x13, 0xcb, 0x2f, 0x5e, 0x51, 0xda, 0x12, 0xd7, 0x99, 0x3a, 0x8c, 0xf0, 0xed, 0xe7, 0xc4, 0xf6, 0x8b, 0x57, 0x94, 0x76, 0x38, 0xd6,
0xe1, 0x58, 0xdf, 0x66, 0xe8, 0x19, 0x54, 0x96, 0x1c, 0xf1, 0x8d, 0x65, 0x41, 0x2a, 0xc5, 0x24, 0xb7, 0x19, 0x7a, 0x06, 0x95, 0x25, 0x8f, 0x38, 0x63, 0x59, 0x30, 0x95, 0x62, 0x26, 0x71, 0xd0,
0xf1, 0xa1, 0x2f, 0x41, 0xf5, 0xe7, 0xec, 0xda, 0x77, 0xbc, 0x6b, 0x32, 0x9a, 0xd8, 0x1e, 0x71, 0x97, 0xa0, 0xfa, 0x73, 0x76, 0xed, 0x3b, 0xde, 0x35, 0x19, 0x4d, 0x6c, 0x8f, 0x38, 0x63, 0x2d,
0xc6, 0x5a, 0xfe, 0x50, 0xa9, 0xa7, 0x4f, 0xb7, 0x5e, 0x29, 0xb8, 0x12, 0xdb, 0x9a, 0x13, 0xdb, 0x7f, 0xa8, 0xd4, 0xd3, 0xa7, 0x5b, 0xaf, 0x14, 0x5c, 0x89, 0x69, 0xcd, 0x89, 0xed, 0x99, 0x63,
0x33, 0xc7, 0xe8, 0x39, 0x54, 0x5d, 0x3b, 0x64, 0x64, 0xe2, 0xcf, 0xc8, 0x6c, 0x7e, 0x79, 0x43, 0xf4, 0x1c, 0xaa, 0xae, 0x1d, 0x32, 0x32, 0xf1, 0x67, 0x64, 0x36, 0xbf, 0xbc, 0xa1, 0xb7, 0x5a,
0x6f, 0xb5, 0x8a, 0xd8, 0x99, 0x32, 0x87, 0xcf, 0xfc, 0xd9, 0x85, 0x00, 0xd1, 0x01, 0x80, 0xd8, 0x45, 0xdc, 0x4c, 0x99, 0xc3, 0x67, 0xfe, 0xec, 0x42, 0x80, 0xe8, 0x00, 0x40, 0xdc, 0x8a, 0x30,
0x15, 0x11, 0x5c, 0x2b, 0x88, 0x6f, 0x28, 0x70, 0x44, 0x04, 0x46, 0x27, 0x50, 0x14, 0xd9, 0x24, 0xae, 0x15, 0xc4, 0x19, 0x0a, 0x1c, 0x11, 0x86, 0xd1, 0x09, 0x14, 0x85, 0x37, 0xc9, 0xc4, 0xf1,
0x13, 0xc7, 0x63, 0xa1, 0x06, 0x87, 0xa9, 0x7a, 0xf1, 0x44, 0x3d, 0x72, 0x3d, 0x9e, 0x58, 0xcc, 0x58, 0xa8, 0xc1, 0x61, 0xaa, 0x5e, 0x3c, 0x51, 0x8f, 0x5c, 0x8f, 0x3b, 0x16, 0x73, 0xca, 0x99,
0x2d, 0x67, 0x8e, 0xc7, 0x70, 0x92, 0x84, 0xc6, 0xb0, 0xc3, 0xd3, 0x48, 0x46, 0xf3, 0x90, 0xf9, 0xe3, 0x31, 0x9c, 0x64, 0x42, 0x63, 0xd8, 0xe1, 0x6e, 0x24, 0xa3, 0x79, 0xc8, 0xfc, 0x29, 0x09,
0x53, 0x12, 0xd0, 0x91, 0x1f, 0x8c, 0x43, 0xad, 0x28, 0xe6, 0xfe, 0xf6, 0x68, 0x71, 0x3a, 0x8e, 0xe8, 0xc8, 0x0f, 0xc6, 0xa1, 0x56, 0x14, 0xb2, 0xbf, 0x3d, 0x5a, 0xbc, 0x8e, 0xa3, 0xbb, 0xcf,
0xee, 0x1e, 0x87, 0xa3, 0x16, 0x0d, 0x59, 0x53, 0xcc, 0xc3, 0x72, 0x9a, 0xe1, 0xb1, 0xe0, 0x16, 0xe1, 0xa8, 0x45, 0x43, 0xd6, 0x14, 0x72, 0x58, 0x8a, 0x19, 0x1e, 0x0b, 0x6e, 0xf1, 0xf6, 0x78,
0x6f, 0x8f, 0xd7, 0x71, 0xf4, 0x12, 0x90, 0xed, 0xba, 0xfe, 0xcf, 0x24, 0xa4, 0xee, 0x15, 0x89, 0x1d, 0x47, 0x2f, 0x01, 0xd9, 0xae, 0xeb, 0xff, 0x4c, 0x42, 0xea, 0x5e, 0x91, 0xc8, 0x3d, 0x5a,
0xd2, 0xa3, 0x55, 0x0f, 0x95, 0x7a, 0x1e, 0xab, 0xc2, 0xd2, 0xa7, 0xee, 0x55, 0xe4, 0x1e, 0xbd, 0xf5, 0x50, 0xa9, 0xe7, 0xb1, 0x2a, 0x28, 0x7d, 0xea, 0x5e, 0x45, 0xea, 0xd1, 0x6b, 0x28, 0x8b,
0x86, 0xb2, 0x58, 0xd3, 0x15, 0xb5, 0xd9, 0x3c, 0xa0, 0xa1, 0xa6, 0x1e, 0xa6, 0xea, 0x95, 0x93, 0x3d, 0x5d, 0x51, 0x9b, 0xcd, 0x03, 0x1a, 0x6a, 0xea, 0x61, 0xaa, 0x5e, 0x39, 0xd9, 0x8e, 0x4e,
0xed, 0xe8, 0x4b, 0xda, 0x12, 0x3e, 0x75, 0x18, 0x2e, 0x71, 0x5e, 0x34, 0x0e, 0x6b, 0x2d, 0xd8, 0xd2, 0x96, 0xf0, 0xa9, 0xc3, 0x70, 0x89, 0xf3, 0x45, 0xeb, 0xb0, 0xd6, 0x82, 0xbd, 0xcd, 0x5b,
0xdb, 0xbc, 0x24, 0x7e, 0x48, 0xf9, 0xa6, 0xf2, 0x73, 0x9b, 0xc6, 0xfc, 0x27, 0xda, 0x85, 0xcc, 0xe2, 0x8f, 0x94, 0x5f, 0x2a, 0x7f, 0xb7, 0x69, 0xcc, 0x3f, 0xd1, 0x2e, 0x64, 0x3e, 0xda, 0xee,
0x47, 0xdb, 0x9d, 0x53, 0x71, 0x70, 0x4b, 0x58, 0x0e, 0x7e, 0xb7, 0xf5, 0x46, 0xd1, 0xdf, 0xc0, 0x9c, 0x8a, 0x87, 0x5b, 0xc2, 0x72, 0xf1, 0xbb, 0xad, 0x37, 0x8a, 0xfe, 0x06, 0x76, 0x06, 0x81,
0xce, 0x20, 0xb0, 0x47, 0x37, 0x6b, 0x67, 0x7f, 0xfd, 0xe8, 0x2a, 0x77, 0x8e, 0xae, 0xfe, 0x17, 0x3d, 0xba, 0x59, 0x7b, 0xfb, 0xeb, 0x4f, 0x57, 0xb9, 0xf3, 0x74, 0xf5, 0xbf, 0x2a, 0x50, 0x8e,
0x05, 0xca, 0xd1, 0xac, 0x3e, 0xb3, 0xd9, 0x3c, 0x44, 0xdf, 0x40, 0x26, 0x64, 0x36, 0xa3, 0x82, 0xa4, 0xfa, 0xcc, 0x66, 0xf3, 0x10, 0x7d, 0x03, 0x99, 0x90, 0xd9, 0x8c, 0x0a, 0xee, 0xca, 0xc9,
0x5d, 0x39, 0x79, 0x98, 0xd8, 0xcf, 0x04, 0x91, 0x62, 0xc9, 0x42, 0x35, 0xc8, 0xcf, 0x02, 0xea, 0xc3, 0xc4, 0x7d, 0x26, 0x18, 0x29, 0x96, 0x5c, 0xa8, 0x06, 0xf9, 0x59, 0x40, 0x9d, 0xa9, 0x7d,
0x4c, 0xed, 0xeb, 0x78, 0x5d, 0x8b, 0x31, 0xd2, 0x21, 0x23, 0x26, 0x8b, 0x9a, 0x29, 0x9e, 0x94, 0x1d, 0xef, 0x6b, 0xb1, 0x46, 0x3a, 0x64, 0x84, 0xb0, 0x88, 0x99, 0xe2, 0x49, 0x29, 0xe9, 0x56,
0x92, 0x69, 0xc5, 0xd2, 0x84, 0xea, 0x90, 0x99, 0x30, 0x77, 0x14, 0x6a, 0x69, 0x91, 0x3e, 0x14, 0x2c, 0x49, 0xa8, 0x0e, 0x99, 0x09, 0x73, 0x47, 0xa1, 0x96, 0x16, 0xee, 0x43, 0x11, 0xcf, 0xd9,
0x71, 0xce, 0x06, 0x9d, 0x66, 0x83, 0x31, 0x3a, 0x9d, 0x31, 0x2c, 0x09, 0xfa, 0xef, 0xa1, 0x2a, 0xa0, 0xd3, 0x6c, 0x30, 0x46, 0xa7, 0x33, 0x86, 0x25, 0x83, 0xfe, 0x7b, 0xa8, 0x0a, 0xc9, 0x36,
0x66, 0xb6, 0x29, 0xfd, 0x5c, 0x71, 0x3f, 0x04, 0x5e, 0xba, 0xa2, 0x14, 0x64, 0x81, 0x67, 0xed, 0xa5, 0x9f, 0x0b, 0xee, 0x87, 0xc0, 0x43, 0x57, 0x84, 0x82, 0x0c, 0xf0, 0xac, 0x3d, 0xe5, 0x51,
0x29, 0xaf, 0x02, 0x7d, 0x0c, 0xea, 0x72, 0x7e, 0x38, 0xf3, 0xbd, 0x90, 0x47, 0x57, 0xf9, 0x32, 0xa0, 0x8f, 0x41, 0x5d, 0xca, 0x87, 0x33, 0xdf, 0x0b, 0xb9, 0x75, 0x95, 0x6f, 0x83, 0x3f, 0x79,
0xf8, 0x91, 0xe7, 0x15, 0x22, 0x6a, 0x43, 0x11, 0xb3, 0x2a, 0x11, 0xde, 0xa6, 0x54, 0x54, 0xc7, 0x1e, 0x21, 0x22, 0x36, 0x14, 0x21, 0x55, 0x89, 0xf0, 0x36, 0xa5, 0x22, 0x3a, 0x9e, 0xcb, 0x80,
0x73, 0x59, 0x90, 0xc4, 0xf5, 0x47, 0x37, 0xbc, 0xc4, 0xed, 0xdb, 0xc8, 0x7d, 0x99, 0xc3, 0x1d, 0x24, 0xae, 0x3f, 0xba, 0xe1, 0x21, 0x6e, 0xdf, 0x46, 0xea, 0xcb, 0x1c, 0xee, 0xf8, 0xa3, 0x9b,
0x7f, 0x74, 0xd3, 0xe2, 0xa0, 0xfe, 0xa3, 0x54, 0xa1, 0x81, 0x2f, 0xbf, 0xf2, 0xff, 0xce, 0xc4, 0x16, 0x07, 0xf5, 0x1f, 0x65, 0x16, 0x1a, 0xf8, 0xf2, 0x94, 0xff, 0xb7, 0x27, 0x96, 0x97, 0xb5,
0x72, 0xb3, 0xb6, 0xee, 0xdd, 0x2c, 0x9d, 0xc0, 0xce, 0x8a, 0xf3, 0xe8, 0x2b, 0x92, 0x39, 0x50, 0x75, 0xef, 0x65, 0xe9, 0x04, 0x76, 0x56, 0x94, 0x47, 0xa7, 0x48, 0xfa, 0x40, 0x59, 0xf3, 0xc1,
0xd6, 0x72, 0xf0, 0x12, 0x72, 0x57, 0xb6, 0xe3, 0xce, 0x83, 0xd8, 0x31, 0x4a, 0x24, 0xb4, 0x2d, 0x4b, 0xc8, 0x5d, 0xd9, 0x8e, 0x3b, 0x0f, 0x62, 0xc5, 0x28, 0xe1, 0xd0, 0xb6, 0xa4, 0xe0, 0x98,
0x2d, 0x38, 0xa6, 0xe8, 0xff, 0xc9, 0x41, 0x2e, 0x02, 0xd1, 0x09, 0xa4, 0x47, 0xfe, 0x38, 0x3e, 0x45, 0xff, 0x53, 0x1e, 0x72, 0x11, 0x88, 0x4e, 0x20, 0x3d, 0xf2, 0xc7, 0xf1, 0x3b, 0xf8, 0xf2,
0x07, 0x5f, 0xde, 0x9d, 0x16, 0xff, 0x6d, 0xfa, 0x63, 0x8a, 0x05, 0x17, 0xfd, 0x01, 0x2a, 0x5c, 0xae, 0x58, 0xfc, 0xdb, 0xf4, 0xc7, 0x14, 0x0b, 0x5e, 0xf4, 0x07, 0xa8, 0xf0, 0xd4, 0xe1, 0x51,
0x3a, 0x3c, 0xea, 0x92, 0xf9, 0x6c, 0x6c, 0x2f, 0x52, 0xaf, 0x25, 0x66, 0x37, 0x25, 0x61, 0x28, 0x97, 0xcc, 0x67, 0x63, 0x7b, 0xe1, 0x7a, 0x2d, 0x21, 0xdd, 0x94, 0x0c, 0x43, 0x41, 0xc7, 0xe5,
0xec, 0xb8, 0x3c, 0x4a, 0x0e, 0xd1, 0x3e, 0x14, 0x78, 0xb6, 0x65, 0x26, 0xd2, 0xe2, 0xec, 0xe7, 0x51, 0x72, 0x89, 0xf6, 0xa1, 0xc0, 0xbd, 0x2d, 0x3d, 0x91, 0x16, 0x6f, 0x3f, 0xcf, 0x01, 0xe1,
0x39, 0x20, 0x72, 0xa0, 0x43, 0xd9, 0xf7, 0x1c, 0xdf, 0x23, 0xe1, 0xc4, 0x26, 0x27, 0xdf, 0xbe, 0x03, 0x1d, 0xca, 0xbe, 0xe7, 0xf8, 0x1e, 0x09, 0x27, 0x36, 0x39, 0xf9, 0xf6, 0xb5, 0xc8, 0x9d,
0x16, 0xda, 0x59, 0xc2, 0x45, 0x01, 0xf6, 0x27, 0xf6, 0xc9, 0xb7, 0xaf, 0xd1, 0x13, 0x28, 0x0a, 0x25, 0x5c, 0x14, 0x60, 0x7f, 0x62, 0x9f, 0x7c, 0xfb, 0x1a, 0x3d, 0x81, 0xa2, 0xc8, 0x37, 0xf4,
0xbd, 0xa1, 0x9f, 0x66, 0x4e, 0x70, 0x2b, 0x44, 0xb3, 0x8c, 0x85, 0x04, 0x19, 0x02, 0xe1, 0x55, 0xd3, 0xcc, 0x09, 0x6e, 0x45, 0xd2, 0x2c, 0x63, 0x91, 0x82, 0x0c, 0x81, 0xf0, 0x28, 0xba, 0x72,
0x74, 0xe5, 0xda, 0xd7, 0xa1, 0x10, 0xca, 0x32, 0x96, 0x03, 0xf4, 0x0a, 0x76, 0xa3, 0x3d, 0x20, 0xed, 0xeb, 0x50, 0x24, 0xca, 0x32, 0x96, 0x0b, 0xf4, 0x0a, 0x76, 0xa3, 0x3b, 0x20, 0xa1, 0x3f,
0xa1, 0x3f, 0x0f, 0x46, 0x94, 0x38, 0xde, 0x98, 0x7e, 0x12, 0x02, 0x58, 0xc6, 0x28, 0xb2, 0xf5, 0x0f, 0x46, 0x94, 0x38, 0xde, 0x98, 0x7e, 0x12, 0x09, 0xb0, 0x8c, 0x51, 0x44, 0xeb, 0x0b, 0x92,
0x85, 0xc9, 0xe4, 0x16, 0xb4, 0x07, 0xd9, 0x09, 0x75, 0xae, 0x27, 0x52, 0xd4, 0xca, 0x38, 0x1a, 0xc9, 0x29, 0x68, 0x0f, 0xb2, 0x13, 0xea, 0x5c, 0x4f, 0x64, 0x52, 0x2b, 0xe3, 0x68, 0xa5, 0xff,
0xe9, 0x7f, 0xcd, 0x40, 0x31, 0xb1, 0x31, 0xa8, 0x04, 0x79, 0x6c, 0xf4, 0x0d, 0xfc, 0xde, 0x68, 0x33, 0x03, 0xc5, 0xc4, 0xc5, 0xa0, 0x12, 0xe4, 0xb1, 0xd1, 0x37, 0xf0, 0x7b, 0xa3, 0xa5, 0x7e,
0xa9, 0x5f, 0xa0, 0x3a, 0x3c, 0x33, 0xad, 0x66, 0x0f, 0x63, 0xa3, 0x39, 0x20, 0x3d, 0x4c, 0x86, 0x81, 0xea, 0xf0, 0xcc, 0xb4, 0x9a, 0x3d, 0x8c, 0x8d, 0xe6, 0x80, 0xf4, 0x30, 0x19, 0x5a, 0x6f,
0xd6, 0x5b, 0xab, 0xf7, 0xbd, 0x45, 0x2e, 0x1a, 0x1f, 0xba, 0x86, 0x35, 0x20, 0x2d, 0x63, 0xd0, 0xad, 0xde, 0xf7, 0x16, 0xb9, 0x68, 0x7c, 0xe8, 0x1a, 0xd6, 0x80, 0xb4, 0x8c, 0x41, 0xc3, 0xec,
0x30, 0x3b, 0x7d, 0x55, 0x41, 0x8f, 0x41, 0x5b, 0x32, 0x63, 0x73, 0xa3, 0xdb, 0x1b, 0x5a, 0x03, 0xf4, 0x55, 0x05, 0x3d, 0x06, 0x6d, 0xc9, 0x19, 0x93, 0x1b, 0xdd, 0xde, 0xd0, 0x1a, 0xa8, 0x5b,
0x75, 0x0b, 0x3d, 0x81, 0xfd, 0xb6, 0x69, 0x35, 0x3a, 0x64, 0xc9, 0x69, 0x76, 0x06, 0xef, 0x89, 0xe8, 0x09, 0xec, 0xb7, 0x4d, 0xab, 0xd1, 0x21, 0x4b, 0x9e, 0x66, 0x67, 0xf0, 0x9e, 0x18, 0x3f,
0xf1, 0xc3, 0x85, 0x89, 0x3f, 0xa8, 0xa9, 0x4d, 0x04, 0x5e, 0x53, 0xb1, 0x87, 0x34, 0x7a, 0x04, 0x5c, 0x98, 0xf8, 0x83, 0x9a, 0xda, 0xc4, 0xc0, 0x63, 0x2a, 0xd6, 0x90, 0x46, 0x8f, 0xe0, 0x81,
0x0f, 0x24, 0x41, 0x4e, 0x21, 0x83, 0x5e, 0x8f, 0xf4, 0x7b, 0x3d, 0x4b, 0xcd, 0xa0, 0x6d, 0x28, 0x64, 0x90, 0x22, 0x64, 0xd0, 0xeb, 0x91, 0x7e, 0xaf, 0x67, 0xa9, 0x19, 0xb4, 0x0d, 0x65, 0xd3,
0x9b, 0xd6, 0xfb, 0x46, 0xc7, 0x6c, 0x11, 0x6c, 0x34, 0x3a, 0x5d, 0x35, 0x8b, 0x76, 0xa0, 0xba, 0x7a, 0xdf, 0xe8, 0x98, 0x2d, 0x82, 0x8d, 0x46, 0xa7, 0xab, 0x66, 0xd1, 0x0e, 0x54, 0xd7, 0xf9,
0xce, 0xcb, 0x71, 0x17, 0x31, 0xaf, 0x67, 0x99, 0x3d, 0x8b, 0xbc, 0x37, 0x70, 0xdf, 0xec, 0x59, 0x72, 0x5c, 0x45, 0xcc, 0xd7, 0xb3, 0xcc, 0x9e, 0x45, 0xde, 0x1b, 0xb8, 0x6f, 0xf6, 0x2c, 0x35,
0x6a, 0x1e, 0xed, 0x01, 0x5a, 0x35, 0x9d, 0x75, 0x1b, 0x4d, 0xb5, 0x80, 0x1e, 0xc0, 0xf6, 0x2a, 0x8f, 0xf6, 0x00, 0xad, 0x92, 0xce, 0xba, 0x8d, 0xa6, 0x5a, 0x40, 0x0f, 0x60, 0x7b, 0x15, 0x7f,
0xfe, 0xd6, 0xf8, 0xa0, 0x02, 0xd2, 0x60, 0x57, 0x2e, 0x8c, 0x9c, 0x1a, 0x9d, 0xde, 0xf7, 0xa4, 0x6b, 0x7c, 0x50, 0x01, 0x69, 0xb0, 0x2b, 0x37, 0x46, 0x4e, 0x8d, 0x4e, 0xef, 0x7b, 0xd2, 0x35,
0x6b, 0x5a, 0x66, 0x77, 0xd8, 0x55, 0x8b, 0x68, 0x17, 0xd4, 0xb6, 0x61, 0x10, 0xd3, 0xea, 0x0f, 0x2d, 0xb3, 0x3b, 0xec, 0xaa, 0x45, 0xb4, 0x0b, 0x6a, 0xdb, 0x30, 0x88, 0x69, 0xf5, 0x87, 0xed,
0xdb, 0x6d, 0xb3, 0x69, 0x1a, 0xd6, 0x40, 0x2d, 0xc9, 0xc8, 0x9b, 0x3e, 0xbc, 0xcc, 0x27, 0x34, 0xb6, 0xd9, 0x34, 0x0d, 0x6b, 0xa0, 0x96, 0xa4, 0xe5, 0x4d, 0x07, 0x2f, 0x73, 0x81, 0xe6, 0x59,
0xcf, 0x1a, 0x96, 0x65, 0x74, 0x48, 0xcb, 0xec, 0x37, 0x4e, 0x3b, 0x46, 0x4b, 0xad, 0xa0, 0x03, 0xc3, 0xb2, 0x8c, 0x0e, 0x69, 0x99, 0xfd, 0xc6, 0x69, 0xc7, 0x68, 0xa9, 0x15, 0x74, 0x00, 0x8f,
0x78, 0x34, 0x30, 0xba, 0x17, 0x3d, 0xdc, 0xc0, 0x1f, 0x48, 0x6c, 0x6f, 0x37, 0xcc, 0xce, 0x10, 0x06, 0x46, 0xf7, 0xa2, 0x87, 0x1b, 0xf8, 0x03, 0x89, 0xe9, 0xed, 0x86, 0xd9, 0x19, 0x62, 0x43,
0x1b, 0x6a, 0x15, 0x7d, 0x05, 0x07, 0xd8, 0x78, 0x37, 0x34, 0xb1, 0xd1, 0x22, 0x56, 0xaf, 0x65, 0xad, 0xa2, 0xaf, 0xe0, 0x00, 0x1b, 0xef, 0x86, 0x26, 0x36, 0x5a, 0xc4, 0xea, 0xb5, 0x0c, 0xd2,
0x90, 0xb6, 0xd1, 0x18, 0x0c, 0xb1, 0x41, 0xba, 0x66, 0xbf, 0x6f, 0x5a, 0xdf, 0xa9, 0x2a, 0x7a, 0x36, 0x1a, 0x83, 0x21, 0x36, 0x48, 0xd7, 0xec, 0xf7, 0x4d, 0xeb, 0x3b, 0x55, 0x45, 0xcf, 0xe0,
0x06, 0x87, 0x0b, 0xca, 0xc2, 0xc1, 0x1a, 0x6b, 0x9b, 0x7f, 0x5f, 0x9c, 0x52, 0xcb, 0xf8, 0x61, 0x70, 0xc1, 0xb2, 0x50, 0xb0, 0xc6, 0xb5, 0xcd, 0xcf, 0x17, 0xbb, 0xd4, 0x32, 0x7e, 0x18, 0x90,
0x40, 0x2e, 0x0c, 0x03, 0xab, 0x08, 0xd5, 0x60, 0x6f, 0x19, 0x5e, 0x06, 0x88, 0x62, 0xef, 0x70, 0x0b, 0xc3, 0xc0, 0x2a, 0x42, 0x35, 0xd8, 0x5b, 0x9a, 0x97, 0x06, 0x22, 0xdb, 0x3b, 0x9c, 0x76,
0xdb, 0x85, 0x81, 0xbb, 0x0d, 0x8b, 0x27, 0x78, 0xc5, 0xb6, 0xcb, 0x97, 0xbd, 0xb4, 0xad, 0x2f, 0x61, 0xe0, 0x6e, 0xc3, 0xe2, 0x0e, 0x5e, 0xa1, 0xed, 0xf2, 0x6d, 0x2f, 0x69, 0xeb, 0xdb, 0x7e,
0xfb, 0x01, 0x42, 0x50, 0x49, 0x64, 0xa5, 0xdd, 0xc0, 0xea, 0x1e, 0xda, 0x85, 0x6a, 0xbc, 0x82, 0x80, 0x10, 0x54, 0x12, 0x5e, 0x69, 0x37, 0xb0, 0xba, 0x87, 0xaa, 0x50, 0xec, 0x5e, 0x5c, 0x90,
0x98, 0xf8, 0xcf, 0x1c, 0x7a, 0x08, 0x68, 0x68, 0x61, 0xa3, 0xd1, 0xe2, 0x1b, 0xb2, 0x30, 0xfc, 0x81, 0xd9, 0x35, 0x7a, 0xc3, 0x81, 0xfa, 0x10, 0xed, 0x42, 0x35, 0xde, 0x52, 0x2c, 0xf9, 0xaf,
0x2b, 0x77, 0x9e, 0xce, 0x6f, 0xa9, 0x29, 0xfd, 0x1f, 0x29, 0x28, 0xaf, 0xd4, 0x25, 0x7a, 0x0c, 0x1c, 0x7a, 0x08, 0x68, 0x68, 0x61, 0xa3, 0xd1, 0xe2, 0x37, 0xb4, 0x20, 0xfc, 0x3b, 0x77, 0x9e,
0x85, 0xd0, 0xb9, 0xf6, 0xc4, 0x35, 0x15, 0x89, 0xca, 0x12, 0x10, 0xb7, 0xfa, 0xc4, 0x76, 0x3c, 0xce, 0x6f, 0xa9, 0x29, 0xfd, 0x1f, 0x29, 0x28, 0xaf, 0x04, 0x2a, 0x7a, 0x0c, 0x85, 0xd0, 0xb9,
0xa9, 0x66, 0x52, 0xf7, 0x0b, 0x02, 0x11, 0x5a, 0xb6, 0x0f, 0xb9, 0xb8, 0x83, 0x48, 0x2d, 0x3a, 0xf6, 0x44, 0xdd, 0x8a, 0xb2, 0xcc, 0x12, 0x10, 0x65, 0x7e, 0x62, 0x3b, 0x9e, 0x4c, 0x6f, 0xb2,
0x88, 0xec, 0x48, 0x76, 0x0e, 0x8f, 0xa1, 0xc0, 0x25, 0x33, 0x64, 0xf6, 0x74, 0x26, 0x4a, 0xbc, 0x10, 0x14, 0x04, 0x22, 0x92, 0xdb, 0x3e, 0xe4, 0xe2, 0x96, 0x22, 0xb5, 0x68, 0x29, 0xb2, 0x23,
0x8c, 0x97, 0x00, 0x7a, 0x0a, 0xe5, 0x29, 0x0d, 0x43, 0xfb, 0x9a, 0x12, 0x59, 0xa6, 0x20, 0x18, 0xd9, 0x4a, 0x3c, 0x86, 0x02, 0xcf, 0xa1, 0x21, 0xb3, 0xa7, 0x33, 0x11, 0xf3, 0x65, 0xbc, 0x04,
0xa5, 0x08, 0x6c, 0x8b, 0x6a, 0x7d, 0x0a, 0xb1, 0x6c, 0x44, 0xa4, 0x8c, 0x24, 0x45, 0xa0, 0x24, 0xd0, 0x53, 0x28, 0x4f, 0x69, 0x18, 0xda, 0xd7, 0x94, 0xc8, 0xb8, 0x05, 0xc1, 0x51, 0x8a, 0xc0,
0xad, 0x2b, 0x36, 0xb3, 0x23, 0x35, 0x48, 0x2a, 0x36, 0xb3, 0xd1, 0x0b, 0xd8, 0x96, 0x92, 0xe3, 0xb6, 0x08, 0xdf, 0xa7, 0x10, 0xe7, 0x91, 0x88, 0x29, 0x23, 0x99, 0x22, 0x50, 0x32, 0xad, 0xa7,
0x78, 0xce, 0x74, 0x3e, 0x95, 0xd2, 0x93, 0x13, 0xd2, 0x53, 0x15, 0xd2, 0x23, 0x71, 0xa1, 0x40, 0x70, 0x66, 0x47, 0xe9, 0x21, 0x99, 0xc2, 0x99, 0x8d, 0x5e, 0xc0, 0xb6, 0xcc, 0x41, 0x8e, 0xe7,
0x8f, 0x20, 0x7f, 0x69, 0x87, 0x94, 0x5f, 0x16, 0x91, 0x34, 0xe4, 0xf8, 0xb8, 0x4d, 0x29, 0x37, 0x4c, 0xe7, 0x53, 0x99, 0x8b, 0x72, 0x22, 0x17, 0x55, 0x45, 0x2e, 0x92, 0xb8, 0x48, 0x49, 0x8f,
0xf1, 0x2b, 0x24, 0xe0, 0xa2, 0x27, 0x15, 0x21, 0x77, 0x45, 0x29, 0xe6, 0x7b, 0xb9, 0x88, 0x60, 0x20, 0x7f, 0x69, 0x87, 0x94, 0x57, 0x8f, 0x28, 0x57, 0xe4, 0xf8, 0xba, 0x4d, 0x29, 0x27, 0xf1,
0x7f, 0x5a, 0x46, 0x28, 0x26, 0x22, 0x48, 0x5c, 0x44, 0x78, 0x01, 0xdb, 0xf4, 0x13, 0x0b, 0x6c, 0x9a, 0x12, 0xf0, 0x2c, 0x28, 0x53, 0x44, 0xee, 0x8a, 0x52, 0xcc, 0xef, 0x72, 0x61, 0xc1, 0xfe,
0xe2, 0xcf, 0xec, 0x9f, 0xe6, 0x94, 0x8c, 0x6d, 0x66, 0x8b, 0x96, 0xb4, 0x84, 0xab, 0xc2, 0xd0, 0xb4, 0xb4, 0x50, 0x4c, 0x58, 0x90, 0xb8, 0xb0, 0xf0, 0x02, 0xb6, 0xe9, 0x27, 0x16, 0xd8, 0xc4,
0x13, 0x78, 0xcb, 0x66, 0xb6, 0xfe, 0x18, 0x6a, 0x98, 0x86, 0x94, 0x75, 0x9d, 0x30, 0x74, 0x7c, 0x9f, 0xd9, 0x3f, 0xcd, 0x29, 0x19, 0xdb, 0xcc, 0x16, 0x3d, 0x6a, 0x09, 0x57, 0x05, 0xa1, 0x27,
0xaf, 0xe9, 0x7b, 0x2c, 0xf0, 0xdd, 0xe8, 0xce, 0xd1, 0x0f, 0x60, 0x7f, 0xa3, 0x55, 0x5e, 0x1a, 0xf0, 0x96, 0xcd, 0x6c, 0xfd, 0x31, 0xd4, 0x30, 0x0d, 0x29, 0xeb, 0x3a, 0x61, 0xe8, 0xf8, 0x5e,
0x7c, 0xf2, 0xbb, 0x39, 0x0d, 0x6e, 0x37, 0x4f, 0x7e, 0x07, 0xfb, 0x1b, 0xad, 0xd1, 0x8d, 0xf3, 0xd3, 0xf7, 0x58, 0xe0, 0xbb, 0x51, 0x11, 0xd2, 0x0f, 0x60, 0x7f, 0x23, 0x55, 0x56, 0x11, 0x2e,
0x12, 0x32, 0x33, 0xdb, 0x09, 0x42, 0x6d, 0x4b, 0xdc, 0xda, 0x7b, 0x2b, 0x4d, 0x82, 0x13, 0x9c, 0xfc, 0x6e, 0x4e, 0x83, 0xdb, 0xcd, 0xc2, 0xef, 0x60, 0x7f, 0x23, 0x35, 0x2a, 0x41, 0x2f, 0x21,
0x39, 0x21, 0xf3, 0x83, 0x5b, 0x2c, 0x49, 0xe7, 0xe9, 0xbc, 0xa2, 0x6e, 0xe9, 0x7f, 0x52, 0xa0, 0x33, 0xb3, 0x9d, 0x20, 0xd4, 0xb6, 0x44, 0x19, 0xdf, 0x5b, 0xe9, 0x1a, 0x9c, 0xe0, 0xcc, 0x09,
0x98, 0x30, 0xf2, 0x73, 0xe0, 0xf9, 0x63, 0x4a, 0xae, 0x02, 0x7f, 0x1a, 0x9f, 0xb0, 0x05, 0x80, 0x99, 0x1f, 0xdc, 0x62, 0xc9, 0x74, 0x9e, 0xce, 0x2b, 0xea, 0x96, 0xfe, 0x67, 0x05, 0x8a, 0x09,
0x34, 0xc8, 0x89, 0x01, 0xf3, 0xa3, 0xe3, 0x15, 0x0f, 0xd1, 0x37, 0x90, 0x9b, 0x48, 0x17, 0x22, 0x22, 0x7f, 0x07, 0x9e, 0x3f, 0xa6, 0xe4, 0x2a, 0xf0, 0xa7, 0xf1, 0x0b, 0x5b, 0x00, 0x48, 0x83,
0x4b, 0xc5, 0x93, 0x9d, 0xb5, 0xe8, 0x7c, 0x6f, 0x70, 0xcc, 0x39, 0x4f, 0xe7, 0x53, 0x6a, 0xfa, 0x9c, 0x58, 0x30, 0x3f, 0x7a, 0x5e, 0xf1, 0x12, 0x7d, 0x03, 0xb9, 0x89, 0x54, 0x21, 0xbc, 0x54,
0x3c, 0x9d, 0x4f, 0xab, 0x99, 0xf3, 0x74, 0x3e, 0xa3, 0x66, 0xcf, 0xd3, 0xf9, 0xac, 0x9a, 0xd3, 0x3c, 0xd9, 0x59, 0xb3, 0xce, 0xef, 0x06, 0xc7, 0x3c, 0xe7, 0xe9, 0x7c, 0x4a, 0x4d, 0x9f, 0xa7,
0xff, 0xad, 0x40, 0x3e, 0x66, 0xf3, 0xb5, 0x70, 0x89, 0x27, 0xfc, 0x64, 0x44, 0x0d, 0xc0, 0x12, 0xf3, 0x69, 0x35, 0x73, 0x9e, 0xce, 0x67, 0xd4, 0xec, 0x79, 0x3a, 0x9f, 0x55, 0x73, 0xfa, 0x7f,
0x40, 0x3a, 0x94, 0xc4, 0x60, 0xb5, 0xaf, 0x58, 0xc1, 0xd0, 0x33, 0x28, 0x2f, 0xc6, 0x8b, 0xcb, 0x14, 0xc8, 0xc7, 0xdc, 0x7c, 0x2f, 0x3c, 0xe7, 0x13, 0xfe, 0x32, 0xa2, 0x8e, 0x60, 0x09, 0x20,
0x2b, 0x85, 0x57, 0x41, 0xee, 0x29, 0x9c, 0x8f, 0x46, 0x34, 0x0c, 0x65, 0xa8, 0x8c, 0xf4, 0x94, 0x1d, 0x4a, 0x62, 0xb1, 0xda, 0x68, 0xac, 0x60, 0xe8, 0x19, 0x94, 0x17, 0xeb, 0x45, 0x35, 0x4b,
0xc4, 0x50, 0x1d, 0xaa, 0xf1, 0x38, 0x0e, 0x98, 0x15, 0xb4, 0x75, 0x18, 0xbd, 0x00, 0x35, 0x09, 0xe1, 0x55, 0x90, 0x6b, 0x0a, 0xe7, 0xa3, 0x11, 0x0d, 0x43, 0x69, 0x2a, 0x23, 0x35, 0x25, 0x31,
0x4d, 0x97, 0xed, 0xff, 0x1d, 0x5c, 0x6e, 0x83, 0x3e, 0x85, 0x87, 0x22, 0xad, 0x17, 0x81, 0x7f, 0x54, 0x87, 0x6a, 0xbc, 0x8e, 0x0d, 0x66, 0x05, 0xdb, 0x3a, 0x8c, 0x5e, 0x80, 0x9a, 0x84, 0xa6,
0x69, 0x5f, 0x3a, 0xae, 0xc3, 0x6e, 0xe3, 0x16, 0x85, 0x6f, 0x41, 0xe0, 0x4f, 0x89, 0x17, 0xdf, 0xcb, 0x79, 0xe0, 0x0e, 0x2e, 0xaf, 0x41, 0x9f, 0xc2, 0x43, 0xe1, 0xd6, 0x8b, 0xc0, 0xbf, 0xb4,
0xf9, 0x25, 0xbc, 0x04, 0x78, 0x3a, 0x98, 0x2f, 0x6d, 0x51, 0x3a, 0xa2, 0x21, 0x6f, 0x3e, 0x16, 0x2f, 0x1d, 0xd7, 0x61, 0xb7, 0x71, 0xcf, 0xc2, 0xaf, 0x20, 0xf0, 0xa7, 0xc4, 0x8b, 0x9b, 0x80,
0xc1, 0x53, 0x22, 0xf8, 0x62, 0xac, 0xdf, 0x80, 0x76, 0x37, 0x5c, 0x74, 0x84, 0x0e, 0xa1, 0x38, 0x12, 0x5e, 0x02, 0xdc, 0x1d, 0xcc, 0x97, 0xb4, 0xc8, 0x1d, 0xd1, 0x92, 0x77, 0x23, 0x0b, 0xe3,
0x5b, 0xc2, 0x22, 0xa2, 0x82, 0x93, 0x50, 0x32, 0xd1, 0x5b, 0xbf, 0x9c, 0x68, 0xfd, 0xcf, 0x0a, 0x29, 0x61, 0x7c, 0xb1, 0xd6, 0x6f, 0x40, 0xbb, 0x6b, 0x2e, 0x7a, 0x42, 0x87, 0x50, 0x9c, 0x2d,
0x6c, 0x9f, 0xce, 0x1d, 0x77, 0xbc, 0xd2, 0x79, 0x25, 0x5f, 0x76, 0xca, 0xea, 0xcb, 0x6e, 0xd3, 0x61, 0x61, 0x51, 0xc1, 0x49, 0x28, 0xe9, 0xe8, 0xad, 0x5f, 0x76, 0xb4, 0xfe, 0x17, 0x05, 0xb6,
0xb3, 0x6d, 0x6b, 0xe3, 0xb3, 0x6d, 0xd3, 0xd3, 0x28, 0x75, 0xef, 0xd3, 0xe8, 0x09, 0x14, 0x97, 0x4f, 0xe7, 0x8e, 0x3b, 0x5e, 0x69, 0xc5, 0x92, 0xa3, 0x9e, 0xb2, 0x3a, 0xea, 0x6d, 0x9a, 0xe3,
0xaf, 0x22, 0xd9, 0xd8, 0x96, 0x30, 0x4c, 0xe2, 0x27, 0x51, 0xa8, 0xbf, 0x01, 0x94, 0x5c, 0x68, 0xb6, 0x36, 0xce, 0x71, 0x9b, 0x66, 0xa5, 0xd4, 0xbd, 0xb3, 0xd2, 0x13, 0x28, 0x2e, 0xc7, 0x24,
0xb4, 0x21, 0x8b, 0x06, 0x50, 0xb9, 0xb7, 0x01, 0x7c, 0xf1, 0x77, 0x05, 0x4a, 0xc9, 0x2e, 0x1c, 0xd9, 0xe9, 0x96, 0x30, 0x4c, 0xe2, 0x19, 0x29, 0xd4, 0xdf, 0x00, 0x4a, 0x6e, 0x34, 0xba, 0x90,
0x95, 0xa1, 0x60, 0x5a, 0xa4, 0xdd, 0x31, 0xbf, 0x3b, 0x1b, 0xa8, 0x5f, 0xf0, 0x61, 0x7f, 0xd8, 0x45, 0x47, 0xa8, 0xdc, 0xdb, 0x11, 0xbe, 0xf8, 0xbb, 0x02, 0xa5, 0x64, 0x5b, 0x8e, 0xca, 0x50,
0x6c, 0x1a, 0x46, 0xcb, 0x68, 0xa9, 0x0a, 0xbf, 0x1f, 0xb8, 0xd4, 0x1b, 0x2d, 0x32, 0x30, 0xbb, 0x30, 0x2d, 0xd2, 0xee, 0x98, 0xdf, 0x9d, 0x0d, 0xd4, 0x2f, 0xf8, 0xb2, 0x3f, 0x6c, 0x36, 0x0d,
0x46, 0x6f, 0xc8, 0x3b, 0x87, 0x1d, 0xa8, 0x46, 0x98, 0xd5, 0x23, 0xb8, 0x37, 0x1c, 0x18, 0x6a, 0xa3, 0x65, 0xb4, 0x54, 0x85, 0x17, 0x0c, 0x9e, 0xea, 0x8d, 0xd6, 0xa2, 0x3e, 0x6c, 0xf1, 0xd2,
0x0a, 0xa9, 0x50, 0x8a, 0x40, 0x03, 0xe3, 0x1e, 0x56, 0xd3, 0xfc, 0xba, 0x8b, 0x90, 0xbb, 0x5d, 0x1e, 0x61, 0x56, 0x8f, 0xe0, 0xde, 0x70, 0x60, 0xa8, 0x29, 0xa4, 0x42, 0x29, 0x02, 0x0d, 0x8c,
0x48, 0xdc, 0xa4, 0x64, 0x44, 0x97, 0x11, 0xb3, 0x96, 0x17, 0x34, 0x39, 0x6d, 0x74, 0x1a, 0x56, 0x7b, 0x58, 0x4d, 0xf3, 0xfa, 0x17, 0x21, 0x77, 0xdb, 0x92, 0xb8, 0x6b, 0xc9, 0x88, 0xb6, 0x23,
0xd3, 0x50, 0xb3, 0x27, 0x7f, 0xcb, 0x40, 0x56, 0x7c, 0x41, 0x80, 0xce, 0xa0, 0x98, 0x78, 0x90, 0xe6, 0x5a, 0x56, 0x6c, 0x72, 0xda, 0xe8, 0x34, 0xac, 0xa6, 0xa1, 0x66, 0x4f, 0xfe, 0x96, 0x81,
0xa1, 0x83, 0xcf, 0x3e, 0xd4, 0x6a, 0xda, 0xe6, 0x77, 0xc7, 0x3c, 0x7c, 0xa5, 0xa0, 0x73, 0x28, 0xac, 0x38, 0x41, 0x80, 0xce, 0xa0, 0x98, 0x98, 0xd0, 0xd0, 0xc1, 0x67, 0x27, 0xb7, 0x9a, 0xb6,
0x25, 0x9f, 0x3b, 0x28, 0xd9, 0x9b, 0x6e, 0x78, 0x07, 0x7d, 0xd6, 0xd7, 0x5b, 0x50, 0x8d, 0x90, 0x79, 0x10, 0x99, 0x87, 0xaf, 0x14, 0x74, 0x0e, 0xa5, 0xe4, 0xfc, 0x83, 0x92, 0xcd, 0xea, 0x86,
0x39, 0x53, 0xde, 0x8b, 0x46, 0xaf, 0x03, 0x54, 0x4b, 0xf0, 0xd7, 0x9e, 0x1c, 0xb5, 0xfd, 0x8d, 0xc1, 0xe8, 0xb3, 0xba, 0xde, 0x82, 0x6a, 0x84, 0xcc, 0x99, 0xf2, 0xe6, 0x34, 0x1a, 0x17, 0x50,
0xb6, 0x28, 0x85, 0x1d, 0xf9, 0x89, 0x51, 0x7f, 0x7e, 0xe7, 0x13, 0x57, 0x1f, 0x05, 0xb5, 0x2f, 0x2d, 0xc1, 0xbf, 0x36, 0x83, 0xd4, 0xf6, 0x37, 0xd2, 0x22, 0x17, 0x76, 0xe4, 0x11, 0xa3, 0x86,
0xef, 0x33, 0x47, 0xde, 0xc6, 0xb0, 0xb3, 0x41, 0xc0, 0xd1, 0xaf, 0x92, 0x2b, 0xb8, 0x57, 0xfe, 0xfd, 0xce, 0x11, 0x57, 0xa7, 0x84, 0xda, 0x97, 0xf7, 0x91, 0x23, 0x6d, 0x63, 0xd8, 0xd9, 0x90,
0x6b, 0xcf, 0x7f, 0x89, 0xb6, 0x8c, 0xb2, 0x41, 0xe9, 0x57, 0xa2, 0xdc, 0x7f, 0x4f, 0xac, 0x44, 0xc0, 0xd1, 0xaf, 0x92, 0x3b, 0xb8, 0x37, 0xfd, 0xd7, 0x9e, 0xff, 0x12, 0xdb, 0xd2, 0xca, 0x86,
0xf9, 0xdc, 0x85, 0xf1, 0x23, 0xa8, 0xeb, 0x4a, 0x80, 0xf4, 0xf5, 0xb9, 0x77, 0x55, 0xa9, 0xf6, 0x4c, 0xbf, 0x62, 0xe5, 0xfe, 0x3a, 0xb1, 0x62, 0xe5, 0x73, 0x05, 0xe3, 0x47, 0x50, 0xd7, 0x33,
0xf4, 0xb3, 0x9c, 0xc8, 0xb9, 0x09, 0xb0, 0xac, 0x27, 0xf4, 0x38, 0x31, 0xe5, 0x8e, 0x1e, 0xd4, 0x01, 0xd2, 0xd7, 0x65, 0xef, 0x66, 0xa5, 0xda, 0xd3, 0xcf, 0xf2, 0x44, 0xca, 0x4d, 0x80, 0x65,
0x0e, 0xee, 0xb1, 0x4a, 0x57, 0xa7, 0xbf, 0xfe, 0xe3, 0xf1, 0xb5, 0xc3, 0x26, 0xf3, 0xcb, 0xa3, 0x3c, 0xa1, 0xc7, 0x09, 0x91, 0x3b, 0xf9, 0xa0, 0x76, 0x70, 0x0f, 0x55, 0xaa, 0x3a, 0xfd, 0xf5,
0x91, 0x3f, 0x3d, 0x76, 0x79, 0x47, 0xef, 0x39, 0xde, 0xb5, 0x47, 0xd9, 0xcf, 0x7e, 0x70, 0x73, 0x1f, 0x8f, 0xaf, 0x1d, 0x36, 0x99, 0x5f, 0x1e, 0x8d, 0xfc, 0xe9, 0xb1, 0xcb, 0x5b, 0x7c, 0xcf,
0xec, 0x7a, 0xe3, 0x63, 0x51, 0x96, 0xc7, 0x0b, 0x2f, 0x97, 0x59, 0xf1, 0xff, 0xa7, 0xdf, 0xfc, 0xf1, 0xae, 0x3d, 0xca, 0x7e, 0xf6, 0x83, 0x9b, 0x63, 0xd7, 0x1b, 0x1f, 0x8b, 0xb0, 0x3c, 0x5e,
0x2f, 0x00, 0x00, 0xff, 0xff, 0x42, 0x87, 0x8c, 0xeb, 0xaf, 0x12, 0x00, 0x00, 0x68, 0xb9, 0xcc, 0x8a, 0x3f, 0xa4, 0x7e, 0xf3, 0xbf, 0x00, 0x00, 0x00, 0xff, 0xff, 0x10, 0xf6,
0xab, 0x74, 0xc0, 0x12, 0x00, 0x00,
} }
// Reference imports to suppress errors if they are not otherwise used. // Reference imports to suppress errors if they are not otherwise used.

@ -256,6 +256,7 @@ message Failure {
PERMANENT_NODE_FAILURE = 20; PERMANENT_NODE_FAILURE = 20;
PERMANENT_CHANNEL_FAILURE = 21; PERMANENT_CHANNEL_FAILURE = 21;
EXPIRY_TOO_FAR = 22; EXPIRY_TOO_FAR = 22;
MPP_TIMEOUT = 23;
/** /**
The error source is known, but the failure itself couldn't be decoded. The error source is known, but the failure itself couldn't be decoded.

@ -415,6 +415,9 @@ func marshallError(sendError error) (*Failure, error) {
case *lnwire.FailPermanentChannelFailure: case *lnwire.FailPermanentChannelFailure:
response.Code = Failure_PERMANENT_CHANNEL_FAILURE response.Code = Failure_PERMANENT_CHANNEL_FAILURE
case *lnwire.FailMPPTimeout:
response.Code = Failure_MPP_TIMEOUT
case nil: case nil:
response.Code = Failure_UNKNOWN_FAILURE response.Code = Failure_UNKNOWN_FAILURE

@ -80,6 +80,7 @@ const (
CodeFinalIncorrectHtlcAmount FailCode = 19 CodeFinalIncorrectHtlcAmount FailCode = 19
CodeExpiryTooFar FailCode = 21 CodeExpiryTooFar FailCode = 21
CodeInvalidOnionPayload = FlagPerm | 22 CodeInvalidOnionPayload = FlagPerm | 22
CodeMPPTimeout FailCode = 23
) )
// String returns the string representation of the failure code. // String returns the string representation of the failure code.
@ -154,6 +155,9 @@ func (c FailCode) String() string {
case CodeInvalidOnionPayload: case CodeInvalidOnionPayload:
return "InvalidOnionPayload" return "InvalidOnionPayload"
case CodeMPPTimeout:
return "MPPTimeout"
default: default:
return "<unknown>" return "<unknown>"
} }
@ -1182,6 +1186,26 @@ func (f *InvalidOnionPayload) Encode(w io.Writer, pver uint32) error {
return WriteElements(w, f.Offset) return WriteElements(w, f.Offset)
} }
// FailMPPTimeout is returned if the complete amount for a multi part payment
// was not received within a reasonable time.
//
// NOTE: May only be returned by the final node in the path.
type FailMPPTimeout struct{}
// Code returns the failure unique code.
//
// NOTE: Part of the FailureMessage interface.
func (f *FailMPPTimeout) Code() FailCode {
return CodeMPPTimeout
}
// Returns a human readable string describing the target FailureMessage.
//
// NOTE: Implements the error interface.
func (f *FailMPPTimeout) Error() string {
return f.Code().String()
}
// DecodeFailure decodes, validates, and parses the lnwire onion failure, for // DecodeFailure decodes, validates, and parses the lnwire onion failure, for
// the provided protocol version. // the provided protocol version.
func DecodeFailure(r io.Reader, pver uint32) (FailureMessage, error) { func DecodeFailure(r io.Reader, pver uint32) (FailureMessage, error) {
@ -1366,6 +1390,9 @@ func makeEmptyOnionError(code FailCode) (FailureMessage, error) {
case CodeInvalidOnionPayload: case CodeInvalidOnionPayload:
return &InvalidOnionPayload{}, nil return &InvalidOnionPayload{}, nil
case CodeMPPTimeout:
return &FailMPPTimeout{}, nil
default: default:
return nil, errors.Errorf("unknown error code: %v", code) return nil, errors.Errorf("unknown error code: %v", code)
} }

@ -38,6 +38,7 @@ var onionFailures = []FailureMessage{
&FailUnknownNextPeer{}, &FailUnknownNextPeer{},
&FailIncorrectPaymentAmount{}, &FailIncorrectPaymentAmount{},
&FailFinalExpiryTooSoon{}, &FailFinalExpiryTooSoon{},
&FailMPPTimeout{},
NewFailIncorrectDetails(99, 100), NewFailIncorrectDetails(99, 100),
NewInvalidOnionVersion(testOnionHash), NewInvalidOnionVersion(testOnionHash),

@ -16,8 +16,12 @@ import (
const testMaxRecords = 2 const testMaxRecords = 2
// TestMissionControlStore tests the recording of payment failure events
// in mission control. It tests encoding and decoding of differing lnwire
// failures (FailIncorrectDetails and FailMppTimeout), pruning of results
// and idempotent writes.
func TestMissionControlStore(t *testing.T) { func TestMissionControlStore(t *testing.T) {
// Set time zone explictly to keep test deterministic. // Set time zone explicitly to keep test deterministic.
time.Local = time.UTC time.Local = time.UTC
file, err := ioutil.TempFile("", "*.db") file, err := ioutil.TempFile("", "*.db")
@ -115,11 +119,12 @@ func TestMissionControlStore(t *testing.T) {
t.Fatal(err) t.Fatal(err)
} }
// Add a newer result. // Add a newer result which failed due to mpp timeout.
result3 := result1 result3 := result1
result3.timeReply = result1.timeReply.Add(2 * time.Hour) result3.timeReply = result1.timeReply.Add(2 * time.Hour)
result3.timeFwd = result1.timeReply.Add(2 * time.Hour) result3.timeFwd = result1.timeReply.Add(2 * time.Hour)
result3.id = 3 result3.id = 3
result3.failure = &lnwire.FailMPPTimeout{}
err = store.AddResult(&result3) err = store.AddResult(&result3)
if err != nil { if err != nil {

@ -216,6 +216,22 @@ func (i *interpretedResult) processPaymentOutcomeFinal(
// deliberately. What to penalize? // deliberately. What to penalize?
i.finalFailureReason = &reasonIncorrectDetails i.finalFailureReason = &reasonIncorrectDetails
case *lnwire.FailMPPTimeout:
// TODO(carla): decide how to penalize mpp timeout. In the
// meantime, attribute success to the hops along the route and
// do not penalize the final node.
i.finalFailureReason = &reasonError
// If this is a direct payment, take no action.
if n == 1 {
return
}
// Assign all pairs a success result except the final hop, as
// the payment reached the destination correctly.
i.successPairRange(route, 0, n-2)
default: default:
// All other errors are considered terminal if coming from the // All other errors are considered terminal if coming from the
// final hop. They indicate that something is wrong at the // final hop. They indicate that something is wrong at the

@ -272,6 +272,39 @@ var resultTestCases = []resultTestCase{
nodeFailure: &hops[1], nodeFailure: &hops[1],
}, },
}, },
// Tests a single hop mpp timeout. Test that final node is not
// penalized. This is a temporary measure while we decide how to
// penalize mpp timeouts.
{
name: "one hop mpp timeout",
route: &routeOneHop,
failureSrcIdx: 1,
failure: &lnwire.FailMPPTimeout{},
expectedResult: &interpretedResult{
finalFailureReason: &reasonError,
nodeFailure: nil,
},
},
// Tests a two hop mpp timeout. Test that final node is not penalized
// and the intermediate hop is attributed the success. This is a
// temporary measure while we decide how to penalize mpp timeouts.
{
name: "two hop mpp timeout",
route: &routeTwoHop,
failureSrcIdx: 2,
failure: &lnwire.FailMPPTimeout{},
expectedResult: &interpretedResult{
pairResults: map[DirectedNodePair]pairResult{
getTestPair(0, 1): successPairResult(100),
},
finalFailureReason: &reasonError,
nodeFailure: nil,
},
},
} }
// TestResultInterpretation executes a list of test cases that test the result // TestResultInterpretation executes a list of test cases that test the result