Merge pull request #3831 from carlaKC/invoices-surfaceupdateresult
Add MPPTimeout ResolutionResult and expose to caller
This commit is contained in:
commit
8d0205e9e4
@ -167,10 +167,10 @@ func (h *htlcIncomingContestResolver) Resolve() (ContractResolver, error) {
|
|||||||
preimageSubscription := h.PreimageDB.SubscribeUpdates()
|
preimageSubscription := h.PreimageDB.SubscribeUpdates()
|
||||||
defer preimageSubscription.CancelSubscription()
|
defer preimageSubscription.CancelSubscription()
|
||||||
|
|
||||||
// Define closure to process hodl events either direct or triggered by
|
// Define closure to process htlc resolutions either direct or triggered by
|
||||||
// later notifcation.
|
// later notification.
|
||||||
processHodlEvent := func(e invoices.HodlEvent) (ContractResolver,
|
processHtlcResolution := func(e invoices.HtlcResolution) (
|
||||||
error) {
|
ContractResolver, error) {
|
||||||
|
|
||||||
if e.Preimage == nil {
|
if e.Preimage == nil {
|
||||||
log.Infof("%T(%v): Exit hop HTLC canceled "+
|
log.Infof("%T(%v): Exit hop HTLC canceled "+
|
||||||
@ -201,21 +201,24 @@ func (h *htlcIncomingContestResolver) Resolve() (ContractResolver, error) {
|
|||||||
HtlcID: h.htlc.HtlcIndex,
|
HtlcID: h.htlc.HtlcIndex,
|
||||||
}
|
}
|
||||||
|
|
||||||
event, err := h.Registry.NotifyExitHopHtlc(
|
resolution, err := h.Registry.NotifyExitHopHtlc(
|
||||||
h.htlc.RHash, h.htlc.Amt, h.htlcExpiry, currentHeight,
|
h.htlc.RHash, h.htlc.Amt, h.htlcExpiry, currentHeight,
|
||||||
circuitKey, hodlChan, payload,
|
circuitKey, hodlChan, payload,
|
||||||
)
|
)
|
||||||
switch err {
|
if err != nil {
|
||||||
case channeldb.ErrInvoiceNotFound:
|
return nil, err
|
||||||
case nil:
|
}
|
||||||
|
|
||||||
defer h.Registry.HodlUnsubscribeAll(hodlChan)
|
defer h.Registry.HodlUnsubscribeAll(hodlChan)
|
||||||
|
|
||||||
// Resolve the htlc directly if possible.
|
// If the resolution is non-nil (indicating that a settle or cancel has
|
||||||
if event != nil {
|
// occurred), and the invoice is known to the registry (indicating that
|
||||||
return processHodlEvent(*event)
|
// the htlc is paying one of our invoices and is not a forward), try to
|
||||||
}
|
// resolve it directly.
|
||||||
default:
|
if resolution != nil &&
|
||||||
return nil, err
|
resolution.Outcome != invoices.ResultInvoiceNotFound {
|
||||||
|
|
||||||
|
return processHtlcResolution(*resolution)
|
||||||
}
|
}
|
||||||
|
|
||||||
// With the epochs and preimage subscriptions initialized, we'll query
|
// With the epochs and preimage subscriptions initialized, we'll query
|
||||||
@ -252,9 +255,9 @@ func (h *htlcIncomingContestResolver) Resolve() (ContractResolver, error) {
|
|||||||
return &h.htlcSuccessResolver, nil
|
return &h.htlcSuccessResolver, nil
|
||||||
|
|
||||||
case hodlItem := <-hodlChan:
|
case hodlItem := <-hodlChan:
|
||||||
hodlEvent := hodlItem.(invoices.HodlEvent)
|
htlcResolution := hodlItem.(invoices.HtlcResolution)
|
||||||
|
|
||||||
return processHodlEvent(hodlEvent)
|
return processHtlcResolution(htlcResolution)
|
||||||
|
|
||||||
case newBlock, ok := <-blockEpochs.Epochs:
|
case newBlock, ok := <-blockEpochs.Epochs:
|
||||||
if !ok {
|
if !ok {
|
||||||
|
@ -25,6 +25,7 @@ var (
|
|||||||
testResHash = testResPreimage.Hash()
|
testResHash = testResPreimage.Hash()
|
||||||
testResCircuitKey = channeldb.CircuitKey{}
|
testResCircuitKey = channeldb.CircuitKey{}
|
||||||
testOnionBlob = []byte{4, 5, 6}
|
testOnionBlob = []byte{4, 5, 6}
|
||||||
|
testAcceptHeight int32 = 1234
|
||||||
)
|
)
|
||||||
|
|
||||||
// TestHtlcIncomingResolverFwdPreimageKnown tests resolution of a forwarded htlc
|
// TestHtlcIncomingResolverFwdPreimageKnown tests resolution of a forwarded htlc
|
||||||
@ -34,7 +35,10 @@ func TestHtlcIncomingResolverFwdPreimageKnown(t *testing.T) {
|
|||||||
defer timeout(t)()
|
defer timeout(t)()
|
||||||
|
|
||||||
ctx := newIncomingResolverTestContext(t)
|
ctx := newIncomingResolverTestContext(t)
|
||||||
ctx.registry.notifyErr = channeldb.ErrInvoiceNotFound
|
ctx.registry.notifyResolution = invoices.NewFailureResolution(
|
||||||
|
testResCircuitKey, testHtlcExpiry,
|
||||||
|
invoices.ResultInvoiceNotFound,
|
||||||
|
)
|
||||||
ctx.witnessBeacon.lookupPreimage[testResHash] = testResPreimage
|
ctx.witnessBeacon.lookupPreimage[testResHash] = testResPreimage
|
||||||
ctx.resolve()
|
ctx.resolve()
|
||||||
ctx.waitForResult(true)
|
ctx.waitForResult(true)
|
||||||
@ -48,7 +52,10 @@ func TestHtlcIncomingResolverFwdContestedSuccess(t *testing.T) {
|
|||||||
defer timeout(t)()
|
defer timeout(t)()
|
||||||
|
|
||||||
ctx := newIncomingResolverTestContext(t)
|
ctx := newIncomingResolverTestContext(t)
|
||||||
ctx.registry.notifyErr = channeldb.ErrInvoiceNotFound
|
ctx.registry.notifyResolution = invoices.NewFailureResolution(
|
||||||
|
testResCircuitKey, testHtlcExpiry,
|
||||||
|
invoices.ResultInvoiceNotFound,
|
||||||
|
)
|
||||||
ctx.resolve()
|
ctx.resolve()
|
||||||
|
|
||||||
// Simulate a new block coming in. HTLC is not yet expired.
|
// Simulate a new block coming in. HTLC is not yet expired.
|
||||||
@ -65,7 +72,10 @@ func TestHtlcIncomingResolverFwdContestedTimeout(t *testing.T) {
|
|||||||
defer timeout(t)()
|
defer timeout(t)()
|
||||||
|
|
||||||
ctx := newIncomingResolverTestContext(t)
|
ctx := newIncomingResolverTestContext(t)
|
||||||
ctx.registry.notifyErr = channeldb.ErrInvoiceNotFound
|
ctx.registry.notifyResolution = invoices.NewFailureResolution(
|
||||||
|
testResCircuitKey, testHtlcExpiry,
|
||||||
|
invoices.ResultInvoiceNotFound,
|
||||||
|
)
|
||||||
ctx.resolve()
|
ctx.resolve()
|
||||||
|
|
||||||
// Simulate a new block coming in. HTLC expires.
|
// Simulate a new block coming in. HTLC expires.
|
||||||
@ -81,8 +91,10 @@ func TestHtlcIncomingResolverFwdTimeout(t *testing.T) {
|
|||||||
defer timeout(t)()
|
defer timeout(t)()
|
||||||
|
|
||||||
ctx := newIncomingResolverTestContext(t)
|
ctx := newIncomingResolverTestContext(t)
|
||||||
|
ctx.registry.notifyResolution = invoices.NewFailureResolution(
|
||||||
ctx.registry.notifyErr = channeldb.ErrInvoiceNotFound
|
testResCircuitKey, testHtlcExpiry,
|
||||||
|
invoices.ResultInvoiceNotFound,
|
||||||
|
)
|
||||||
ctx.witnessBeacon.lookupPreimage[testResHash] = testResPreimage
|
ctx.witnessBeacon.lookupPreimage[testResHash] = testResPreimage
|
||||||
ctx.resolver.htlcExpiry = 90
|
ctx.resolver.htlcExpiry = 90
|
||||||
ctx.resolve()
|
ctx.resolve()
|
||||||
@ -96,10 +108,11 @@ func TestHtlcIncomingResolverExitSettle(t *testing.T) {
|
|||||||
defer timeout(t)()
|
defer timeout(t)()
|
||||||
|
|
||||||
ctx := newIncomingResolverTestContext(t)
|
ctx := newIncomingResolverTestContext(t)
|
||||||
ctx.registry.notifyEvent = &invoices.HodlEvent{
|
ctx.registry.notifyResolution = invoices.NewSettleResolution(
|
||||||
CircuitKey: testResCircuitKey,
|
testResPreimage, testResCircuitKey, testAcceptHeight,
|
||||||
Preimage: &testResPreimage,
|
invoices.ResultReplayToSettled,
|
||||||
}
|
)
|
||||||
|
|
||||||
ctx.resolve()
|
ctx.resolve()
|
||||||
|
|
||||||
data := <-ctx.registry.notifyChan
|
data := <-ctx.registry.notifyChan
|
||||||
@ -126,9 +139,11 @@ func TestHtlcIncomingResolverExitCancel(t *testing.T) {
|
|||||||
defer timeout(t)()
|
defer timeout(t)()
|
||||||
|
|
||||||
ctx := newIncomingResolverTestContext(t)
|
ctx := newIncomingResolverTestContext(t)
|
||||||
ctx.registry.notifyEvent = &invoices.HodlEvent{
|
ctx.registry.notifyResolution = invoices.NewFailureResolution(
|
||||||
CircuitKey: testResCircuitKey,
|
testResCircuitKey, testAcceptHeight,
|
||||||
}
|
invoices.ResultInvoiceAlreadyCanceled,
|
||||||
|
)
|
||||||
|
|
||||||
ctx.resolve()
|
ctx.resolve()
|
||||||
ctx.waitForResult(false)
|
ctx.waitForResult(false)
|
||||||
}
|
}
|
||||||
@ -143,10 +158,10 @@ func TestHtlcIncomingResolverExitSettleHodl(t *testing.T) {
|
|||||||
ctx.resolve()
|
ctx.resolve()
|
||||||
|
|
||||||
notifyData := <-ctx.registry.notifyChan
|
notifyData := <-ctx.registry.notifyChan
|
||||||
notifyData.hodlChan <- invoices.HodlEvent{
|
notifyData.hodlChan <- *invoices.NewSettleResolution(
|
||||||
CircuitKey: testResCircuitKey,
|
testResPreimage, testResCircuitKey, testAcceptHeight,
|
||||||
Preimage: &testResPreimage,
|
invoices.ResultSettled,
|
||||||
}
|
)
|
||||||
|
|
||||||
ctx.waitForResult(true)
|
ctx.waitForResult(true)
|
||||||
}
|
}
|
||||||
@ -172,9 +187,10 @@ func TestHtlcIncomingResolverExitCancelHodl(t *testing.T) {
|
|||||||
ctx := newIncomingResolverTestContext(t)
|
ctx := newIncomingResolverTestContext(t)
|
||||||
ctx.resolve()
|
ctx.resolve()
|
||||||
notifyData := <-ctx.registry.notifyChan
|
notifyData := <-ctx.registry.notifyChan
|
||||||
notifyData.hodlChan <- invoices.HodlEvent{
|
notifyData.hodlChan <- *invoices.NewFailureResolution(
|
||||||
CircuitKey: testResCircuitKey,
|
testResCircuitKey, testAcceptHeight, invoices.ResultCanceled,
|
||||||
}
|
)
|
||||||
|
|
||||||
ctx.waitForResult(false)
|
ctx.waitForResult(false)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -27,9 +27,9 @@ type Registry interface {
|
|||||||
NotifyExitHopHtlc(payHash lntypes.Hash, paidAmount lnwire.MilliSatoshi,
|
NotifyExitHopHtlc(payHash lntypes.Hash, paidAmount lnwire.MilliSatoshi,
|
||||||
expiry uint32, currentHeight int32,
|
expiry uint32, currentHeight int32,
|
||||||
circuitKey channeldb.CircuitKey, hodlChan chan<- interface{},
|
circuitKey channeldb.CircuitKey, hodlChan chan<- interface{},
|
||||||
payload invoices.Payload) (*invoices.HodlEvent, error)
|
payload invoices.Payload) (*invoices.HtlcResolution, error)
|
||||||
|
|
||||||
// HodlUnsubscribeAll unsubscribes from all hodl events.
|
// HodlUnsubscribeAll unsubscribes from all htlc resolutions.
|
||||||
HodlUnsubscribeAll(subscriber chan<- interface{})
|
HodlUnsubscribeAll(subscriber chan<- interface{})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -18,13 +18,13 @@ type notifyExitHopData struct {
|
|||||||
type mockRegistry struct {
|
type mockRegistry struct {
|
||||||
notifyChan chan notifyExitHopData
|
notifyChan chan notifyExitHopData
|
||||||
notifyErr error
|
notifyErr error
|
||||||
notifyEvent *invoices.HodlEvent
|
notifyResolution *invoices.HtlcResolution
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *mockRegistry) NotifyExitHopHtlc(payHash lntypes.Hash,
|
func (r *mockRegistry) NotifyExitHopHtlc(payHash lntypes.Hash,
|
||||||
paidAmount lnwire.MilliSatoshi, expiry uint32, currentHeight int32,
|
paidAmount lnwire.MilliSatoshi, expiry uint32, currentHeight int32,
|
||||||
circuitKey channeldb.CircuitKey, hodlChan chan<- interface{},
|
circuitKey channeldb.CircuitKey, hodlChan chan<- interface{},
|
||||||
payload invoices.Payload) (*invoices.HodlEvent, error) {
|
payload invoices.Payload) (*invoices.HtlcResolution, error) {
|
||||||
|
|
||||||
r.notifyChan <- notifyExitHopData{
|
r.notifyChan <- notifyExitHopData{
|
||||||
hodlChan: hodlChan,
|
hodlChan: hodlChan,
|
||||||
@ -34,7 +34,7 @@ func (r *mockRegistry) NotifyExitHopHtlc(payHash lntypes.Hash,
|
|||||||
currentHeight: currentHeight,
|
currentHeight: currentHeight,
|
||||||
}
|
}
|
||||||
|
|
||||||
return r.notifyEvent, r.notifyErr
|
return r.notifyResolution, r.notifyErr
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *mockRegistry) HodlUnsubscribeAll(subscriber chan<- interface{}) {}
|
func (r *mockRegistry) HodlUnsubscribeAll(subscriber chan<- interface{}) {}
|
||||||
|
@ -27,7 +27,7 @@ type InvoiceDatabase interface {
|
|||||||
NotifyExitHopHtlc(payHash lntypes.Hash, paidAmount lnwire.MilliSatoshi,
|
NotifyExitHopHtlc(payHash lntypes.Hash, paidAmount lnwire.MilliSatoshi,
|
||||||
expiry uint32, currentHeight int32,
|
expiry uint32, currentHeight int32,
|
||||||
circuitKey channeldb.CircuitKey, hodlChan chan<- interface{},
|
circuitKey channeldb.CircuitKey, hodlChan chan<- interface{},
|
||||||
payload invoices.Payload) (*invoices.HodlEvent, error)
|
payload invoices.Payload) (*invoices.HtlcResolution, error)
|
||||||
|
|
||||||
// CancelInvoice attempts to cancel the invoice corresponding to the
|
// CancelInvoice attempts to cancel the invoice corresponding to the
|
||||||
// passed payment hash.
|
// passed payment hash.
|
||||||
@ -36,7 +36,7 @@ type InvoiceDatabase interface {
|
|||||||
// SettleHodlInvoice settles a hold invoice.
|
// SettleHodlInvoice settles a hold invoice.
|
||||||
SettleHodlInvoice(preimage lntypes.Preimage) error
|
SettleHodlInvoice(preimage lntypes.Preimage) error
|
||||||
|
|
||||||
// HodlUnsubscribeAll unsubscribes from all hodl events.
|
// HodlUnsubscribeAll unsubscribes from all htlc resolutions.
|
||||||
HodlUnsubscribeAll(subscriber chan<- interface{})
|
HodlUnsubscribeAll(subscriber chan<- interface{})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -489,8 +489,8 @@ func (l *channelLink) Stop() {
|
|||||||
|
|
||||||
l.log.Info("stopping")
|
l.log.Info("stopping")
|
||||||
|
|
||||||
// As the link is stopping, we are no longer interested in hodl events
|
// As the link is stopping, we are no longer interested in htlc
|
||||||
// coming from the invoice registry.
|
// resolutions coming from the invoice registry.
|
||||||
l.cfg.Registry.HodlUnsubscribeAll(l.hodlQueue.ChanIn())
|
l.cfg.Registry.HodlUnsubscribeAll(l.hodlQueue.ChanIn())
|
||||||
|
|
||||||
if l.cfg.ChainEvents.Cancel != nil {
|
if l.cfg.ChainEvents.Cancel != nil {
|
||||||
@ -1126,11 +1126,11 @@ out:
|
|||||||
case msg := <-l.upstream:
|
case msg := <-l.upstream:
|
||||||
l.handleUpstreamMsg(msg)
|
l.handleUpstreamMsg(msg)
|
||||||
|
|
||||||
// A hodl event is received. This means that we now have a
|
// A htlc resolution is received. This means that we now have a
|
||||||
// resolution for a previously accepted htlc.
|
// resolution for a previously accepted htlc.
|
||||||
case hodlItem := <-l.hodlQueue.ChanOut():
|
case hodlItem := <-l.hodlQueue.ChanOut():
|
||||||
hodlEvent := hodlItem.(invoices.HodlEvent)
|
htlcResolution := hodlItem.(invoices.HtlcResolution)
|
||||||
err := l.processHodlQueue(hodlEvent)
|
err := l.processHodlQueue(htlcResolution)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
l.fail(LinkFailureError{code: ErrInternalError},
|
l.fail(LinkFailureError{code: ErrInternalError},
|
||||||
fmt.Sprintf("process hodl queue: %v",
|
fmt.Sprintf("process hodl queue: %v",
|
||||||
@ -1145,24 +1145,26 @@ out:
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// processHodlQueue processes a received hodl event and continues reading from
|
// processHodlQueue processes a received htlc resolution and continues reading
|
||||||
// the hodl queue until no more events remain. When this function returns
|
// from the hodl queue until no more resolutions remain. When this function
|
||||||
// without an error, the commit tx should be updated.
|
// returns without an error, the commit tx should be updated.
|
||||||
func (l *channelLink) processHodlQueue(firstHodlEvent invoices.HodlEvent) error {
|
func (l *channelLink) processHodlQueue(
|
||||||
|
firstResolution invoices.HtlcResolution) error {
|
||||||
|
|
||||||
// Try to read all waiting resolution messages, so that they can all be
|
// Try to read all waiting resolution messages, so that they can all be
|
||||||
// processed in a single commitment tx update.
|
// processed in a single commitment tx update.
|
||||||
hodlEvent := firstHodlEvent
|
htlcResolution := firstResolution
|
||||||
loop:
|
loop:
|
||||||
for {
|
for {
|
||||||
// Lookup all hodl htlcs that can be failed or settled with this event.
|
// Lookup all hodl htlcs that can be failed or settled with this event.
|
||||||
// The hodl htlc must be present in the map.
|
// The hodl htlc must be present in the map.
|
||||||
circuitKey := hodlEvent.CircuitKey
|
circuitKey := htlcResolution.CircuitKey
|
||||||
hodlHtlc, ok := l.hodlMap[circuitKey]
|
hodlHtlc, ok := l.hodlMap[circuitKey]
|
||||||
if !ok {
|
if !ok {
|
||||||
return fmt.Errorf("hodl htlc not found: %v", circuitKey)
|
return fmt.Errorf("hodl htlc not found: %v", circuitKey)
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := l.processHodlEvent(hodlEvent, hodlHtlc); err != nil {
|
if err := l.processHtlcResolution(htlcResolution, hodlHtlc); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1171,7 +1173,7 @@ loop:
|
|||||||
|
|
||||||
select {
|
select {
|
||||||
case item := <-l.hodlQueue.ChanOut():
|
case item := <-l.hodlQueue.ChanOut():
|
||||||
hodlEvent = item.(invoices.HodlEvent)
|
htlcResolution = item.(invoices.HtlcResolution)
|
||||||
default:
|
default:
|
||||||
break loop
|
break loop
|
||||||
}
|
}
|
||||||
@ -1185,30 +1187,30 @@ loop:
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// processHodlEvent applies a received hodl event to the provided htlc. When
|
// processHtlcResolution applies a received htlc resolution to the provided
|
||||||
// this function returns without an error, the commit tx should be updated.
|
// htlc. When this function returns without an error, the commit tx should be
|
||||||
func (l *channelLink) processHodlEvent(hodlEvent invoices.HodlEvent,
|
// updated.
|
||||||
|
func (l *channelLink) processHtlcResolution(resolution invoices.HtlcResolution,
|
||||||
htlc hodlHtlc) error {
|
htlc hodlHtlc) error {
|
||||||
|
|
||||||
circuitKey := hodlEvent.CircuitKey
|
circuitKey := resolution.CircuitKey
|
||||||
|
|
||||||
// Determine required action for the resolution.
|
// Determine required action for the resolution. If the event's preimage is
|
||||||
if hodlEvent.Preimage != nil {
|
// non-nil, the htlc must be settled. Otherwise, it should be canceled.
|
||||||
l.log.Debugf("received hodl settle event for %v", circuitKey)
|
if resolution.Preimage != nil {
|
||||||
|
l.log.Debugf("received settle resolution for %v", circuitKey)
|
||||||
|
|
||||||
return l.settleHTLC(
|
return l.settleHTLC(
|
||||||
*hodlEvent.Preimage, htlc.pd.HtlcIndex,
|
*resolution.Preimage, htlc.pd.HtlcIndex,
|
||||||
htlc.pd.SourceRef,
|
htlc.pd.SourceRef,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
l.log.Debugf("received hodl cancel event for %v", circuitKey)
|
l.log.Debugf("received cancel resolution for %v with outcome: %v",
|
||||||
|
circuitKey, resolution.Outcome)
|
||||||
|
|
||||||
// In case of a cancel, always return
|
// Get the lnwire failure message based on the resolution result.
|
||||||
// incorrect_or_unknown_payment_details in order to avoid leaking info.
|
failure := getResolutionFailure(resolution, htlc.pd.Amount)
|
||||||
failure := lnwire.NewFailIncorrectDetails(
|
|
||||||
htlc.pd.Amount, uint32(hodlEvent.AcceptHeight),
|
|
||||||
)
|
|
||||||
|
|
||||||
l.sendHTLCError(
|
l.sendHTLCError(
|
||||||
htlc.pd.HtlcIndex, failure, htlc.obfuscator,
|
htlc.pd.HtlcIndex, failure, htlc.obfuscator,
|
||||||
@ -1217,6 +1219,25 @@ func (l *channelLink) processHodlEvent(hodlEvent invoices.HodlEvent,
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// getResolutionFailure returns the wire message that a htlc resolution should
|
||||||
|
// be failed with.
|
||||||
|
func getResolutionFailure(resolution invoices.HtlcResolution,
|
||||||
|
amount lnwire.MilliSatoshi) lnwire.FailureMessage {
|
||||||
|
|
||||||
|
// If the resolution has been resolved as part of a MPP timeout, we need
|
||||||
|
// to fail the htlc with lnwire.FailMppTimeout.
|
||||||
|
if resolution.Outcome == invoices.ResultMppTimeout {
|
||||||
|
return &lnwire.FailMPPTimeout{}
|
||||||
|
}
|
||||||
|
|
||||||
|
// If the htlc is not a MPP timeout, we fail it with FailIncorrectDetails
|
||||||
|
// This covers hodl cancels (which return it to avoid leaking information
|
||||||
|
// and other invoice failures such as underpayment or expiry too soon.
|
||||||
|
return lnwire.NewFailIncorrectDetails(
|
||||||
|
amount, uint32(resolution.AcceptHeight),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
// randomFeeUpdateTimeout returns a random timeout between the bounds defined
|
// randomFeeUpdateTimeout returns a random timeout between the bounds defined
|
||||||
// within the link's configuration that will be used to determine when the link
|
// within the link's configuration that will be used to determine when the link
|
||||||
// should propose an update to its commitment fee rate.
|
// should propose an update to its commitment fee rate.
|
||||||
@ -2817,21 +2838,7 @@ func (l *channelLink) processExitHop(pd *lnwallet.PaymentDescriptor,
|
|||||||
invoiceHash, pd.Amount, pd.Timeout, int32(heightNow),
|
invoiceHash, pd.Amount, pd.Timeout, int32(heightNow),
|
||||||
circuitKey, l.hodlQueue.ChanIn(), payload,
|
circuitKey, l.hodlQueue.ChanIn(), payload,
|
||||||
)
|
)
|
||||||
|
if err != nil {
|
||||||
switch err {
|
|
||||||
|
|
||||||
// Cancel htlc if we don't have an invoice for it.
|
|
||||||
case channeldb.ErrInvoiceNotFound:
|
|
||||||
failure := lnwire.NewFailIncorrectDetails(pd.Amount, heightNow)
|
|
||||||
l.sendHTLCError(pd.HtlcIndex, failure, obfuscator, pd.SourceRef)
|
|
||||||
|
|
||||||
return nil
|
|
||||||
|
|
||||||
// No error.
|
|
||||||
case nil:
|
|
||||||
|
|
||||||
// Pass error to caller.
|
|
||||||
default:
|
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -2841,15 +2848,15 @@ func (l *channelLink) processExitHop(pd *lnwallet.PaymentDescriptor,
|
|||||||
obfuscator: obfuscator,
|
obfuscator: obfuscator,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// If the event is nil, the invoice is being held, so we save payment
|
||||||
|
// descriptor for future reference.
|
||||||
if event == nil {
|
if event == nil {
|
||||||
// Save payment descriptor for future reference.
|
|
||||||
l.hodlMap[circuitKey] = htlc
|
l.hodlMap[circuitKey] = htlc
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Process the received resolution.
|
// Process the received resolution.
|
||||||
return l.processHodlEvent(*event, htlc)
|
return l.processHtlcResolution(*event, htlc)
|
||||||
}
|
}
|
||||||
|
|
||||||
// settleHTLC settles the HTLC on the channel.
|
// settleHTLC settles the HTLC on the channel.
|
||||||
|
@ -819,7 +819,7 @@ func (i *mockInvoiceRegistry) SettleHodlInvoice(preimage lntypes.Preimage) error
|
|||||||
func (i *mockInvoiceRegistry) NotifyExitHopHtlc(rhash lntypes.Hash,
|
func (i *mockInvoiceRegistry) NotifyExitHopHtlc(rhash lntypes.Hash,
|
||||||
amt lnwire.MilliSatoshi, expiry uint32, currentHeight int32,
|
amt lnwire.MilliSatoshi, expiry uint32, currentHeight int32,
|
||||||
circuitKey channeldb.CircuitKey, hodlChan chan<- interface{},
|
circuitKey channeldb.CircuitKey, hodlChan chan<- interface{},
|
||||||
payload invoices.Payload) (*invoices.HodlEvent, error) {
|
payload invoices.Payload) (*invoices.HtlcResolution, error) {
|
||||||
|
|
||||||
event, err := i.registry.NotifyExitHopHtlc(
|
event, err := i.registry.NotifyExitHopHtlc(
|
||||||
rhash, amt, expiry, currentHeight, circuitKey, hodlChan,
|
rhash, amt, expiry, currentHeight, circuitKey, hodlChan,
|
||||||
|
@ -35,10 +35,10 @@ const (
|
|||||||
DefaultHtlcHoldDuration = 120 * time.Second
|
DefaultHtlcHoldDuration = 120 * time.Second
|
||||||
)
|
)
|
||||||
|
|
||||||
// HodlEvent describes how an htlc should be resolved. If HodlEvent.Preimage is
|
// HtlcResolution describes how an htlc should be resolved. If the preimage
|
||||||
// set, the event indicates a settle event. If Preimage is nil, it is a cancel
|
// field is set, the event indicates a settle event. If Preimage is nil, it is
|
||||||
// event.
|
// a cancel event.
|
||||||
type HodlEvent struct {
|
type HtlcResolution struct {
|
||||||
// Preimage is the htlc preimage. Its value is nil in case of a cancel.
|
// Preimage is the htlc preimage. Its value is nil in case of a cancel.
|
||||||
Preimage *lntypes.Preimage
|
Preimage *lntypes.Preimage
|
||||||
|
|
||||||
@ -48,6 +48,33 @@ type HodlEvent struct {
|
|||||||
|
|
||||||
// AcceptHeight is the original height at which the htlc was accepted.
|
// AcceptHeight is the original height at which the htlc was accepted.
|
||||||
AcceptHeight int32
|
AcceptHeight int32
|
||||||
|
|
||||||
|
// Outcome indicates the outcome of the invoice registry update.
|
||||||
|
Outcome ResolutionResult
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewFailureResolution returns a htlc failure resolution.
|
||||||
|
func NewFailureResolution(key channeldb.CircuitKey,
|
||||||
|
acceptHeight int32, outcome ResolutionResult) *HtlcResolution {
|
||||||
|
|
||||||
|
return &HtlcResolution{
|
||||||
|
CircuitKey: key,
|
||||||
|
AcceptHeight: acceptHeight,
|
||||||
|
Outcome: outcome,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewSettleResolution returns a htlc resolution which is associated with a
|
||||||
|
// settle.
|
||||||
|
func NewSettleResolution(preimage lntypes.Preimage, key channeldb.CircuitKey,
|
||||||
|
acceptHeight int32, outcome ResolutionResult) *HtlcResolution {
|
||||||
|
|
||||||
|
return &HtlcResolution{
|
||||||
|
Preimage: &preimage,
|
||||||
|
CircuitKey: key,
|
||||||
|
AcceptHeight: acceptHeight,
|
||||||
|
Outcome: outcome,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// RegistryConfig contains the configuration parameters for invoice registry.
|
// RegistryConfig contains the configuration parameters for invoice registry.
|
||||||
@ -325,7 +352,7 @@ func (i *InvoiceRegistry) invoiceEventLoop() {
|
|||||||
case <-nextReleaseTick:
|
case <-nextReleaseTick:
|
||||||
event := autoReleaseHeap.Pop().(*htlcReleaseEvent)
|
event := autoReleaseHeap.Pop().(*htlcReleaseEvent)
|
||||||
err := i.cancelSingleHtlc(
|
err := i.cancelSingleHtlc(
|
||||||
event.hash, event.key,
|
event.hash, event.key, ResultMppTimeout,
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Errorf("HTLC timer: %v", err)
|
log.Errorf("HTLC timer: %v", err)
|
||||||
@ -574,9 +601,11 @@ func (i *InvoiceRegistry) startHtlcTimer(hash lntypes.Hash,
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// cancelSingleHtlc cancels a single accepted htlc on an invoice.
|
// cancelSingleHtlc cancels a single accepted htlc on an invoice. It takes
|
||||||
|
// a resolution result which will be used to notify subscribed links and
|
||||||
|
// resolvers of the details of the htlc cancellation.
|
||||||
func (i *InvoiceRegistry) cancelSingleHtlc(hash lntypes.Hash,
|
func (i *InvoiceRegistry) cancelSingleHtlc(hash lntypes.Hash,
|
||||||
key channeldb.CircuitKey) error {
|
key channeldb.CircuitKey, result ResolutionResult) error {
|
||||||
|
|
||||||
i.Lock()
|
i.Lock()
|
||||||
defer i.Unlock()
|
defer i.Unlock()
|
||||||
@ -652,11 +681,11 @@ func (i *InvoiceRegistry) cancelSingleHtlc(hash lntypes.Hash,
|
|||||||
return fmt.Errorf("htlc %v not found", key)
|
return fmt.Errorf("htlc %v not found", key)
|
||||||
}
|
}
|
||||||
if htlc.State == channeldb.HtlcStateCanceled {
|
if htlc.State == channeldb.HtlcStateCanceled {
|
||||||
i.notifyHodlSubscribers(HodlEvent{
|
resolution := *NewFailureResolution(
|
||||||
CircuitKey: key,
|
key, int32(htlc.AcceptHeight), result,
|
||||||
AcceptHeight: int32(htlc.AcceptHeight),
|
)
|
||||||
Preimage: nil,
|
|
||||||
})
|
i.notifyHodlSubscribers(resolution)
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
@ -679,7 +708,7 @@ func (i *InvoiceRegistry) cancelSingleHtlc(hash lntypes.Hash,
|
|||||||
func (i *InvoiceRegistry) NotifyExitHopHtlc(rHash lntypes.Hash,
|
func (i *InvoiceRegistry) NotifyExitHopHtlc(rHash lntypes.Hash,
|
||||||
amtPaid lnwire.MilliSatoshi, expiry uint32, currentHeight int32,
|
amtPaid lnwire.MilliSatoshi, expiry uint32, currentHeight int32,
|
||||||
circuitKey channeldb.CircuitKey, hodlChan chan<- interface{},
|
circuitKey channeldb.CircuitKey, hodlChan chan<- interface{},
|
||||||
payload Payload) (*HodlEvent, error) {
|
payload Payload) (*HtlcResolution, error) {
|
||||||
|
|
||||||
i.Lock()
|
i.Lock()
|
||||||
defer i.Unlock()
|
defer i.Unlock()
|
||||||
@ -706,7 +735,7 @@ func (i *InvoiceRegistry) NotifyExitHopHtlc(rHash lntypes.Hash,
|
|||||||
// We'll attempt to settle an invoice matching this rHash on disk (if
|
// We'll attempt to settle an invoice matching this rHash on disk (if
|
||||||
// one exists). The callback will update the invoice state and/or htlcs.
|
// one exists). The callback will update the invoice state and/or htlcs.
|
||||||
var (
|
var (
|
||||||
result updateResult
|
result ResolutionResult
|
||||||
updateSubscribers bool
|
updateSubscribers bool
|
||||||
)
|
)
|
||||||
invoice, err := i.cdb.UpdateInvoice(
|
invoice, err := i.cdb.UpdateInvoice(
|
||||||
@ -729,11 +758,21 @@ func (i *InvoiceRegistry) NotifyExitHopHtlc(rHash lntypes.Hash,
|
|||||||
return updateDesc, nil
|
return updateDesc, nil
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
if err != nil {
|
switch err {
|
||||||
debugLog(err.Error())
|
case channeldb.ErrInvoiceNotFound:
|
||||||
|
// If the invoice was not found, return a failure resolution
|
||||||
|
// with an invoice not found result.
|
||||||
|
return NewFailureResolution(
|
||||||
|
circuitKey, currentHeight, ResultInvoiceNotFound,
|
||||||
|
), nil
|
||||||
|
|
||||||
|
case nil:
|
||||||
|
|
||||||
|
default:
|
||||||
|
debugLog(err.Error())
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
debugLog(result.String())
|
debugLog(result.String())
|
||||||
|
|
||||||
if updateSubscribers {
|
if updateSubscribers {
|
||||||
@ -745,10 +784,9 @@ func (i *InvoiceRegistry) NotifyExitHopHtlc(rHash lntypes.Hash,
|
|||||||
|
|
||||||
// If it isn't recorded, cancel htlc.
|
// If it isn't recorded, cancel htlc.
|
||||||
if !ok {
|
if !ok {
|
||||||
return &HodlEvent{
|
return NewFailureResolution(
|
||||||
CircuitKey: circuitKey,
|
circuitKey, currentHeight, result,
|
||||||
AcceptHeight: currentHeight,
|
), nil
|
||||||
}, nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Determine accepted height of this htlc. If the htlc reached the
|
// Determine accepted height of this htlc. If the htlc reached the
|
||||||
@ -759,10 +797,9 @@ func (i *InvoiceRegistry) NotifyExitHopHtlc(rHash lntypes.Hash,
|
|||||||
|
|
||||||
switch invoiceHtlc.State {
|
switch invoiceHtlc.State {
|
||||||
case channeldb.HtlcStateCanceled:
|
case channeldb.HtlcStateCanceled:
|
||||||
return &HodlEvent{
|
return NewFailureResolution(
|
||||||
CircuitKey: circuitKey,
|
circuitKey, acceptHeight, result,
|
||||||
AcceptHeight: acceptHeight,
|
), nil
|
||||||
}, nil
|
|
||||||
|
|
||||||
case channeldb.HtlcStateSettled:
|
case channeldb.HtlcStateSettled:
|
||||||
// Also settle any previously accepted htlcs. The invoice state
|
// Also settle any previously accepted htlcs. The invoice state
|
||||||
@ -773,18 +810,24 @@ func (i *InvoiceRegistry) NotifyExitHopHtlc(rHash lntypes.Hash,
|
|||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
i.notifyHodlSubscribers(HodlEvent{
|
// Notify subscribers that the htlcs should be settled
|
||||||
CircuitKey: key,
|
// with our peer. Note that the outcome of the
|
||||||
Preimage: &invoice.Terms.PaymentPreimage,
|
// resolution is set based on the outcome of the single
|
||||||
AcceptHeight: int32(htlc.AcceptHeight),
|
// htlc that we just settled, so may not be accurate
|
||||||
})
|
// for all htlcs.
|
||||||
|
resolution := *NewSettleResolution(
|
||||||
|
invoice.Terms.PaymentPreimage, key,
|
||||||
|
acceptHeight, result,
|
||||||
|
)
|
||||||
|
|
||||||
|
i.notifyHodlSubscribers(resolution)
|
||||||
}
|
}
|
||||||
|
|
||||||
return &HodlEvent{
|
resolution := NewSettleResolution(
|
||||||
CircuitKey: circuitKey,
|
invoice.Terms.PaymentPreimage, circuitKey,
|
||||||
Preimage: &invoice.Terms.PaymentPreimage,
|
acceptHeight, result,
|
||||||
AcceptHeight: acceptHeight,
|
)
|
||||||
}, nil
|
return resolution, nil
|
||||||
|
|
||||||
case channeldb.HtlcStateAccepted:
|
case channeldb.HtlcStateAccepted:
|
||||||
// (Re)start the htlc timer if the invoice is still open. It can
|
// (Re)start the htlc timer if the invoice is still open. It can
|
||||||
@ -836,7 +879,9 @@ func (i *InvoiceRegistry) SettleHodlInvoice(preimage lntypes.Preimage) error {
|
|||||||
hash := preimage.Hash()
|
hash := preimage.Hash()
|
||||||
invoice, err := i.cdb.UpdateInvoice(hash, updateInvoice)
|
invoice, err := i.cdb.UpdateInvoice(hash, updateInvoice)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Errorf("SettleHodlInvoice with preimage %v: %v", preimage, err)
|
log.Errorf("SettleHodlInvoice with preimage %v: %v",
|
||||||
|
preimage, err)
|
||||||
|
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -854,11 +899,11 @@ func (i *InvoiceRegistry) SettleHodlInvoice(preimage lntypes.Preimage) error {
|
|||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
i.notifyHodlSubscribers(HodlEvent{
|
resolution := *NewSettleResolution(
|
||||||
CircuitKey: key,
|
preimage, key, int32(htlc.AcceptHeight), ResultSettled,
|
||||||
Preimage: &preimage,
|
)
|
||||||
AcceptHeight: int32(htlc.AcceptHeight),
|
|
||||||
})
|
i.notifyHodlSubscribers(resolution)
|
||||||
}
|
}
|
||||||
i.notifyClients(hash, invoice, invoice.State)
|
i.notifyClients(hash, invoice, invoice.State)
|
||||||
|
|
||||||
@ -873,7 +918,8 @@ func (i *InvoiceRegistry) CancelInvoice(payHash lntypes.Hash) error {
|
|||||||
|
|
||||||
// cancelInvoice attempts to cancel the invoice corresponding to the passed
|
// cancelInvoice attempts to cancel the invoice corresponding to the passed
|
||||||
// payment hash. Accepted invoices will only be canceled if explicitly
|
// payment hash. Accepted invoices will only be canceled if explicitly
|
||||||
// requested to do so.
|
// requested to do so. It notifies subscribing links and resolvers that
|
||||||
|
// the associated htlcs were canceled if they change state.
|
||||||
func (i *InvoiceRegistry) cancelInvoiceImpl(payHash lntypes.Hash,
|
func (i *InvoiceRegistry) cancelInvoiceImpl(payHash lntypes.Hash,
|
||||||
cancelAccepted bool) error {
|
cancelAccepted bool) error {
|
||||||
|
|
||||||
@ -932,10 +978,11 @@ func (i *InvoiceRegistry) cancelInvoiceImpl(payHash lntypes.Hash,
|
|||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
i.notifyHodlSubscribers(HodlEvent{
|
i.notifyHodlSubscribers(
|
||||||
CircuitKey: key,
|
*NewFailureResolution(
|
||||||
AcceptHeight: int32(htlc.AcceptHeight),
|
key, int32(htlc.AcceptHeight), ResultCanceled,
|
||||||
})
|
),
|
||||||
|
)
|
||||||
}
|
}
|
||||||
i.notifyClients(payHash, invoice, channeldb.ContractCanceled)
|
i.notifyClients(payHash, invoice, channeldb.ContractCanceled)
|
||||||
|
|
||||||
@ -1201,9 +1248,10 @@ func (i *InvoiceRegistry) SubscribeSingleInvoice(
|
|||||||
return client, nil
|
return client, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// notifyHodlSubscribers sends out the hodl event to all current subscribers.
|
// notifyHodlSubscribers sends out the htlc resolution to all current
|
||||||
func (i *InvoiceRegistry) notifyHodlSubscribers(hodlEvent HodlEvent) {
|
// subscribers.
|
||||||
subscribers, ok := i.hodlSubscriptions[hodlEvent.CircuitKey]
|
func (i *InvoiceRegistry) notifyHodlSubscribers(htlcResolution HtlcResolution) {
|
||||||
|
subscribers, ok := i.hodlSubscriptions[htlcResolution.CircuitKey]
|
||||||
if !ok {
|
if !ok {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@ -1213,18 +1261,18 @@ func (i *InvoiceRegistry) notifyHodlSubscribers(hodlEvent HodlEvent) {
|
|||||||
// single resolution for each hash.
|
// single resolution for each hash.
|
||||||
for subscriber := range subscribers {
|
for subscriber := range subscribers {
|
||||||
select {
|
select {
|
||||||
case subscriber <- hodlEvent:
|
case subscriber <- htlcResolution:
|
||||||
case <-i.quit:
|
case <-i.quit:
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
delete(
|
delete(
|
||||||
i.hodlReverseSubscriptions[subscriber],
|
i.hodlReverseSubscriptions[subscriber],
|
||||||
hodlEvent.CircuitKey,
|
htlcResolution.CircuitKey,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
delete(i.hodlSubscriptions, hodlEvent.CircuitKey)
|
delete(i.hodlSubscriptions, htlcResolution.CircuitKey)
|
||||||
}
|
}
|
||||||
|
|
||||||
// hodlSubscribe adds a new invoice subscription.
|
// hodlSubscribe adds a new invoice subscription.
|
||||||
|
@ -66,7 +66,7 @@ func TestSettleInvoice(t *testing.T) {
|
|||||||
hodlChan := make(chan interface{}, 1)
|
hodlChan := make(chan interface{}, 1)
|
||||||
|
|
||||||
// Try to settle invoice with an htlc that expires too soon.
|
// Try to settle invoice with an htlc that expires too soon.
|
||||||
event, err := ctx.registry.NotifyExitHopHtlc(
|
resolution, err := ctx.registry.NotifyExitHopHtlc(
|
||||||
testInvoicePaymentHash, testInvoice.Terms.Value,
|
testInvoicePaymentHash, testInvoice.Terms.Value,
|
||||||
uint32(testCurrentHeight)+testInvoiceCltvDelta-1,
|
uint32(testCurrentHeight)+testInvoiceCltvDelta-1,
|
||||||
testCurrentHeight, getCircuitKey(10), hodlChan, testPayload,
|
testCurrentHeight, getCircuitKey(10), hodlChan, testPayload,
|
||||||
@ -74,23 +74,30 @@ func TestSettleInvoice(t *testing.T) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
if event.Preimage != nil {
|
if resolution.Preimage != nil {
|
||||||
t.Fatal("expected cancel event")
|
t.Fatal("expected cancel resolution")
|
||||||
}
|
}
|
||||||
if event.AcceptHeight != testCurrentHeight {
|
if resolution.AcceptHeight != testCurrentHeight {
|
||||||
t.Fatalf("expected acceptHeight %v, but got %v",
|
t.Fatalf("expected acceptHeight %v, but got %v",
|
||||||
testCurrentHeight, event.AcceptHeight)
|
testCurrentHeight, resolution.AcceptHeight)
|
||||||
|
}
|
||||||
|
if resolution.Outcome != ResultExpiryTooSoon {
|
||||||
|
t.Fatalf("expected expiry too soon, got: %v",
|
||||||
|
resolution.Outcome)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Settle invoice with a slightly higher amount.
|
// Settle invoice with a slightly higher amount.
|
||||||
amtPaid := lnwire.MilliSatoshi(100500)
|
amtPaid := lnwire.MilliSatoshi(100500)
|
||||||
_, err = ctx.registry.NotifyExitHopHtlc(
|
resolution, err = ctx.registry.NotifyExitHopHtlc(
|
||||||
testInvoicePaymentHash, amtPaid, testHtlcExpiry, testCurrentHeight,
|
testInvoicePaymentHash, amtPaid, testHtlcExpiry, testCurrentHeight,
|
||||||
getCircuitKey(0), hodlChan, testPayload,
|
getCircuitKey(0), hodlChan, testPayload,
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
|
if resolution.Outcome != ResultSettled {
|
||||||
|
t.Fatalf("expected settled, got: %v", resolution.Outcome)
|
||||||
|
}
|
||||||
|
|
||||||
// We expect the settled state to be sent to the single invoice
|
// We expect the settled state to be sent to the single invoice
|
||||||
// subscriber.
|
// subscriber.
|
||||||
@ -120,42 +127,54 @@ func TestSettleInvoice(t *testing.T) {
|
|||||||
|
|
||||||
// Try to settle again with the same htlc id. We need this idempotent
|
// Try to settle again with the same htlc id. We need this idempotent
|
||||||
// behaviour after a restart.
|
// behaviour after a restart.
|
||||||
event, err = ctx.registry.NotifyExitHopHtlc(
|
resolution, err = ctx.registry.NotifyExitHopHtlc(
|
||||||
testInvoicePaymentHash, amtPaid, testHtlcExpiry, testCurrentHeight,
|
testInvoicePaymentHash, amtPaid, testHtlcExpiry, testCurrentHeight,
|
||||||
getCircuitKey(0), hodlChan, testPayload,
|
getCircuitKey(0), hodlChan, testPayload,
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("unexpected NotifyExitHopHtlc error: %v", err)
|
t.Fatalf("unexpected NotifyExitHopHtlc error: %v", err)
|
||||||
}
|
}
|
||||||
if event.Preimage == nil {
|
if resolution.Preimage == nil {
|
||||||
t.Fatal("expected settle event")
|
t.Fatal("expected settle resolution")
|
||||||
|
}
|
||||||
|
if resolution.Outcome != ResultReplayToSettled {
|
||||||
|
t.Fatalf("expected replay settled, got: %v",
|
||||||
|
resolution.Outcome)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Try to settle again with a new higher-valued htlc. This payment
|
// Try to settle again with a new higher-valued htlc. This payment
|
||||||
// should also be accepted, to prevent any change in behaviour for a
|
// should also be accepted, to prevent any change in behaviour for a
|
||||||
// paid invoice that may open up a probe vector.
|
// paid invoice that may open up a probe vector.
|
||||||
event, err = ctx.registry.NotifyExitHopHtlc(
|
resolution, err = ctx.registry.NotifyExitHopHtlc(
|
||||||
testInvoicePaymentHash, amtPaid+600, testHtlcExpiry, testCurrentHeight,
|
testInvoicePaymentHash, amtPaid+600, testHtlcExpiry, testCurrentHeight,
|
||||||
getCircuitKey(1), hodlChan, testPayload,
|
getCircuitKey(1), hodlChan, testPayload,
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("unexpected NotifyExitHopHtlc error: %v", err)
|
t.Fatalf("unexpected NotifyExitHopHtlc error: %v", err)
|
||||||
}
|
}
|
||||||
if event.Preimage == nil {
|
if resolution.Preimage == nil {
|
||||||
t.Fatal("expected settle event")
|
t.Fatal("expected settle resolution")
|
||||||
|
}
|
||||||
|
if resolution.Outcome != ResultDuplicateToSettled {
|
||||||
|
t.Fatalf("expected duplicate settled, got: %v",
|
||||||
|
resolution.Outcome)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Try to settle again with a lower amount. This should fail just as it
|
// Try to settle again with a lower amount. This should fail just as it
|
||||||
// would have failed if it were the first payment.
|
// would have failed if it were the first payment.
|
||||||
event, err = ctx.registry.NotifyExitHopHtlc(
|
resolution, err = ctx.registry.NotifyExitHopHtlc(
|
||||||
testInvoicePaymentHash, amtPaid-600, testHtlcExpiry, testCurrentHeight,
|
testInvoicePaymentHash, amtPaid-600, testHtlcExpiry, testCurrentHeight,
|
||||||
getCircuitKey(2), hodlChan, testPayload,
|
getCircuitKey(2), hodlChan, testPayload,
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("unexpected NotifyExitHopHtlc error: %v", err)
|
t.Fatalf("unexpected NotifyExitHopHtlc error: %v", err)
|
||||||
}
|
}
|
||||||
if event.Preimage != nil {
|
if resolution.Preimage != nil {
|
||||||
t.Fatal("expected cancel event")
|
t.Fatal("expected cancel resolution")
|
||||||
|
}
|
||||||
|
if resolution.Outcome != ResultAmountTooLow {
|
||||||
|
t.Fatalf("expected amount too low, got: %v",
|
||||||
|
resolution.Outcome)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check that settled amount is equal to the sum of values of the htlcs
|
// Check that settled amount is equal to the sum of values of the htlcs
|
||||||
@ -177,7 +196,7 @@ func TestSettleInvoice(t *testing.T) {
|
|||||||
// As this is a direct sette, we expect nothing on the hodl chan.
|
// As this is a direct sette, we expect nothing on the hodl chan.
|
||||||
select {
|
select {
|
||||||
case <-hodlChan:
|
case <-hodlChan:
|
||||||
t.Fatal("unexpected event")
|
t.Fatal("unexpected resolution")
|
||||||
default:
|
default:
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -270,9 +289,9 @@ func TestCancelInvoice(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Notify arrival of a new htlc paying to this invoice. This should
|
// Notify arrival of a new htlc paying to this invoice. This should
|
||||||
// result in a cancel event.
|
// result in a cancel resolution.
|
||||||
hodlChan := make(chan interface{})
|
hodlChan := make(chan interface{})
|
||||||
event, err := ctx.registry.NotifyExitHopHtlc(
|
resolution, err := ctx.registry.NotifyExitHopHtlc(
|
||||||
testInvoicePaymentHash, amt, testHtlcExpiry, testCurrentHeight,
|
testInvoicePaymentHash, amt, testHtlcExpiry, testCurrentHeight,
|
||||||
getCircuitKey(0), hodlChan, testPayload,
|
getCircuitKey(0), hodlChan, testPayload,
|
||||||
)
|
)
|
||||||
@ -280,12 +299,16 @@ func TestCancelInvoice(t *testing.T) {
|
|||||||
t.Fatal("expected settlement of a canceled invoice to succeed")
|
t.Fatal("expected settlement of a canceled invoice to succeed")
|
||||||
}
|
}
|
||||||
|
|
||||||
if event.Preimage != nil {
|
if resolution.Preimage != nil {
|
||||||
t.Fatal("expected cancel hodl event")
|
t.Fatal("expected cancel htlc resolution")
|
||||||
}
|
}
|
||||||
if event.AcceptHeight != testCurrentHeight {
|
if resolution.AcceptHeight != testCurrentHeight {
|
||||||
t.Fatalf("expected acceptHeight %v, but got %v",
|
t.Fatalf("expected acceptHeight %v, but got %v",
|
||||||
testCurrentHeight, event.AcceptHeight)
|
testCurrentHeight, resolution.AcceptHeight)
|
||||||
|
}
|
||||||
|
if resolution.Outcome != ResultInvoiceAlreadyCanceled {
|
||||||
|
t.Fatalf("expected invoice already canceled, got: %v",
|
||||||
|
resolution.Outcome)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -354,54 +377,58 @@ func TestSettleHoldInvoice(t *testing.T) {
|
|||||||
|
|
||||||
// NotifyExitHopHtlc without a preimage present in the invoice registry
|
// NotifyExitHopHtlc without a preimage present in the invoice registry
|
||||||
// should be possible.
|
// should be possible.
|
||||||
event, err := registry.NotifyExitHopHtlc(
|
resolution, err := registry.NotifyExitHopHtlc(
|
||||||
testInvoicePaymentHash, amtPaid, testHtlcExpiry, testCurrentHeight,
|
testInvoicePaymentHash, amtPaid, testHtlcExpiry, testCurrentHeight,
|
||||||
getCircuitKey(0), hodlChan, testPayload,
|
getCircuitKey(0), hodlChan, testPayload,
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("expected settle to succeed but got %v", err)
|
t.Fatalf("expected settle to succeed but got %v", err)
|
||||||
}
|
}
|
||||||
if event != nil {
|
if resolution != nil {
|
||||||
t.Fatalf("expected htlc to be held")
|
t.Fatalf("expected htlc to be held")
|
||||||
}
|
}
|
||||||
|
|
||||||
// Test idempotency.
|
// Test idempotency.
|
||||||
event, err = registry.NotifyExitHopHtlc(
|
resolution, err = registry.NotifyExitHopHtlc(
|
||||||
testInvoicePaymentHash, amtPaid, testHtlcExpiry, testCurrentHeight,
|
testInvoicePaymentHash, amtPaid, testHtlcExpiry, testCurrentHeight,
|
||||||
getCircuitKey(0), hodlChan, testPayload,
|
getCircuitKey(0), hodlChan, testPayload,
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("expected settle to succeed but got %v", err)
|
t.Fatalf("expected settle to succeed but got %v", err)
|
||||||
}
|
}
|
||||||
if event != nil {
|
if resolution != nil {
|
||||||
t.Fatalf("expected htlc to be held")
|
t.Fatalf("expected htlc to be held")
|
||||||
}
|
}
|
||||||
|
|
||||||
// Test replay at a higher height. We expect the same result because it
|
// Test replay at a higher height. We expect the same result because it
|
||||||
// is a replay.
|
// is a replay.
|
||||||
event, err = registry.NotifyExitHopHtlc(
|
resolution, err = registry.NotifyExitHopHtlc(
|
||||||
testInvoicePaymentHash, amtPaid, testHtlcExpiry, testCurrentHeight+10,
|
testInvoicePaymentHash, amtPaid, testHtlcExpiry, testCurrentHeight+10,
|
||||||
getCircuitKey(0), hodlChan, testPayload,
|
getCircuitKey(0), hodlChan, testPayload,
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("expected settle to succeed but got %v", err)
|
t.Fatalf("expected settle to succeed but got %v", err)
|
||||||
}
|
}
|
||||||
if event != nil {
|
if resolution != nil {
|
||||||
t.Fatalf("expected htlc to be held")
|
t.Fatalf("expected htlc to be held")
|
||||||
}
|
}
|
||||||
|
|
||||||
// Test a new htlc coming in that doesn't meet the final cltv delta
|
// Test a new htlc coming in that doesn't meet the final cltv delta
|
||||||
// requirement. It should be rejected.
|
// requirement. It should be rejected.
|
||||||
event, err = registry.NotifyExitHopHtlc(
|
resolution, err = registry.NotifyExitHopHtlc(
|
||||||
testInvoicePaymentHash, amtPaid, 1, testCurrentHeight,
|
testInvoicePaymentHash, amtPaid, 1, testCurrentHeight,
|
||||||
getCircuitKey(1), hodlChan, testPayload,
|
getCircuitKey(1), hodlChan, testPayload,
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("expected settle to succeed but got %v", err)
|
t.Fatalf("expected settle to succeed but got %v", err)
|
||||||
}
|
}
|
||||||
if event == nil || event.Preimage != nil {
|
if resolution == nil || resolution.Preimage != nil {
|
||||||
t.Fatalf("expected htlc to be canceled")
|
t.Fatalf("expected htlc to be canceled")
|
||||||
}
|
}
|
||||||
|
if resolution.Outcome != ResultExpiryTooSoon {
|
||||||
|
t.Fatalf("expected expiry too soon, got: %v",
|
||||||
|
resolution.Outcome)
|
||||||
|
}
|
||||||
|
|
||||||
// We expect the accepted state to be sent to the single invoice
|
// We expect the accepted state to be sent to the single invoice
|
||||||
// subscriber. For all invoice subscribers, we don't expect an update.
|
// subscriber. For all invoice subscribers, we don't expect an update.
|
||||||
@ -421,13 +448,17 @@ func TestSettleHoldInvoice(t *testing.T) {
|
|||||||
t.Fatal("expected set preimage to succeed")
|
t.Fatal("expected set preimage to succeed")
|
||||||
}
|
}
|
||||||
|
|
||||||
hodlEvent := (<-hodlChan).(HodlEvent)
|
htlcResolution := (<-hodlChan).(HtlcResolution)
|
||||||
if *hodlEvent.Preimage != testInvoicePreimage {
|
if *htlcResolution.Preimage != testInvoicePreimage {
|
||||||
t.Fatal("unexpected preimage in hodl event")
|
t.Fatal("unexpected preimage in hodl resolution")
|
||||||
}
|
}
|
||||||
if hodlEvent.AcceptHeight != testCurrentHeight {
|
if htlcResolution.AcceptHeight != testCurrentHeight {
|
||||||
t.Fatalf("expected acceptHeight %v, but got %v",
|
t.Fatalf("expected acceptHeight %v, but got %v",
|
||||||
testCurrentHeight, event.AcceptHeight)
|
testCurrentHeight, resolution.AcceptHeight)
|
||||||
|
}
|
||||||
|
if htlcResolution.Outcome != ResultSettled {
|
||||||
|
t.Fatalf("expected result settled, got: %v",
|
||||||
|
htlcResolution.Outcome)
|
||||||
}
|
}
|
||||||
|
|
||||||
// We expect a settled notification to be sent out for both all and
|
// We expect a settled notification to be sent out for both all and
|
||||||
@ -496,14 +527,14 @@ func TestCancelHoldInvoice(t *testing.T) {
|
|||||||
|
|
||||||
// NotifyExitHopHtlc without a preimage present in the invoice registry
|
// NotifyExitHopHtlc without a preimage present in the invoice registry
|
||||||
// should be possible.
|
// should be possible.
|
||||||
event, err := registry.NotifyExitHopHtlc(
|
resolution, err := registry.NotifyExitHopHtlc(
|
||||||
testInvoicePaymentHash, amtPaid, testHtlcExpiry, testCurrentHeight,
|
testInvoicePaymentHash, amtPaid, testHtlcExpiry, testCurrentHeight,
|
||||||
getCircuitKey(0), hodlChan, testPayload,
|
getCircuitKey(0), hodlChan, testPayload,
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("expected settle to succeed but got %v", err)
|
t.Fatalf("expected settle to succeed but got %v", err)
|
||||||
}
|
}
|
||||||
if event != nil {
|
if resolution != nil {
|
||||||
t.Fatalf("expected htlc to be held")
|
t.Fatalf("expected htlc to be held")
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -513,35 +544,39 @@ func TestCancelHoldInvoice(t *testing.T) {
|
|||||||
t.Fatal("cancel invoice failed")
|
t.Fatal("cancel invoice failed")
|
||||||
}
|
}
|
||||||
|
|
||||||
hodlEvent := (<-hodlChan).(HodlEvent)
|
htlcResolution := (<-hodlChan).(HtlcResolution)
|
||||||
if hodlEvent.Preimage != nil {
|
if htlcResolution.Preimage != nil {
|
||||||
t.Fatal("expected cancel hodl event")
|
t.Fatal("expected cancel htlc resolution")
|
||||||
}
|
}
|
||||||
|
|
||||||
// Offering the same htlc again at a higher height should still result
|
// Offering the same htlc again at a higher height should still result
|
||||||
// in a rejection. The accept height is expected to be the original
|
// in a rejection. The accept height is expected to be the original
|
||||||
// accept height.
|
// accept height.
|
||||||
event, err = registry.NotifyExitHopHtlc(
|
resolution, err = registry.NotifyExitHopHtlc(
|
||||||
testInvoicePaymentHash, amtPaid, testHtlcExpiry, testCurrentHeight+1,
|
testInvoicePaymentHash, amtPaid, testHtlcExpiry, testCurrentHeight+1,
|
||||||
getCircuitKey(0), hodlChan, testPayload,
|
getCircuitKey(0), hodlChan, testPayload,
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("expected settle to succeed but got %v", err)
|
t.Fatalf("expected settle to succeed but got %v", err)
|
||||||
}
|
}
|
||||||
if event.Preimage != nil {
|
if resolution.Preimage != nil {
|
||||||
t.Fatalf("expected htlc to be canceled")
|
t.Fatalf("expected htlc to be canceled")
|
||||||
}
|
}
|
||||||
if event.AcceptHeight != testCurrentHeight {
|
if resolution.AcceptHeight != testCurrentHeight {
|
||||||
t.Fatalf("expected acceptHeight %v, but got %v",
|
t.Fatalf("expected acceptHeight %v, but got %v",
|
||||||
testCurrentHeight, event.AcceptHeight)
|
testCurrentHeight, resolution.AcceptHeight)
|
||||||
|
}
|
||||||
|
if resolution.Outcome != ResultReplayToCanceled {
|
||||||
|
t.Fatalf("expected replay to canceled, got %v",
|
||||||
|
resolution.Outcome)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// TestUnknownInvoice tests that invoice registry returns an error when the
|
// TestUnknownInvoice tests that invoice registry returns an error when the
|
||||||
// invoice is unknown. This is to guard against returning a cancel hodl event
|
// invoice is unknown. This is to guard against returning a cancel htlc
|
||||||
// for forwarded htlcs. In the link, NotifyExitHopHtlc is only called if we are
|
// resolution for forwarded htlcs. In the link, NotifyExitHopHtlc is only called
|
||||||
// the exit hop, but in htlcIncomingContestResolver it is called with forwarded
|
// if we are the exit hop, but in htlcIncomingContestResolver it is called with
|
||||||
// htlc hashes as well.
|
// forwarded htlc hashes as well.
|
||||||
func TestUnknownInvoice(t *testing.T) {
|
func TestUnknownInvoice(t *testing.T) {
|
||||||
ctx := newTestContext(t)
|
ctx := newTestContext(t)
|
||||||
defer ctx.cleanup()
|
defer ctx.cleanup()
|
||||||
@ -550,17 +585,23 @@ func TestUnknownInvoice(t *testing.T) {
|
|||||||
// succeed.
|
// succeed.
|
||||||
hodlChan := make(chan interface{})
|
hodlChan := make(chan interface{})
|
||||||
amt := lnwire.MilliSatoshi(100000)
|
amt := lnwire.MilliSatoshi(100000)
|
||||||
_, err := ctx.registry.NotifyExitHopHtlc(
|
result, err := ctx.registry.NotifyExitHopHtlc(
|
||||||
testInvoicePaymentHash, amt, testHtlcExpiry, testCurrentHeight,
|
testInvoicePaymentHash, amt, testHtlcExpiry, testCurrentHeight,
|
||||||
getCircuitKey(0), hodlChan, testPayload,
|
getCircuitKey(0), hodlChan, testPayload,
|
||||||
)
|
)
|
||||||
if err != channeldb.ErrInvoiceNotFound {
|
if err != nil {
|
||||||
t.Fatal("expected invoice not found error")
|
t.Fatal("unexpected error")
|
||||||
|
}
|
||||||
|
if result.Outcome != ResultInvoiceNotFound {
|
||||||
|
t.Fatalf("expected ResultInvoiceNotFound, got: %v",
|
||||||
|
result.Outcome)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// TestSettleMpp tests settling of an invoice with multiple partial payments.
|
// TestMppPayment tests settling of an invoice with multiple partial payments.
|
||||||
func TestSettleMpp(t *testing.T) {
|
// It covers the case where there is a mpp timeout before the whole invoice is
|
||||||
|
// paid and the case where the invoice is settled in time.
|
||||||
|
func TestMppPayment(t *testing.T) {
|
||||||
defer timeout()()
|
defer timeout()()
|
||||||
|
|
||||||
ctx := newTestContext(t)
|
ctx := newTestContext(t)
|
||||||
@ -578,7 +619,7 @@ func TestSettleMpp(t *testing.T) {
|
|||||||
|
|
||||||
// Send htlc 1.
|
// Send htlc 1.
|
||||||
hodlChan1 := make(chan interface{}, 1)
|
hodlChan1 := make(chan interface{}, 1)
|
||||||
event, err := ctx.registry.NotifyExitHopHtlc(
|
resolution, err := ctx.registry.NotifyExitHopHtlc(
|
||||||
testInvoicePaymentHash, testInvoice.Terms.Value/2,
|
testInvoicePaymentHash, testInvoice.Terms.Value/2,
|
||||||
testHtlcExpiry,
|
testHtlcExpiry,
|
||||||
testCurrentHeight, getCircuitKey(10), hodlChan1, mppPayload,
|
testCurrentHeight, getCircuitKey(10), hodlChan1, mppPayload,
|
||||||
@ -586,21 +627,25 @@ func TestSettleMpp(t *testing.T) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
if event != nil {
|
if resolution != nil {
|
||||||
t.Fatal("expected no direct resolution")
|
t.Fatal("expected no direct resolution")
|
||||||
}
|
}
|
||||||
|
|
||||||
// Simulate mpp timeout releasing htlc 1.
|
// Simulate mpp timeout releasing htlc 1.
|
||||||
ctx.clock.SetTime(testTime.Add(30 * time.Second))
|
ctx.clock.SetTime(testTime.Add(30 * time.Second))
|
||||||
|
|
||||||
hodlEvent := (<-hodlChan1).(HodlEvent)
|
htlcResolution := (<-hodlChan1).(HtlcResolution)
|
||||||
if hodlEvent.Preimage != nil {
|
if htlcResolution.Preimage != nil {
|
||||||
t.Fatal("expected cancel event")
|
t.Fatal("expected cancel resolution")
|
||||||
|
}
|
||||||
|
if htlcResolution.Outcome != ResultMppTimeout {
|
||||||
|
t.Fatalf("expected mpp timeout, got: %v",
|
||||||
|
htlcResolution.Outcome)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Send htlc 2.
|
// Send htlc 2.
|
||||||
hodlChan2 := make(chan interface{}, 1)
|
hodlChan2 := make(chan interface{}, 1)
|
||||||
event, err = ctx.registry.NotifyExitHopHtlc(
|
resolution, err = ctx.registry.NotifyExitHopHtlc(
|
||||||
testInvoicePaymentHash, testInvoice.Terms.Value/2,
|
testInvoicePaymentHash, testInvoice.Terms.Value/2,
|
||||||
testHtlcExpiry,
|
testHtlcExpiry,
|
||||||
testCurrentHeight, getCircuitKey(11), hodlChan2, mppPayload,
|
testCurrentHeight, getCircuitKey(11), hodlChan2, mppPayload,
|
||||||
@ -608,13 +653,13 @@ func TestSettleMpp(t *testing.T) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
if event != nil {
|
if resolution != nil {
|
||||||
t.Fatal("expected no direct resolution")
|
t.Fatal("expected no direct resolution")
|
||||||
}
|
}
|
||||||
|
|
||||||
// Send htlc 3.
|
// Send htlc 3.
|
||||||
hodlChan3 := make(chan interface{}, 1)
|
hodlChan3 := make(chan interface{}, 1)
|
||||||
event, err = ctx.registry.NotifyExitHopHtlc(
|
resolution, err = ctx.registry.NotifyExitHopHtlc(
|
||||||
testInvoicePaymentHash, testInvoice.Terms.Value/2,
|
testInvoicePaymentHash, testInvoice.Terms.Value/2,
|
||||||
testHtlcExpiry,
|
testHtlcExpiry,
|
||||||
testCurrentHeight, getCircuitKey(12), hodlChan3, mppPayload,
|
testCurrentHeight, getCircuitKey(12), hodlChan3, mppPayload,
|
||||||
@ -622,12 +667,16 @@ func TestSettleMpp(t *testing.T) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
if event == nil {
|
if resolution == nil {
|
||||||
t.Fatal("expected a settle event")
|
t.Fatal("expected a settle resolution")
|
||||||
|
}
|
||||||
|
if resolution.Outcome != ResultSettled {
|
||||||
|
t.Fatalf("expected result settled, got: %v",
|
||||||
|
resolution.Outcome)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check that settled amount is equal to the sum of values of the htlcs
|
// Check that settled amount is equal to the sum of values of the htlcs
|
||||||
// 0 and 1.
|
// 2 and 3.
|
||||||
inv, err := ctx.registry.LookupInvoice(testInvoicePaymentHash)
|
inv, err := ctx.registry.LookupInvoice(testInvoicePaymentHash)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
|
@ -8,86 +8,141 @@ import (
|
|||||||
"github.com/lightningnetwork/lnd/record"
|
"github.com/lightningnetwork/lnd/record"
|
||||||
)
|
)
|
||||||
|
|
||||||
// updateResult is the result of the invoice update call.
|
// ResolutionResult provides metadata which about an invoice update which can
|
||||||
type updateResult uint8
|
// be used to take custom actions on resolution of the htlc. Only results which
|
||||||
|
// are actionable by the link are exported.
|
||||||
|
type ResolutionResult uint8
|
||||||
|
|
||||||
const (
|
const (
|
||||||
resultInvalid updateResult = iota
|
resultInvalid ResolutionResult = iota
|
||||||
resultReplayToCanceled
|
|
||||||
resultReplayToAccepted
|
// ResultReplayToCanceled is returned when we replay a canceled invoice.
|
||||||
resultReplayToSettled
|
ResultReplayToCanceled
|
||||||
resultInvoiceAlreadyCanceled
|
|
||||||
resultAmountTooLow
|
// ResultReplayToAccepted is returned when we replay an accepted invoice.
|
||||||
resultExpiryTooSoon
|
ResultReplayToAccepted
|
||||||
resultDuplicateToAccepted
|
|
||||||
resultDuplicateToSettled
|
// ResultReplayToSettled is returned when we replay a settled invoice.
|
||||||
resultAccepted
|
ResultReplayToSettled
|
||||||
resultSettled
|
|
||||||
resultInvoiceNotOpen
|
// ResultInvoiceAlreadyCanceled is returned when trying to pay an invoice
|
||||||
resultPartialAccepted
|
// that is already canceled.
|
||||||
resultMppInProgress
|
ResultInvoiceAlreadyCanceled
|
||||||
resultAddressMismatch
|
|
||||||
resultHtlcSetTotalMismatch
|
// ResultAmountTooLow is returned when an invoice is underpaid.
|
||||||
resultHtlcSetTotalTooLow
|
ResultAmountTooLow
|
||||||
resultHtlcSetOverpayment
|
|
||||||
|
// ResultExpiryTooSoon is returned when we do not accept an invoice payment
|
||||||
|
// because it expires too soon.
|
||||||
|
ResultExpiryTooSoon
|
||||||
|
|
||||||
|
// ResultDuplicateToAccepted is returned when we accept a duplicate htlc.
|
||||||
|
ResultDuplicateToAccepted
|
||||||
|
|
||||||
|
// ResultDuplicateToSettled is returned when we settle an invoice which has
|
||||||
|
// already been settled at least once.
|
||||||
|
ResultDuplicateToSettled
|
||||||
|
|
||||||
|
// ResultAccepted is returned when we accept a hodl invoice.
|
||||||
|
ResultAccepted
|
||||||
|
|
||||||
|
// ResultSettled is returned when we settle an invoice.
|
||||||
|
ResultSettled
|
||||||
|
|
||||||
|
// ResultCanceled is returned when we cancel an invoice and its associated
|
||||||
|
// htlcs.
|
||||||
|
ResultCanceled
|
||||||
|
|
||||||
|
// ResultInvoiceNotOpen is returned when a mpp invoice is not open.
|
||||||
|
ResultInvoiceNotOpen
|
||||||
|
|
||||||
|
// ResultPartialAccepted is returned when we have partially received
|
||||||
|
// payment.
|
||||||
|
ResultPartialAccepted
|
||||||
|
|
||||||
|
// ResultMppInProgress is returned when we are busy receiving a mpp payment.
|
||||||
|
ResultMppInProgress
|
||||||
|
|
||||||
|
// ResultMppTimeout is returned when an invoice paid with multiple partial
|
||||||
|
// payments times out before it is fully paid.
|
||||||
|
ResultMppTimeout
|
||||||
|
|
||||||
|
// ResultAddressMismatch is returned when the payment address for a mpp
|
||||||
|
// invoice does not match.
|
||||||
|
ResultAddressMismatch
|
||||||
|
|
||||||
|
// ResultHtlcSetTotalMismatch is returned when the amount paid by a htlc
|
||||||
|
// does not match its set total.
|
||||||
|
ResultHtlcSetTotalMismatch
|
||||||
|
|
||||||
|
// ResultHtlcSetTotalTooLow is returned when a mpp set total is too low for
|
||||||
|
// an invoice.
|
||||||
|
ResultHtlcSetTotalTooLow
|
||||||
|
|
||||||
|
// ResultHtlcSetOverpayment is returned when a mpp set is overpaid.
|
||||||
|
ResultHtlcSetOverpayment
|
||||||
|
|
||||||
|
// ResultInvoiceNotFound is returned when an attempt is made to pay an
|
||||||
|
// invoice that is unknown to us.
|
||||||
|
ResultInvoiceNotFound
|
||||||
)
|
)
|
||||||
|
|
||||||
// String returns a human-readable representation of the invoice update result.
|
// String returns a human-readable representation of the invoice update result.
|
||||||
func (u updateResult) String() string {
|
func (u ResolutionResult) String() string {
|
||||||
switch u {
|
switch u {
|
||||||
|
|
||||||
case resultInvalid:
|
case resultInvalid:
|
||||||
return "invalid"
|
return "invalid"
|
||||||
|
|
||||||
case resultReplayToCanceled:
|
case ResultReplayToCanceled:
|
||||||
return "replayed htlc to canceled invoice"
|
return "replayed htlc to canceled invoice"
|
||||||
|
|
||||||
case resultReplayToAccepted:
|
case ResultReplayToAccepted:
|
||||||
return "replayed htlc to accepted invoice"
|
return "replayed htlc to accepted invoice"
|
||||||
|
|
||||||
case resultReplayToSettled:
|
case ResultReplayToSettled:
|
||||||
return "replayed htlc to settled invoice"
|
return "replayed htlc to settled invoice"
|
||||||
|
|
||||||
case resultInvoiceAlreadyCanceled:
|
case ResultInvoiceAlreadyCanceled:
|
||||||
return "invoice already canceled"
|
return "invoice already canceled"
|
||||||
|
|
||||||
case resultAmountTooLow:
|
case ResultAmountTooLow:
|
||||||
return "amount too low"
|
return "amount too low"
|
||||||
|
|
||||||
case resultExpiryTooSoon:
|
case ResultExpiryTooSoon:
|
||||||
return "expiry too soon"
|
return "expiry too soon"
|
||||||
|
|
||||||
case resultDuplicateToAccepted:
|
case ResultDuplicateToAccepted:
|
||||||
return "accepting duplicate payment to accepted invoice"
|
return "accepting duplicate payment to accepted invoice"
|
||||||
|
|
||||||
case resultDuplicateToSettled:
|
case ResultDuplicateToSettled:
|
||||||
return "accepting duplicate payment to settled invoice"
|
return "accepting duplicate payment to settled invoice"
|
||||||
|
|
||||||
case resultAccepted:
|
case ResultAccepted:
|
||||||
return "accepted"
|
return "accepted"
|
||||||
|
|
||||||
case resultSettled:
|
case ResultSettled:
|
||||||
return "settled"
|
return "settled"
|
||||||
|
|
||||||
case resultInvoiceNotOpen:
|
case ResultInvoiceNotOpen:
|
||||||
return "invoice no longer open"
|
return "invoice no longer open"
|
||||||
|
|
||||||
case resultPartialAccepted:
|
case ResultPartialAccepted:
|
||||||
return "partial payment accepted"
|
return "partial payment accepted"
|
||||||
|
|
||||||
case resultMppInProgress:
|
case ResultMppInProgress:
|
||||||
return "mpp reception in progress"
|
return "mpp reception in progress"
|
||||||
|
|
||||||
case resultAddressMismatch:
|
case ResultAddressMismatch:
|
||||||
return "payment address mismatch"
|
return "payment address mismatch"
|
||||||
|
|
||||||
case resultHtlcSetTotalMismatch:
|
case ResultHtlcSetTotalMismatch:
|
||||||
return "htlc total amt doesn't match set total"
|
return "htlc total amt doesn't match set total"
|
||||||
|
|
||||||
case resultHtlcSetTotalTooLow:
|
case ResultHtlcSetTotalTooLow:
|
||||||
return "set total too low for invoice"
|
return "set total too low for invoice"
|
||||||
|
|
||||||
case resultHtlcSetOverpayment:
|
case ResultHtlcSetOverpayment:
|
||||||
return "mpp is overpaying set total"
|
return "mpp is overpaying set total"
|
||||||
|
|
||||||
default:
|
default:
|
||||||
@ -110,20 +165,20 @@ type invoiceUpdateCtx struct {
|
|||||||
// updateInvoice is a callback for DB.UpdateInvoice that contains the invoice
|
// updateInvoice is a callback for DB.UpdateInvoice that contains the invoice
|
||||||
// settlement logic.
|
// settlement logic.
|
||||||
func updateInvoice(ctx *invoiceUpdateCtx, inv *channeldb.Invoice) (
|
func updateInvoice(ctx *invoiceUpdateCtx, inv *channeldb.Invoice) (
|
||||||
*channeldb.InvoiceUpdateDesc, updateResult, error) {
|
*channeldb.InvoiceUpdateDesc, ResolutionResult, error) {
|
||||||
|
|
||||||
// Don't update the invoice when this is a replayed htlc.
|
// Don't update the invoice when this is a replayed htlc.
|
||||||
htlc, ok := inv.Htlcs[ctx.circuitKey]
|
htlc, ok := inv.Htlcs[ctx.circuitKey]
|
||||||
if ok {
|
if ok {
|
||||||
switch htlc.State {
|
switch htlc.State {
|
||||||
case channeldb.HtlcStateCanceled:
|
case channeldb.HtlcStateCanceled:
|
||||||
return nil, resultReplayToCanceled, nil
|
return nil, ResultReplayToCanceled, nil
|
||||||
|
|
||||||
case channeldb.HtlcStateAccepted:
|
case channeldb.HtlcStateAccepted:
|
||||||
return nil, resultReplayToAccepted, nil
|
return nil, ResultReplayToAccepted, nil
|
||||||
|
|
||||||
case channeldb.HtlcStateSettled:
|
case channeldb.HtlcStateSettled:
|
||||||
return nil, resultReplayToSettled, nil
|
return nil, ResultReplayToSettled, nil
|
||||||
|
|
||||||
default:
|
default:
|
||||||
return nil, 0, errors.New("unknown htlc state")
|
return nil, 0, errors.New("unknown htlc state")
|
||||||
@ -140,7 +195,7 @@ func updateInvoice(ctx *invoiceUpdateCtx, inv *channeldb.Invoice) (
|
|||||||
// updateMpp is a callback for DB.UpdateInvoice that contains the invoice
|
// updateMpp is a callback for DB.UpdateInvoice that contains the invoice
|
||||||
// settlement logic for mpp payments.
|
// settlement logic for mpp payments.
|
||||||
func updateMpp(ctx *invoiceUpdateCtx, inv *channeldb.Invoice) (
|
func updateMpp(ctx *invoiceUpdateCtx, inv *channeldb.Invoice) (
|
||||||
*channeldb.InvoiceUpdateDesc, updateResult, error) {
|
*channeldb.InvoiceUpdateDesc, ResolutionResult, error) {
|
||||||
|
|
||||||
// Start building the accept descriptor.
|
// Start building the accept descriptor.
|
||||||
acceptDesc := &channeldb.HtlcAcceptDesc{
|
acceptDesc := &channeldb.HtlcAcceptDesc{
|
||||||
@ -156,23 +211,23 @@ func updateMpp(ctx *invoiceUpdateCtx, inv *channeldb.Invoice) (
|
|||||||
// Because non-mpp payments don't have a payment address, this is needed
|
// Because non-mpp payments don't have a payment address, this is needed
|
||||||
// to thwart probing.
|
// to thwart probing.
|
||||||
if inv.State != channeldb.ContractOpen {
|
if inv.State != channeldb.ContractOpen {
|
||||||
return nil, resultInvoiceNotOpen, nil
|
return nil, ResultInvoiceNotOpen, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check the payment address that authorizes the payment.
|
// Check the payment address that authorizes the payment.
|
||||||
if ctx.mpp.PaymentAddr() != inv.Terms.PaymentAddr {
|
if ctx.mpp.PaymentAddr() != inv.Terms.PaymentAddr {
|
||||||
return nil, resultAddressMismatch, nil
|
return nil, ResultAddressMismatch, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Don't accept zero-valued sets.
|
// Don't accept zero-valued sets.
|
||||||
if ctx.mpp.TotalMsat() == 0 {
|
if ctx.mpp.TotalMsat() == 0 {
|
||||||
return nil, resultHtlcSetTotalTooLow, nil
|
return nil, ResultHtlcSetTotalTooLow, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check that the total amt of the htlc set is high enough. In case this
|
// Check that the total amt of the htlc set is high enough. In case this
|
||||||
// is a zero-valued invoice, it will always be enough.
|
// is a zero-valued invoice, it will always be enough.
|
||||||
if ctx.mpp.TotalMsat() < inv.Terms.Value {
|
if ctx.mpp.TotalMsat() < inv.Terms.Value {
|
||||||
return nil, resultHtlcSetTotalTooLow, nil
|
return nil, ResultHtlcSetTotalTooLow, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check whether total amt matches other htlcs in the set.
|
// Check whether total amt matches other htlcs in the set.
|
||||||
@ -186,7 +241,7 @@ func updateMpp(ctx *invoiceUpdateCtx, inv *channeldb.Invoice) (
|
|||||||
}
|
}
|
||||||
|
|
||||||
if ctx.mpp.TotalMsat() != htlc.MppTotalAmt {
|
if ctx.mpp.TotalMsat() != htlc.MppTotalAmt {
|
||||||
return nil, resultHtlcSetTotalMismatch, nil
|
return nil, ResultHtlcSetTotalMismatch, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
newSetTotal += htlc.Amt
|
newSetTotal += htlc.Amt
|
||||||
@ -197,16 +252,16 @@ func updateMpp(ctx *invoiceUpdateCtx, inv *channeldb.Invoice) (
|
|||||||
|
|
||||||
// Make sure the communicated set total isn't overpaid.
|
// Make sure the communicated set total isn't overpaid.
|
||||||
if newSetTotal > ctx.mpp.TotalMsat() {
|
if newSetTotal > ctx.mpp.TotalMsat() {
|
||||||
return nil, resultHtlcSetOverpayment, nil
|
return nil, ResultHtlcSetOverpayment, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// The invoice is still open. Check the expiry.
|
// The invoice is still open. Check the expiry.
|
||||||
if ctx.expiry < uint32(ctx.currentHeight+ctx.finalCltvRejectDelta) {
|
if ctx.expiry < uint32(ctx.currentHeight+ctx.finalCltvRejectDelta) {
|
||||||
return nil, resultExpiryTooSoon, nil
|
return nil, ResultExpiryTooSoon, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
if ctx.expiry < uint32(ctx.currentHeight+inv.Terms.FinalCltvDelta) {
|
if ctx.expiry < uint32(ctx.currentHeight+inv.Terms.FinalCltvDelta) {
|
||||||
return nil, resultExpiryTooSoon, nil
|
return nil, ResultExpiryTooSoon, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Record HTLC in the invoice database.
|
// Record HTLC in the invoice database.
|
||||||
@ -221,7 +276,7 @@ func updateMpp(ctx *invoiceUpdateCtx, inv *channeldb.Invoice) (
|
|||||||
// If the invoice cannot be settled yet, only record the htlc.
|
// If the invoice cannot be settled yet, only record the htlc.
|
||||||
setComplete := newSetTotal == ctx.mpp.TotalMsat()
|
setComplete := newSetTotal == ctx.mpp.TotalMsat()
|
||||||
if !setComplete {
|
if !setComplete {
|
||||||
return &update, resultPartialAccepted, nil
|
return &update, ResultPartialAccepted, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check to see if we can settle or this is an hold invoice and
|
// Check to see if we can settle or this is an hold invoice and
|
||||||
@ -231,7 +286,7 @@ func updateMpp(ctx *invoiceUpdateCtx, inv *channeldb.Invoice) (
|
|||||||
update.State = &channeldb.InvoiceStateUpdateDesc{
|
update.State = &channeldb.InvoiceStateUpdateDesc{
|
||||||
NewState: channeldb.ContractAccepted,
|
NewState: channeldb.ContractAccepted,
|
||||||
}
|
}
|
||||||
return &update, resultAccepted, nil
|
return &update, ResultAccepted, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
update.State = &channeldb.InvoiceStateUpdateDesc{
|
update.State = &channeldb.InvoiceStateUpdateDesc{
|
||||||
@ -239,18 +294,18 @@ func updateMpp(ctx *invoiceUpdateCtx, inv *channeldb.Invoice) (
|
|||||||
Preimage: inv.Terms.PaymentPreimage,
|
Preimage: inv.Terms.PaymentPreimage,
|
||||||
}
|
}
|
||||||
|
|
||||||
return &update, resultSettled, nil
|
return &update, ResultSettled, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// updateLegacy is a callback for DB.UpdateInvoice that contains the invoice
|
// updateLegacy is a callback for DB.UpdateInvoice that contains the invoice
|
||||||
// settlement logic for legacy payments.
|
// settlement logic for legacy payments.
|
||||||
func updateLegacy(ctx *invoiceUpdateCtx, inv *channeldb.Invoice) (
|
func updateLegacy(ctx *invoiceUpdateCtx, inv *channeldb.Invoice) (
|
||||||
*channeldb.InvoiceUpdateDesc, updateResult, error) {
|
*channeldb.InvoiceUpdateDesc, ResolutionResult, error) {
|
||||||
|
|
||||||
// If the invoice is already canceled, there is no further
|
// If the invoice is already canceled, there is no further
|
||||||
// checking to do.
|
// checking to do.
|
||||||
if inv.State == channeldb.ContractCanceled {
|
if inv.State == channeldb.ContractCanceled {
|
||||||
return nil, resultInvoiceAlreadyCanceled, nil
|
return nil, ResultInvoiceAlreadyCanceled, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// If an invoice amount is specified, check that enough is paid. Also
|
// If an invoice amount is specified, check that enough is paid. Also
|
||||||
@ -258,7 +313,7 @@ func updateLegacy(ctx *invoiceUpdateCtx, inv *channeldb.Invoice) (
|
|||||||
// or accepted. In case this is a zero-valued invoice, it will always be
|
// or accepted. In case this is a zero-valued invoice, it will always be
|
||||||
// enough.
|
// enough.
|
||||||
if ctx.amtPaid < inv.Terms.Value {
|
if ctx.amtPaid < inv.Terms.Value {
|
||||||
return nil, resultAmountTooLow, nil
|
return nil, ResultAmountTooLow, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO(joostjager): Check invoice mpp required feature
|
// TODO(joostjager): Check invoice mpp required feature
|
||||||
@ -271,17 +326,17 @@ func updateLegacy(ctx *invoiceUpdateCtx, inv *channeldb.Invoice) (
|
|||||||
if htlc.State == channeldb.HtlcStateAccepted &&
|
if htlc.State == channeldb.HtlcStateAccepted &&
|
||||||
htlc.MppTotalAmt > 0 {
|
htlc.MppTotalAmt > 0 {
|
||||||
|
|
||||||
return nil, resultMppInProgress, nil
|
return nil, ResultMppInProgress, nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// The invoice is still open. Check the expiry.
|
// The invoice is still open. Check the expiry.
|
||||||
if ctx.expiry < uint32(ctx.currentHeight+ctx.finalCltvRejectDelta) {
|
if ctx.expiry < uint32(ctx.currentHeight+ctx.finalCltvRejectDelta) {
|
||||||
return nil, resultExpiryTooSoon, nil
|
return nil, ResultExpiryTooSoon, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
if ctx.expiry < uint32(ctx.currentHeight+inv.Terms.FinalCltvDelta) {
|
if ctx.expiry < uint32(ctx.currentHeight+inv.Terms.FinalCltvDelta) {
|
||||||
return nil, resultExpiryTooSoon, nil
|
return nil, ResultExpiryTooSoon, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Record HTLC in the invoice database.
|
// Record HTLC in the invoice database.
|
||||||
@ -302,10 +357,10 @@ func updateLegacy(ctx *invoiceUpdateCtx, inv *channeldb.Invoice) (
|
|||||||
// We do accept or settle the HTLC.
|
// We do accept or settle the HTLC.
|
||||||
switch inv.State {
|
switch inv.State {
|
||||||
case channeldb.ContractAccepted:
|
case channeldb.ContractAccepted:
|
||||||
return &update, resultDuplicateToAccepted, nil
|
return &update, ResultDuplicateToAccepted, nil
|
||||||
|
|
||||||
case channeldb.ContractSettled:
|
case channeldb.ContractSettled:
|
||||||
return &update, resultDuplicateToSettled, nil
|
return &update, ResultDuplicateToSettled, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check to see if we can settle or this is an hold invoice and we need
|
// Check to see if we can settle or this is an hold invoice and we need
|
||||||
@ -315,7 +370,7 @@ func updateLegacy(ctx *invoiceUpdateCtx, inv *channeldb.Invoice) (
|
|||||||
update.State = &channeldb.InvoiceStateUpdateDesc{
|
update.State = &channeldb.InvoiceStateUpdateDesc{
|
||||||
NewState: channeldb.ContractAccepted,
|
NewState: channeldb.ContractAccepted,
|
||||||
}
|
}
|
||||||
return &update, resultAccepted, nil
|
return &update, ResultAccepted, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
update.State = &channeldb.InvoiceStateUpdateDesc{
|
update.State = &channeldb.InvoiceStateUpdateDesc{
|
||||||
@ -323,5 +378,5 @@ func updateLegacy(ctx *invoiceUpdateCtx, inv *channeldb.Invoice) (
|
|||||||
Preimage: inv.Terms.PaymentPreimage,
|
Preimage: inv.Terms.PaymentPreimage,
|
||||||
}
|
}
|
||||||
|
|
||||||
return &update, resultSettled, nil
|
return &update, ResultSettled, nil
|
||||||
}
|
}
|
||||||
|
@ -109,6 +109,7 @@ const (
|
|||||||
Failure_PERMANENT_NODE_FAILURE Failure_FailureCode = 20
|
Failure_PERMANENT_NODE_FAILURE Failure_FailureCode = 20
|
||||||
Failure_PERMANENT_CHANNEL_FAILURE Failure_FailureCode = 21
|
Failure_PERMANENT_CHANNEL_FAILURE Failure_FailureCode = 21
|
||||||
Failure_EXPIRY_TOO_FAR Failure_FailureCode = 22
|
Failure_EXPIRY_TOO_FAR Failure_FailureCode = 22
|
||||||
|
Failure_MPP_TIMEOUT Failure_FailureCode = 23
|
||||||
//*
|
//*
|
||||||
//The error source is known, but the failure itself couldn't be decoded.
|
//The error source is known, but the failure itself couldn't be decoded.
|
||||||
Failure_UNKNOWN_FAILURE Failure_FailureCode = 998
|
Failure_UNKNOWN_FAILURE Failure_FailureCode = 998
|
||||||
@ -142,6 +143,7 @@ var Failure_FailureCode_name = map[int32]string{
|
|||||||
20: "PERMANENT_NODE_FAILURE",
|
20: "PERMANENT_NODE_FAILURE",
|
||||||
21: "PERMANENT_CHANNEL_FAILURE",
|
21: "PERMANENT_CHANNEL_FAILURE",
|
||||||
22: "EXPIRY_TOO_FAR",
|
22: "EXPIRY_TOO_FAR",
|
||||||
|
23: "MPP_TIMEOUT",
|
||||||
998: "UNKNOWN_FAILURE",
|
998: "UNKNOWN_FAILURE",
|
||||||
999: "UNREADABLE_FAILURE",
|
999: "UNREADABLE_FAILURE",
|
||||||
}
|
}
|
||||||
@ -170,6 +172,7 @@ var Failure_FailureCode_value = map[string]int32{
|
|||||||
"PERMANENT_NODE_FAILURE": 20,
|
"PERMANENT_NODE_FAILURE": 20,
|
||||||
"PERMANENT_CHANNEL_FAILURE": 21,
|
"PERMANENT_CHANNEL_FAILURE": 21,
|
||||||
"EXPIRY_TOO_FAR": 22,
|
"EXPIRY_TOO_FAR": 22,
|
||||||
|
"MPP_TIMEOUT": 23,
|
||||||
"UNKNOWN_FAILURE": 998,
|
"UNKNOWN_FAILURE": 998,
|
||||||
"UNREADABLE_FAILURE": 999,
|
"UNREADABLE_FAILURE": 999,
|
||||||
}
|
}
|
||||||
@ -1526,138 +1529,139 @@ func init() {
|
|||||||
func init() { proto.RegisterFile("routerrpc/router.proto", fileDescriptor_7a0613f69d37b0a5) }
|
func init() { proto.RegisterFile("routerrpc/router.proto", fileDescriptor_7a0613f69d37b0a5) }
|
||||||
|
|
||||||
var fileDescriptor_7a0613f69d37b0a5 = []byte{
|
var fileDescriptor_7a0613f69d37b0a5 = []byte{
|
||||||
// 2093 bytes of a gzipped FileDescriptorProto
|
// 2102 bytes of a gzipped FileDescriptorProto
|
||||||
0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x8c, 0x58, 0x4f, 0x73, 0xdb, 0xb8,
|
0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x8c, 0x58, 0xcb, 0x73, 0xdb, 0xc6,
|
||||||
0x15, 0x5f, 0x5a, 0xff, 0x9f, 0xfe, 0xd1, 0xb0, 0xe3, 0x30, 0x72, 0xbc, 0xf1, 0x32, 0x69, 0x56,
|
0x19, 0x0f, 0xc4, 0xf7, 0xc7, 0x17, 0xb4, 0x92, 0x65, 0x98, 0xb2, 0x62, 0x05, 0x76, 0x1d, 0x8e,
|
||||||
0x93, 0xc9, 0xda, 0xa9, 0xdb, 0xcd, 0x64, 0x7a, 0x68, 0x47, 0x96, 0xa8, 0x35, 0x1d, 0x89, 0x72,
|
0xc7, 0x91, 0x5c, 0xb5, 0xf1, 0x78, 0x7a, 0x68, 0x87, 0x22, 0xc1, 0x08, 0x32, 0x09, 0xca, 0x4b,
|
||||||
0x20, 0x29, 0xbb, 0xe9, 0x1e, 0x30, 0xb4, 0x04, 0x5b, 0x1c, 0x53, 0xa4, 0x96, 0x84, 0xb2, 0xf1,
|
0xd2, 0x89, 0x9b, 0xc3, 0x0e, 0x44, 0xae, 0x44, 0x8c, 0x40, 0x80, 0x01, 0x96, 0x8e, 0xf5, 0x0f,
|
||||||
0x77, 0xe8, 0xf7, 0x68, 0x0f, 0x6d, 0x2f, 0xfd, 0x4e, 0xed, 0xbd, 0x33, 0x3d, 0xf4, 0xd6, 0x01,
|
0xf4, 0xd4, 0xff, 0xa3, 0xbd, 0xb4, 0x97, 0x9e, 0xfb, 0xef, 0xb4, 0xf7, 0xde, 0x7a, 0xeb, 0xec,
|
||||||
0x40, 0x4a, 0x94, 0x2c, 0x67, 0x7b, 0xb2, 0xf0, 0x7b, 0x3f, 0xbc, 0x07, 0xe2, 0xe1, 0xfd, 0xf0,
|
0x2e, 0x40, 0x82, 0x14, 0xe5, 0xf4, 0x44, 0xec, 0xef, 0x7b, 0xed, 0xee, 0xb7, 0xdf, 0x8b, 0xb0,
|
||||||
0x60, 0xd8, 0x0b, 0xfc, 0x39, 0xa3, 0x41, 0x30, 0x1b, 0x1d, 0xcb, 0x5f, 0x47, 0xb3, 0xc0, 0x67,
|
0x17, 0xf8, 0x73, 0x46, 0x83, 0x60, 0x36, 0x3a, 0x96, 0x5f, 0x47, 0xb3, 0xc0, 0x67, 0x3e, 0x2a,
|
||||||
0x3e, 0x2a, 0x2c, 0xf0, 0x5a, 0x21, 0x98, 0x8d, 0x24, 0xaa, 0xff, 0x37, 0x03, 0xa8, 0x4f, 0xbd,
|
0x2c, 0xf0, 0x5a, 0x21, 0x98, 0x8d, 0x24, 0xaa, 0xff, 0x37, 0x03, 0xa8, 0x4f, 0xbd, 0xf1, 0x85,
|
||||||
0xf1, 0x85, 0x7d, 0x3b, 0xa5, 0x1e, 0xc3, 0xf4, 0xa7, 0x39, 0x0d, 0x19, 0x42, 0x90, 0x1e, 0xd3,
|
0x7d, 0x3b, 0xa5, 0x1e, 0xc3, 0xf4, 0xa7, 0x39, 0x0d, 0x19, 0x42, 0x90, 0x1e, 0xd3, 0x90, 0x69,
|
||||||
0x90, 0x69, 0xca, 0xa1, 0x52, 0x2f, 0x61, 0xf1, 0x1b, 0xa9, 0x90, 0xb2, 0xa7, 0x4c, 0xdb, 0x3a,
|
0xca, 0xa1, 0x52, 0x2f, 0x61, 0xf1, 0x8d, 0x54, 0x48, 0xd9, 0x53, 0xa6, 0x6d, 0x1d, 0x2a, 0xf5,
|
||||||
0x54, 0xea, 0x29, 0xcc, 0x7f, 0xa2, 0x47, 0x90, 0xb7, 0xa7, 0x8c, 0x4c, 0x43, 0x9b, 0x69, 0x25,
|
0x14, 0xe6, 0x9f, 0xe8, 0x11, 0xe4, 0xed, 0x29, 0x23, 0xd3, 0xd0, 0x66, 0x5a, 0x49, 0xc0, 0x39,
|
||||||
0x01, 0xe7, 0xec, 0x29, 0xeb, 0x86, 0x36, 0x43, 0x5f, 0x41, 0x69, 0x26, 0x5d, 0x92, 0x89, 0x1d,
|
0x7b, 0xca, 0xba, 0xa1, 0xcd, 0xd0, 0x57, 0x50, 0x9a, 0x49, 0x95, 0x64, 0x62, 0x87, 0x13, 0x2d,
|
||||||
0x4e, 0xb4, 0x94, 0x70, 0x54, 0x8c, 0xb0, 0x33, 0x3b, 0x9c, 0xa0, 0x3a, 0xa8, 0x57, 0x8e, 0x67,
|
0x25, 0x14, 0x15, 0x23, 0xec, 0xcc, 0x0e, 0x27, 0xa8, 0x0e, 0xea, 0x95, 0xe3, 0xd9, 0x2e, 0x19,
|
||||||
0xbb, 0x64, 0xe4, 0xb2, 0x8f, 0x64, 0x4c, 0x5d, 0x66, 0x6b, 0xe9, 0x43, 0xa5, 0x9e, 0xc1, 0x15,
|
0xb9, 0xec, 0x23, 0x19, 0x53, 0x97, 0xd9, 0x5a, 0xfa, 0x50, 0xa9, 0x67, 0x70, 0x45, 0xe0, 0x4d,
|
||||||
0x81, 0x37, 0x5d, 0xf6, 0xb1, 0xc5, 0x51, 0xf4, 0x35, 0x54, 0x63, 0x67, 0x81, 0x5c, 0xa0, 0x96,
|
0x97, 0x7d, 0x6c, 0x71, 0x14, 0x7d, 0x0d, 0xd5, 0x58, 0x59, 0x20, 0x37, 0xa8, 0x65, 0x0e, 0x95,
|
||||||
0x39, 0x54, 0xea, 0x05, 0x5c, 0x99, 0xad, 0x2e, 0xfb, 0x6b, 0xa8, 0x32, 0x67, 0x4a, 0xfd, 0x39,
|
0x7a, 0x01, 0x57, 0x66, 0xab, 0xdb, 0xfe, 0x1a, 0xaa, 0xcc, 0x99, 0x52, 0x7f, 0xce, 0x48, 0x48,
|
||||||
0x23, 0x21, 0x1d, 0xf9, 0xde, 0x38, 0xd4, 0xb2, 0xd2, 0x63, 0x04, 0xf7, 0x25, 0x8a, 0x74, 0x28,
|
0x47, 0xbe, 0x37, 0x0e, 0xb5, 0xac, 0xd4, 0x18, 0xc1, 0x7d, 0x89, 0x22, 0x1d, 0xca, 0x57, 0x94,
|
||||||
0x5f, 0x51, 0x4a, 0x5c, 0x67, 0xea, 0x30, 0xc2, 0x97, 0x9f, 0x13, 0xcb, 0x2f, 0x5e, 0x51, 0xda,
|
0x12, 0xd7, 0x99, 0x3a, 0x8c, 0xf0, 0xed, 0xe7, 0xc4, 0xf6, 0x8b, 0x57, 0x94, 0x76, 0x38, 0xd6,
|
||||||
0xe1, 0x58, 0xdf, 0x66, 0xe8, 0x19, 0x54, 0x96, 0x1c, 0xf1, 0x8d, 0x65, 0x41, 0x2a, 0xc5, 0x24,
|
0xb7, 0x19, 0x7a, 0x06, 0x95, 0x25, 0x8f, 0x38, 0x63, 0x59, 0x30, 0x95, 0x62, 0x26, 0x71, 0xd0,
|
||||||
0xf1, 0xa1, 0x2f, 0x41, 0xf5, 0xe7, 0xec, 0xda, 0x77, 0xbc, 0x6b, 0x32, 0x9a, 0xd8, 0x1e, 0x71,
|
0x97, 0xa0, 0xfa, 0x73, 0x76, 0xed, 0x3b, 0xde, 0x35, 0x19, 0x4d, 0x6c, 0x8f, 0x38, 0x63, 0x2d,
|
||||||
0xc6, 0x5a, 0xfe, 0x50, 0xa9, 0xa7, 0x4f, 0xb7, 0x5e, 0x29, 0xb8, 0x12, 0xdb, 0x9a, 0x13, 0xdb,
|
0x7f, 0xa8, 0xd4, 0xd3, 0xa7, 0x5b, 0xaf, 0x14, 0x5c, 0x89, 0x69, 0xcd, 0x89, 0xed, 0x99, 0x63,
|
||||||
0x33, 0xc7, 0xe8, 0x39, 0x54, 0x5d, 0x3b, 0x64, 0x64, 0xe2, 0xcf, 0xc8, 0x6c, 0x7e, 0x79, 0x43,
|
0xf4, 0x1c, 0xaa, 0xae, 0x1d, 0x32, 0x32, 0xf1, 0x67, 0x64, 0x36, 0xbf, 0xbc, 0xa1, 0xb7, 0x5a,
|
||||||
0x6f, 0xb5, 0x8a, 0xd8, 0x99, 0x32, 0x87, 0xcf, 0xfc, 0xd9, 0x85, 0x00, 0xd1, 0x01, 0x80, 0xd8,
|
0x45, 0xdc, 0x4c, 0x99, 0xc3, 0x67, 0xfe, 0xec, 0x42, 0x80, 0xe8, 0x00, 0x40, 0xdc, 0x8a, 0x30,
|
||||||
0x15, 0x11, 0x5c, 0x2b, 0x88, 0x6f, 0x28, 0x70, 0x44, 0x04, 0x46, 0x27, 0x50, 0x14, 0xd9, 0x24,
|
0xae, 0x15, 0xc4, 0x19, 0x0a, 0x1c, 0x11, 0x86, 0xd1, 0x09, 0x14, 0x85, 0x37, 0xc9, 0xc4, 0xf1,
|
||||||
0x13, 0xc7, 0x63, 0xa1, 0x06, 0x87, 0xa9, 0x7a, 0xf1, 0x44, 0x3d, 0x72, 0x3d, 0x9e, 0x58, 0xcc,
|
0x58, 0xa8, 0xc1, 0x61, 0xaa, 0x5e, 0x3c, 0x51, 0x8f, 0x5c, 0x8f, 0x3b, 0x16, 0x73, 0xca, 0x99,
|
||||||
0x2d, 0x67, 0x8e, 0xc7, 0x70, 0x92, 0x84, 0xc6, 0xb0, 0xc3, 0xd3, 0x48, 0x46, 0xf3, 0x90, 0xf9,
|
0xe3, 0x31, 0x9c, 0x64, 0x42, 0x63, 0xd8, 0xe1, 0x6e, 0x24, 0xa3, 0x79, 0xc8, 0xfc, 0x29, 0x09,
|
||||||
0x53, 0x12, 0xd0, 0x91, 0x1f, 0x8c, 0x43, 0xad, 0x28, 0xe6, 0xfe, 0xf6, 0x68, 0x71, 0x3a, 0x8e,
|
0xe8, 0xc8, 0x0f, 0xc6, 0xa1, 0x56, 0x14, 0xb2, 0xbf, 0x3d, 0x5a, 0xbc, 0x8e, 0xa3, 0xbb, 0xcf,
|
||||||
0xee, 0x1e, 0x87, 0xa3, 0x16, 0x0d, 0x59, 0x53, 0xcc, 0xc3, 0x72, 0x9a, 0xe1, 0xb1, 0xe0, 0x16,
|
0xe1, 0xa8, 0x45, 0x43, 0xd6, 0x14, 0x72, 0x58, 0x8a, 0x19, 0x1e, 0x0b, 0x6e, 0xf1, 0xf6, 0x78,
|
||||||
0x6f, 0x8f, 0xd7, 0x71, 0xf4, 0x12, 0x90, 0xed, 0xba, 0xfe, 0xcf, 0x24, 0xa4, 0xee, 0x15, 0x89,
|
0x1d, 0x47, 0x2f, 0x01, 0xd9, 0xae, 0xeb, 0xff, 0x4c, 0x42, 0xea, 0x5e, 0x91, 0xc8, 0x3d, 0x5a,
|
||||||
0xd2, 0xa3, 0x55, 0x0f, 0x95, 0x7a, 0x1e, 0xab, 0xc2, 0xd2, 0xa7, 0xee, 0x55, 0xe4, 0x1e, 0xbd,
|
0xf5, 0x50, 0xa9, 0xe7, 0xb1, 0x2a, 0x28, 0x7d, 0xea, 0x5e, 0x45, 0xea, 0xd1, 0x6b, 0x28, 0x8b,
|
||||||
0x86, 0xb2, 0x58, 0xd3, 0x15, 0xb5, 0xd9, 0x3c, 0xa0, 0xa1, 0xa6, 0x1e, 0xa6, 0xea, 0x95, 0x93,
|
0x3d, 0x5d, 0x51, 0x9b, 0xcd, 0x03, 0x1a, 0x6a, 0xea, 0x61, 0xaa, 0x5e, 0x39, 0xd9, 0x8e, 0x4e,
|
||||||
0xed, 0xe8, 0x4b, 0xda, 0x12, 0x3e, 0x75, 0x18, 0x2e, 0x71, 0x5e, 0x34, 0x0e, 0x6b, 0x2d, 0xd8,
|
0xd2, 0x96, 0xf0, 0xa9, 0xc3, 0x70, 0x89, 0xf3, 0x45, 0xeb, 0xb0, 0xd6, 0x82, 0xbd, 0xcd, 0x5b,
|
||||||
0xdb, 0xbc, 0x24, 0x7e, 0x48, 0xf9, 0xa6, 0xf2, 0x73, 0x9b, 0xc6, 0xfc, 0x27, 0xda, 0x85, 0xcc,
|
0xe2, 0x8f, 0x94, 0x5f, 0x2a, 0x7f, 0xb7, 0x69, 0xcc, 0x3f, 0xd1, 0x2e, 0x64, 0x3e, 0xda, 0xee,
|
||||||
0x47, 0xdb, 0x9d, 0x53, 0x71, 0x70, 0x4b, 0x58, 0x0e, 0x7e, 0xb7, 0xf5, 0x46, 0xd1, 0xdf, 0xc0,
|
0x9c, 0x8a, 0x87, 0x5b, 0xc2, 0x72, 0xf1, 0xbb, 0xad, 0x37, 0x8a, 0xfe, 0x06, 0x76, 0x06, 0x81,
|
||||||
0xce, 0x20, 0xb0, 0x47, 0x37, 0x6b, 0x67, 0x7f, 0xfd, 0xe8, 0x2a, 0x77, 0x8e, 0xae, 0xfe, 0x17,
|
0x3d, 0xba, 0x59, 0x7b, 0xfb, 0xeb, 0x4f, 0x57, 0xb9, 0xf3, 0x74, 0xf5, 0xbf, 0x2a, 0x50, 0x8e,
|
||||||
0x05, 0xca, 0xd1, 0xac, 0x3e, 0xb3, 0xd9, 0x3c, 0x44, 0xdf, 0x40, 0x26, 0x64, 0x36, 0xa3, 0x82,
|
0xa4, 0xfa, 0xcc, 0x66, 0xf3, 0x10, 0x7d, 0x03, 0x99, 0x90, 0xd9, 0x8c, 0x0a, 0xee, 0xca, 0xc9,
|
||||||
0x5d, 0x39, 0x79, 0x98, 0xd8, 0xcf, 0x04, 0x91, 0x62, 0xc9, 0x42, 0x35, 0xc8, 0xcf, 0x02, 0xea,
|
0xc3, 0xc4, 0x7d, 0x26, 0x18, 0x29, 0x96, 0x5c, 0xa8, 0x06, 0xf9, 0x59, 0x40, 0x9d, 0xa9, 0x7d,
|
||||||
0x4c, 0xed, 0xeb, 0x78, 0x5d, 0x8b, 0x31, 0xd2, 0x21, 0x23, 0x26, 0x8b, 0x9a, 0x29, 0x9e, 0x94,
|
0x1d, 0xef, 0x6b, 0xb1, 0x46, 0x3a, 0x64, 0x84, 0xb0, 0x88, 0x99, 0xe2, 0x49, 0x29, 0xe9, 0x56,
|
||||||
0x92, 0x69, 0xc5, 0xd2, 0x84, 0xea, 0x90, 0x99, 0x30, 0x77, 0x14, 0x6a, 0x69, 0x91, 0x3e, 0x14,
|
0x2c, 0x49, 0xa8, 0x0e, 0x99, 0x09, 0x73, 0x47, 0xa1, 0x96, 0x16, 0xee, 0x43, 0x11, 0xcf, 0xd9,
|
||||||
0x71, 0xce, 0x06, 0x9d, 0x66, 0x83, 0x31, 0x3a, 0x9d, 0x31, 0x2c, 0x09, 0xfa, 0xef, 0xa1, 0x2a,
|
0xa0, 0xd3, 0x6c, 0x30, 0x46, 0xa7, 0x33, 0x86, 0x25, 0x83, 0xfe, 0x7b, 0xa8, 0x0a, 0xc9, 0x36,
|
||||||
0x66, 0xb6, 0x29, 0xfd, 0x5c, 0x71, 0x3f, 0x04, 0x5e, 0xba, 0xa2, 0x14, 0x64, 0x81, 0x67, 0xed,
|
0xa5, 0x9f, 0x0b, 0xee, 0x87, 0xc0, 0x43, 0x57, 0x84, 0x82, 0x0c, 0xf0, 0xac, 0x3d, 0xe5, 0x51,
|
||||||
0x29, 0xaf, 0x02, 0x7d, 0x0c, 0xea, 0x72, 0x7e, 0x38, 0xf3, 0xbd, 0x90, 0x47, 0x57, 0xf9, 0x32,
|
0xa0, 0x8f, 0x41, 0x5d, 0xca, 0x87, 0x33, 0xdf, 0x0b, 0xb9, 0x75, 0x95, 0x6f, 0x83, 0x3f, 0x79,
|
||||||
0xf8, 0x91, 0xe7, 0x15, 0x22, 0x6a, 0x43, 0x11, 0xb3, 0x2a, 0x11, 0xde, 0xa6, 0x54, 0x54, 0xc7,
|
0x1e, 0x21, 0x22, 0x36, 0x14, 0x21, 0x55, 0x89, 0xf0, 0x36, 0xa5, 0x22, 0x3a, 0x9e, 0xcb, 0x80,
|
||||||
0x73, 0x59, 0x90, 0xc4, 0xf5, 0x47, 0x37, 0xbc, 0xc4, 0xed, 0xdb, 0xc8, 0x7d, 0x99, 0xc3, 0x1d,
|
0x24, 0xae, 0x3f, 0xba, 0xe1, 0x21, 0x6e, 0xdf, 0x46, 0xea, 0xcb, 0x1c, 0xee, 0xf8, 0xa3, 0x9b,
|
||||||
0x7f, 0x74, 0xd3, 0xe2, 0xa0, 0xfe, 0xa3, 0x54, 0xa1, 0x81, 0x2f, 0xbf, 0xf2, 0xff, 0xce, 0xc4,
|
0x16, 0x07, 0xf5, 0x1f, 0x65, 0x16, 0x1a, 0xf8, 0xf2, 0x94, 0xff, 0xb7, 0x27, 0x96, 0x97, 0xb5,
|
||||||
0x72, 0xb3, 0xb6, 0xee, 0xdd, 0x2c, 0x9d, 0xc0, 0xce, 0x8a, 0xf3, 0xe8, 0x2b, 0x92, 0x39, 0x50,
|
0x75, 0xef, 0x65, 0xe9, 0x04, 0x76, 0x56, 0x94, 0x47, 0xa7, 0x48, 0xfa, 0x40, 0x59, 0xf3, 0xc1,
|
||||||
0xd6, 0x72, 0xf0, 0x12, 0x72, 0x57, 0xb6, 0xe3, 0xce, 0x83, 0xd8, 0x31, 0x4a, 0x24, 0xb4, 0x2d,
|
0x4b, 0xc8, 0x5d, 0xd9, 0x8e, 0x3b, 0x0f, 0x62, 0xc5, 0x28, 0xe1, 0xd0, 0xb6, 0xa4, 0xe0, 0x98,
|
||||||
0x2d, 0x38, 0xa6, 0xe8, 0xff, 0xc9, 0x41, 0x2e, 0x02, 0xd1, 0x09, 0xa4, 0x47, 0xfe, 0x38, 0x3e,
|
0x45, 0xff, 0x53, 0x1e, 0x72, 0x11, 0x88, 0x4e, 0x20, 0x3d, 0xf2, 0xc7, 0xf1, 0x3b, 0xf8, 0xf2,
|
||||||
0x07, 0x5f, 0xde, 0x9d, 0x16, 0xff, 0x6d, 0xfa, 0x63, 0x8a, 0x05, 0x17, 0xfd, 0x01, 0x2a, 0x5c,
|
0xae, 0x58, 0xfc, 0xdb, 0xf4, 0xc7, 0x14, 0x0b, 0x5e, 0xf4, 0x07, 0xa8, 0xf0, 0xd4, 0xe1, 0x51,
|
||||||
0x3a, 0x3c, 0xea, 0x92, 0xf9, 0x6c, 0x6c, 0x2f, 0x52, 0xaf, 0x25, 0x66, 0x37, 0x25, 0x61, 0x28,
|
0x97, 0xcc, 0x67, 0x63, 0x7b, 0xe1, 0x7a, 0x2d, 0x21, 0xdd, 0x94, 0x0c, 0x43, 0x41, 0xc7, 0xe5,
|
||||||
0xec, 0xb8, 0x3c, 0x4a, 0x0e, 0xd1, 0x3e, 0x14, 0x78, 0xb6, 0x65, 0x26, 0xd2, 0xe2, 0xec, 0xe7,
|
0x51, 0x72, 0x89, 0xf6, 0xa1, 0xc0, 0xbd, 0x2d, 0x3d, 0x91, 0x16, 0x6f, 0x3f, 0xcf, 0x01, 0xe1,
|
||||||
0x39, 0x20, 0x72, 0xa0, 0x43, 0xd9, 0xf7, 0x1c, 0xdf, 0x23, 0xe1, 0xc4, 0x26, 0x27, 0xdf, 0xbe,
|
0x03, 0x1d, 0xca, 0xbe, 0xe7, 0xf8, 0x1e, 0x09, 0x27, 0x36, 0x39, 0xf9, 0xf6, 0xb5, 0xc8, 0x9d,
|
||||||
0x16, 0xda, 0x59, 0xc2, 0x45, 0x01, 0xf6, 0x27, 0xf6, 0xc9, 0xb7, 0xaf, 0xd1, 0x13, 0x28, 0x0a,
|
0x25, 0x5c, 0x14, 0x60, 0x7f, 0x62, 0x9f, 0x7c, 0xfb, 0x1a, 0x3d, 0x81, 0xa2, 0xc8, 0x37, 0xf4,
|
||||||
0xbd, 0xa1, 0x9f, 0x66, 0x4e, 0x70, 0x2b, 0x44, 0xb3, 0x8c, 0x85, 0x04, 0x19, 0x02, 0xe1, 0x55,
|
0xd3, 0xcc, 0x09, 0x6e, 0x45, 0xd2, 0x2c, 0x63, 0x91, 0x82, 0x0c, 0x81, 0xf0, 0x28, 0xba, 0x72,
|
||||||
0x74, 0xe5, 0xda, 0xd7, 0xa1, 0x10, 0xca, 0x32, 0x96, 0x03, 0xf4, 0x0a, 0x76, 0xa3, 0x3d, 0x20,
|
0xed, 0xeb, 0x50, 0x24, 0xca, 0x32, 0x96, 0x0b, 0xf4, 0x0a, 0x76, 0xa3, 0x3b, 0x20, 0xa1, 0x3f,
|
||||||
0xa1, 0x3f, 0x0f, 0x46, 0x94, 0x38, 0xde, 0x98, 0x7e, 0x12, 0x02, 0x58, 0xc6, 0x28, 0xb2, 0xf5,
|
0x0f, 0x46, 0x94, 0x38, 0xde, 0x98, 0x7e, 0x12, 0x09, 0xb0, 0x8c, 0x51, 0x44, 0xeb, 0x0b, 0x92,
|
||||||
0x85, 0xc9, 0xe4, 0x16, 0xb4, 0x07, 0xd9, 0x09, 0x75, 0xae, 0x27, 0x52, 0xd4, 0xca, 0x38, 0x1a,
|
0xc9, 0x29, 0x68, 0x0f, 0xb2, 0x13, 0xea, 0x5c, 0x4f, 0x64, 0x52, 0x2b, 0xe3, 0x68, 0xa5, 0xff,
|
||||||
0xe9, 0x7f, 0xcd, 0x40, 0x31, 0xb1, 0x31, 0xa8, 0x04, 0x79, 0x6c, 0xf4, 0x0d, 0xfc, 0xde, 0x68,
|
0x33, 0x03, 0xc5, 0xc4, 0xc5, 0xa0, 0x12, 0xe4, 0xb1, 0xd1, 0x37, 0xf0, 0x7b, 0xa3, 0xa5, 0x7e,
|
||||||
0xa9, 0x5f, 0xa0, 0x3a, 0x3c, 0x33, 0xad, 0x66, 0x0f, 0x63, 0xa3, 0x39, 0x20, 0x3d, 0x4c, 0x86,
|
0x81, 0xea, 0xf0, 0xcc, 0xb4, 0x9a, 0x3d, 0x8c, 0x8d, 0xe6, 0x80, 0xf4, 0x30, 0x19, 0x5a, 0x6f,
|
||||||
0xd6, 0x5b, 0xab, 0xf7, 0xbd, 0x45, 0x2e, 0x1a, 0x1f, 0xba, 0x86, 0x35, 0x20, 0x2d, 0x63, 0xd0,
|
0xad, 0xde, 0xf7, 0x16, 0xb9, 0x68, 0x7c, 0xe8, 0x1a, 0xd6, 0x80, 0xb4, 0x8c, 0x41, 0xc3, 0xec,
|
||||||
0x30, 0x3b, 0x7d, 0x55, 0x41, 0x8f, 0x41, 0x5b, 0x32, 0x63, 0x73, 0xa3, 0xdb, 0x1b, 0x5a, 0x03,
|
0xf4, 0x55, 0x05, 0x3d, 0x06, 0x6d, 0xc9, 0x19, 0x93, 0x1b, 0xdd, 0xde, 0xd0, 0x1a, 0xa8, 0x5b,
|
||||||
0x75, 0x0b, 0x3d, 0x81, 0xfd, 0xb6, 0x69, 0x35, 0x3a, 0x64, 0xc9, 0x69, 0x76, 0x06, 0xef, 0x89,
|
0xe8, 0x09, 0xec, 0xb7, 0x4d, 0xab, 0xd1, 0x21, 0x4b, 0x9e, 0x66, 0x67, 0xf0, 0x9e, 0x18, 0x3f,
|
||||||
0xf1, 0xc3, 0x85, 0x89, 0x3f, 0xa8, 0xa9, 0x4d, 0x04, 0x5e, 0x53, 0xb1, 0x87, 0x34, 0x7a, 0x04,
|
0x5c, 0x98, 0xf8, 0x83, 0x9a, 0xda, 0xc4, 0xc0, 0x63, 0x2a, 0xd6, 0x90, 0x46, 0x8f, 0xe0, 0x81,
|
||||||
0x0f, 0x24, 0x41, 0x4e, 0x21, 0x83, 0x5e, 0x8f, 0xf4, 0x7b, 0x3d, 0x4b, 0xcd, 0xa0, 0x6d, 0x28,
|
0x64, 0x90, 0x22, 0x64, 0xd0, 0xeb, 0x91, 0x7e, 0xaf, 0x67, 0xa9, 0x19, 0xb4, 0x0d, 0x65, 0xd3,
|
||||||
0x9b, 0xd6, 0xfb, 0x46, 0xc7, 0x6c, 0x11, 0x6c, 0x34, 0x3a, 0x5d, 0x35, 0x8b, 0x76, 0xa0, 0xba,
|
0x7a, 0xdf, 0xe8, 0x98, 0x2d, 0x82, 0x8d, 0x46, 0xa7, 0xab, 0x66, 0xd1, 0x0e, 0x54, 0xd7, 0xf9,
|
||||||
0xce, 0xcb, 0x71, 0x17, 0x31, 0xaf, 0x67, 0x99, 0x3d, 0x8b, 0xbc, 0x37, 0x70, 0xdf, 0xec, 0x59,
|
0x72, 0x5c, 0x45, 0xcc, 0xd7, 0xb3, 0xcc, 0x9e, 0x45, 0xde, 0x1b, 0xb8, 0x6f, 0xf6, 0x2c, 0x35,
|
||||||
0x6a, 0x1e, 0xed, 0x01, 0x5a, 0x35, 0x9d, 0x75, 0x1b, 0x4d, 0xb5, 0x80, 0x1e, 0xc0, 0xf6, 0x2a,
|
0x8f, 0xf6, 0x00, 0xad, 0x92, 0xce, 0xba, 0x8d, 0xa6, 0x5a, 0x40, 0x0f, 0x60, 0x7b, 0x15, 0x7f,
|
||||||
0xfe, 0xd6, 0xf8, 0xa0, 0x02, 0xd2, 0x60, 0x57, 0x2e, 0x8c, 0x9c, 0x1a, 0x9d, 0xde, 0xf7, 0xa4,
|
0x6b, 0x7c, 0x50, 0x01, 0x69, 0xb0, 0x2b, 0x37, 0x46, 0x4e, 0x8d, 0x4e, 0xef, 0x7b, 0xd2, 0x35,
|
||||||
0x6b, 0x5a, 0x66, 0x77, 0xd8, 0x55, 0x8b, 0x68, 0x17, 0xd4, 0xb6, 0x61, 0x10, 0xd3, 0xea, 0x0f,
|
0x2d, 0xb3, 0x3b, 0xec, 0xaa, 0x45, 0xb4, 0x0b, 0x6a, 0xdb, 0x30, 0x88, 0x69, 0xf5, 0x87, 0xed,
|
||||||
0xdb, 0x6d, 0xb3, 0x69, 0x1a, 0xd6, 0x40, 0x2d, 0xc9, 0xc8, 0x9b, 0x3e, 0xbc, 0xcc, 0x27, 0x34,
|
0xb6, 0xd9, 0x34, 0x0d, 0x6b, 0xa0, 0x96, 0xa4, 0xe5, 0x4d, 0x07, 0x2f, 0x73, 0x81, 0xe6, 0x59,
|
||||||
0xcf, 0x1a, 0x96, 0x65, 0x74, 0x48, 0xcb, 0xec, 0x37, 0x4e, 0x3b, 0x46, 0x4b, 0xad, 0xa0, 0x03,
|
0xc3, 0xb2, 0x8c, 0x0e, 0x69, 0x99, 0xfd, 0xc6, 0x69, 0xc7, 0x68, 0xa9, 0x15, 0x74, 0x00, 0x8f,
|
||||||
0x78, 0x34, 0x30, 0xba, 0x17, 0x3d, 0xdc, 0xc0, 0x1f, 0x48, 0x6c, 0x6f, 0x37, 0xcc, 0xce, 0x10,
|
0x06, 0x46, 0xf7, 0xa2, 0x87, 0x1b, 0xf8, 0x03, 0x89, 0xe9, 0xed, 0x86, 0xd9, 0x19, 0x62, 0x43,
|
||||||
0x1b, 0x6a, 0x15, 0x7d, 0x05, 0x07, 0xd8, 0x78, 0x37, 0x34, 0xb1, 0xd1, 0x22, 0x56, 0xaf, 0x65,
|
0xad, 0xa2, 0xaf, 0xe0, 0x00, 0x1b, 0xef, 0x86, 0x26, 0x36, 0x5a, 0xc4, 0xea, 0xb5, 0x0c, 0xd2,
|
||||||
0x90, 0xb6, 0xd1, 0x18, 0x0c, 0xb1, 0x41, 0xba, 0x66, 0xbf, 0x6f, 0x5a, 0xdf, 0xa9, 0x2a, 0x7a,
|
0x36, 0x1a, 0x83, 0x21, 0x36, 0x48, 0xd7, 0xec, 0xf7, 0x4d, 0xeb, 0x3b, 0x55, 0x45, 0xcf, 0xe0,
|
||||||
0x06, 0x87, 0x0b, 0xca, 0xc2, 0xc1, 0x1a, 0x6b, 0x9b, 0x7f, 0x5f, 0x9c, 0x52, 0xcb, 0xf8, 0x61,
|
0x70, 0xc1, 0xb2, 0x50, 0xb0, 0xc6, 0xb5, 0xcd, 0xcf, 0x17, 0xbb, 0xd4, 0x32, 0x7e, 0x18, 0x90,
|
||||||
0x40, 0x2e, 0x0c, 0x03, 0xab, 0x08, 0xd5, 0x60, 0x6f, 0x19, 0x5e, 0x06, 0x88, 0x62, 0xef, 0x70,
|
0x0b, 0xc3, 0xc0, 0x2a, 0x42, 0x35, 0xd8, 0x5b, 0x9a, 0x97, 0x06, 0x22, 0xdb, 0x3b, 0x9c, 0x76,
|
||||||
0xdb, 0x85, 0x81, 0xbb, 0x0d, 0x8b, 0x27, 0x78, 0xc5, 0xb6, 0xcb, 0x97, 0xbd, 0xb4, 0xad, 0x2f,
|
0x61, 0xe0, 0x6e, 0xc3, 0xe2, 0x0e, 0x5e, 0xa1, 0xed, 0xf2, 0x6d, 0x2f, 0x69, 0xeb, 0xdb, 0x7e,
|
||||||
0xfb, 0x01, 0x42, 0x50, 0x49, 0x64, 0xa5, 0xdd, 0xc0, 0xea, 0x1e, 0xda, 0x85, 0x6a, 0xbc, 0x82,
|
0x80, 0x10, 0x54, 0x12, 0x5e, 0x69, 0x37, 0xb0, 0xba, 0x87, 0xaa, 0x50, 0xec, 0x5e, 0x5c, 0x90,
|
||||||
0x98, 0xf8, 0xcf, 0x1c, 0x7a, 0x08, 0x68, 0x68, 0x61, 0xa3, 0xd1, 0xe2, 0x1b, 0xb2, 0x30, 0xfc,
|
0x81, 0xd9, 0x35, 0x7a, 0xc3, 0x81, 0xfa, 0x10, 0xed, 0x42, 0x35, 0xde, 0x52, 0x2c, 0xf9, 0xaf,
|
||||||
0x2b, 0x77, 0x9e, 0xce, 0x6f, 0xa9, 0x29, 0xfd, 0x1f, 0x29, 0x28, 0xaf, 0xd4, 0x25, 0x7a, 0x0c,
|
0x1c, 0x7a, 0x08, 0x68, 0x68, 0x61, 0xa3, 0xd1, 0xe2, 0x37, 0xb4, 0x20, 0xfc, 0x3b, 0x77, 0x9e,
|
||||||
0x85, 0xd0, 0xb9, 0xf6, 0xc4, 0x35, 0x15, 0x89, 0xca, 0x12, 0x10, 0xb7, 0xfa, 0xc4, 0x76, 0x3c,
|
0xce, 0x6f, 0xa9, 0x29, 0xfd, 0x1f, 0x29, 0x28, 0xaf, 0x04, 0x2a, 0x7a, 0x0c, 0x85, 0xd0, 0xb9,
|
||||||
0xa9, 0x66, 0x52, 0xf7, 0x0b, 0x02, 0x11, 0x5a, 0xb6, 0x0f, 0xb9, 0xb8, 0x83, 0x48, 0x2d, 0x3a,
|
0xf6, 0x44, 0xdd, 0x8a, 0xb2, 0xcc, 0x12, 0x10, 0x65, 0x7e, 0x62, 0x3b, 0x9e, 0x4c, 0x6f, 0xb2,
|
||||||
0x88, 0xec, 0x48, 0x76, 0x0e, 0x8f, 0xa1, 0xc0, 0x25, 0x33, 0x64, 0xf6, 0x74, 0x26, 0x4a, 0xbc,
|
0x10, 0x14, 0x04, 0x22, 0x92, 0xdb, 0x3e, 0xe4, 0xe2, 0x96, 0x22, 0xb5, 0x68, 0x29, 0xb2, 0x23,
|
||||||
0x8c, 0x97, 0x00, 0x7a, 0x0a, 0xe5, 0x29, 0x0d, 0x43, 0xfb, 0x9a, 0x12, 0x59, 0xa6, 0x20, 0x18,
|
0xd9, 0x4a, 0x3c, 0x86, 0x02, 0xcf, 0xa1, 0x21, 0xb3, 0xa7, 0x33, 0x11, 0xf3, 0x65, 0xbc, 0x04,
|
||||||
0xa5, 0x08, 0x6c, 0x8b, 0x6a, 0x7d, 0x0a, 0xb1, 0x6c, 0x44, 0xa4, 0x8c, 0x24, 0x45, 0xa0, 0x24,
|
0xd0, 0x53, 0x28, 0x4f, 0x69, 0x18, 0xda, 0xd7, 0x94, 0xc8, 0xb8, 0x05, 0xc1, 0x51, 0x8a, 0xc0,
|
||||||
0xad, 0x2b, 0x36, 0xb3, 0x23, 0x35, 0x48, 0x2a, 0x36, 0xb3, 0xd1, 0x0b, 0xd8, 0x96, 0x92, 0xe3,
|
0xb6, 0x08, 0xdf, 0xa7, 0x10, 0xe7, 0x91, 0x88, 0x29, 0x23, 0x99, 0x22, 0x50, 0x32, 0xad, 0xa7,
|
||||||
0x78, 0xce, 0x74, 0x3e, 0x95, 0xd2, 0x93, 0x13, 0xd2, 0x53, 0x15, 0xd2, 0x23, 0x71, 0xa1, 0x40,
|
0x70, 0x66, 0x47, 0xe9, 0x21, 0x99, 0xc2, 0x99, 0x8d, 0x5e, 0xc0, 0xb6, 0xcc, 0x41, 0x8e, 0xe7,
|
||||||
0x8f, 0x20, 0x7f, 0x69, 0x87, 0x94, 0x5f, 0x16, 0x91, 0x34, 0xe4, 0xf8, 0xb8, 0x4d, 0x29, 0x37,
|
0x4c, 0xe7, 0x53, 0x99, 0x8b, 0x72, 0x22, 0x17, 0x55, 0x45, 0x2e, 0x92, 0xb8, 0x48, 0x49, 0x8f,
|
||||||
0xf1, 0x2b, 0x24, 0xe0, 0xa2, 0x27, 0x15, 0x21, 0x77, 0x45, 0x29, 0xe6, 0x7b, 0xb9, 0x88, 0x60,
|
0x20, 0x7f, 0x69, 0x87, 0x94, 0x57, 0x8f, 0x28, 0x57, 0xe4, 0xf8, 0xba, 0x4d, 0x29, 0x27, 0xf1,
|
||||||
0x7f, 0x5a, 0x46, 0x28, 0x26, 0x22, 0x48, 0x5c, 0x44, 0x78, 0x01, 0xdb, 0xf4, 0x13, 0x0b, 0x6c,
|
0x9a, 0x12, 0xf0, 0x2c, 0x28, 0x53, 0x44, 0xee, 0x8a, 0x52, 0xcc, 0xef, 0x72, 0x61, 0xc1, 0xfe,
|
||||||
0xe2, 0xcf, 0xec, 0x9f, 0xe6, 0x94, 0x8c, 0x6d, 0x66, 0x8b, 0x96, 0xb4, 0x84, 0xab, 0xc2, 0xd0,
|
0xb4, 0xb4, 0x50, 0x4c, 0x58, 0x90, 0xb8, 0xb0, 0xf0, 0x02, 0xb6, 0xe9, 0x27, 0x16, 0xd8, 0xc4,
|
||||||
0x13, 0x78, 0xcb, 0x66, 0xb6, 0xfe, 0x18, 0x6a, 0x98, 0x86, 0x94, 0x75, 0x9d, 0x30, 0x74, 0x7c,
|
0x9f, 0xd9, 0x3f, 0xcd, 0x29, 0x19, 0xdb, 0xcc, 0x16, 0x3d, 0x6a, 0x09, 0x57, 0x05, 0xa1, 0x27,
|
||||||
0xaf, 0xe9, 0x7b, 0x2c, 0xf0, 0xdd, 0xe8, 0xce, 0xd1, 0x0f, 0x60, 0x7f, 0xa3, 0x55, 0x5e, 0x1a,
|
0xf0, 0x96, 0xcd, 0x6c, 0xfd, 0x31, 0xd4, 0x30, 0x0d, 0x29, 0xeb, 0x3a, 0x61, 0xe8, 0xf8, 0x5e,
|
||||||
0x7c, 0xf2, 0xbb, 0x39, 0x0d, 0x6e, 0x37, 0x4f, 0x7e, 0x07, 0xfb, 0x1b, 0xad, 0xd1, 0x8d, 0xf3,
|
0xd3, 0xf7, 0x58, 0xe0, 0xbb, 0x51, 0x11, 0xd2, 0x0f, 0x60, 0x7f, 0x23, 0x55, 0x56, 0x11, 0x2e,
|
||||||
0x12, 0x32, 0x33, 0xdb, 0x09, 0x42, 0x6d, 0x4b, 0xdc, 0xda, 0x7b, 0x2b, 0x4d, 0x82, 0x13, 0x9c,
|
0xfc, 0x6e, 0x4e, 0x83, 0xdb, 0xcd, 0xc2, 0xef, 0x60, 0x7f, 0x23, 0x35, 0x2a, 0x41, 0x2f, 0x21,
|
||||||
0x39, 0x21, 0xf3, 0x83, 0x5b, 0x2c, 0x49, 0xe7, 0xe9, 0xbc, 0xa2, 0x6e, 0xe9, 0x7f, 0x52, 0xa0,
|
0x33, 0xb3, 0x9d, 0x20, 0xd4, 0xb6, 0x44, 0x19, 0xdf, 0x5b, 0xe9, 0x1a, 0x9c, 0xe0, 0xcc, 0x09,
|
||||||
0x98, 0x30, 0xf2, 0x73, 0xe0, 0xf9, 0x63, 0x4a, 0xae, 0x02, 0x7f, 0x1a, 0x9f, 0xb0, 0x05, 0x80,
|
0x99, 0x1f, 0xdc, 0x62, 0xc9, 0x74, 0x9e, 0xce, 0x2b, 0xea, 0x96, 0xfe, 0x67, 0x05, 0x8a, 0x09,
|
||||||
0x34, 0xc8, 0x89, 0x01, 0xf3, 0xa3, 0xe3, 0x15, 0x0f, 0xd1, 0x37, 0x90, 0x9b, 0x48, 0x17, 0x22,
|
0x22, 0x7f, 0x07, 0x9e, 0x3f, 0xa6, 0xe4, 0x2a, 0xf0, 0xa7, 0xf1, 0x0b, 0x5b, 0x00, 0x48, 0x83,
|
||||||
0x4b, 0xc5, 0x93, 0x9d, 0xb5, 0xe8, 0x7c, 0x6f, 0x70, 0xcc, 0x39, 0x4f, 0xe7, 0x53, 0x6a, 0xfa,
|
0x9c, 0x58, 0x30, 0x3f, 0x7a, 0x5e, 0xf1, 0x12, 0x7d, 0x03, 0xb9, 0x89, 0x54, 0x21, 0xbc, 0x54,
|
||||||
0x3c, 0x9d, 0x4f, 0xab, 0x99, 0xf3, 0x74, 0x3e, 0xa3, 0x66, 0xcf, 0xd3, 0xf9, 0xac, 0x9a, 0xd3,
|
0x3c, 0xd9, 0x59, 0xb3, 0xce, 0xef, 0x06, 0xc7, 0x3c, 0xe7, 0xe9, 0x7c, 0x4a, 0x4d, 0x9f, 0xa7,
|
||||||
0xff, 0xad, 0x40, 0x3e, 0x66, 0xf3, 0xb5, 0x70, 0x89, 0x27, 0xfc, 0x64, 0x44, 0x0d, 0xc0, 0x12,
|
0xf3, 0x69, 0x35, 0x73, 0x9e, 0xce, 0x67, 0xd4, 0xec, 0x79, 0x3a, 0x9f, 0x55, 0x73, 0xfa, 0x7f,
|
||||||
0x40, 0x3a, 0x94, 0xc4, 0x60, 0xb5, 0xaf, 0x58, 0xc1, 0xd0, 0x33, 0x28, 0x2f, 0xc6, 0x8b, 0xcb,
|
0x14, 0xc8, 0xc7, 0xdc, 0x7c, 0x2f, 0x3c, 0xe7, 0x13, 0xfe, 0x32, 0xa2, 0x8e, 0x60, 0x09, 0x20,
|
||||||
0x2b, 0x85, 0x57, 0x41, 0xee, 0x29, 0x9c, 0x8f, 0x46, 0x34, 0x0c, 0x65, 0xa8, 0x8c, 0xf4, 0x94,
|
0x1d, 0x4a, 0x62, 0xb1, 0xda, 0x68, 0xac, 0x60, 0xe8, 0x19, 0x94, 0x17, 0xeb, 0x45, 0x35, 0x4b,
|
||||||
0xc4, 0x50, 0x1d, 0xaa, 0xf1, 0x38, 0x0e, 0x98, 0x15, 0xb4, 0x75, 0x18, 0xbd, 0x00, 0x35, 0x09,
|
0xe1, 0x55, 0x90, 0x6b, 0x0a, 0xe7, 0xa3, 0x11, 0x0d, 0x43, 0x69, 0x2a, 0x23, 0x35, 0x25, 0x31,
|
||||||
0x4d, 0x97, 0xed, 0xff, 0x1d, 0x5c, 0x6e, 0x83, 0x3e, 0x85, 0x87, 0x22, 0xad, 0x17, 0x81, 0x7f,
|
0x54, 0x87, 0x6a, 0xbc, 0x8e, 0x0d, 0x66, 0x05, 0xdb, 0x3a, 0x8c, 0x5e, 0x80, 0x9a, 0x84, 0xa6,
|
||||||
0x69, 0x5f, 0x3a, 0xae, 0xc3, 0x6e, 0xe3, 0x16, 0x85, 0x6f, 0x41, 0xe0, 0x4f, 0x89, 0x17, 0xdf,
|
0xcb, 0x79, 0xe0, 0x0e, 0x2e, 0xaf, 0x41, 0x9f, 0xc2, 0x43, 0xe1, 0xd6, 0x8b, 0xc0, 0xbf, 0xb4,
|
||||||
0xf9, 0x25, 0xbc, 0x04, 0x78, 0x3a, 0x98, 0x2f, 0x6d, 0x51, 0x3a, 0xa2, 0x21, 0x6f, 0x3e, 0x16,
|
0x2f, 0x1d, 0xd7, 0x61, 0xb7, 0x71, 0xcf, 0xc2, 0xaf, 0x20, 0xf0, 0xa7, 0xc4, 0x8b, 0x9b, 0x80,
|
||||||
0xc1, 0x53, 0x22, 0xf8, 0x62, 0xac, 0xdf, 0x80, 0x76, 0x37, 0x5c, 0x74, 0x84, 0x0e, 0xa1, 0x38,
|
0x12, 0x5e, 0x02, 0xdc, 0x1d, 0xcc, 0x97, 0xb4, 0xc8, 0x1d, 0xd1, 0x92, 0x77, 0x23, 0x0b, 0xe3,
|
||||||
0x5b, 0xc2, 0x22, 0xa2, 0x82, 0x93, 0x50, 0x32, 0xd1, 0x5b, 0xbf, 0x9c, 0x68, 0xfd, 0xcf, 0x0a,
|
0x29, 0x61, 0x7c, 0xb1, 0xd6, 0x6f, 0x40, 0xbb, 0x6b, 0x2e, 0x7a, 0x42, 0x87, 0x50, 0x9c, 0x2d,
|
||||||
0x6c, 0x9f, 0xce, 0x1d, 0x77, 0xbc, 0xd2, 0x79, 0x25, 0x5f, 0x76, 0xca, 0xea, 0xcb, 0x6e, 0xd3,
|
0x61, 0x61, 0x51, 0xc1, 0x49, 0x28, 0xe9, 0xe8, 0xad, 0x5f, 0x76, 0xb4, 0xfe, 0x17, 0x05, 0xb6,
|
||||||
0xb3, 0x6d, 0x6b, 0xe3, 0xb3, 0x6d, 0xd3, 0xd3, 0x28, 0x75, 0xef, 0xd3, 0xe8, 0x09, 0x14, 0x97,
|
0x4f, 0xe7, 0x8e, 0x3b, 0x5e, 0x69, 0xc5, 0x92, 0xa3, 0x9e, 0xb2, 0x3a, 0xea, 0x6d, 0x9a, 0xe3,
|
||||||
0xaf, 0x22, 0xd9, 0xd8, 0x96, 0x30, 0x4c, 0xe2, 0x27, 0x51, 0xa8, 0xbf, 0x01, 0x94, 0x5c, 0x68,
|
0xb6, 0x36, 0xce, 0x71, 0x9b, 0x66, 0xa5, 0xd4, 0xbd, 0xb3, 0xd2, 0x13, 0x28, 0x2e, 0xc7, 0x24,
|
||||||
0xb4, 0x21, 0x8b, 0x06, 0x50, 0xb9, 0xb7, 0x01, 0x7c, 0xf1, 0x77, 0x05, 0x4a, 0xc9, 0x2e, 0x1c,
|
0xd9, 0xe9, 0x96, 0x30, 0x4c, 0xe2, 0x19, 0x29, 0xd4, 0xdf, 0x00, 0x4a, 0x6e, 0x34, 0xba, 0x90,
|
||||||
0x95, 0xa1, 0x60, 0x5a, 0xa4, 0xdd, 0x31, 0xbf, 0x3b, 0x1b, 0xa8, 0x5f, 0xf0, 0x61, 0x7f, 0xd8,
|
0x45, 0x47, 0xa8, 0xdc, 0xdb, 0x11, 0xbe, 0xf8, 0xbb, 0x02, 0xa5, 0x64, 0x5b, 0x8e, 0xca, 0x50,
|
||||||
0x6c, 0x1a, 0x46, 0xcb, 0x68, 0xa9, 0x0a, 0xbf, 0x1f, 0xb8, 0xd4, 0x1b, 0x2d, 0x32, 0x30, 0xbb,
|
0x30, 0x2d, 0xd2, 0xee, 0x98, 0xdf, 0x9d, 0x0d, 0xd4, 0x2f, 0xf8, 0xb2, 0x3f, 0x6c, 0x36, 0x0d,
|
||||||
0x46, 0x6f, 0xc8, 0x3b, 0x87, 0x1d, 0xa8, 0x46, 0x98, 0xd5, 0x23, 0xb8, 0x37, 0x1c, 0x18, 0x6a,
|
0xa3, 0x65, 0xb4, 0x54, 0x85, 0x17, 0x0c, 0x9e, 0xea, 0x8d, 0xd6, 0xa2, 0x3e, 0x6c, 0xf1, 0xd2,
|
||||||
0x0a, 0xa9, 0x50, 0x8a, 0x40, 0x03, 0xe3, 0x1e, 0x56, 0xd3, 0xfc, 0xba, 0x8b, 0x90, 0xbb, 0x5d,
|
0x1e, 0x61, 0x56, 0x8f, 0xe0, 0xde, 0x70, 0x60, 0xa8, 0x29, 0xa4, 0x42, 0x29, 0x02, 0x0d, 0x8c,
|
||||||
0x48, 0xdc, 0xa4, 0x64, 0x44, 0x97, 0x11, 0xb3, 0x96, 0x17, 0x34, 0x39, 0x6d, 0x74, 0x1a, 0x56,
|
0x7b, 0x58, 0x4d, 0xf3, 0xfa, 0x17, 0x21, 0x77, 0xdb, 0x92, 0xb8, 0x6b, 0xc9, 0x88, 0xb6, 0x23,
|
||||||
0xd3, 0x50, 0xb3, 0x27, 0x7f, 0xcb, 0x40, 0x56, 0x7c, 0x41, 0x80, 0xce, 0xa0, 0x98, 0x78, 0x90,
|
0xe6, 0x5a, 0x56, 0x6c, 0x72, 0xda, 0xe8, 0x34, 0xac, 0xa6, 0xa1, 0x66, 0x4f, 0xfe, 0x96, 0x81,
|
||||||
0xa1, 0x83, 0xcf, 0x3e, 0xd4, 0x6a, 0xda, 0xe6, 0x77, 0xc7, 0x3c, 0x7c, 0xa5, 0xa0, 0x73, 0x28,
|
0xac, 0x38, 0x41, 0x80, 0xce, 0xa0, 0x98, 0x98, 0xd0, 0xd0, 0xc1, 0x67, 0x27, 0xb7, 0x9a, 0xb6,
|
||||||
0x25, 0x9f, 0x3b, 0x28, 0xd9, 0x9b, 0x6e, 0x78, 0x07, 0x7d, 0xd6, 0xd7, 0x5b, 0x50, 0x8d, 0x90,
|
0x79, 0x10, 0x99, 0x87, 0xaf, 0x14, 0x74, 0x0e, 0xa5, 0xe4, 0xfc, 0x83, 0x92, 0xcd, 0xea, 0x86,
|
||||||
0x39, 0x53, 0xde, 0x8b, 0x46, 0xaf, 0x03, 0x54, 0x4b, 0xf0, 0xd7, 0x9e, 0x1c, 0xb5, 0xfd, 0x8d,
|
0xc1, 0xe8, 0xb3, 0xba, 0xde, 0x82, 0x6a, 0x84, 0xcc, 0x99, 0xf2, 0xe6, 0x34, 0x1a, 0x17, 0x50,
|
||||||
0xb6, 0x28, 0x85, 0x1d, 0xf9, 0x89, 0x51, 0x7f, 0x7e, 0xe7, 0x13, 0x57, 0x1f, 0x05, 0xb5, 0x2f,
|
0x2d, 0xc1, 0xbf, 0x36, 0x83, 0xd4, 0xf6, 0x37, 0xd2, 0x22, 0x17, 0x76, 0xe4, 0x11, 0xa3, 0x86,
|
||||||
0xef, 0x33, 0x47, 0xde, 0xc6, 0xb0, 0xb3, 0x41, 0xc0, 0xd1, 0xaf, 0x92, 0x2b, 0xb8, 0x57, 0xfe,
|
0xfd, 0xce, 0x11, 0x57, 0xa7, 0x84, 0xda, 0x97, 0xf7, 0x91, 0x23, 0x6d, 0x63, 0xd8, 0xd9, 0x90,
|
||||||
0x6b, 0xcf, 0x7f, 0x89, 0xb6, 0x8c, 0xb2, 0x41, 0xe9, 0x57, 0xa2, 0xdc, 0x7f, 0x4f, 0xac, 0x44,
|
0xc0, 0xd1, 0xaf, 0x92, 0x3b, 0xb8, 0x37, 0xfd, 0xd7, 0x9e, 0xff, 0x12, 0xdb, 0xd2, 0xca, 0x86,
|
||||||
0xf9, 0xdc, 0x85, 0xf1, 0x23, 0xa8, 0xeb, 0x4a, 0x80, 0xf4, 0xf5, 0xb9, 0x77, 0x55, 0xa9, 0xf6,
|
0x4c, 0xbf, 0x62, 0xe5, 0xfe, 0x3a, 0xb1, 0x62, 0xe5, 0x73, 0x05, 0xe3, 0x47, 0x50, 0xd7, 0x33,
|
||||||
0xf4, 0xb3, 0x9c, 0xc8, 0xb9, 0x09, 0xb0, 0xac, 0x27, 0xf4, 0x38, 0x31, 0xe5, 0x8e, 0x1e, 0xd4,
|
0x01, 0xd2, 0xd7, 0x65, 0xef, 0x66, 0xa5, 0xda, 0xd3, 0xcf, 0xf2, 0x44, 0xca, 0x4d, 0x80, 0x65,
|
||||||
0x0e, 0xee, 0xb1, 0x4a, 0x57, 0xa7, 0xbf, 0xfe, 0xe3, 0xf1, 0xb5, 0xc3, 0x26, 0xf3, 0xcb, 0xa3,
|
0x3c, 0xa1, 0xc7, 0x09, 0x91, 0x3b, 0xf9, 0xa0, 0x76, 0x70, 0x0f, 0x55, 0xaa, 0x3a, 0xfd, 0xf5,
|
||||||
0x91, 0x3f, 0x3d, 0x76, 0x79, 0x47, 0xef, 0x39, 0xde, 0xb5, 0x47, 0xd9, 0xcf, 0x7e, 0x70, 0x73,
|
0x1f, 0x8f, 0xaf, 0x1d, 0x36, 0x99, 0x5f, 0x1e, 0x8d, 0xfc, 0xe9, 0xb1, 0xcb, 0x5b, 0x7c, 0xcf,
|
||||||
0xec, 0x7a, 0xe3, 0x63, 0x51, 0x96, 0xc7, 0x0b, 0x2f, 0x97, 0x59, 0xf1, 0xff, 0xa7, 0xdf, 0xfc,
|
0xf1, 0xae, 0x3d, 0xca, 0x7e, 0xf6, 0x83, 0x9b, 0x63, 0xd7, 0x1b, 0x1f, 0x8b, 0xb0, 0x3c, 0x5e,
|
||||||
0x2f, 0x00, 0x00, 0xff, 0xff, 0x42, 0x87, 0x8c, 0xeb, 0xaf, 0x12, 0x00, 0x00,
|
0x68, 0xb9, 0xcc, 0x8a, 0x3f, 0xa4, 0x7e, 0xf3, 0xbf, 0x00, 0x00, 0x00, 0xff, 0xff, 0x10, 0xf6,
|
||||||
|
0xab, 0x74, 0xc0, 0x12, 0x00, 0x00,
|
||||||
}
|
}
|
||||||
|
|
||||||
// Reference imports to suppress errors if they are not otherwise used.
|
// Reference imports to suppress errors if they are not otherwise used.
|
||||||
|
@ -256,6 +256,7 @@ message Failure {
|
|||||||
PERMANENT_NODE_FAILURE = 20;
|
PERMANENT_NODE_FAILURE = 20;
|
||||||
PERMANENT_CHANNEL_FAILURE = 21;
|
PERMANENT_CHANNEL_FAILURE = 21;
|
||||||
EXPIRY_TOO_FAR = 22;
|
EXPIRY_TOO_FAR = 22;
|
||||||
|
MPP_TIMEOUT = 23;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
The error source is known, but the failure itself couldn't be decoded.
|
The error source is known, but the failure itself couldn't be decoded.
|
||||||
|
@ -415,6 +415,9 @@ func marshallError(sendError error) (*Failure, error) {
|
|||||||
case *lnwire.FailPermanentChannelFailure:
|
case *lnwire.FailPermanentChannelFailure:
|
||||||
response.Code = Failure_PERMANENT_CHANNEL_FAILURE
|
response.Code = Failure_PERMANENT_CHANNEL_FAILURE
|
||||||
|
|
||||||
|
case *lnwire.FailMPPTimeout:
|
||||||
|
response.Code = Failure_MPP_TIMEOUT
|
||||||
|
|
||||||
case nil:
|
case nil:
|
||||||
response.Code = Failure_UNKNOWN_FAILURE
|
response.Code = Failure_UNKNOWN_FAILURE
|
||||||
|
|
||||||
|
@ -80,6 +80,7 @@ const (
|
|||||||
CodeFinalIncorrectHtlcAmount FailCode = 19
|
CodeFinalIncorrectHtlcAmount FailCode = 19
|
||||||
CodeExpiryTooFar FailCode = 21
|
CodeExpiryTooFar FailCode = 21
|
||||||
CodeInvalidOnionPayload = FlagPerm | 22
|
CodeInvalidOnionPayload = FlagPerm | 22
|
||||||
|
CodeMPPTimeout FailCode = 23
|
||||||
)
|
)
|
||||||
|
|
||||||
// String returns the string representation of the failure code.
|
// String returns the string representation of the failure code.
|
||||||
@ -154,6 +155,9 @@ func (c FailCode) String() string {
|
|||||||
case CodeInvalidOnionPayload:
|
case CodeInvalidOnionPayload:
|
||||||
return "InvalidOnionPayload"
|
return "InvalidOnionPayload"
|
||||||
|
|
||||||
|
case CodeMPPTimeout:
|
||||||
|
return "MPPTimeout"
|
||||||
|
|
||||||
default:
|
default:
|
||||||
return "<unknown>"
|
return "<unknown>"
|
||||||
}
|
}
|
||||||
@ -1182,6 +1186,26 @@ func (f *InvalidOnionPayload) Encode(w io.Writer, pver uint32) error {
|
|||||||
return WriteElements(w, f.Offset)
|
return WriteElements(w, f.Offset)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// FailMPPTimeout is returned if the complete amount for a multi part payment
|
||||||
|
// was not received within a reasonable time.
|
||||||
|
//
|
||||||
|
// NOTE: May only be returned by the final node in the path.
|
||||||
|
type FailMPPTimeout struct{}
|
||||||
|
|
||||||
|
// Code returns the failure unique code.
|
||||||
|
//
|
||||||
|
// NOTE: Part of the FailureMessage interface.
|
||||||
|
func (f *FailMPPTimeout) Code() FailCode {
|
||||||
|
return CodeMPPTimeout
|
||||||
|
}
|
||||||
|
|
||||||
|
// Returns a human readable string describing the target FailureMessage.
|
||||||
|
//
|
||||||
|
// NOTE: Implements the error interface.
|
||||||
|
func (f *FailMPPTimeout) Error() string {
|
||||||
|
return f.Code().String()
|
||||||
|
}
|
||||||
|
|
||||||
// DecodeFailure decodes, validates, and parses the lnwire onion failure, for
|
// DecodeFailure decodes, validates, and parses the lnwire onion failure, for
|
||||||
// the provided protocol version.
|
// the provided protocol version.
|
||||||
func DecodeFailure(r io.Reader, pver uint32) (FailureMessage, error) {
|
func DecodeFailure(r io.Reader, pver uint32) (FailureMessage, error) {
|
||||||
@ -1366,6 +1390,9 @@ func makeEmptyOnionError(code FailCode) (FailureMessage, error) {
|
|||||||
case CodeInvalidOnionPayload:
|
case CodeInvalidOnionPayload:
|
||||||
return &InvalidOnionPayload{}, nil
|
return &InvalidOnionPayload{}, nil
|
||||||
|
|
||||||
|
case CodeMPPTimeout:
|
||||||
|
return &FailMPPTimeout{}, nil
|
||||||
|
|
||||||
default:
|
default:
|
||||||
return nil, errors.Errorf("unknown error code: %v", code)
|
return nil, errors.Errorf("unknown error code: %v", code)
|
||||||
}
|
}
|
||||||
|
@ -38,6 +38,7 @@ var onionFailures = []FailureMessage{
|
|||||||
&FailUnknownNextPeer{},
|
&FailUnknownNextPeer{},
|
||||||
&FailIncorrectPaymentAmount{},
|
&FailIncorrectPaymentAmount{},
|
||||||
&FailFinalExpiryTooSoon{},
|
&FailFinalExpiryTooSoon{},
|
||||||
|
&FailMPPTimeout{},
|
||||||
|
|
||||||
NewFailIncorrectDetails(99, 100),
|
NewFailIncorrectDetails(99, 100),
|
||||||
NewInvalidOnionVersion(testOnionHash),
|
NewInvalidOnionVersion(testOnionHash),
|
||||||
|
@ -16,8 +16,12 @@ import (
|
|||||||
|
|
||||||
const testMaxRecords = 2
|
const testMaxRecords = 2
|
||||||
|
|
||||||
|
// TestMissionControlStore tests the recording of payment failure events
|
||||||
|
// in mission control. It tests encoding and decoding of differing lnwire
|
||||||
|
// failures (FailIncorrectDetails and FailMppTimeout), pruning of results
|
||||||
|
// and idempotent writes.
|
||||||
func TestMissionControlStore(t *testing.T) {
|
func TestMissionControlStore(t *testing.T) {
|
||||||
// Set time zone explictly to keep test deterministic.
|
// Set time zone explicitly to keep test deterministic.
|
||||||
time.Local = time.UTC
|
time.Local = time.UTC
|
||||||
|
|
||||||
file, err := ioutil.TempFile("", "*.db")
|
file, err := ioutil.TempFile("", "*.db")
|
||||||
@ -115,11 +119,12 @@ func TestMissionControlStore(t *testing.T) {
|
|||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Add a newer result.
|
// Add a newer result which failed due to mpp timeout.
|
||||||
result3 := result1
|
result3 := result1
|
||||||
result3.timeReply = result1.timeReply.Add(2 * time.Hour)
|
result3.timeReply = result1.timeReply.Add(2 * time.Hour)
|
||||||
result3.timeFwd = result1.timeReply.Add(2 * time.Hour)
|
result3.timeFwd = result1.timeReply.Add(2 * time.Hour)
|
||||||
result3.id = 3
|
result3.id = 3
|
||||||
|
result3.failure = &lnwire.FailMPPTimeout{}
|
||||||
|
|
||||||
err = store.AddResult(&result3)
|
err = store.AddResult(&result3)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -216,6 +216,22 @@ func (i *interpretedResult) processPaymentOutcomeFinal(
|
|||||||
// deliberately. What to penalize?
|
// deliberately. What to penalize?
|
||||||
i.finalFailureReason = &reasonIncorrectDetails
|
i.finalFailureReason = &reasonIncorrectDetails
|
||||||
|
|
||||||
|
case *lnwire.FailMPPTimeout:
|
||||||
|
// TODO(carla): decide how to penalize mpp timeout. In the
|
||||||
|
// meantime, attribute success to the hops along the route and
|
||||||
|
// do not penalize the final node.
|
||||||
|
|
||||||
|
i.finalFailureReason = &reasonError
|
||||||
|
|
||||||
|
// If this is a direct payment, take no action.
|
||||||
|
if n == 1 {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Assign all pairs a success result except the final hop, as
|
||||||
|
// the payment reached the destination correctly.
|
||||||
|
i.successPairRange(route, 0, n-2)
|
||||||
|
|
||||||
default:
|
default:
|
||||||
// All other errors are considered terminal if coming from the
|
// All other errors are considered terminal if coming from the
|
||||||
// final hop. They indicate that something is wrong at the
|
// final hop. They indicate that something is wrong at the
|
||||||
|
@ -272,6 +272,39 @@ var resultTestCases = []resultTestCase{
|
|||||||
nodeFailure: &hops[1],
|
nodeFailure: &hops[1],
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
|
// Tests a single hop mpp timeout. Test that final node is not
|
||||||
|
// penalized. This is a temporary measure while we decide how to
|
||||||
|
// penalize mpp timeouts.
|
||||||
|
{
|
||||||
|
name: "one hop mpp timeout",
|
||||||
|
route: &routeOneHop,
|
||||||
|
failureSrcIdx: 1,
|
||||||
|
failure: &lnwire.FailMPPTimeout{},
|
||||||
|
|
||||||
|
expectedResult: &interpretedResult{
|
||||||
|
finalFailureReason: &reasonError,
|
||||||
|
nodeFailure: nil,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
// Tests a two hop mpp timeout. Test that final node is not penalized
|
||||||
|
// and the intermediate hop is attributed the success. This is a
|
||||||
|
// temporary measure while we decide how to penalize mpp timeouts.
|
||||||
|
{
|
||||||
|
name: "two hop mpp timeout",
|
||||||
|
route: &routeTwoHop,
|
||||||
|
failureSrcIdx: 2,
|
||||||
|
failure: &lnwire.FailMPPTimeout{},
|
||||||
|
|
||||||
|
expectedResult: &interpretedResult{
|
||||||
|
pairResults: map[DirectedNodePair]pairResult{
|
||||||
|
getTestPair(0, 1): successPairResult(100),
|
||||||
|
},
|
||||||
|
finalFailureReason: &reasonError,
|
||||||
|
nodeFailure: nil,
|
||||||
|
},
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
// TestResultInterpretation executes a list of test cases that test the result
|
// TestResultInterpretation executes a list of test cases that test the result
|
||||||
|
Loading…
Reference in New Issue
Block a user