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()
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 {

@ -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)
}

@ -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{})
}

@ -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{}) {}

@ -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{})
}

@ -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.

@ -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,

@ -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.

@ -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)

@ -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
}

@ -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.

@ -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.

@ -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

@ -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 "<unknown>"
}
@ -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)
}

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

@ -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 {

@ -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

@ -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