diff --git a/contractcourt/htlc_incoming_contest_resolver.go b/contractcourt/htlc_incoming_contest_resolver.go index abedcc14..98e52500 100644 --- a/contractcourt/htlc_incoming_contest_resolver.go +++ b/contractcourt/htlc_incoming_contest_resolver.go @@ -167,10 +167,10 @@ func (h *htlcIncomingContestResolver) Resolve() (ContractResolver, error) { preimageSubscription := h.PreimageDB.SubscribeUpdates() defer preimageSubscription.CancelSubscription() - // Define closure to process hodl events either direct or triggered by - // later notifcation. - processHodlEvent := func(e invoices.HodlEvent) (ContractResolver, - error) { + // Define closure to process htlc resolutions either direct or triggered by + // later notification. + processHtlcResolution := func(e invoices.HtlcResolution) ( + ContractResolver, error) { if e.Preimage == nil { log.Infof("%T(%v): Exit hop HTLC canceled "+ @@ -201,23 +201,26 @@ func (h *htlcIncomingContestResolver) Resolve() (ContractResolver, error) { HtlcID: h.htlc.HtlcIndex, } - event, err := h.Registry.NotifyExitHopHtlc( + resolution, err := h.Registry.NotifyExitHopHtlc( h.htlc.RHash, h.htlc.Amt, h.htlcExpiry, currentHeight, circuitKey, hodlChan, payload, ) - switch err { - case channeldb.ErrInvoiceNotFound: - case nil: - defer h.Registry.HodlUnsubscribeAll(hodlChan) - - // Resolve the htlc directly if possible. - if event != nil { - return processHodlEvent(*event) - } - default: + if err != nil { 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 // to see if we already know the preimage. preimage, ok := h.PreimageDB.LookupPreimage(h.htlc.RHash) @@ -252,9 +255,9 @@ func (h *htlcIncomingContestResolver) Resolve() (ContractResolver, error) { return &h.htlcSuccessResolver, nil case hodlItem := <-hodlChan: - hodlEvent := hodlItem.(invoices.HodlEvent) + htlcResolution := hodlItem.(invoices.HtlcResolution) - return processHodlEvent(hodlEvent) + return processHtlcResolution(htlcResolution) case newBlock, ok := <-blockEpochs.Epochs: if !ok { diff --git a/contractcourt/htlc_incoming_resolver_test.go b/contractcourt/htlc_incoming_resolver_test.go index 850126c0..773b22c0 100644 --- a/contractcourt/htlc_incoming_resolver_test.go +++ b/contractcourt/htlc_incoming_resolver_test.go @@ -21,10 +21,11 @@ const ( ) var ( - testResPreimage = lntypes.Preimage{1, 2, 3} - testResHash = testResPreimage.Hash() - testResCircuitKey = channeldb.CircuitKey{} - testOnionBlob = []byte{4, 5, 6} + testResPreimage = lntypes.Preimage{1, 2, 3} + testResHash = testResPreimage.Hash() + testResCircuitKey = channeldb.CircuitKey{} + testOnionBlob = []byte{4, 5, 6} + testAcceptHeight int32 = 1234 ) // TestHtlcIncomingResolverFwdPreimageKnown tests resolution of a forwarded htlc @@ -34,7 +35,10 @@ func TestHtlcIncomingResolverFwdPreimageKnown(t *testing.T) { defer timeout(t)() ctx := newIncomingResolverTestContext(t) - ctx.registry.notifyErr = channeldb.ErrInvoiceNotFound + ctx.registry.notifyResolution = invoices.NewFailureResolution( + testResCircuitKey, testHtlcExpiry, + invoices.ResultInvoiceNotFound, + ) ctx.witnessBeacon.lookupPreimage[testResHash] = testResPreimage ctx.resolve() ctx.waitForResult(true) @@ -48,7 +52,10 @@ func TestHtlcIncomingResolverFwdContestedSuccess(t *testing.T) { defer timeout(t)() ctx := newIncomingResolverTestContext(t) - ctx.registry.notifyErr = channeldb.ErrInvoiceNotFound + ctx.registry.notifyResolution = invoices.NewFailureResolution( + testResCircuitKey, testHtlcExpiry, + invoices.ResultInvoiceNotFound, + ) ctx.resolve() // Simulate a new block coming in. HTLC is not yet expired. @@ -65,7 +72,10 @@ func TestHtlcIncomingResolverFwdContestedTimeout(t *testing.T) { defer timeout(t)() ctx := newIncomingResolverTestContext(t) - ctx.registry.notifyErr = channeldb.ErrInvoiceNotFound + ctx.registry.notifyResolution = invoices.NewFailureResolution( + testResCircuitKey, testHtlcExpiry, + invoices.ResultInvoiceNotFound, + ) ctx.resolve() // Simulate a new block coming in. HTLC expires. @@ -81,8 +91,10 @@ func TestHtlcIncomingResolverFwdTimeout(t *testing.T) { defer timeout(t)() ctx := newIncomingResolverTestContext(t) - - ctx.registry.notifyErr = channeldb.ErrInvoiceNotFound + ctx.registry.notifyResolution = invoices.NewFailureResolution( + testResCircuitKey, testHtlcExpiry, + invoices.ResultInvoiceNotFound, + ) ctx.witnessBeacon.lookupPreimage[testResHash] = testResPreimage ctx.resolver.htlcExpiry = 90 ctx.resolve() @@ -96,10 +108,11 @@ func TestHtlcIncomingResolverExitSettle(t *testing.T) { defer timeout(t)() ctx := newIncomingResolverTestContext(t) - ctx.registry.notifyEvent = &invoices.HodlEvent{ - CircuitKey: testResCircuitKey, - Preimage: &testResPreimage, - } + ctx.registry.notifyResolution = invoices.NewSettleResolution( + testResPreimage, testResCircuitKey, testAcceptHeight, + invoices.ResultReplayToSettled, + ) + ctx.resolve() data := <-ctx.registry.notifyChan @@ -126,9 +139,11 @@ func TestHtlcIncomingResolverExitCancel(t *testing.T) { defer timeout(t)() ctx := newIncomingResolverTestContext(t) - ctx.registry.notifyEvent = &invoices.HodlEvent{ - CircuitKey: testResCircuitKey, - } + ctx.registry.notifyResolution = invoices.NewFailureResolution( + testResCircuitKey, testAcceptHeight, + invoices.ResultInvoiceAlreadyCanceled, + ) + ctx.resolve() ctx.waitForResult(false) } @@ -143,10 +158,10 @@ func TestHtlcIncomingResolverExitSettleHodl(t *testing.T) { ctx.resolve() notifyData := <-ctx.registry.notifyChan - notifyData.hodlChan <- invoices.HodlEvent{ - CircuitKey: testResCircuitKey, - Preimage: &testResPreimage, - } + notifyData.hodlChan <- *invoices.NewSettleResolution( + testResPreimage, testResCircuitKey, testAcceptHeight, + invoices.ResultSettled, + ) ctx.waitForResult(true) } @@ -172,9 +187,10 @@ func TestHtlcIncomingResolverExitCancelHodl(t *testing.T) { ctx := newIncomingResolverTestContext(t) ctx.resolve() notifyData := <-ctx.registry.notifyChan - notifyData.hodlChan <- invoices.HodlEvent{ - CircuitKey: testResCircuitKey, - } + notifyData.hodlChan <- *invoices.NewFailureResolution( + testResCircuitKey, testAcceptHeight, invoices.ResultCanceled, + ) + ctx.waitForResult(false) } diff --git a/contractcourt/interfaces.go b/contractcourt/interfaces.go index 45e9b0bd..086a2aee 100644 --- a/contractcourt/interfaces.go +++ b/contractcourt/interfaces.go @@ -27,9 +27,9 @@ type Registry interface { NotifyExitHopHtlc(payHash lntypes.Hash, paidAmount lnwire.MilliSatoshi, expiry uint32, currentHeight int32, 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{}) } diff --git a/contractcourt/mock_registry_test.go b/contractcourt/mock_registry_test.go index 43195249..dca27d62 100644 --- a/contractcourt/mock_registry_test.go +++ b/contractcourt/mock_registry_test.go @@ -16,15 +16,15 @@ type notifyExitHopData struct { } type mockRegistry struct { - notifyChan chan notifyExitHopData - notifyErr error - notifyEvent *invoices.HodlEvent + notifyChan chan notifyExitHopData + notifyErr error + notifyResolution *invoices.HtlcResolution } func (r *mockRegistry) NotifyExitHopHtlc(payHash lntypes.Hash, paidAmount lnwire.MilliSatoshi, expiry uint32, currentHeight int32, circuitKey channeldb.CircuitKey, hodlChan chan<- interface{}, - payload invoices.Payload) (*invoices.HodlEvent, error) { + payload invoices.Payload) (*invoices.HtlcResolution, error) { r.notifyChan <- notifyExitHopData{ hodlChan: hodlChan, @@ -34,7 +34,7 @@ func (r *mockRegistry) NotifyExitHopHtlc(payHash lntypes.Hash, currentHeight: currentHeight, } - return r.notifyEvent, r.notifyErr + return r.notifyResolution, r.notifyErr } func (r *mockRegistry) HodlUnsubscribeAll(subscriber chan<- interface{}) {} diff --git a/htlcswitch/interfaces.go b/htlcswitch/interfaces.go index 3b9ac6df..bb34b283 100644 --- a/htlcswitch/interfaces.go +++ b/htlcswitch/interfaces.go @@ -27,7 +27,7 @@ type InvoiceDatabase interface { NotifyExitHopHtlc(payHash lntypes.Hash, paidAmount lnwire.MilliSatoshi, expiry uint32, currentHeight int32, 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 // passed payment hash. @@ -36,7 +36,7 @@ type InvoiceDatabase interface { // SettleHodlInvoice settles a hold invoice. SettleHodlInvoice(preimage lntypes.Preimage) error - // HodlUnsubscribeAll unsubscribes from all hodl events. + // HodlUnsubscribeAll unsubscribes from all htlc resolutions. HodlUnsubscribeAll(subscriber chan<- interface{}) } diff --git a/htlcswitch/link.go b/htlcswitch/link.go index ce4d0073..0bba3f65 100644 --- a/htlcswitch/link.go +++ b/htlcswitch/link.go @@ -489,8 +489,8 @@ func (l *channelLink) Stop() { l.log.Info("stopping") - // As the link is stopping, we are no longer interested in hodl events - // coming from the invoice registry. + // As the link is stopping, we are no longer interested in htlc + // resolutions coming from the invoice registry. l.cfg.Registry.HodlUnsubscribeAll(l.hodlQueue.ChanIn()) if l.cfg.ChainEvents.Cancel != nil { @@ -1126,11 +1126,11 @@ out: case msg := <-l.upstream: 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. case hodlItem := <-l.hodlQueue.ChanOut(): - hodlEvent := hodlItem.(invoices.HodlEvent) - err := l.processHodlQueue(hodlEvent) + htlcResolution := hodlItem.(invoices.HtlcResolution) + err := l.processHodlQueue(htlcResolution) if err != nil { l.fail(LinkFailureError{code: ErrInternalError}, fmt.Sprintf("process hodl queue: %v", @@ -1145,24 +1145,26 @@ out: } } -// processHodlQueue processes a received hodl event and continues reading from -// the hodl queue until no more events remain. When this function returns -// without an error, the commit tx should be updated. -func (l *channelLink) processHodlQueue(firstHodlEvent invoices.HodlEvent) error { +// processHodlQueue processes a received htlc resolution and continues reading +// from the hodl queue until no more resolutions remain. When this function +// returns without an error, the commit tx should be updated. +func (l *channelLink) processHodlQueue( + firstResolution invoices.HtlcResolution) error { + // Try to read all waiting resolution messages, so that they can all be // processed in a single commitment tx update. - hodlEvent := firstHodlEvent + htlcResolution := firstResolution loop: for { // Lookup all hodl htlcs that can be failed or settled with this event. // The hodl htlc must be present in the map. - circuitKey := hodlEvent.CircuitKey + circuitKey := htlcResolution.CircuitKey hodlHtlc, ok := l.hodlMap[circuitKey] if !ok { 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 } @@ -1171,7 +1173,7 @@ loop: select { case item := <-l.hodlQueue.ChanOut(): - hodlEvent = item.(invoices.HodlEvent) + htlcResolution = item.(invoices.HtlcResolution) default: break loop } @@ -1185,30 +1187,30 @@ loop: return nil } -// processHodlEvent applies a received hodl event to the provided htlc. When -// this function returns without an error, the commit tx should be updated. -func (l *channelLink) processHodlEvent(hodlEvent invoices.HodlEvent, +// processHtlcResolution applies a received htlc resolution to the provided +// htlc. When this function returns without an error, the commit tx should be +// updated. +func (l *channelLink) processHtlcResolution(resolution invoices.HtlcResolution, htlc hodlHtlc) error { - circuitKey := hodlEvent.CircuitKey + circuitKey := resolution.CircuitKey - // Determine required action for the resolution. - if hodlEvent.Preimage != nil { - l.log.Debugf("received hodl settle event for %v", circuitKey) + // Determine required action for the resolution. If the event's preimage is + // non-nil, the htlc must be settled. Otherwise, it should be canceled. + if resolution.Preimage != nil { + l.log.Debugf("received settle resolution for %v", circuitKey) return l.settleHTLC( - *hodlEvent.Preimage, htlc.pd.HtlcIndex, + *resolution.Preimage, htlc.pd.HtlcIndex, 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 - // incorrect_or_unknown_payment_details in order to avoid leaking info. - failure := lnwire.NewFailIncorrectDetails( - htlc.pd.Amount, uint32(hodlEvent.AcceptHeight), - ) + // Get the lnwire failure message based on the resolution result. + failure := getResolutionFailure(resolution, htlc.pd.Amount) l.sendHTLCError( htlc.pd.HtlcIndex, failure, htlc.obfuscator, @@ -1217,6 +1219,25 @@ func (l *channelLink) processHodlEvent(hodlEvent invoices.HodlEvent, 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 // within the link's configuration that will be used to determine when the link // 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), circuitKey, l.hodlQueue.ChanIn(), payload, ) - - 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: + if err != nil { return err } @@ -2841,15 +2848,15 @@ func (l *channelLink) processExitHop(pd *lnwallet.PaymentDescriptor, obfuscator: obfuscator, } + // If the event is nil, the invoice is being held, so we save payment + // descriptor for future reference. if event == nil { - // Save payment descriptor for future reference. l.hodlMap[circuitKey] = htlc - return nil } // Process the received resolution. - return l.processHodlEvent(*event, htlc) + return l.processHtlcResolution(*event, htlc) } // settleHTLC settles the HTLC on the channel. diff --git a/htlcswitch/mock.go b/htlcswitch/mock.go index 1d0731e8..5c905822 100644 --- a/htlcswitch/mock.go +++ b/htlcswitch/mock.go @@ -819,7 +819,7 @@ func (i *mockInvoiceRegistry) SettleHodlInvoice(preimage lntypes.Preimage) error func (i *mockInvoiceRegistry) NotifyExitHopHtlc(rhash lntypes.Hash, amt lnwire.MilliSatoshi, expiry uint32, currentHeight int32, circuitKey channeldb.CircuitKey, hodlChan chan<- interface{}, - payload invoices.Payload) (*invoices.HodlEvent, error) { + payload invoices.Payload) (*invoices.HtlcResolution, error) { event, err := i.registry.NotifyExitHopHtlc( rhash, amt, expiry, currentHeight, circuitKey, hodlChan, diff --git a/invoices/invoiceregistry.go b/invoices/invoiceregistry.go index d4e77ad5..0b316324 100644 --- a/invoices/invoiceregistry.go +++ b/invoices/invoiceregistry.go @@ -35,10 +35,10 @@ const ( DefaultHtlcHoldDuration = 120 * time.Second ) -// HodlEvent describes how an htlc should be resolved. If HodlEvent.Preimage is -// set, the event indicates a settle event. If Preimage is nil, it is a cancel -// event. -type HodlEvent struct { +// HtlcResolution describes how an htlc should be resolved. If the preimage +// field is set, the event indicates a settle event. If Preimage is nil, it is +// a cancel event. +type HtlcResolution struct { // Preimage is the htlc preimage. Its value is nil in case of a cancel. Preimage *lntypes.Preimage @@ -48,6 +48,33 @@ type HodlEvent struct { // AcceptHeight is the original height at which the htlc was accepted. 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. @@ -325,7 +352,7 @@ func (i *InvoiceRegistry) invoiceEventLoop() { case <-nextReleaseTick: event := autoReleaseHeap.Pop().(*htlcReleaseEvent) err := i.cancelSingleHtlc( - event.hash, event.key, + event.hash, event.key, ResultMppTimeout, ) if err != nil { 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, - key channeldb.CircuitKey) error { + key channeldb.CircuitKey, result ResolutionResult) error { i.Lock() defer i.Unlock() @@ -652,11 +681,11 @@ func (i *InvoiceRegistry) cancelSingleHtlc(hash lntypes.Hash, return fmt.Errorf("htlc %v not found", key) } if htlc.State == channeldb.HtlcStateCanceled { - i.notifyHodlSubscribers(HodlEvent{ - CircuitKey: key, - AcceptHeight: int32(htlc.AcceptHeight), - Preimage: nil, - }) + resolution := *NewFailureResolution( + key, int32(htlc.AcceptHeight), result, + ) + + i.notifyHodlSubscribers(resolution) } return nil } @@ -679,7 +708,7 @@ func (i *InvoiceRegistry) cancelSingleHtlc(hash lntypes.Hash, func (i *InvoiceRegistry) NotifyExitHopHtlc(rHash lntypes.Hash, amtPaid lnwire.MilliSatoshi, expiry uint32, currentHeight int32, circuitKey channeldb.CircuitKey, hodlChan chan<- interface{}, - payload Payload) (*HodlEvent, error) { + payload Payload) (*HtlcResolution, error) { i.Lock() 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 // one exists). The callback will update the invoice state and/or htlcs. var ( - result updateResult + result ResolutionResult updateSubscribers bool ) invoice, err := i.cdb.UpdateInvoice( @@ -729,11 +758,21 @@ func (i *InvoiceRegistry) NotifyExitHopHtlc(rHash lntypes.Hash, return updateDesc, nil }, ) - if err != nil { - debugLog(err.Error()) + switch err { + 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 } + debugLog(result.String()) if updateSubscribers { @@ -745,10 +784,9 @@ func (i *InvoiceRegistry) NotifyExitHopHtlc(rHash lntypes.Hash, // If it isn't recorded, cancel htlc. if !ok { - return &HodlEvent{ - CircuitKey: circuitKey, - AcceptHeight: currentHeight, - }, nil + return NewFailureResolution( + circuitKey, currentHeight, result, + ), nil } // 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 { case channeldb.HtlcStateCanceled: - return &HodlEvent{ - CircuitKey: circuitKey, - AcceptHeight: acceptHeight, - }, nil + return NewFailureResolution( + circuitKey, acceptHeight, result, + ), nil case channeldb.HtlcStateSettled: // Also settle any previously accepted htlcs. The invoice state @@ -773,18 +810,24 @@ func (i *InvoiceRegistry) NotifyExitHopHtlc(rHash lntypes.Hash, continue } - i.notifyHodlSubscribers(HodlEvent{ - CircuitKey: key, - Preimage: &invoice.Terms.PaymentPreimage, - AcceptHeight: int32(htlc.AcceptHeight), - }) + // Notify subscribers that the htlcs should be settled + // with our peer. Note that the outcome of the + // resolution is set based on the outcome of the single + // 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{ - CircuitKey: circuitKey, - Preimage: &invoice.Terms.PaymentPreimage, - AcceptHeight: acceptHeight, - }, nil + resolution := NewSettleResolution( + invoice.Terms.PaymentPreimage, circuitKey, + acceptHeight, result, + ) + return resolution, nil case channeldb.HtlcStateAccepted: // (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() invoice, err := i.cdb.UpdateInvoice(hash, updateInvoice) if err != nil { - log.Errorf("SettleHodlInvoice with preimage %v: %v", preimage, err) + log.Errorf("SettleHodlInvoice with preimage %v: %v", + preimage, err) + return err } @@ -854,11 +899,11 @@ func (i *InvoiceRegistry) SettleHodlInvoice(preimage lntypes.Preimage) error { continue } - i.notifyHodlSubscribers(HodlEvent{ - CircuitKey: key, - Preimage: &preimage, - AcceptHeight: int32(htlc.AcceptHeight), - }) + resolution := *NewSettleResolution( + preimage, key, int32(htlc.AcceptHeight), ResultSettled, + ) + + i.notifyHodlSubscribers(resolution) } 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 // 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, cancelAccepted bool) error { @@ -932,10 +978,11 @@ func (i *InvoiceRegistry) cancelInvoiceImpl(payHash lntypes.Hash, continue } - i.notifyHodlSubscribers(HodlEvent{ - CircuitKey: key, - AcceptHeight: int32(htlc.AcceptHeight), - }) + i.notifyHodlSubscribers( + *NewFailureResolution( + key, int32(htlc.AcceptHeight), ResultCanceled, + ), + ) } i.notifyClients(payHash, invoice, channeldb.ContractCanceled) @@ -1201,9 +1248,10 @@ func (i *InvoiceRegistry) SubscribeSingleInvoice( return client, nil } -// notifyHodlSubscribers sends out the hodl event to all current subscribers. -func (i *InvoiceRegistry) notifyHodlSubscribers(hodlEvent HodlEvent) { - subscribers, ok := i.hodlSubscriptions[hodlEvent.CircuitKey] +// notifyHodlSubscribers sends out the htlc resolution to all current +// subscribers. +func (i *InvoiceRegistry) notifyHodlSubscribers(htlcResolution HtlcResolution) { + subscribers, ok := i.hodlSubscriptions[htlcResolution.CircuitKey] if !ok { return } @@ -1213,18 +1261,18 @@ func (i *InvoiceRegistry) notifyHodlSubscribers(hodlEvent HodlEvent) { // single resolution for each hash. for subscriber := range subscribers { select { - case subscriber <- hodlEvent: + case subscriber <- htlcResolution: case <-i.quit: return } delete( i.hodlReverseSubscriptions[subscriber], - hodlEvent.CircuitKey, + htlcResolution.CircuitKey, ) } - delete(i.hodlSubscriptions, hodlEvent.CircuitKey) + delete(i.hodlSubscriptions, htlcResolution.CircuitKey) } // hodlSubscribe adds a new invoice subscription. diff --git a/invoices/invoiceregistry_test.go b/invoices/invoiceregistry_test.go index e47eaa37..2750e942 100644 --- a/invoices/invoiceregistry_test.go +++ b/invoices/invoiceregistry_test.go @@ -66,7 +66,7 @@ func TestSettleInvoice(t *testing.T) { hodlChan := make(chan interface{}, 1) // 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, uint32(testCurrentHeight)+testInvoiceCltvDelta-1, testCurrentHeight, getCircuitKey(10), hodlChan, testPayload, @@ -74,23 +74,30 @@ func TestSettleInvoice(t *testing.T) { if err != nil { t.Fatal(err) } - if event.Preimage != nil { - t.Fatal("expected cancel event") + if resolution.Preimage != nil { + t.Fatal("expected cancel resolution") } - if event.AcceptHeight != testCurrentHeight { + if resolution.AcceptHeight != testCurrentHeight { 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. amtPaid := lnwire.MilliSatoshi(100500) - _, err = ctx.registry.NotifyExitHopHtlc( + resolution, err = ctx.registry.NotifyExitHopHtlc( testInvoicePaymentHash, amtPaid, testHtlcExpiry, testCurrentHeight, getCircuitKey(0), hodlChan, testPayload, ) if err != nil { 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 // subscriber. @@ -120,42 +127,54 @@ func TestSettleInvoice(t *testing.T) { // Try to settle again with the same htlc id. We need this idempotent // behaviour after a restart. - event, err = ctx.registry.NotifyExitHopHtlc( + resolution, err = ctx.registry.NotifyExitHopHtlc( testInvoicePaymentHash, amtPaid, testHtlcExpiry, testCurrentHeight, getCircuitKey(0), hodlChan, testPayload, ) if err != nil { t.Fatalf("unexpected NotifyExitHopHtlc error: %v", err) } - if event.Preimage == nil { - t.Fatal("expected settle event") + if resolution.Preimage == nil { + 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 // should also be accepted, to prevent any change in behaviour for a // paid invoice that may open up a probe vector. - event, err = ctx.registry.NotifyExitHopHtlc( + resolution, err = ctx.registry.NotifyExitHopHtlc( testInvoicePaymentHash, amtPaid+600, testHtlcExpiry, testCurrentHeight, getCircuitKey(1), hodlChan, testPayload, ) if err != nil { t.Fatalf("unexpected NotifyExitHopHtlc error: %v", err) } - if event.Preimage == nil { - t.Fatal("expected settle event") + if resolution.Preimage == nil { + 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 // would have failed if it were the first payment. - event, err = ctx.registry.NotifyExitHopHtlc( + resolution, err = ctx.registry.NotifyExitHopHtlc( testInvoicePaymentHash, amtPaid-600, testHtlcExpiry, testCurrentHeight, getCircuitKey(2), hodlChan, testPayload, ) if err != nil { t.Fatalf("unexpected NotifyExitHopHtlc error: %v", err) } - if event.Preimage != nil { - t.Fatal("expected cancel event") + if resolution.Preimage != nil { + 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 @@ -177,7 +196,7 @@ func TestSettleInvoice(t *testing.T) { // As this is a direct sette, we expect nothing on the hodl chan. select { case <-hodlChan: - t.Fatal("unexpected event") + t.Fatal("unexpected resolution") default: } } @@ -270,9 +289,9 @@ func TestCancelInvoice(t *testing.T) { } // 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{}) - event, err := ctx.registry.NotifyExitHopHtlc( + resolution, err := ctx.registry.NotifyExitHopHtlc( testInvoicePaymentHash, amt, testHtlcExpiry, testCurrentHeight, getCircuitKey(0), hodlChan, testPayload, ) @@ -280,12 +299,16 @@ func TestCancelInvoice(t *testing.T) { t.Fatal("expected settlement of a canceled invoice to succeed") } - if event.Preimage != nil { - t.Fatal("expected cancel hodl event") + if resolution.Preimage != nil { + t.Fatal("expected cancel htlc resolution") } - if event.AcceptHeight != testCurrentHeight { + if resolution.AcceptHeight != testCurrentHeight { 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 // should be possible. - event, err := registry.NotifyExitHopHtlc( + resolution, err := registry.NotifyExitHopHtlc( testInvoicePaymentHash, amtPaid, testHtlcExpiry, testCurrentHeight, getCircuitKey(0), hodlChan, testPayload, ) if err != nil { t.Fatalf("expected settle to succeed but got %v", err) } - if event != nil { + if resolution != nil { t.Fatalf("expected htlc to be held") } // Test idempotency. - event, err = registry.NotifyExitHopHtlc( + resolution, err = registry.NotifyExitHopHtlc( testInvoicePaymentHash, amtPaid, testHtlcExpiry, testCurrentHeight, getCircuitKey(0), hodlChan, testPayload, ) if err != nil { t.Fatalf("expected settle to succeed but got %v", err) } - if event != nil { + if resolution != nil { t.Fatalf("expected htlc to be held") } // Test replay at a higher height. We expect the same result because it // is a replay. - event, err = registry.NotifyExitHopHtlc( + resolution, err = registry.NotifyExitHopHtlc( testInvoicePaymentHash, amtPaid, testHtlcExpiry, testCurrentHeight+10, getCircuitKey(0), hodlChan, testPayload, ) if err != nil { t.Fatalf("expected settle to succeed but got %v", err) } - if event != nil { + if resolution != nil { t.Fatalf("expected htlc to be held") } // Test a new htlc coming in that doesn't meet the final cltv delta // requirement. It should be rejected. - event, err = registry.NotifyExitHopHtlc( + resolution, err = registry.NotifyExitHopHtlc( testInvoicePaymentHash, amtPaid, 1, testCurrentHeight, getCircuitKey(1), hodlChan, testPayload, ) if err != nil { 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") } + 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 // 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") } - hodlEvent := (<-hodlChan).(HodlEvent) - if *hodlEvent.Preimage != testInvoicePreimage { - t.Fatal("unexpected preimage in hodl event") + htlcResolution := (<-hodlChan).(HtlcResolution) + if *htlcResolution.Preimage != testInvoicePreimage { + t.Fatal("unexpected preimage in hodl resolution") } - if hodlEvent.AcceptHeight != testCurrentHeight { + if htlcResolution.AcceptHeight != testCurrentHeight { 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 @@ -496,14 +527,14 @@ func TestCancelHoldInvoice(t *testing.T) { // NotifyExitHopHtlc without a preimage present in the invoice registry // should be possible. - event, err := registry.NotifyExitHopHtlc( + resolution, err := registry.NotifyExitHopHtlc( testInvoicePaymentHash, amtPaid, testHtlcExpiry, testCurrentHeight, getCircuitKey(0), hodlChan, testPayload, ) if err != nil { t.Fatalf("expected settle to succeed but got %v", err) } - if event != nil { + if resolution != nil { t.Fatalf("expected htlc to be held") } @@ -513,35 +544,39 @@ func TestCancelHoldInvoice(t *testing.T) { t.Fatal("cancel invoice failed") } - hodlEvent := (<-hodlChan).(HodlEvent) - if hodlEvent.Preimage != nil { - t.Fatal("expected cancel hodl event") + htlcResolution := (<-hodlChan).(HtlcResolution) + if htlcResolution.Preimage != nil { + t.Fatal("expected cancel htlc resolution") } // Offering the same htlc again at a higher height should still result // in a rejection. The accept height is expected to be the original // accept height. - event, err = registry.NotifyExitHopHtlc( + resolution, err = registry.NotifyExitHopHtlc( testInvoicePaymentHash, amtPaid, testHtlcExpiry, testCurrentHeight+1, getCircuitKey(0), hodlChan, testPayload, ) if err != nil { 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") } - if event.AcceptHeight != testCurrentHeight { + if resolution.AcceptHeight != testCurrentHeight { 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 -// invoice is unknown. This is to guard against returning a cancel hodl event -// for forwarded htlcs. In the link, NotifyExitHopHtlc is only called if we are -// the exit hop, but in htlcIncomingContestResolver it is called with forwarded -// htlc hashes as well. +// invoice is unknown. This is to guard against returning a cancel htlc +// resolution for forwarded htlcs. In the link, NotifyExitHopHtlc is only called +// if we are the exit hop, but in htlcIncomingContestResolver it is called with +// forwarded htlc hashes as well. func TestUnknownInvoice(t *testing.T) { ctx := newTestContext(t) defer ctx.cleanup() @@ -550,17 +585,23 @@ func TestUnknownInvoice(t *testing.T) { // succeed. hodlChan := make(chan interface{}) amt := lnwire.MilliSatoshi(100000) - _, err := ctx.registry.NotifyExitHopHtlc( + result, err := ctx.registry.NotifyExitHopHtlc( testInvoicePaymentHash, amt, testHtlcExpiry, testCurrentHeight, getCircuitKey(0), hodlChan, testPayload, ) - if err != channeldb.ErrInvoiceNotFound { - t.Fatal("expected invoice not found error") + if err != nil { + 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. -func TestSettleMpp(t *testing.T) { +// TestMppPayment tests settling of an invoice with multiple partial payments. +// 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()() ctx := newTestContext(t) @@ -578,7 +619,7 @@ func TestSettleMpp(t *testing.T) { // Send htlc 1. hodlChan1 := make(chan interface{}, 1) - event, err := ctx.registry.NotifyExitHopHtlc( + resolution, err := ctx.registry.NotifyExitHopHtlc( testInvoicePaymentHash, testInvoice.Terms.Value/2, testHtlcExpiry, testCurrentHeight, getCircuitKey(10), hodlChan1, mppPayload, @@ -586,21 +627,25 @@ func TestSettleMpp(t *testing.T) { if err != nil { t.Fatal(err) } - if event != nil { + if resolution != nil { t.Fatal("expected no direct resolution") } // Simulate mpp timeout releasing htlc 1. ctx.clock.SetTime(testTime.Add(30 * time.Second)) - hodlEvent := (<-hodlChan1).(HodlEvent) - if hodlEvent.Preimage != nil { - t.Fatal("expected cancel event") + htlcResolution := (<-hodlChan1).(HtlcResolution) + if htlcResolution.Preimage != nil { + t.Fatal("expected cancel resolution") + } + if htlcResolution.Outcome != ResultMppTimeout { + t.Fatalf("expected mpp timeout, got: %v", + htlcResolution.Outcome) } // Send htlc 2. hodlChan2 := make(chan interface{}, 1) - event, err = ctx.registry.NotifyExitHopHtlc( + resolution, err = ctx.registry.NotifyExitHopHtlc( testInvoicePaymentHash, testInvoice.Terms.Value/2, testHtlcExpiry, testCurrentHeight, getCircuitKey(11), hodlChan2, mppPayload, @@ -608,13 +653,13 @@ func TestSettleMpp(t *testing.T) { if err != nil { t.Fatal(err) } - if event != nil { + if resolution != nil { t.Fatal("expected no direct resolution") } // Send htlc 3. hodlChan3 := make(chan interface{}, 1) - event, err = ctx.registry.NotifyExitHopHtlc( + resolution, err = ctx.registry.NotifyExitHopHtlc( testInvoicePaymentHash, testInvoice.Terms.Value/2, testHtlcExpiry, testCurrentHeight, getCircuitKey(12), hodlChan3, mppPayload, @@ -622,12 +667,16 @@ func TestSettleMpp(t *testing.T) { if err != nil { t.Fatal(err) } - if event == nil { - t.Fatal("expected a settle event") + if resolution == nil { + 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 - // 0 and 1. + // 2 and 3. inv, err := ctx.registry.LookupInvoice(testInvoicePaymentHash) if err != nil { t.Fatal(err) diff --git a/invoices/update.go b/invoices/update.go index 913caeb0..2d512dbe 100644 --- a/invoices/update.go +++ b/invoices/update.go @@ -8,86 +8,141 @@ import ( "github.com/lightningnetwork/lnd/record" ) -// updateResult is the result of the invoice update call. -type updateResult uint8 +// ResolutionResult provides metadata which about an invoice update which can +// 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 ( - resultInvalid updateResult = iota - resultReplayToCanceled - resultReplayToAccepted - resultReplayToSettled - resultInvoiceAlreadyCanceled - resultAmountTooLow - resultExpiryTooSoon - resultDuplicateToAccepted - resultDuplicateToSettled - resultAccepted - resultSettled - resultInvoiceNotOpen - resultPartialAccepted - resultMppInProgress - resultAddressMismatch - resultHtlcSetTotalMismatch - resultHtlcSetTotalTooLow - resultHtlcSetOverpayment + resultInvalid ResolutionResult = iota + + // ResultReplayToCanceled is returned when we replay a canceled invoice. + ResultReplayToCanceled + + // ResultReplayToAccepted is returned when we replay an accepted invoice. + ResultReplayToAccepted + + // ResultReplayToSettled is returned when we replay a settled invoice. + ResultReplayToSettled + + // ResultInvoiceAlreadyCanceled is returned when trying to pay an invoice + // that is already canceled. + ResultInvoiceAlreadyCanceled + + // ResultAmountTooLow is returned when an invoice is underpaid. + ResultAmountTooLow + + // 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. -func (u updateResult) String() string { +func (u ResolutionResult) String() string { switch u { case resultInvalid: return "invalid" - case resultReplayToCanceled: + case ResultReplayToCanceled: return "replayed htlc to canceled invoice" - case resultReplayToAccepted: + case ResultReplayToAccepted: return "replayed htlc to accepted invoice" - case resultReplayToSettled: + case ResultReplayToSettled: return "replayed htlc to settled invoice" - case resultInvoiceAlreadyCanceled: + case ResultInvoiceAlreadyCanceled: return "invoice already canceled" - case resultAmountTooLow: + case ResultAmountTooLow: return "amount too low" - case resultExpiryTooSoon: + case ResultExpiryTooSoon: return "expiry too soon" - case resultDuplicateToAccepted: + case ResultDuplicateToAccepted: return "accepting duplicate payment to accepted invoice" - case resultDuplicateToSettled: + case ResultDuplicateToSettled: return "accepting duplicate payment to settled invoice" - case resultAccepted: + case ResultAccepted: return "accepted" - case resultSettled: + case ResultSettled: return "settled" - case resultInvoiceNotOpen: + case ResultInvoiceNotOpen: return "invoice no longer open" - case resultPartialAccepted: + case ResultPartialAccepted: return "partial payment accepted" - case resultMppInProgress: + case ResultMppInProgress: return "mpp reception in progress" - case resultAddressMismatch: + case ResultAddressMismatch: return "payment address mismatch" - case resultHtlcSetTotalMismatch: + case ResultHtlcSetTotalMismatch: return "htlc total amt doesn't match set total" - case resultHtlcSetTotalTooLow: + case ResultHtlcSetTotalTooLow: return "set total too low for invoice" - case resultHtlcSetOverpayment: + case ResultHtlcSetOverpayment: return "mpp is overpaying set total" default: @@ -110,20 +165,20 @@ type invoiceUpdateCtx struct { // updateInvoice is a callback for DB.UpdateInvoice that contains the invoice // settlement logic. 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. htlc, ok := inv.Htlcs[ctx.circuitKey] if ok { switch htlc.State { case channeldb.HtlcStateCanceled: - return nil, resultReplayToCanceled, nil + return nil, ResultReplayToCanceled, nil case channeldb.HtlcStateAccepted: - return nil, resultReplayToAccepted, nil + return nil, ResultReplayToAccepted, nil case channeldb.HtlcStateSettled: - return nil, resultReplayToSettled, nil + return nil, ResultReplayToSettled, nil default: 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 // settlement logic for mpp payments. func updateMpp(ctx *invoiceUpdateCtx, inv *channeldb.Invoice) ( - *channeldb.InvoiceUpdateDesc, updateResult, error) { + *channeldb.InvoiceUpdateDesc, ResolutionResult, error) { // Start building the accept descriptor. 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 // to thwart probing. if inv.State != channeldb.ContractOpen { - return nil, resultInvoiceNotOpen, nil + return nil, ResultInvoiceNotOpen, nil } // Check the payment address that authorizes the payment. if ctx.mpp.PaymentAddr() != inv.Terms.PaymentAddr { - return nil, resultAddressMismatch, nil + return nil, ResultAddressMismatch, nil } // Don't accept zero-valued sets. 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 // is a zero-valued invoice, it will always be enough. 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. @@ -186,7 +241,7 @@ func updateMpp(ctx *invoiceUpdateCtx, inv *channeldb.Invoice) ( } if ctx.mpp.TotalMsat() != htlc.MppTotalAmt { - return nil, resultHtlcSetTotalMismatch, nil + return nil, ResultHtlcSetTotalMismatch, nil } newSetTotal += htlc.Amt @@ -197,16 +252,16 @@ func updateMpp(ctx *invoiceUpdateCtx, inv *channeldb.Invoice) ( // Make sure the communicated set total isn't overpaid. if newSetTotal > ctx.mpp.TotalMsat() { - return nil, resultHtlcSetOverpayment, nil + return nil, ResultHtlcSetOverpayment, nil } // The invoice is still open. Check the expiry. 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) { - return nil, resultExpiryTooSoon, nil + return nil, ResultExpiryTooSoon, nil } // 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. setComplete := newSetTotal == ctx.mpp.TotalMsat() if !setComplete { - return &update, resultPartialAccepted, nil + return &update, ResultPartialAccepted, nil } // 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{ NewState: channeldb.ContractAccepted, } - return &update, resultAccepted, nil + return &update, ResultAccepted, nil } update.State = &channeldb.InvoiceStateUpdateDesc{ @@ -239,18 +294,18 @@ func updateMpp(ctx *invoiceUpdateCtx, inv *channeldb.Invoice) ( Preimage: inv.Terms.PaymentPreimage, } - return &update, resultSettled, nil + return &update, ResultSettled, nil } // updateLegacy is a callback for DB.UpdateInvoice that contains the invoice // settlement logic for legacy payments. 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 // checking to do. 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 @@ -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 // enough. if ctx.amtPaid < inv.Terms.Value { - return nil, resultAmountTooLow, nil + return nil, ResultAmountTooLow, nil } // TODO(joostjager): Check invoice mpp required feature @@ -271,17 +326,17 @@ func updateLegacy(ctx *invoiceUpdateCtx, inv *channeldb.Invoice) ( if htlc.State == channeldb.HtlcStateAccepted && htlc.MppTotalAmt > 0 { - return nil, resultMppInProgress, nil + return nil, ResultMppInProgress, nil } } // The invoice is still open. Check the expiry. 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) { - return nil, resultExpiryTooSoon, nil + return nil, ResultExpiryTooSoon, nil } // Record HTLC in the invoice database. @@ -302,10 +357,10 @@ func updateLegacy(ctx *invoiceUpdateCtx, inv *channeldb.Invoice) ( // We do accept or settle the HTLC. switch inv.State { case channeldb.ContractAccepted: - return &update, resultDuplicateToAccepted, nil + return &update, ResultDuplicateToAccepted, nil 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 @@ -315,7 +370,7 @@ func updateLegacy(ctx *invoiceUpdateCtx, inv *channeldb.Invoice) ( update.State = &channeldb.InvoiceStateUpdateDesc{ NewState: channeldb.ContractAccepted, } - return &update, resultAccepted, nil + return &update, ResultAccepted, nil } update.State = &channeldb.InvoiceStateUpdateDesc{ @@ -323,5 +378,5 @@ func updateLegacy(ctx *invoiceUpdateCtx, inv *channeldb.Invoice) ( Preimage: inv.Terms.PaymentPreimage, } - return &update, resultSettled, nil + return &update, ResultSettled, nil } diff --git a/lnrpc/routerrpc/router.pb.go b/lnrpc/routerrpc/router.pb.go index b80c3f2c..1be1423e 100644 --- a/lnrpc/routerrpc/router.pb.go +++ b/lnrpc/routerrpc/router.pb.go @@ -109,6 +109,7 @@ const ( Failure_PERMANENT_NODE_FAILURE Failure_FailureCode = 20 Failure_PERMANENT_CHANNEL_FAILURE Failure_FailureCode = 21 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. Failure_UNKNOWN_FAILURE Failure_FailureCode = 998 @@ -142,6 +143,7 @@ var Failure_FailureCode_name = map[int32]string{ 20: "PERMANENT_NODE_FAILURE", 21: "PERMANENT_CHANNEL_FAILURE", 22: "EXPIRY_TOO_FAR", + 23: "MPP_TIMEOUT", 998: "UNKNOWN_FAILURE", 999: "UNREADABLE_FAILURE", } @@ -170,6 +172,7 @@ var Failure_FailureCode_value = map[string]int32{ "PERMANENT_NODE_FAILURE": 20, "PERMANENT_CHANNEL_FAILURE": 21, "EXPIRY_TOO_FAR": 22, + "MPP_TIMEOUT": 23, "UNKNOWN_FAILURE": 998, "UNREADABLE_FAILURE": 999, } @@ -1526,138 +1529,139 @@ func init() { func init() { proto.RegisterFile("routerrpc/router.proto", fileDescriptor_7a0613f69d37b0a5) } var fileDescriptor_7a0613f69d37b0a5 = []byte{ - // 2093 bytes of a gzipped FileDescriptorProto - 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x8c, 0x58, 0x4f, 0x73, 0xdb, 0xb8, - 0x15, 0x5f, 0x5a, 0xff, 0x9f, 0xfe, 0xd1, 0xb0, 0xe3, 0x30, 0x72, 0xbc, 0xf1, 0x32, 0x69, 0x56, - 0x93, 0xc9, 0xda, 0xa9, 0xdb, 0xcd, 0x64, 0x7a, 0x68, 0x47, 0x96, 0xa8, 0x35, 0x1d, 0x89, 0x72, - 0x20, 0x29, 0xbb, 0xe9, 0x1e, 0x30, 0xb4, 0x04, 0x5b, 0x1c, 0x53, 0xa4, 0x96, 0x84, 0xb2, 0xf1, - 0x77, 0xe8, 0xf7, 0x68, 0x0f, 0x6d, 0x2f, 0xfd, 0x4e, 0xed, 0xbd, 0x33, 0x3d, 0xf4, 0xd6, 0x01, - 0x40, 0x4a, 0x94, 0x2c, 0x67, 0x7b, 0xb2, 0xf0, 0x7b, 0x3f, 0xbc, 0x07, 0xe2, 0xe1, 0xfd, 0xf0, - 0x60, 0xd8, 0x0b, 0xfc, 0x39, 0xa3, 0x41, 0x30, 0x1b, 0x1d, 0xcb, 0x5f, 0x47, 0xb3, 0xc0, 0x67, - 0x3e, 0x2a, 0x2c, 0xf0, 0x5a, 0x21, 0x98, 0x8d, 0x24, 0xaa, 0xff, 0x37, 0x03, 0xa8, 0x4f, 0xbd, - 0xf1, 0x85, 0x7d, 0x3b, 0xa5, 0x1e, 0xc3, 0xf4, 0xa7, 0x39, 0x0d, 0x19, 0x42, 0x90, 0x1e, 0xd3, - 0x90, 0x69, 0xca, 0xa1, 0x52, 0x2f, 0x61, 0xf1, 0x1b, 0xa9, 0x90, 0xb2, 0xa7, 0x4c, 0xdb, 0x3a, - 0x54, 0xea, 0x29, 0xcc, 0x7f, 0xa2, 0x47, 0x90, 0xb7, 0xa7, 0x8c, 0x4c, 0x43, 0x9b, 0x69, 0x25, - 0x01, 0xe7, 0xec, 0x29, 0xeb, 0x86, 0x36, 0x43, 0x5f, 0x41, 0x69, 0x26, 0x5d, 0x92, 0x89, 0x1d, - 0x4e, 0xb4, 0x94, 0x70, 0x54, 0x8c, 0xb0, 0x33, 0x3b, 0x9c, 0xa0, 0x3a, 0xa8, 0x57, 0x8e, 0x67, - 0xbb, 0x64, 0xe4, 0xb2, 0x8f, 0x64, 0x4c, 0x5d, 0x66, 0x6b, 0xe9, 0x43, 0xa5, 0x9e, 0xc1, 0x15, - 0x81, 0x37, 0x5d, 0xf6, 0xb1, 0xc5, 0x51, 0xf4, 0x35, 0x54, 0x63, 0x67, 0x81, 0x5c, 0xa0, 0x96, - 0x39, 0x54, 0xea, 0x05, 0x5c, 0x99, 0xad, 0x2e, 0xfb, 0x6b, 0xa8, 0x32, 0x67, 0x4a, 0xfd, 0x39, - 0x23, 0x21, 0x1d, 0xf9, 0xde, 0x38, 0xd4, 0xb2, 0xd2, 0x63, 0x04, 0xf7, 0x25, 0x8a, 0x74, 0x28, - 0x5f, 0x51, 0x4a, 0x5c, 0x67, 0xea, 0x30, 0xc2, 0x97, 0x9f, 0x13, 0xcb, 0x2f, 0x5e, 0x51, 0xda, - 0xe1, 0x58, 0xdf, 0x66, 0xe8, 0x19, 0x54, 0x96, 0x1c, 0xf1, 0x8d, 0x65, 0x41, 0x2a, 0xc5, 0x24, - 0xf1, 0xa1, 0x2f, 0x41, 0xf5, 0xe7, 0xec, 0xda, 0x77, 0xbc, 0x6b, 0x32, 0x9a, 0xd8, 0x1e, 0x71, - 0xc6, 0x5a, 0xfe, 0x50, 0xa9, 0xa7, 0x4f, 0xb7, 0x5e, 0x29, 0xb8, 0x12, 0xdb, 0x9a, 0x13, 0xdb, - 0x33, 0xc7, 0xe8, 0x39, 0x54, 0x5d, 0x3b, 0x64, 0x64, 0xe2, 0xcf, 0xc8, 0x6c, 0x7e, 0x79, 0x43, - 0x6f, 0xb5, 0x8a, 0xd8, 0x99, 0x32, 0x87, 0xcf, 0xfc, 0xd9, 0x85, 0x00, 0xd1, 0x01, 0x80, 0xd8, - 0x15, 0x11, 0x5c, 0x2b, 0x88, 0x6f, 0x28, 0x70, 0x44, 0x04, 0x46, 0x27, 0x50, 0x14, 0xd9, 0x24, - 0x13, 0xc7, 0x63, 0xa1, 0x06, 0x87, 0xa9, 0x7a, 0xf1, 0x44, 0x3d, 0x72, 0x3d, 0x9e, 0x58, 0xcc, - 0x2d, 0x67, 0x8e, 0xc7, 0x70, 0x92, 0x84, 0xc6, 0xb0, 0xc3, 0xd3, 0x48, 0x46, 0xf3, 0x90, 0xf9, - 0x53, 0x12, 0xd0, 0x91, 0x1f, 0x8c, 0x43, 0xad, 0x28, 0xe6, 0xfe, 0xf6, 0x68, 0x71, 0x3a, 0x8e, - 0xee, 0x1e, 0x87, 0xa3, 0x16, 0x0d, 0x59, 0x53, 0xcc, 0xc3, 0x72, 0x9a, 0xe1, 0xb1, 0xe0, 0x16, - 0x6f, 0x8f, 0xd7, 0x71, 0xf4, 0x12, 0x90, 0xed, 0xba, 0xfe, 0xcf, 0x24, 0xa4, 0xee, 0x15, 0x89, - 0xd2, 0xa3, 0x55, 0x0f, 0x95, 0x7a, 0x1e, 0xab, 0xc2, 0xd2, 0xa7, 0xee, 0x55, 0xe4, 0x1e, 0xbd, - 0x86, 0xb2, 0x58, 0xd3, 0x15, 0xb5, 0xd9, 0x3c, 0xa0, 0xa1, 0xa6, 0x1e, 0xa6, 0xea, 0x95, 0x93, - 0xed, 0xe8, 0x4b, 0xda, 0x12, 0x3e, 0x75, 0x18, 0x2e, 0x71, 0x5e, 0x34, 0x0e, 0x6b, 0x2d, 0xd8, - 0xdb, 0xbc, 0x24, 0x7e, 0x48, 0xf9, 0xa6, 0xf2, 0x73, 0x9b, 0xc6, 0xfc, 0x27, 0xda, 0x85, 0xcc, - 0x47, 0xdb, 0x9d, 0x53, 0x71, 0x70, 0x4b, 0x58, 0x0e, 0x7e, 0xb7, 0xf5, 0x46, 0xd1, 0xdf, 0xc0, - 0xce, 0x20, 0xb0, 0x47, 0x37, 0x6b, 0x67, 0x7f, 0xfd, 0xe8, 0x2a, 0x77, 0x8e, 0xae, 0xfe, 0x17, - 0x05, 0xca, 0xd1, 0xac, 0x3e, 0xb3, 0xd9, 0x3c, 0x44, 0xdf, 0x40, 0x26, 0x64, 0x36, 0xa3, 0x82, - 0x5d, 0x39, 0x79, 0x98, 0xd8, 0xcf, 0x04, 0x91, 0x62, 0xc9, 0x42, 0x35, 0xc8, 0xcf, 0x02, 0xea, - 0x4c, 0xed, 0xeb, 0x78, 0x5d, 0x8b, 0x31, 0xd2, 0x21, 0x23, 0x26, 0x8b, 0x9a, 0x29, 0x9e, 0x94, - 0x92, 0x69, 0xc5, 0xd2, 0x84, 0xea, 0x90, 0x99, 0x30, 0x77, 0x14, 0x6a, 0x69, 0x91, 0x3e, 0x14, - 0x71, 0xce, 0x06, 0x9d, 0x66, 0x83, 0x31, 0x3a, 0x9d, 0x31, 0x2c, 0x09, 0xfa, 0xef, 0xa1, 0x2a, - 0x66, 0xb6, 0x29, 0xfd, 0x5c, 0x71, 0x3f, 0x04, 0x5e, 0xba, 0xa2, 0x14, 0x64, 0x81, 0x67, 0xed, - 0x29, 0xaf, 0x02, 0x7d, 0x0c, 0xea, 0x72, 0x7e, 0x38, 0xf3, 0xbd, 0x90, 0x47, 0x57, 0xf9, 0x32, - 0xf8, 0x91, 0xe7, 0x15, 0x22, 0x6a, 0x43, 0x11, 0xb3, 0x2a, 0x11, 0xde, 0xa6, 0x54, 0x54, 0xc7, - 0x73, 0x59, 0x90, 0xc4, 0xf5, 0x47, 0x37, 0xbc, 0xc4, 0xed, 0xdb, 0xc8, 0x7d, 0x99, 0xc3, 0x1d, - 0x7f, 0x74, 0xd3, 0xe2, 0xa0, 0xfe, 0xa3, 0x54, 0xa1, 0x81, 0x2f, 0xbf, 0xf2, 0xff, 0xce, 0xc4, - 0x72, 0xb3, 0xb6, 0xee, 0xdd, 0x2c, 0x9d, 0xc0, 0xce, 0x8a, 0xf3, 0xe8, 0x2b, 0x92, 0x39, 0x50, - 0xd6, 0x72, 0xf0, 0x12, 0x72, 0x57, 0xb6, 0xe3, 0xce, 0x83, 0xd8, 0x31, 0x4a, 0x24, 0xb4, 0x2d, - 0x2d, 0x38, 0xa6, 0xe8, 0xff, 0xc9, 0x41, 0x2e, 0x02, 0xd1, 0x09, 0xa4, 0x47, 0xfe, 0x38, 0x3e, - 0x07, 0x5f, 0xde, 0x9d, 0x16, 0xff, 0x6d, 0xfa, 0x63, 0x8a, 0x05, 0x17, 0xfd, 0x01, 0x2a, 0x5c, - 0x3a, 0x3c, 0xea, 0x92, 0xf9, 0x6c, 0x6c, 0x2f, 0x52, 0xaf, 0x25, 0x66, 0x37, 0x25, 0x61, 0x28, - 0xec, 0xb8, 0x3c, 0x4a, 0x0e, 0xd1, 0x3e, 0x14, 0x78, 0xb6, 0x65, 0x26, 0xd2, 0xe2, 0xec, 0xe7, - 0x39, 0x20, 0x72, 0xa0, 0x43, 0xd9, 0xf7, 0x1c, 0xdf, 0x23, 0xe1, 0xc4, 0x26, 0x27, 0xdf, 0xbe, - 0x16, 0xda, 0x59, 0xc2, 0x45, 0x01, 0xf6, 0x27, 0xf6, 0xc9, 0xb7, 0xaf, 0xd1, 0x13, 0x28, 0x0a, - 0xbd, 0xa1, 0x9f, 0x66, 0x4e, 0x70, 0x2b, 0x44, 0xb3, 0x8c, 0x85, 0x04, 0x19, 0x02, 0xe1, 0x55, - 0x74, 0xe5, 0xda, 0xd7, 0xa1, 0x10, 0xca, 0x32, 0x96, 0x03, 0xf4, 0x0a, 0x76, 0xa3, 0x3d, 0x20, - 0xa1, 0x3f, 0x0f, 0x46, 0x94, 0x38, 0xde, 0x98, 0x7e, 0x12, 0x02, 0x58, 0xc6, 0x28, 0xb2, 0xf5, - 0x85, 0xc9, 0xe4, 0x16, 0xb4, 0x07, 0xd9, 0x09, 0x75, 0xae, 0x27, 0x52, 0xd4, 0xca, 0x38, 0x1a, - 0xe9, 0x7f, 0xcd, 0x40, 0x31, 0xb1, 0x31, 0xa8, 0x04, 0x79, 0x6c, 0xf4, 0x0d, 0xfc, 0xde, 0x68, - 0xa9, 0x5f, 0xa0, 0x3a, 0x3c, 0x33, 0xad, 0x66, 0x0f, 0x63, 0xa3, 0x39, 0x20, 0x3d, 0x4c, 0x86, - 0xd6, 0x5b, 0xab, 0xf7, 0xbd, 0x45, 0x2e, 0x1a, 0x1f, 0xba, 0x86, 0x35, 0x20, 0x2d, 0x63, 0xd0, - 0x30, 0x3b, 0x7d, 0x55, 0x41, 0x8f, 0x41, 0x5b, 0x32, 0x63, 0x73, 0xa3, 0xdb, 0x1b, 0x5a, 0x03, - 0x75, 0x0b, 0x3d, 0x81, 0xfd, 0xb6, 0x69, 0x35, 0x3a, 0x64, 0xc9, 0x69, 0x76, 0x06, 0xef, 0x89, - 0xf1, 0xc3, 0x85, 0x89, 0x3f, 0xa8, 0xa9, 0x4d, 0x04, 0x5e, 0x53, 0xb1, 0x87, 0x34, 0x7a, 0x04, - 0x0f, 0x24, 0x41, 0x4e, 0x21, 0x83, 0x5e, 0x8f, 0xf4, 0x7b, 0x3d, 0x4b, 0xcd, 0xa0, 0x6d, 0x28, - 0x9b, 0xd6, 0xfb, 0x46, 0xc7, 0x6c, 0x11, 0x6c, 0x34, 0x3a, 0x5d, 0x35, 0x8b, 0x76, 0xa0, 0xba, - 0xce, 0xcb, 0x71, 0x17, 0x31, 0xaf, 0x67, 0x99, 0x3d, 0x8b, 0xbc, 0x37, 0x70, 0xdf, 0xec, 0x59, - 0x6a, 0x1e, 0xed, 0x01, 0x5a, 0x35, 0x9d, 0x75, 0x1b, 0x4d, 0xb5, 0x80, 0x1e, 0xc0, 0xf6, 0x2a, - 0xfe, 0xd6, 0xf8, 0xa0, 0x02, 0xd2, 0x60, 0x57, 0x2e, 0x8c, 0x9c, 0x1a, 0x9d, 0xde, 0xf7, 0xa4, - 0x6b, 0x5a, 0x66, 0x77, 0xd8, 0x55, 0x8b, 0x68, 0x17, 0xd4, 0xb6, 0x61, 0x10, 0xd3, 0xea, 0x0f, - 0xdb, 0x6d, 0xb3, 0x69, 0x1a, 0xd6, 0x40, 0x2d, 0xc9, 0xc8, 0x9b, 0x3e, 0xbc, 0xcc, 0x27, 0x34, - 0xcf, 0x1a, 0x96, 0x65, 0x74, 0x48, 0xcb, 0xec, 0x37, 0x4e, 0x3b, 0x46, 0x4b, 0xad, 0xa0, 0x03, - 0x78, 0x34, 0x30, 0xba, 0x17, 0x3d, 0xdc, 0xc0, 0x1f, 0x48, 0x6c, 0x6f, 0x37, 0xcc, 0xce, 0x10, - 0x1b, 0x6a, 0x15, 0x7d, 0x05, 0x07, 0xd8, 0x78, 0x37, 0x34, 0xb1, 0xd1, 0x22, 0x56, 0xaf, 0x65, - 0x90, 0xb6, 0xd1, 0x18, 0x0c, 0xb1, 0x41, 0xba, 0x66, 0xbf, 0x6f, 0x5a, 0xdf, 0xa9, 0x2a, 0x7a, - 0x06, 0x87, 0x0b, 0xca, 0xc2, 0xc1, 0x1a, 0x6b, 0x9b, 0x7f, 0x5f, 0x9c, 0x52, 0xcb, 0xf8, 0x61, - 0x40, 0x2e, 0x0c, 0x03, 0xab, 0x08, 0xd5, 0x60, 0x6f, 0x19, 0x5e, 0x06, 0x88, 0x62, 0xef, 0x70, - 0xdb, 0x85, 0x81, 0xbb, 0x0d, 0x8b, 0x27, 0x78, 0xc5, 0xb6, 0xcb, 0x97, 0xbd, 0xb4, 0xad, 0x2f, - 0xfb, 0x01, 0x42, 0x50, 0x49, 0x64, 0xa5, 0xdd, 0xc0, 0xea, 0x1e, 0xda, 0x85, 0x6a, 0xbc, 0x82, - 0x98, 0xf8, 0xcf, 0x1c, 0x7a, 0x08, 0x68, 0x68, 0x61, 0xa3, 0xd1, 0xe2, 0x1b, 0xb2, 0x30, 0xfc, - 0x2b, 0x77, 0x9e, 0xce, 0x6f, 0xa9, 0x29, 0xfd, 0x1f, 0x29, 0x28, 0xaf, 0xd4, 0x25, 0x7a, 0x0c, - 0x85, 0xd0, 0xb9, 0xf6, 0xc4, 0x35, 0x15, 0x89, 0xca, 0x12, 0x10, 0xb7, 0xfa, 0xc4, 0x76, 0x3c, - 0xa9, 0x66, 0x52, 0xf7, 0x0b, 0x02, 0x11, 0x5a, 0xb6, 0x0f, 0xb9, 0xb8, 0x83, 0x48, 0x2d, 0x3a, - 0x88, 0xec, 0x48, 0x76, 0x0e, 0x8f, 0xa1, 0xc0, 0x25, 0x33, 0x64, 0xf6, 0x74, 0x26, 0x4a, 0xbc, - 0x8c, 0x97, 0x00, 0x7a, 0x0a, 0xe5, 0x29, 0x0d, 0x43, 0xfb, 0x9a, 0x12, 0x59, 0xa6, 0x20, 0x18, - 0xa5, 0x08, 0x6c, 0x8b, 0x6a, 0x7d, 0x0a, 0xb1, 0x6c, 0x44, 0xa4, 0x8c, 0x24, 0x45, 0xa0, 0x24, - 0xad, 0x2b, 0x36, 0xb3, 0x23, 0x35, 0x48, 0x2a, 0x36, 0xb3, 0xd1, 0x0b, 0xd8, 0x96, 0x92, 0xe3, - 0x78, 0xce, 0x74, 0x3e, 0x95, 0xd2, 0x93, 0x13, 0xd2, 0x53, 0x15, 0xd2, 0x23, 0x71, 0xa1, 0x40, - 0x8f, 0x20, 0x7f, 0x69, 0x87, 0x94, 0x5f, 0x16, 0x91, 0x34, 0xe4, 0xf8, 0xb8, 0x4d, 0x29, 0x37, - 0xf1, 0x2b, 0x24, 0xe0, 0xa2, 0x27, 0x15, 0x21, 0x77, 0x45, 0x29, 0xe6, 0x7b, 0xb9, 0x88, 0x60, - 0x7f, 0x5a, 0x46, 0x28, 0x26, 0x22, 0x48, 0x5c, 0x44, 0x78, 0x01, 0xdb, 0xf4, 0x13, 0x0b, 0x6c, - 0xe2, 0xcf, 0xec, 0x9f, 0xe6, 0x94, 0x8c, 0x6d, 0x66, 0x8b, 0x96, 0xb4, 0x84, 0xab, 0xc2, 0xd0, - 0x13, 0x78, 0xcb, 0x66, 0xb6, 0xfe, 0x18, 0x6a, 0x98, 0x86, 0x94, 0x75, 0x9d, 0x30, 0x74, 0x7c, - 0xaf, 0xe9, 0x7b, 0x2c, 0xf0, 0xdd, 0xe8, 0xce, 0xd1, 0x0f, 0x60, 0x7f, 0xa3, 0x55, 0x5e, 0x1a, - 0x7c, 0xf2, 0xbb, 0x39, 0x0d, 0x6e, 0x37, 0x4f, 0x7e, 0x07, 0xfb, 0x1b, 0xad, 0xd1, 0x8d, 0xf3, - 0x12, 0x32, 0x33, 0xdb, 0x09, 0x42, 0x6d, 0x4b, 0xdc, 0xda, 0x7b, 0x2b, 0x4d, 0x82, 0x13, 0x9c, - 0x39, 0x21, 0xf3, 0x83, 0x5b, 0x2c, 0x49, 0xe7, 0xe9, 0xbc, 0xa2, 0x6e, 0xe9, 0x7f, 0x52, 0xa0, - 0x98, 0x30, 0xf2, 0x73, 0xe0, 0xf9, 0x63, 0x4a, 0xae, 0x02, 0x7f, 0x1a, 0x9f, 0xb0, 0x05, 0x80, - 0x34, 0xc8, 0x89, 0x01, 0xf3, 0xa3, 0xe3, 0x15, 0x0f, 0xd1, 0x37, 0x90, 0x9b, 0x48, 0x17, 0x22, - 0x4b, 0xc5, 0x93, 0x9d, 0xb5, 0xe8, 0x7c, 0x6f, 0x70, 0xcc, 0x39, 0x4f, 0xe7, 0x53, 0x6a, 0xfa, - 0x3c, 0x9d, 0x4f, 0xab, 0x99, 0xf3, 0x74, 0x3e, 0xa3, 0x66, 0xcf, 0xd3, 0xf9, 0xac, 0x9a, 0xd3, - 0xff, 0xad, 0x40, 0x3e, 0x66, 0xf3, 0xb5, 0x70, 0x89, 0x27, 0xfc, 0x64, 0x44, 0x0d, 0xc0, 0x12, - 0x40, 0x3a, 0x94, 0xc4, 0x60, 0xb5, 0xaf, 0x58, 0xc1, 0xd0, 0x33, 0x28, 0x2f, 0xc6, 0x8b, 0xcb, - 0x2b, 0x85, 0x57, 0x41, 0xee, 0x29, 0x9c, 0x8f, 0x46, 0x34, 0x0c, 0x65, 0xa8, 0x8c, 0xf4, 0x94, - 0xc4, 0x50, 0x1d, 0xaa, 0xf1, 0x38, 0x0e, 0x98, 0x15, 0xb4, 0x75, 0x18, 0xbd, 0x00, 0x35, 0x09, - 0x4d, 0x97, 0xed, 0xff, 0x1d, 0x5c, 0x6e, 0x83, 0x3e, 0x85, 0x87, 0x22, 0xad, 0x17, 0x81, 0x7f, - 0x69, 0x5f, 0x3a, 0xae, 0xc3, 0x6e, 0xe3, 0x16, 0x85, 0x6f, 0x41, 0xe0, 0x4f, 0x89, 0x17, 0xdf, - 0xf9, 0x25, 0xbc, 0x04, 0x78, 0x3a, 0x98, 0x2f, 0x6d, 0x51, 0x3a, 0xa2, 0x21, 0x6f, 0x3e, 0x16, - 0xc1, 0x53, 0x22, 0xf8, 0x62, 0xac, 0xdf, 0x80, 0x76, 0x37, 0x5c, 0x74, 0x84, 0x0e, 0xa1, 0x38, - 0x5b, 0xc2, 0x22, 0xa2, 0x82, 0x93, 0x50, 0x32, 0xd1, 0x5b, 0xbf, 0x9c, 0x68, 0xfd, 0xcf, 0x0a, - 0x6c, 0x9f, 0xce, 0x1d, 0x77, 0xbc, 0xd2, 0x79, 0x25, 0x5f, 0x76, 0xca, 0xea, 0xcb, 0x6e, 0xd3, - 0xb3, 0x6d, 0x6b, 0xe3, 0xb3, 0x6d, 0xd3, 0xd3, 0x28, 0x75, 0xef, 0xd3, 0xe8, 0x09, 0x14, 0x97, - 0xaf, 0x22, 0xd9, 0xd8, 0x96, 0x30, 0x4c, 0xe2, 0x27, 0x51, 0xa8, 0xbf, 0x01, 0x94, 0x5c, 0x68, - 0xb4, 0x21, 0x8b, 0x06, 0x50, 0xb9, 0xb7, 0x01, 0x7c, 0xf1, 0x77, 0x05, 0x4a, 0xc9, 0x2e, 0x1c, - 0x95, 0xa1, 0x60, 0x5a, 0xa4, 0xdd, 0x31, 0xbf, 0x3b, 0x1b, 0xa8, 0x5f, 0xf0, 0x61, 0x7f, 0xd8, - 0x6c, 0x1a, 0x46, 0xcb, 0x68, 0xa9, 0x0a, 0xbf, 0x1f, 0xb8, 0xd4, 0x1b, 0x2d, 0x32, 0x30, 0xbb, - 0x46, 0x6f, 0xc8, 0x3b, 0x87, 0x1d, 0xa8, 0x46, 0x98, 0xd5, 0x23, 0xb8, 0x37, 0x1c, 0x18, 0x6a, - 0x0a, 0xa9, 0x50, 0x8a, 0x40, 0x03, 0xe3, 0x1e, 0x56, 0xd3, 0xfc, 0xba, 0x8b, 0x90, 0xbb, 0x5d, - 0x48, 0xdc, 0xa4, 0x64, 0x44, 0x97, 0x11, 0xb3, 0x96, 0x17, 0x34, 0x39, 0x6d, 0x74, 0x1a, 0x56, - 0xd3, 0x50, 0xb3, 0x27, 0x7f, 0xcb, 0x40, 0x56, 0x7c, 0x41, 0x80, 0xce, 0xa0, 0x98, 0x78, 0x90, - 0xa1, 0x83, 0xcf, 0x3e, 0xd4, 0x6a, 0xda, 0xe6, 0x77, 0xc7, 0x3c, 0x7c, 0xa5, 0xa0, 0x73, 0x28, - 0x25, 0x9f, 0x3b, 0x28, 0xd9, 0x9b, 0x6e, 0x78, 0x07, 0x7d, 0xd6, 0xd7, 0x5b, 0x50, 0x8d, 0x90, - 0x39, 0x53, 0xde, 0x8b, 0x46, 0xaf, 0x03, 0x54, 0x4b, 0xf0, 0xd7, 0x9e, 0x1c, 0xb5, 0xfd, 0x8d, - 0xb6, 0x28, 0x85, 0x1d, 0xf9, 0x89, 0x51, 0x7f, 0x7e, 0xe7, 0x13, 0x57, 0x1f, 0x05, 0xb5, 0x2f, - 0xef, 0x33, 0x47, 0xde, 0xc6, 0xb0, 0xb3, 0x41, 0xc0, 0xd1, 0xaf, 0x92, 0x2b, 0xb8, 0x57, 0xfe, - 0x6b, 0xcf, 0x7f, 0x89, 0xb6, 0x8c, 0xb2, 0x41, 0xe9, 0x57, 0xa2, 0xdc, 0x7f, 0x4f, 0xac, 0x44, - 0xf9, 0xdc, 0x85, 0xf1, 0x23, 0xa8, 0xeb, 0x4a, 0x80, 0xf4, 0xf5, 0xb9, 0x77, 0x55, 0xa9, 0xf6, - 0xf4, 0xb3, 0x9c, 0xc8, 0xb9, 0x09, 0xb0, 0xac, 0x27, 0xf4, 0x38, 0x31, 0xe5, 0x8e, 0x1e, 0xd4, - 0x0e, 0xee, 0xb1, 0x4a, 0x57, 0xa7, 0xbf, 0xfe, 0xe3, 0xf1, 0xb5, 0xc3, 0x26, 0xf3, 0xcb, 0xa3, - 0x91, 0x3f, 0x3d, 0x76, 0x79, 0x47, 0xef, 0x39, 0xde, 0xb5, 0x47, 0xd9, 0xcf, 0x7e, 0x70, 0x73, - 0xec, 0x7a, 0xe3, 0x63, 0x51, 0x96, 0xc7, 0x0b, 0x2f, 0x97, 0x59, 0xf1, 0xff, 0xa7, 0xdf, 0xfc, - 0x2f, 0x00, 0x00, 0xff, 0xff, 0x42, 0x87, 0x8c, 0xeb, 0xaf, 0x12, 0x00, 0x00, + // 2102 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x8c, 0x58, 0xcb, 0x73, 0xdb, 0xc6, + 0x19, 0x0f, 0xc4, 0xf7, 0xc7, 0x17, 0xb4, 0x92, 0x65, 0x98, 0xb2, 0x62, 0x05, 0x76, 0x1d, 0x8e, + 0xc7, 0x91, 0x5c, 0xb5, 0xf1, 0x78, 0x7a, 0x68, 0x87, 0x22, 0xc1, 0x08, 0x32, 0x09, 0xca, 0x4b, + 0xd2, 0x89, 0x9b, 0xc3, 0x0e, 0x44, 0xae, 0x44, 0x8c, 0x40, 0x80, 0x01, 0x96, 0x8e, 0xf5, 0x0f, + 0xf4, 0xd4, 0xff, 0xa3, 0xbd, 0xb4, 0x97, 0x9e, 0xfb, 0xef, 0xb4, 0xf7, 0xde, 0x7a, 0xeb, 0xec, + 0x2e, 0x40, 0x82, 0x14, 0xe5, 0xf4, 0x44, 0xec, 0xef, 0x7b, 0xed, 0xee, 0xb7, 0xdf, 0x8b, 0xb0, + 0x17, 0xf8, 0x73, 0x46, 0x83, 0x60, 0x36, 0x3a, 0x96, 0x5f, 0x47, 0xb3, 0xc0, 0x67, 0x3e, 0x2a, + 0x2c, 0xf0, 0x5a, 0x21, 0x98, 0x8d, 0x24, 0xaa, 0xff, 0x37, 0x03, 0xa8, 0x4f, 0xbd, 0xf1, 0x85, + 0x7d, 0x3b, 0xa5, 0x1e, 0xc3, 0xf4, 0xa7, 0x39, 0x0d, 0x19, 0x42, 0x90, 0x1e, 0xd3, 0x90, 0x69, + 0xca, 0xa1, 0x52, 0x2f, 0x61, 0xf1, 0x8d, 0x54, 0x48, 0xd9, 0x53, 0xa6, 0x6d, 0x1d, 0x2a, 0xf5, + 0x14, 0xe6, 0x9f, 0xe8, 0x11, 0xe4, 0xed, 0x29, 0x23, 0xd3, 0xd0, 0x66, 0x5a, 0x49, 0xc0, 0x39, + 0x7b, 0xca, 0xba, 0xa1, 0xcd, 0xd0, 0x57, 0x50, 0x9a, 0x49, 0x95, 0x64, 0x62, 0x87, 0x13, 0x2d, + 0x25, 0x14, 0x15, 0x23, 0xec, 0xcc, 0x0e, 0x27, 0xa8, 0x0e, 0xea, 0x95, 0xe3, 0xd9, 0x2e, 0x19, + 0xb9, 0xec, 0x23, 0x19, 0x53, 0x97, 0xd9, 0x5a, 0xfa, 0x50, 0xa9, 0x67, 0x70, 0x45, 0xe0, 0x4d, + 0x97, 0x7d, 0x6c, 0x71, 0x14, 0x7d, 0x0d, 0xd5, 0x58, 0x59, 0x20, 0x37, 0xa8, 0x65, 0x0e, 0x95, + 0x7a, 0x01, 0x57, 0x66, 0xab, 0xdb, 0xfe, 0x1a, 0xaa, 0xcc, 0x99, 0x52, 0x7f, 0xce, 0x48, 0x48, + 0x47, 0xbe, 0x37, 0x0e, 0xb5, 0xac, 0xd4, 0x18, 0xc1, 0x7d, 0x89, 0x22, 0x1d, 0xca, 0x57, 0x94, + 0x12, 0xd7, 0x99, 0x3a, 0x8c, 0xf0, 0xed, 0xe7, 0xc4, 0xf6, 0x8b, 0x57, 0x94, 0x76, 0x38, 0xd6, + 0xb7, 0x19, 0x7a, 0x06, 0x95, 0x25, 0x8f, 0x38, 0x63, 0x59, 0x30, 0x95, 0x62, 0x26, 0x71, 0xd0, + 0x97, 0xa0, 0xfa, 0x73, 0x76, 0xed, 0x3b, 0xde, 0x35, 0x19, 0x4d, 0x6c, 0x8f, 0x38, 0x63, 0x2d, + 0x7f, 0xa8, 0xd4, 0xd3, 0xa7, 0x5b, 0xaf, 0x14, 0x5c, 0x89, 0x69, 0xcd, 0x89, 0xed, 0x99, 0x63, + 0xf4, 0x1c, 0xaa, 0xae, 0x1d, 0x32, 0x32, 0xf1, 0x67, 0x64, 0x36, 0xbf, 0xbc, 0xa1, 0xb7, 0x5a, + 0x45, 0xdc, 0x4c, 0x99, 0xc3, 0x67, 0xfe, 0xec, 0x42, 0x80, 0xe8, 0x00, 0x40, 0xdc, 0x8a, 0x30, + 0xae, 0x15, 0xc4, 0x19, 0x0a, 0x1c, 0x11, 0x86, 0xd1, 0x09, 0x14, 0x85, 0x37, 0xc9, 0xc4, 0xf1, + 0x58, 0xa8, 0xc1, 0x61, 0xaa, 0x5e, 0x3c, 0x51, 0x8f, 0x5c, 0x8f, 0x3b, 0x16, 0x73, 0xca, 0x99, + 0xe3, 0x31, 0x9c, 0x64, 0x42, 0x63, 0xd8, 0xe1, 0x6e, 0x24, 0xa3, 0x79, 0xc8, 0xfc, 0x29, 0x09, + 0xe8, 0xc8, 0x0f, 0xc6, 0xa1, 0x56, 0x14, 0xb2, 0xbf, 0x3d, 0x5a, 0xbc, 0x8e, 0xa3, 0xbb, 0xcf, + 0xe1, 0xa8, 0x45, 0x43, 0xd6, 0x14, 0x72, 0x58, 0x8a, 0x19, 0x1e, 0x0b, 0x6e, 0xf1, 0xf6, 0x78, + 0x1d, 0x47, 0x2f, 0x01, 0xd9, 0xae, 0xeb, 0xff, 0x4c, 0x42, 0xea, 0x5e, 0x91, 0xc8, 0x3d, 0x5a, + 0xf5, 0x50, 0xa9, 0xe7, 0xb1, 0x2a, 0x28, 0x7d, 0xea, 0x5e, 0x45, 0xea, 0xd1, 0x6b, 0x28, 0x8b, + 0x3d, 0x5d, 0x51, 0x9b, 0xcd, 0x03, 0x1a, 0x6a, 0xea, 0x61, 0xaa, 0x5e, 0x39, 0xd9, 0x8e, 0x4e, + 0xd2, 0x96, 0xf0, 0xa9, 0xc3, 0x70, 0x89, 0xf3, 0x45, 0xeb, 0xb0, 0xd6, 0x82, 0xbd, 0xcd, 0x5b, + 0xe2, 0x8f, 0x94, 0x5f, 0x2a, 0x7f, 0xb7, 0x69, 0xcc, 0x3f, 0xd1, 0x2e, 0x64, 0x3e, 0xda, 0xee, + 0x9c, 0x8a, 0x87, 0x5b, 0xc2, 0x72, 0xf1, 0xbb, 0xad, 0x37, 0x8a, 0xfe, 0x06, 0x76, 0x06, 0x81, + 0x3d, 0xba, 0x59, 0x7b, 0xfb, 0xeb, 0x4f, 0x57, 0xb9, 0xf3, 0x74, 0xf5, 0xbf, 0x2a, 0x50, 0x8e, + 0xa4, 0xfa, 0xcc, 0x66, 0xf3, 0x10, 0x7d, 0x03, 0x99, 0x90, 0xd9, 0x8c, 0x0a, 0xee, 0xca, 0xc9, + 0xc3, 0xc4, 0x7d, 0x26, 0x18, 0x29, 0x96, 0x5c, 0xa8, 0x06, 0xf9, 0x59, 0x40, 0x9d, 0xa9, 0x7d, + 0x1d, 0xef, 0x6b, 0xb1, 0x46, 0x3a, 0x64, 0x84, 0xb0, 0x88, 0x99, 0xe2, 0x49, 0x29, 0xe9, 0x56, + 0x2c, 0x49, 0xa8, 0x0e, 0x99, 0x09, 0x73, 0x47, 0xa1, 0x96, 0x16, 0xee, 0x43, 0x11, 0xcf, 0xd9, + 0xa0, 0xd3, 0x6c, 0x30, 0x46, 0xa7, 0x33, 0x86, 0x25, 0x83, 0xfe, 0x7b, 0xa8, 0x0a, 0xc9, 0x36, + 0xa5, 0x9f, 0x0b, 0xee, 0x87, 0xc0, 0x43, 0x57, 0x84, 0x82, 0x0c, 0xf0, 0xac, 0x3d, 0xe5, 0x51, + 0xa0, 0x8f, 0x41, 0x5d, 0xca, 0x87, 0x33, 0xdf, 0x0b, 0xb9, 0x75, 0x95, 0x6f, 0x83, 0x3f, 0x79, + 0x1e, 0x21, 0x22, 0x36, 0x14, 0x21, 0x55, 0x89, 0xf0, 0x36, 0xa5, 0x22, 0x3a, 0x9e, 0xcb, 0x80, + 0x24, 0xae, 0x3f, 0xba, 0xe1, 0x21, 0x6e, 0xdf, 0x46, 0xea, 0xcb, 0x1c, 0xee, 0xf8, 0xa3, 0x9b, + 0x16, 0x07, 0xf5, 0x1f, 0x65, 0x16, 0x1a, 0xf8, 0xf2, 0x94, 0xff, 0xb7, 0x27, 0x96, 0x97, 0xb5, + 0x75, 0xef, 0x65, 0xe9, 0x04, 0x76, 0x56, 0x94, 0x47, 0xa7, 0x48, 0xfa, 0x40, 0x59, 0xf3, 0xc1, + 0x4b, 0xc8, 0x5d, 0xd9, 0x8e, 0x3b, 0x0f, 0x62, 0xc5, 0x28, 0xe1, 0xd0, 0xb6, 0xa4, 0xe0, 0x98, + 0x45, 0xff, 0x53, 0x1e, 0x72, 0x11, 0x88, 0x4e, 0x20, 0x3d, 0xf2, 0xc7, 0xf1, 0x3b, 0xf8, 0xf2, + 0xae, 0x58, 0xfc, 0xdb, 0xf4, 0xc7, 0x14, 0x0b, 0x5e, 0xf4, 0x07, 0xa8, 0xf0, 0xd4, 0xe1, 0x51, + 0x97, 0xcc, 0x67, 0x63, 0x7b, 0xe1, 0x7a, 0x2d, 0x21, 0xdd, 0x94, 0x0c, 0x43, 0x41, 0xc7, 0xe5, + 0x51, 0x72, 0x89, 0xf6, 0xa1, 0xc0, 0xbd, 0x2d, 0x3d, 0x91, 0x16, 0x6f, 0x3f, 0xcf, 0x01, 0xe1, + 0x03, 0x1d, 0xca, 0xbe, 0xe7, 0xf8, 0x1e, 0x09, 0x27, 0x36, 0x39, 0xf9, 0xf6, 0xb5, 0xc8, 0x9d, + 0x25, 0x5c, 0x14, 0x60, 0x7f, 0x62, 0x9f, 0x7c, 0xfb, 0x1a, 0x3d, 0x81, 0xa2, 0xc8, 0x37, 0xf4, + 0xd3, 0xcc, 0x09, 0x6e, 0x45, 0xd2, 0x2c, 0x63, 0x91, 0x82, 0x0c, 0x81, 0xf0, 0x28, 0xba, 0x72, + 0xed, 0xeb, 0x50, 0x24, 0xca, 0x32, 0x96, 0x0b, 0xf4, 0x0a, 0x76, 0xa3, 0x3b, 0x20, 0xa1, 0x3f, + 0x0f, 0x46, 0x94, 0x38, 0xde, 0x98, 0x7e, 0x12, 0x09, 0xb0, 0x8c, 0x51, 0x44, 0xeb, 0x0b, 0x92, + 0xc9, 0x29, 0x68, 0x0f, 0xb2, 0x13, 0xea, 0x5c, 0x4f, 0x64, 0x52, 0x2b, 0xe3, 0x68, 0xa5, 0xff, + 0x33, 0x03, 0xc5, 0xc4, 0xc5, 0xa0, 0x12, 0xe4, 0xb1, 0xd1, 0x37, 0xf0, 0x7b, 0xa3, 0xa5, 0x7e, + 0x81, 0xea, 0xf0, 0xcc, 0xb4, 0x9a, 0x3d, 0x8c, 0x8d, 0xe6, 0x80, 0xf4, 0x30, 0x19, 0x5a, 0x6f, + 0xad, 0xde, 0xf7, 0x16, 0xb9, 0x68, 0x7c, 0xe8, 0x1a, 0xd6, 0x80, 0xb4, 0x8c, 0x41, 0xc3, 0xec, + 0xf4, 0x55, 0x05, 0x3d, 0x06, 0x6d, 0xc9, 0x19, 0x93, 0x1b, 0xdd, 0xde, 0xd0, 0x1a, 0xa8, 0x5b, + 0xe8, 0x09, 0xec, 0xb7, 0x4d, 0xab, 0xd1, 0x21, 0x4b, 0x9e, 0x66, 0x67, 0xf0, 0x9e, 0x18, 0x3f, + 0x5c, 0x98, 0xf8, 0x83, 0x9a, 0xda, 0xc4, 0xc0, 0x63, 0x2a, 0xd6, 0x90, 0x46, 0x8f, 0xe0, 0x81, + 0x64, 0x90, 0x22, 0x64, 0xd0, 0xeb, 0x91, 0x7e, 0xaf, 0x67, 0xa9, 0x19, 0xb4, 0x0d, 0x65, 0xd3, + 0x7a, 0xdf, 0xe8, 0x98, 0x2d, 0x82, 0x8d, 0x46, 0xa7, 0xab, 0x66, 0xd1, 0x0e, 0x54, 0xd7, 0xf9, + 0x72, 0x5c, 0x45, 0xcc, 0xd7, 0xb3, 0xcc, 0x9e, 0x45, 0xde, 0x1b, 0xb8, 0x6f, 0xf6, 0x2c, 0x35, + 0x8f, 0xf6, 0x00, 0xad, 0x92, 0xce, 0xba, 0x8d, 0xa6, 0x5a, 0x40, 0x0f, 0x60, 0x7b, 0x15, 0x7f, + 0x6b, 0x7c, 0x50, 0x01, 0x69, 0xb0, 0x2b, 0x37, 0x46, 0x4e, 0x8d, 0x4e, 0xef, 0x7b, 0xd2, 0x35, + 0x2d, 0xb3, 0x3b, 0xec, 0xaa, 0x45, 0xb4, 0x0b, 0x6a, 0xdb, 0x30, 0x88, 0x69, 0xf5, 0x87, 0xed, + 0xb6, 0xd9, 0x34, 0x0d, 0x6b, 0xa0, 0x96, 0xa4, 0xe5, 0x4d, 0x07, 0x2f, 0x73, 0x81, 0xe6, 0x59, + 0xc3, 0xb2, 0x8c, 0x0e, 0x69, 0x99, 0xfd, 0xc6, 0x69, 0xc7, 0x68, 0xa9, 0x15, 0x74, 0x00, 0x8f, + 0x06, 0x46, 0xf7, 0xa2, 0x87, 0x1b, 0xf8, 0x03, 0x89, 0xe9, 0xed, 0x86, 0xd9, 0x19, 0x62, 0x43, + 0xad, 0xa2, 0xaf, 0xe0, 0x00, 0x1b, 0xef, 0x86, 0x26, 0x36, 0x5a, 0xc4, 0xea, 0xb5, 0x0c, 0xd2, + 0x36, 0x1a, 0x83, 0x21, 0x36, 0x48, 0xd7, 0xec, 0xf7, 0x4d, 0xeb, 0x3b, 0x55, 0x45, 0xcf, 0xe0, + 0x70, 0xc1, 0xb2, 0x50, 0xb0, 0xc6, 0xb5, 0xcd, 0xcf, 0x17, 0xbb, 0xd4, 0x32, 0x7e, 0x18, 0x90, + 0x0b, 0xc3, 0xc0, 0x2a, 0x42, 0x35, 0xd8, 0x5b, 0x9a, 0x97, 0x06, 0x22, 0xdb, 0x3b, 0x9c, 0x76, + 0x61, 0xe0, 0x6e, 0xc3, 0xe2, 0x0e, 0x5e, 0xa1, 0xed, 0xf2, 0x6d, 0x2f, 0x69, 0xeb, 0xdb, 0x7e, + 0x80, 0x10, 0x54, 0x12, 0x5e, 0x69, 0x37, 0xb0, 0xba, 0x87, 0xaa, 0x50, 0xec, 0x5e, 0x5c, 0x90, + 0x81, 0xd9, 0x35, 0x7a, 0xc3, 0x81, 0xfa, 0x10, 0xed, 0x42, 0x35, 0xde, 0x52, 0x2c, 0xf9, 0xaf, + 0x1c, 0x7a, 0x08, 0x68, 0x68, 0x61, 0xa3, 0xd1, 0xe2, 0x37, 0xb4, 0x20, 0xfc, 0x3b, 0x77, 0x9e, + 0xce, 0x6f, 0xa9, 0x29, 0xfd, 0x1f, 0x29, 0x28, 0xaf, 0x04, 0x2a, 0x7a, 0x0c, 0x85, 0xd0, 0xb9, + 0xf6, 0x44, 0xdd, 0x8a, 0xb2, 0xcc, 0x12, 0x10, 0x65, 0x7e, 0x62, 0x3b, 0x9e, 0x4c, 0x6f, 0xb2, + 0x10, 0x14, 0x04, 0x22, 0x92, 0xdb, 0x3e, 0xe4, 0xe2, 0x96, 0x22, 0xb5, 0x68, 0x29, 0xb2, 0x23, + 0xd9, 0x4a, 0x3c, 0x86, 0x02, 0xcf, 0xa1, 0x21, 0xb3, 0xa7, 0x33, 0x11, 0xf3, 0x65, 0xbc, 0x04, + 0xd0, 0x53, 0x28, 0x4f, 0x69, 0x18, 0xda, 0xd7, 0x94, 0xc8, 0xb8, 0x05, 0xc1, 0x51, 0x8a, 0xc0, + 0xb6, 0x08, 0xdf, 0xa7, 0x10, 0xe7, 0x91, 0x88, 0x29, 0x23, 0x99, 0x22, 0x50, 0x32, 0xad, 0xa7, + 0x70, 0x66, 0x47, 0xe9, 0x21, 0x99, 0xc2, 0x99, 0x8d, 0x5e, 0xc0, 0xb6, 0xcc, 0x41, 0x8e, 0xe7, + 0x4c, 0xe7, 0x53, 0x99, 0x8b, 0x72, 0x22, 0x17, 0x55, 0x45, 0x2e, 0x92, 0xb8, 0x48, 0x49, 0x8f, + 0x20, 0x7f, 0x69, 0x87, 0x94, 0x57, 0x8f, 0x28, 0x57, 0xe4, 0xf8, 0xba, 0x4d, 0x29, 0x27, 0xf1, + 0x9a, 0x12, 0xf0, 0x2c, 0x28, 0x53, 0x44, 0xee, 0x8a, 0x52, 0xcc, 0xef, 0x72, 0x61, 0xc1, 0xfe, + 0xb4, 0xb4, 0x50, 0x4c, 0x58, 0x90, 0xb8, 0xb0, 0xf0, 0x02, 0xb6, 0xe9, 0x27, 0x16, 0xd8, 0xc4, + 0x9f, 0xd9, 0x3f, 0xcd, 0x29, 0x19, 0xdb, 0xcc, 0x16, 0x3d, 0x6a, 0x09, 0x57, 0x05, 0xa1, 0x27, + 0xf0, 0x96, 0xcd, 0x6c, 0xfd, 0x31, 0xd4, 0x30, 0x0d, 0x29, 0xeb, 0x3a, 0x61, 0xe8, 0xf8, 0x5e, + 0xd3, 0xf7, 0x58, 0xe0, 0xbb, 0x51, 0x11, 0xd2, 0x0f, 0x60, 0x7f, 0x23, 0x55, 0x56, 0x11, 0x2e, + 0xfc, 0x6e, 0x4e, 0x83, 0xdb, 0xcd, 0xc2, 0xef, 0x60, 0x7f, 0x23, 0x35, 0x2a, 0x41, 0x2f, 0x21, + 0x33, 0xb3, 0x9d, 0x20, 0xd4, 0xb6, 0x44, 0x19, 0xdf, 0x5b, 0xe9, 0x1a, 0x9c, 0xe0, 0xcc, 0x09, + 0x99, 0x1f, 0xdc, 0x62, 0xc9, 0x74, 0x9e, 0xce, 0x2b, 0xea, 0x96, 0xfe, 0x67, 0x05, 0x8a, 0x09, + 0x22, 0x7f, 0x07, 0x9e, 0x3f, 0xa6, 0xe4, 0x2a, 0xf0, 0xa7, 0xf1, 0x0b, 0x5b, 0x00, 0x48, 0x83, + 0x9c, 0x58, 0x30, 0x3f, 0x7a, 0x5e, 0xf1, 0x12, 0x7d, 0x03, 0xb9, 0x89, 0x54, 0x21, 0xbc, 0x54, + 0x3c, 0xd9, 0x59, 0xb3, 0xce, 0xef, 0x06, 0xc7, 0x3c, 0xe7, 0xe9, 0x7c, 0x4a, 0x4d, 0x9f, 0xa7, + 0xf3, 0x69, 0x35, 0x73, 0x9e, 0xce, 0x67, 0xd4, 0xec, 0x79, 0x3a, 0x9f, 0x55, 0x73, 0xfa, 0x7f, + 0x14, 0xc8, 0xc7, 0xdc, 0x7c, 0x2f, 0x3c, 0xe7, 0x13, 0xfe, 0x32, 0xa2, 0x8e, 0x60, 0x09, 0x20, + 0x1d, 0x4a, 0x62, 0xb1, 0xda, 0x68, 0xac, 0x60, 0xe8, 0x19, 0x94, 0x17, 0xeb, 0x45, 0x35, 0x4b, + 0xe1, 0x55, 0x90, 0x6b, 0x0a, 0xe7, 0xa3, 0x11, 0x0d, 0x43, 0x69, 0x2a, 0x23, 0x35, 0x25, 0x31, + 0x54, 0x87, 0x6a, 0xbc, 0x8e, 0x0d, 0x66, 0x05, 0xdb, 0x3a, 0x8c, 0x5e, 0x80, 0x9a, 0x84, 0xa6, + 0xcb, 0x79, 0xe0, 0x0e, 0x2e, 0xaf, 0x41, 0x9f, 0xc2, 0x43, 0xe1, 0xd6, 0x8b, 0xc0, 0xbf, 0xb4, + 0x2f, 0x1d, 0xd7, 0x61, 0xb7, 0x71, 0xcf, 0xc2, 0xaf, 0x20, 0xf0, 0xa7, 0xc4, 0x8b, 0x9b, 0x80, + 0x12, 0x5e, 0x02, 0xdc, 0x1d, 0xcc, 0x97, 0xb4, 0xc8, 0x1d, 0xd1, 0x92, 0x77, 0x23, 0x0b, 0xe3, + 0x29, 0x61, 0x7c, 0xb1, 0xd6, 0x6f, 0x40, 0xbb, 0x6b, 0x2e, 0x7a, 0x42, 0x87, 0x50, 0x9c, 0x2d, + 0x61, 0x61, 0x51, 0xc1, 0x49, 0x28, 0xe9, 0xe8, 0xad, 0x5f, 0x76, 0xb4, 0xfe, 0x17, 0x05, 0xb6, + 0x4f, 0xe7, 0x8e, 0x3b, 0x5e, 0x69, 0xc5, 0x92, 0xa3, 0x9e, 0xb2, 0x3a, 0xea, 0x6d, 0x9a, 0xe3, + 0xb6, 0x36, 0xce, 0x71, 0x9b, 0x66, 0xa5, 0xd4, 0xbd, 0xb3, 0xd2, 0x13, 0x28, 0x2e, 0xc7, 0x24, + 0xd9, 0xe9, 0x96, 0x30, 0x4c, 0xe2, 0x19, 0x29, 0xd4, 0xdf, 0x00, 0x4a, 0x6e, 0x34, 0xba, 0x90, + 0x45, 0x47, 0xa8, 0xdc, 0xdb, 0x11, 0xbe, 0xf8, 0xbb, 0x02, 0xa5, 0x64, 0x5b, 0x8e, 0xca, 0x50, + 0x30, 0x2d, 0xd2, 0xee, 0x98, 0xdf, 0x9d, 0x0d, 0xd4, 0x2f, 0xf8, 0xb2, 0x3f, 0x6c, 0x36, 0x0d, + 0xa3, 0x65, 0xb4, 0x54, 0x85, 0x17, 0x0c, 0x9e, 0xea, 0x8d, 0xd6, 0xa2, 0x3e, 0x6c, 0xf1, 0xd2, + 0x1e, 0x61, 0x56, 0x8f, 0xe0, 0xde, 0x70, 0x60, 0xa8, 0x29, 0xa4, 0x42, 0x29, 0x02, 0x0d, 0x8c, + 0x7b, 0x58, 0x4d, 0xf3, 0xfa, 0x17, 0x21, 0x77, 0xdb, 0x92, 0xb8, 0x6b, 0xc9, 0x88, 0xb6, 0x23, + 0xe6, 0x5a, 0x56, 0x6c, 0x72, 0xda, 0xe8, 0x34, 0xac, 0xa6, 0xa1, 0x66, 0x4f, 0xfe, 0x96, 0x81, + 0xac, 0x38, 0x41, 0x80, 0xce, 0xa0, 0x98, 0x98, 0xd0, 0xd0, 0xc1, 0x67, 0x27, 0xb7, 0x9a, 0xb6, + 0x79, 0x10, 0x99, 0x87, 0xaf, 0x14, 0x74, 0x0e, 0xa5, 0xe4, 0xfc, 0x83, 0x92, 0xcd, 0xea, 0x86, + 0xc1, 0xe8, 0xb3, 0xba, 0xde, 0x82, 0x6a, 0x84, 0xcc, 0x99, 0xf2, 0xe6, 0x34, 0x1a, 0x17, 0x50, + 0x2d, 0xc1, 0xbf, 0x36, 0x83, 0xd4, 0xf6, 0x37, 0xd2, 0x22, 0x17, 0x76, 0xe4, 0x11, 0xa3, 0x86, + 0xfd, 0xce, 0x11, 0x57, 0xa7, 0x84, 0xda, 0x97, 0xf7, 0x91, 0x23, 0x6d, 0x63, 0xd8, 0xd9, 0x90, + 0xc0, 0xd1, 0xaf, 0x92, 0x3b, 0xb8, 0x37, 0xfd, 0xd7, 0x9e, 0xff, 0x12, 0xdb, 0xd2, 0xca, 0x86, + 0x4c, 0xbf, 0x62, 0xe5, 0xfe, 0x3a, 0xb1, 0x62, 0xe5, 0x73, 0x05, 0xe3, 0x47, 0x50, 0xd7, 0x33, + 0x01, 0xd2, 0xd7, 0x65, 0xef, 0x66, 0xa5, 0xda, 0xd3, 0xcf, 0xf2, 0x44, 0xca, 0x4d, 0x80, 0x65, + 0x3c, 0xa1, 0xc7, 0x09, 0x91, 0x3b, 0xf9, 0xa0, 0x76, 0x70, 0x0f, 0x55, 0xaa, 0x3a, 0xfd, 0xf5, + 0x1f, 0x8f, 0xaf, 0x1d, 0x36, 0x99, 0x5f, 0x1e, 0x8d, 0xfc, 0xe9, 0xb1, 0xcb, 0x5b, 0x7c, 0xcf, + 0xf1, 0xae, 0x3d, 0xca, 0x7e, 0xf6, 0x83, 0x9b, 0x63, 0xd7, 0x1b, 0x1f, 0x8b, 0xb0, 0x3c, 0x5e, + 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. diff --git a/lnrpc/routerrpc/router.proto b/lnrpc/routerrpc/router.proto index aa0d9676..034ee2c5 100644 --- a/lnrpc/routerrpc/router.proto +++ b/lnrpc/routerrpc/router.proto @@ -256,6 +256,7 @@ message Failure { PERMANENT_NODE_FAILURE = 20; PERMANENT_CHANNEL_FAILURE = 21; EXPIRY_TOO_FAR = 22; + MPP_TIMEOUT = 23; /** The error source is known, but the failure itself couldn't be decoded. diff --git a/lnrpc/routerrpc/router_server.go b/lnrpc/routerrpc/router_server.go index c872afe3..ba23a079 100644 --- a/lnrpc/routerrpc/router_server.go +++ b/lnrpc/routerrpc/router_server.go @@ -415,6 +415,9 @@ func marshallError(sendError error) (*Failure, error) { case *lnwire.FailPermanentChannelFailure: response.Code = Failure_PERMANENT_CHANNEL_FAILURE + case *lnwire.FailMPPTimeout: + response.Code = Failure_MPP_TIMEOUT + case nil: response.Code = Failure_UNKNOWN_FAILURE diff --git a/lnwire/onion_error.go b/lnwire/onion_error.go index 961373b9..c6235552 100644 --- a/lnwire/onion_error.go +++ b/lnwire/onion_error.go @@ -80,6 +80,7 @@ const ( CodeFinalIncorrectHtlcAmount FailCode = 19 CodeExpiryTooFar FailCode = 21 CodeInvalidOnionPayload = FlagPerm | 22 + CodeMPPTimeout FailCode = 23 ) // String returns the string representation of the failure code. @@ -154,6 +155,9 @@ func (c FailCode) String() string { case CodeInvalidOnionPayload: return "InvalidOnionPayload" + case CodeMPPTimeout: + return "MPPTimeout" + default: return "" } @@ -1182,6 +1186,26 @@ func (f *InvalidOnionPayload) Encode(w io.Writer, pver uint32) error { 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 // the provided protocol version. func DecodeFailure(r io.Reader, pver uint32) (FailureMessage, error) { @@ -1366,6 +1390,9 @@ func makeEmptyOnionError(code FailCode) (FailureMessage, error) { case CodeInvalidOnionPayload: return &InvalidOnionPayload{}, nil + case CodeMPPTimeout: + return &FailMPPTimeout{}, nil + default: return nil, errors.Errorf("unknown error code: %v", code) } diff --git a/lnwire/onion_error_test.go b/lnwire/onion_error_test.go index 5dafcedd..3ec147d1 100644 --- a/lnwire/onion_error_test.go +++ b/lnwire/onion_error_test.go @@ -38,6 +38,7 @@ var onionFailures = []FailureMessage{ &FailUnknownNextPeer{}, &FailIncorrectPaymentAmount{}, &FailFinalExpiryTooSoon{}, + &FailMPPTimeout{}, NewFailIncorrectDetails(99, 100), NewInvalidOnionVersion(testOnionHash), diff --git a/routing/missioncontrol_store_test.go b/routing/missioncontrol_store_test.go index 9d070f4d..e44c5919 100644 --- a/routing/missioncontrol_store_test.go +++ b/routing/missioncontrol_store_test.go @@ -16,8 +16,12 @@ import ( 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) { - // Set time zone explictly to keep test deterministic. + // Set time zone explicitly to keep test deterministic. time.Local = time.UTC file, err := ioutil.TempFile("", "*.db") @@ -115,11 +119,12 @@ func TestMissionControlStore(t *testing.T) { t.Fatal(err) } - // Add a newer result. + // Add a newer result which failed due to mpp timeout. result3 := result1 result3.timeReply = result1.timeReply.Add(2 * time.Hour) result3.timeFwd = result1.timeReply.Add(2 * time.Hour) result3.id = 3 + result3.failure = &lnwire.FailMPPTimeout{} err = store.AddResult(&result3) if err != nil { diff --git a/routing/result_interpretation.go b/routing/result_interpretation.go index 7b3d539a..fae2835a 100644 --- a/routing/result_interpretation.go +++ b/routing/result_interpretation.go @@ -216,6 +216,22 @@ func (i *interpretedResult) processPaymentOutcomeFinal( // deliberately. What to penalize? 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: // All other errors are considered terminal if coming from the // final hop. They indicate that something is wrong at the diff --git a/routing/result_interpretation_test.go b/routing/result_interpretation_test.go index 42f3ec87..a5ed7989 100644 --- a/routing/result_interpretation_test.go +++ b/routing/result_interpretation_test.go @@ -272,6 +272,39 @@ var resultTestCases = []resultTestCase{ 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