From 49a20a87a27e721a35b7d4f3cb78496e8d68c589 Mon Sep 17 00:00:00 2001 From: Joost Jager Date: Fri, 13 Sep 2019 09:25:46 +0200 Subject: [PATCH 1/5] channeldb+invoices: make htlc cancelation stricter Previously it was possible to cancel a canceled htlc. This would subtract the htlc amount from the invoice amount again. --- channeldb/invoices.go | 6 +++--- invoices/invoiceregistry.go | 16 +++++++++++++++- 2 files changed, 18 insertions(+), 4 deletions(-) diff --git a/channeldb/invoices.go b/channeldb/invoices.go index f7912987..18f00da6 100644 --- a/channeldb/invoices.go +++ b/channeldb/invoices.go @@ -1224,9 +1224,9 @@ func (d *DB) updateInvoice(hash lntypes.Hash, invoices, settleIndex *bbolt.Bucke if !ok { return nil, fmt.Errorf("unknown htlc %v", key) } - if htlc.State == HtlcStateSettled { - return nil, fmt.Errorf("cannot cancel a " + - "settled htlc") + if htlc.State != HtlcStateAccepted { + return nil, fmt.Errorf("can only cancel " + + "accepted htlcs") } htlc.State = HtlcStateCancelled diff --git a/invoices/invoiceregistry.go b/invoices/invoiceregistry.go index 602014d4..2859e1c5 100644 --- a/invoices/invoiceregistry.go +++ b/invoices/invoiceregistry.go @@ -640,7 +640,21 @@ func (i *InvoiceRegistry) CancelInvoice(payHash lntypes.Hash) error { canceledHtlcs := make( map[channeldb.CircuitKey]*channeldb.HtlcAcceptDesc, ) - for key := range invoice.Htlcs { + for key, htlc := range invoice.Htlcs { + switch htlc.State { + + // If we get here, there shouldn't be any settled htlcs. + case channeldb.HtlcStateSettled: + return nil, errors.New("cannot cancel " + + "invoice with settled htlc(s)") + + // Don't cancel htlcs that were already cancelled, + // because it would incorrectly modify the invoice paid + // amt. + case channeldb.HtlcStateCancelled: + continue + } + canceledHtlcs[key] = nil } From 4e140213f945ee56d0965a9be5d7b637d7aa6c70 Mon Sep 17 00:00:00 2001 From: Joost Jager Date: Wed, 14 Aug 2019 21:11:34 +0200 Subject: [PATCH 2/5] htlcswitch+invoices: circuit key based hodl notifications This commit modifies hodl htlc notification from invoice registry from a single notification per hash to distinct notifications per htlc. This prepares for htlc-specific information (accept height) to be added to the notification. --- contractcourt/htlc_incoming_resolver_test.go | 17 ++-- htlcswitch/link.go | 96 +++++++------------- invoices/invoiceregistry.go | 88 ++++++++++++------ 3 files changed, 102 insertions(+), 99 deletions(-) diff --git a/contractcourt/htlc_incoming_resolver_test.go b/contractcourt/htlc_incoming_resolver_test.go index 5f623428..43d01817 100644 --- a/contractcourt/htlc_incoming_resolver_test.go +++ b/contractcourt/htlc_incoming_resolver_test.go @@ -18,8 +18,9 @@ const ( ) var ( - testResPreimage = lntypes.Preimage{1, 2, 3} - testResHash = testResPreimage.Hash() + testResPreimage = lntypes.Preimage{1, 2, 3} + testResHash = testResPreimage.Hash() + testResCircuitKey = channeldb.CircuitKey{} ) // TestHtlcIncomingResolverFwdPreimageKnown tests resolution of a forwarded htlc @@ -92,8 +93,8 @@ func TestHtlcIncomingResolverExitSettle(t *testing.T) { ctx := newIncomingResolverTestContext(t) ctx.registry.notifyEvent = &invoices.HodlEvent{ - Hash: testResHash, - Preimage: &testResPreimage, + CircuitKey: testResCircuitKey, + Preimage: &testResPreimage, } ctx.resolve() @@ -116,7 +117,7 @@ func TestHtlcIncomingResolverExitCancel(t *testing.T) { ctx := newIncomingResolverTestContext(t) ctx.registry.notifyEvent = &invoices.HodlEvent{ - Hash: testResHash, + CircuitKey: testResCircuitKey, } ctx.resolve() ctx.waitForResult(false) @@ -133,8 +134,8 @@ func TestHtlcIncomingResolverExitSettleHodl(t *testing.T) { notifyData := <-ctx.registry.notifyChan notifyData.hodlChan <- invoices.HodlEvent{ - Hash: testResHash, - Preimage: &testResPreimage, + CircuitKey: testResCircuitKey, + Preimage: &testResPreimage, } ctx.waitForResult(true) @@ -162,7 +163,7 @@ func TestHtlcIncomingResolverExitCancelHodl(t *testing.T) { ctx.resolve() notifyData := <-ctx.registry.notifyChan notifyData.hodlChan <- invoices.HodlEvent{ - Hash: testResHash, + CircuitKey: testResCircuitKey, } ctx.waitForResult(false) } diff --git a/htlcswitch/link.go b/htlcswitch/link.go index 1c462577..2f790ba2 100644 --- a/htlcswitch/link.go +++ b/htlcswitch/link.go @@ -364,9 +364,9 @@ type channelLink struct { // registry. hodlQueue *queue.ConcurrentQueue - // hodlMap stores a list of htlc data structs per hash. It allows + // hodlMap stores related htlc data for a circuit key. It allows // resolving those htlcs when we receive a message on hodlQueue. - hodlMap map[lntypes.Hash][]hodlHtlc + hodlMap map[channeldb.CircuitKey]hodlHtlc wg sync.WaitGroup quit chan struct{} @@ -391,7 +391,7 @@ func NewChannelLink(cfg ChannelLinkConfig, logCommitTimer: time.NewTimer(300 * time.Millisecond), overflowQueue: newPacketQueue(input.MaxHTLCNumber / 2), htlcUpdates: make(chan *contractcourt.ContractUpdate), - hodlMap: make(map[lntypes.Hash][]hodlHtlc), + hodlMap: make(map[channeldb.CircuitKey]hodlHtlc), hodlQueue: queue.NewConcurrentQueue(10), quit: make(chan struct{}), } @@ -1151,10 +1151,21 @@ func (l *channelLink) processHodlQueue(firstHodlEvent invoices.HodlEvent) error hodlEvent := firstHodlEvent loop: for { - if err := l.processHodlMapEvent(hodlEvent); err != nil { + // Lookup all hodl htlcs that can be failed or settled with this event. + // The hodl htlc must be present in the map. + circuitKey := hodlEvent.CircuitKey + hodlHtlc, ok := l.hodlMap[circuitKey] + if !ok { + return fmt.Errorf("hodl htlc not found: %v", circuitKey) + } + + if err := l.processHodlEvent(hodlEvent, hodlHtlc); err != nil { return err } + // Clean up hodl map. + delete(l.hodlMap, circuitKey) + select { case item := <-l.hodlQueue.ChanOut(): hodlEvent = item.(invoices.HodlEvent) @@ -1171,73 +1182,37 @@ loop: return nil } -// processHodlMapEvent resolves stored hodl htlcs based using the information in -// hodlEvent. -func (l *channelLink) processHodlMapEvent(hodlEvent invoices.HodlEvent) error { - // Lookup all hodl htlcs that can be failed or settled with this event. - // The hodl htlc must be present in the map. - hash := hodlEvent.Hash - hodlHtlcs, ok := l.hodlMap[hash] - if !ok { - return fmt.Errorf("hodl htlc not found: %v", hash) - } - - if err := l.processHodlEvent(hodlEvent, hodlHtlcs...); err != nil { - return err - } - - // Clean up hodl map. - delete(l.hodlMap, hash) - - return nil -} - // processHodlEvent applies a received hodl event to the provided htlc. When // this function returns without an error, the commit tx should be updated. func (l *channelLink) processHodlEvent(hodlEvent invoices.HodlEvent, - htlcs ...hodlHtlc) error { + htlc hodlHtlc) error { - hash := hodlEvent.Hash + l.batchCounter++ + + circuitKey := hodlEvent.CircuitKey // Determine required action for the resolution. - var hodlAction func(htlc hodlHtlc) error if hodlEvent.Preimage != nil { - l.debugf("Received hodl settle event for %v", hash) + l.debugf("Received hodl settle event for %v", circuitKey) - hodlAction = func(htlc hodlHtlc) error { - return l.settleHTLC( - *hodlEvent.Preimage, htlc.pd.HtlcIndex, - htlc.pd.SourceRef, - ) - } - } else { - l.debugf("Received hodl cancel event for %v", hash) - - hodlAction = func(htlc hodlHtlc) error { - // In case of a cancel, always return - // incorrect_or_unknown_payment_details in order to - // avoid leaking info. - failure := lnwire.NewFailIncorrectDetails( - htlc.pd.Amount, - ) - - l.sendHTLCError( - htlc.pd.HtlcIndex, failure, htlc.obfuscator, - htlc.pd.SourceRef, - ) - return nil - } + return l.settleHTLC( + *hodlEvent.Preimage, htlc.pd.HtlcIndex, + htlc.pd.SourceRef, + ) } - // Apply action for all htlcs matching this hash. - for _, htlc := range htlcs { - if err := hodlAction(htlc); err != nil { - return err - } + l.debugf("Received hodl cancel event for %v", circuitKey) - l.batchCounter++ - } + // In case of a cancel, always return + // incorrect_or_unknown_payment_details in order to avoid leaking info. + failure := lnwire.NewFailIncorrectDetails( + htlc.pd.Amount, + ) + l.sendHTLCError( + htlc.pd.HtlcIndex, failure, htlc.obfuscator, + htlc.pd.SourceRef, + ) return nil } @@ -2913,8 +2888,7 @@ func (l *channelLink) processExitHop(pd *lnwallet.PaymentDescriptor, if event == nil { // Save payment descriptor for future reference. - hodlHtlcs := l.hodlMap[invoiceHash] - l.hodlMap[invoiceHash] = append(hodlHtlcs, htlc) + l.hodlMap[circuitKey] = htlc return false, nil } diff --git a/invoices/invoiceregistry.go b/invoices/invoiceregistry.go index 2859e1c5..39d6dec0 100644 --- a/invoices/invoiceregistry.go +++ b/invoices/invoiceregistry.go @@ -36,8 +36,9 @@ type HodlEvent struct { // Preimage is the htlc preimage. Its value is nil in case of a cancel. Preimage *lntypes.Preimage - // Hash is the htlc hash. - Hash lntypes.Hash + // CircuitKey is the key of the htlc for which we have a resolution + // decision. + CircuitKey channeldb.CircuitKey } // InvoiceRegistry is a central registry of all the outstanding invoices @@ -60,13 +61,13 @@ type InvoiceRegistry struct { // new single invoice subscriptions are carried. invoiceEvents chan interface{} - // subscriptions is a map from a payment hash to a list of subscribers. + // subscriptions is a map from a circuit key to a list of subscribers. // It is used for efficient notification of links. - hodlSubscriptions map[lntypes.Hash]map[chan<- interface{}]struct{} + hodlSubscriptions map[channeldb.CircuitKey]map[chan<- interface{}]struct{} - // reverseSubscriptions tracks hashes subscribed to per subscriber. This - // is used to unsubscribe from all hashes efficiently. - hodlReverseSubscriptions map[chan<- interface{}]map[lntypes.Hash]struct{} + // reverseSubscriptions tracks circuit keys subscribed to per + // subscriber. This is used to unsubscribe from all hashes efficiently. + hodlReverseSubscriptions map[chan<- interface{}]map[channeldb.CircuitKey]struct{} // finalCltvRejectDelta defines the number of blocks before the expiry // of the htlc where we no longer settle it as an exit hop and instead @@ -92,8 +93,8 @@ func NewRegistry(cdb *channeldb.DB, finalCltvRejectDelta int32) *InvoiceRegistry newSubscriptions: make(chan *InvoiceSubscription), subscriptionCancels: make(chan uint32), invoiceEvents: make(chan interface{}, 100), - hodlSubscriptions: make(map[lntypes.Hash]map[chan<- interface{}]struct{}), - hodlReverseSubscriptions: make(map[chan<- interface{}]map[lntypes.Hash]struct{}), + hodlSubscriptions: make(map[channeldb.CircuitKey]map[chan<- interface{}]struct{}), + hodlReverseSubscriptions: make(map[chan<- interface{}]map[channeldb.CircuitKey]struct{}), finalCltvRejectDelta: finalCltvRejectDelta, quit: make(chan struct{}), } @@ -551,24 +552,24 @@ func (i *InvoiceRegistry) NotifyExitHopHtlc(rHash lntypes.Hash, // If it isn't recorded, cancel htlc. if !ok { return &HodlEvent{ - Hash: rHash, + CircuitKey: circuitKey, }, nil } switch invoiceHtlc.State { case channeldb.HtlcStateCancelled: return &HodlEvent{ - Hash: rHash, + CircuitKey: circuitKey, }, nil case channeldb.HtlcStateSettled: return &HodlEvent{ - Hash: rHash, - Preimage: &invoice.Terms.PaymentPreimage, + CircuitKey: circuitKey, + Preimage: &invoice.Terms.PaymentPreimage, }, nil case channeldb.HtlcStateAccepted: - i.hodlSubscribe(hodlChan, rHash) + i.hodlSubscribe(hodlChan, circuitKey) return nil, nil default: @@ -609,10 +610,22 @@ func (i *InvoiceRegistry) SettleHodlInvoice(preimage lntypes.Preimage) error { log.Debugf("Invoice(%v): settled with preimage %v", hash, invoice.Terms.PaymentPreimage) - i.notifyHodlSubscribers(HodlEvent{ - Hash: hash, - Preimage: &preimage, - }) + // In the callback, we marked the invoice as settled. UpdateInvoice will + // have seen this and should have moved all htlcs that were accepted to + // the settled state. In the loop below, we go through all of these and + // notify links and resolvers that are waiting for resolution. Any htlcs + // that were already settled before, will be notified again. This isn't + // necessary but doesn't hurt either. + for key, htlc := range invoice.Htlcs { + if htlc.State != channeldb.HtlcStateSettled { + continue + } + + i.notifyHodlSubscribers(HodlEvent{ + CircuitKey: key, + Preimage: &preimage, + }) + } i.notifyClients(hash, invoice, invoice.Terms.State) return nil @@ -678,9 +691,21 @@ func (i *InvoiceRegistry) CancelInvoice(payHash lntypes.Hash) error { } log.Debugf("Invoice(%v): canceled", payHash) - i.notifyHodlSubscribers(HodlEvent{ - Hash: payHash, - }) + + // In the callback, some htlcs may have been moved to the canceled + // state. We now go through all of these and notify links and resolvers + // that are waiting for resolution. Any htlcs that were already canceled + // before, will be notified again. This isn't necessary but doesn't hurt + // either. + for key, htlc := range invoice.Htlcs { + if htlc.State != channeldb.HtlcStateCancelled { + continue + } + + i.notifyHodlSubscribers(HodlEvent{ + CircuitKey: key, + }) + } i.notifyClients(payHash, invoice, channeldb.ContractCanceled) return nil @@ -947,7 +972,7 @@ func (i *InvoiceRegistry) SubscribeSingleInvoice( // notifyHodlSubscribers sends out the hodl event to all current subscribers. func (i *InvoiceRegistry) notifyHodlSubscribers(hodlEvent HodlEvent) { - subscribers, ok := i.hodlSubscriptions[hodlEvent.Hash] + subscribers, ok := i.hodlSubscriptions[hodlEvent.CircuitKey] if !ok { return } @@ -962,31 +987,34 @@ func (i *InvoiceRegistry) notifyHodlSubscribers(hodlEvent HodlEvent) { return } - delete(i.hodlReverseSubscriptions[subscriber], hodlEvent.Hash) + delete( + i.hodlReverseSubscriptions[subscriber], + hodlEvent.CircuitKey, + ) } - delete(i.hodlSubscriptions, hodlEvent.Hash) + delete(i.hodlSubscriptions, hodlEvent.CircuitKey) } // hodlSubscribe adds a new invoice subscription. func (i *InvoiceRegistry) hodlSubscribe(subscriber chan<- interface{}, - hash lntypes.Hash) { + circuitKey channeldb.CircuitKey) { - log.Debugf("Hodl subscribe for %v", hash) + log.Debugf("Hodl subscribe for %v", circuitKey) - subscriptions, ok := i.hodlSubscriptions[hash] + subscriptions, ok := i.hodlSubscriptions[circuitKey] if !ok { subscriptions = make(map[chan<- interface{}]struct{}) - i.hodlSubscriptions[hash] = subscriptions + i.hodlSubscriptions[circuitKey] = subscriptions } subscriptions[subscriber] = struct{}{} reverseSubscriptions, ok := i.hodlReverseSubscriptions[subscriber] if !ok { - reverseSubscriptions = make(map[lntypes.Hash]struct{}) + reverseSubscriptions = make(map[channeldb.CircuitKey]struct{}) i.hodlReverseSubscriptions[subscriber] = reverseSubscriptions } - reverseSubscriptions[hash] = struct{}{} + reverseSubscriptions[circuitKey] = struct{}{} } // HodlUnsubscribeAll cancels the subscription. From d3e206ef958cf2d427af86c5e9d521aade0b628a Mon Sep 17 00:00:00 2001 From: Joost Jager Date: Wed, 14 Aug 2019 21:21:39 +0200 Subject: [PATCH 3/5] invoices: return accept height in hodl event This is a preparation for passing back the accept height in the incorrect payment details failure message to the sender. --- channeldb/invoices.go | 2 +- invoices/invoiceregistry.go | 28 ++++++-- invoices/invoiceregistry_test.go | 115 +++++++++++++++++++++++++++++-- 3 files changed, 133 insertions(+), 12 deletions(-) diff --git a/channeldb/invoices.go b/channeldb/invoices.go index 18f00da6..3f3dbfd9 100644 --- a/channeldb/invoices.go +++ b/channeldb/invoices.go @@ -272,7 +272,7 @@ type InvoiceHTLC struct { // State indicates the state the invoice htlc is currently in. A // cancelled htlc isn't just removed from the invoice htlcs map, because - // we need AcceptedHeight to properly cancel the htlc back. + // we need AcceptHeight to properly cancel the htlc back. State HtlcState } diff --git a/invoices/invoiceregistry.go b/invoices/invoiceregistry.go index 39d6dec0..7efb9244 100644 --- a/invoices/invoiceregistry.go +++ b/invoices/invoiceregistry.go @@ -39,6 +39,9 @@ type HodlEvent struct { // CircuitKey is the key of the htlc for which we have a resolution // decision. CircuitKey channeldb.CircuitKey + + // AcceptHeight is the original height at which the htlc was accepted. + AcceptHeight int32 } // InvoiceRegistry is a central registry of all the outstanding invoices @@ -552,20 +555,29 @@ func (i *InvoiceRegistry) NotifyExitHopHtlc(rHash lntypes.Hash, // If it isn't recorded, cancel htlc. if !ok { return &HodlEvent{ - CircuitKey: circuitKey, + CircuitKey: circuitKey, + AcceptHeight: currentHeight, }, nil } + // Determine accepted height of this htlc. If the htlc reached the + // invoice database (possibly in a previous call to the invoice + // registry), we'll take the original accepted height as it was recorded + // in the database. + acceptHeight := int32(invoiceHtlc.AcceptHeight) + switch invoiceHtlc.State { case channeldb.HtlcStateCancelled: return &HodlEvent{ - CircuitKey: circuitKey, + CircuitKey: circuitKey, + AcceptHeight: acceptHeight, }, nil case channeldb.HtlcStateSettled: return &HodlEvent{ - CircuitKey: circuitKey, - Preimage: &invoice.Terms.PaymentPreimage, + CircuitKey: circuitKey, + Preimage: &invoice.Terms.PaymentPreimage, + AcceptHeight: acceptHeight, }, nil case channeldb.HtlcStateAccepted: @@ -622,8 +634,9 @@ func (i *InvoiceRegistry) SettleHodlInvoice(preimage lntypes.Preimage) error { } i.notifyHodlSubscribers(HodlEvent{ - CircuitKey: key, - Preimage: &preimage, + CircuitKey: key, + Preimage: &preimage, + AcceptHeight: int32(htlc.AcceptHeight), }) } i.notifyClients(hash, invoice, invoice.Terms.State) @@ -703,7 +716,8 @@ func (i *InvoiceRegistry) CancelInvoice(payHash lntypes.Hash) error { } i.notifyHodlSubscribers(HodlEvent{ - CircuitKey: key, + CircuitKey: key, + AcceptHeight: int32(htlc.AcceptHeight), }) } i.notifyClients(payHash, invoice, channeldb.ContractCanceled) diff --git a/invoices/invoiceregistry_test.go b/invoices/invoiceregistry_test.go index ee457c5d..8145d3fe 100644 --- a/invoices/invoiceregistry_test.go +++ b/invoices/invoiceregistry_test.go @@ -23,6 +23,8 @@ var ( testHtlcExpiry = uint32(5) + testInvoiceCltvDelta = uint32(4) + testFinalCltvRejectDelta = int32(4) testCurrentHeight = int32(1) @@ -121,6 +123,23 @@ func TestSettleInvoice(t *testing.T) { hodlChan := make(chan interface{}, 1) + // Try to settle invoice with an htlc that expires too soon. + event, err := registry.NotifyExitHopHtlc( + hash, testInvoice.Terms.Value, + uint32(testCurrentHeight)+testInvoiceCltvDelta-1, + testCurrentHeight, getCircuitKey(10), hodlChan, nil, + ) + if err != nil { + t.Fatal(err) + } + if event.Preimage != nil { + t.Fatal("expected cancel event") + } + if event.AcceptHeight != testCurrentHeight { + t.Fatalf("expected acceptHeight %v, but got %v", + testCurrentHeight, event.AcceptHeight) + } + // Settle invoice with a slightly higher amount. amtPaid := lnwire.MilliSatoshi(100500) _, err = registry.NotifyExitHopHtlc( @@ -159,7 +178,7 @@ func TestSettleInvoice(t *testing.T) { // Try to settle again with the same htlc id. We need this idempotent // behaviour after a restart. - event, err := registry.NotifyExitHopHtlc( + event, err = registry.NotifyExitHopHtlc( hash, amtPaid, testHtlcExpiry, testCurrentHeight, getCircuitKey(0), hodlChan, nil, ) @@ -309,7 +328,7 @@ func TestCancelInvoice(t *testing.T) { } // Notify arrival of a new htlc paying to this invoice. This should - // succeed. + // result in a cancel event. hodlChan := make(chan interface{}) event, err := registry.NotifyExitHopHtlc( hash, amt, testHtlcExpiry, testCurrentHeight, @@ -322,10 +341,15 @@ func TestCancelInvoice(t *testing.T) { if event.Preimage != nil { t.Fatal("expected cancel hodl event") } + if event.AcceptHeight != testCurrentHeight { + t.Fatalf("expected acceptHeight %v, but got %v", + testCurrentHeight, event.AcceptHeight) + } } -// TestHoldInvoice tests settling of a hold invoice and related notifications. -func TestHoldInvoice(t *testing.T) { +// TestSettleHoldInvoice tests settling of a hold invoice and related +// notifications. +func TestSettleHoldInvoice(t *testing.T) { defer timeout(t)() cdb, cleanup, err := newDB() @@ -462,6 +486,10 @@ func TestHoldInvoice(t *testing.T) { if *hodlEvent.Preimage != preimage { t.Fatal("unexpected preimage in hodl event") } + if hodlEvent.AcceptHeight != testCurrentHeight { + t.Fatalf("expected acceptHeight %v, but got %v", + testCurrentHeight, event.AcceptHeight) + } // We expect a settled notification to be sent out for both all and // single invoice subscribers. @@ -494,6 +522,85 @@ func TestHoldInvoice(t *testing.T) { } } +// TestCancelHoldInvoice tests canceling of a hold invoice and related +// notifications. +func TestCancelHoldInvoice(t *testing.T) { + defer timeout(t)() + + cdb, cleanup, err := newDB() + if err != nil { + t.Fatal(err) + } + defer cleanup() + + // Instantiate and start the invoice registry. + registry := NewRegistry(cdb, testFinalCltvRejectDelta) + + err = registry.Start() + if err != nil { + t.Fatal(err) + } + defer registry.Stop() + + // Add the invoice. + invoice := &channeldb.Invoice{ + Terms: channeldb.ContractTerm{ + PaymentPreimage: channeldb.UnknownPreimage, + Value: lnwire.MilliSatoshi(100000), + }, + } + + _, err = registry.AddInvoice(invoice, hash) + if err != nil { + t.Fatal(err) + } + + amtPaid := lnwire.MilliSatoshi(100000) + hodlChan := make(chan interface{}, 1) + + // NotifyExitHopHtlc without a preimage present in the invoice registry + // should be possible. + event, err := registry.NotifyExitHopHtlc( + hash, amtPaid, testHtlcExpiry, testCurrentHeight, + getCircuitKey(0), hodlChan, nil, + ) + if err != nil { + t.Fatalf("expected settle to succeed but got %v", err) + } + if event != nil { + t.Fatalf("expected htlc to be held") + } + + // Cancel invoice. + err = registry.CancelInvoice(hash) + if err != nil { + t.Fatal("cancel invoice failed") + } + + hodlEvent := (<-hodlChan).(HodlEvent) + if hodlEvent.Preimage != nil { + t.Fatal("expected cancel hodl event") + } + + // Offering the same htlc again at a higher height should still result + // in a rejection. The accept height is expected to be the original + // accept height. + event, err = registry.NotifyExitHopHtlc( + hash, amtPaid, testHtlcExpiry, testCurrentHeight+1, + getCircuitKey(0), hodlChan, nil, + ) + if err != nil { + t.Fatalf("expected settle to succeed but got %v", err) + } + if event.Preimage != nil { + t.Fatalf("expected htlc to be canceled") + } + if event.AcceptHeight != testCurrentHeight { + t.Fatalf("expected acceptHeight %v, but got %v", + testCurrentHeight, event.AcceptHeight) + } +} + func newDB() (*channeldb.DB, func(), error) { // First, create a temporary directory to be used for the duration of // this test. From 1de08c478051506121a95f114cab811a8fdeece3 Mon Sep 17 00:00:00 2001 From: Joost Jager Date: Sat, 10 Aug 2019 12:46:36 +0200 Subject: [PATCH 4/5] lnwire/test: fix TestFailIncorrectDetailsOptionalAmount This test relied on specific internals of the failure encode function. Changes to failure message pointer receiver everywhere subtly broke this test. --- lnwire/onion_error_test.go | 36 +++++++++++++++++++++++++++++------- 1 file changed, 29 insertions(+), 7 deletions(-) diff --git a/lnwire/onion_error_test.go b/lnwire/onion_error_test.go index 21fe3fcd..a29d3ee2 100644 --- a/lnwire/onion_error_test.go +++ b/lnwire/onion_error_test.go @@ -4,6 +4,7 @@ import ( "bufio" "bytes" "encoding/binary" + "io" "reflect" "testing" @@ -174,10 +175,7 @@ func TestWriteOnionErrorChanUpdate(t *testing.T) { func TestFailIncorrectDetailsOptionalAmount(t *testing.T) { t.Parallel() - // Creation an error that is a non-pointer will allow us to skip the - // type assertion for the Serializable interface. As a result, the - // amount body won't be written. - onionError := &FailIncorrectDetails{} + onionError := &mockFailIncorrectDetailsNoAmt{} var b bytes.Buffer if err := EncodeFailure(&b, onionError, 0); err != nil { @@ -189,8 +187,32 @@ func TestFailIncorrectDetailsOptionalAmount(t *testing.T) { t.Fatalf("unable to decode error: %v", err) } - if !reflect.DeepEqual(onionError, onionError2) { - t.Fatalf("expected %v, got %v", spew.Sdump(onionError), - spew.Sdump(onionError2)) + invalidDetailsErr, ok := onionError2.(*FailIncorrectDetails) + if !ok { + t.Fatalf("expected FailIncorrectDetails, but got %T", + onionError2) + } + + if invalidDetailsErr.amount != 0 { + t.Fatalf("expected amount to be zero") } } + +type mockFailIncorrectDetailsNoAmt struct { +} + +func (f *mockFailIncorrectDetailsNoAmt) Code() FailCode { + return CodeIncorrectOrUnknownPaymentDetails +} + +func (f *mockFailIncorrectDetailsNoAmt) Error() string { + return "" +} + +func (f *mockFailIncorrectDetailsNoAmt) Decode(r io.Reader, pver uint32) error { + return nil +} + +func (f *mockFailIncorrectDetailsNoAmt) Encode(w io.Writer, pver uint32) error { + return nil +} From f60e4b1e14547a76747a012cff663e828c9da313 Mon Sep 17 00:00:00 2001 From: Joost Jager Date: Wed, 12 Jun 2019 12:18:58 +0200 Subject: [PATCH 5/5] lnwire+htlcswitch: report height for invalid payment details failure Extends the invalid payment details failure with the new accept height field. This allows sender to distinguish between a genuine invalid details situation and a delay caused by intermediate nodes. --- htlcswitch/link.go | 4 +- htlcswitch/switch_test.go | 2 +- lnrpc/routerrpc/router.pb.go | 226 ++++++++++++++------------ lnrpc/routerrpc/router.proto | 3 + lnrpc/routerrpc/router_server.go | 1 + lnwire/onion_error.go | 31 +++- lnwire/onion_error_test.go | 59 ++++++- routing/missioncontrol_store_test.go | 2 +- routing/result_interpretation_test.go | 2 +- 9 files changed, 212 insertions(+), 118 deletions(-) diff --git a/htlcswitch/link.go b/htlcswitch/link.go index 2f790ba2..c904aa3e 100644 --- a/htlcswitch/link.go +++ b/htlcswitch/link.go @@ -1206,7 +1206,7 @@ func (l *channelLink) processHodlEvent(hodlEvent invoices.HodlEvent, // In case of a cancel, always return // incorrect_or_unknown_payment_details in order to avoid leaking info. failure := lnwire.NewFailIncorrectDetails( - htlc.pd.Amount, + htlc.pd.Amount, uint32(hodlEvent.AcceptHeight), ) l.sendHTLCError( @@ -2867,7 +2867,7 @@ func (l *channelLink) processExitHop(pd *lnwallet.PaymentDescriptor, // Cancel htlc if we don't have an invoice for it. case channeldb.ErrInvoiceNotFound: - failure := lnwire.NewFailIncorrectDetails(pd.Amount) + failure := lnwire.NewFailIncorrectDetails(pd.Amount, heightNow) l.sendHTLCError(pd.HtlcIndex, failure, obfuscator, pd.SourceRef) return true, nil diff --git a/htlcswitch/switch_test.go b/htlcswitch/switch_test.go index 59f6fae9..b0a53eb3 100644 --- a/htlcswitch/switch_test.go +++ b/htlcswitch/switch_test.go @@ -1803,7 +1803,7 @@ func TestSwitchSendPayment(t *testing.T) { // the add htlc request with error and sent the htlc fail request // back. This request should be forwarded back to alice channel link. obfuscator := NewMockObfuscator() - failure := lnwire.NewFailIncorrectDetails(update.Amount) + failure := lnwire.NewFailIncorrectDetails(update.Amount, 100) reason, err := obfuscator.EncryptFirstHop(failure) if err != nil { t.Fatalf("unable obfuscate failure: %v", err) diff --git a/lnrpc/routerrpc/router.pb.go b/lnrpc/routerrpc/router.pb.go index 86a69c2c..fdb45dfb 100644 --- a/lnrpc/routerrpc/router.pb.go +++ b/lnrpc/routerrpc/router.pb.go @@ -646,7 +646,9 @@ type Failure struct { //* //The position in the path of the intermediate or final node that generated //the failure message. Position zero is the sender node. - FailureSourceIndex uint32 `protobuf:"varint,8,opt,name=failure_source_index,json=failureSourceIndex,proto3" json:"failure_source_index,omitempty"` + FailureSourceIndex uint32 `protobuf:"varint,8,opt,name=failure_source_index,json=failureSourceIndex,proto3" json:"failure_source_index,omitempty"` + /// A failure type-dependent block height. + Height uint32 `protobuf:"varint,9,opt,name=height,proto3" json:"height,omitempty"` XXX_NoUnkeyedLiteral struct{} `json:"-"` XXX_unrecognized []byte `json:"-"` XXX_sizecache int32 `json:"-"` @@ -726,6 +728,13 @@ func (m *Failure) GetFailureSourceIndex() uint32 { return 0 } +func (m *Failure) GetHeight() uint32 { + if m != nil { + return m.Height + } + return 0 +} + type ChannelUpdate struct { //* //The signature that validates the announced data and proves the ownership @@ -1212,117 +1221,118 @@ func init() { func init() { proto.RegisterFile("routerrpc/router.proto", fileDescriptor_7a0613f69d37b0a5) } var fileDescriptor_7a0613f69d37b0a5 = []byte{ - // 1759 bytes of a gzipped FileDescriptorProto + // 1769 bytes of a gzipped FileDescriptorProto 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x8c, 0x57, 0x41, 0x73, 0x22, 0xb9, 0x15, 0x5e, 0x0c, 0x18, 0x78, 0x80, 0xdd, 0x96, 0x3d, 0x76, 0x0f, 0x1e, 0xef, 0x7a, 0xd9, 0xcd, 0xac, 0x6b, 0x6a, 0x63, 0x6f, 0x9c, 0xda, 0xad, 0xa9, 0x3d, 0x24, 0xc5, 0x80, 0x58, 0xf7, 0x0c, 0x74, 0x7b, 0x05, 0xcc, 0xee, 0x24, 0x07, 0x95, 0x0c, 0xb2, 0xe9, 0x72, 0xd3, 0xcd, 0x74, 0x0b, - 0x67, 0x9c, 0x43, 0x2e, 0xa9, 0x1c, 0x73, 0xcf, 0xbf, 0xc8, 0xef, 0xc8, 0x1f, 0x49, 0x7e, 0x41, - 0x8e, 0xa9, 0x4a, 0x49, 0xea, 0x86, 0x06, 0xe3, 0x49, 0x4e, 0xb4, 0xbe, 0xf7, 0xe9, 0x49, 0x7a, - 0x4f, 0xef, 0xd3, 0x03, 0xf6, 0xc3, 0x60, 0x26, 0x78, 0x18, 0x4e, 0x87, 0x67, 0xfa, 0xeb, 0x74, - 0x1a, 0x06, 0x22, 0x40, 0xa5, 0x39, 0x5e, 0x2b, 0x85, 0xd3, 0xa1, 0x46, 0xeb, 0xff, 0xc9, 0x02, - 0xea, 0x71, 0x7f, 0x74, 0xc9, 0xee, 0x27, 0xdc, 0x17, 0x84, 0xbf, 0x9f, 0xf1, 0x48, 0x20, 0x04, - 0xb9, 0x11, 0x8f, 0x84, 0x99, 0x39, 0xce, 0x9c, 0x54, 0x88, 0xfa, 0x46, 0x06, 0x64, 0xd9, 0x44, - 0x98, 0x1b, 0xc7, 0x99, 0x93, 0x2c, 0x91, 0x9f, 0xe8, 0x73, 0xa8, 0x4c, 0xf5, 0x3c, 0x3a, 0x66, - 0xd1, 0xd8, 0xcc, 0x2a, 0x76, 0x39, 0xc6, 0x2e, 0x58, 0x34, 0x46, 0x27, 0x60, 0x5c, 0xbb, 0x3e, - 0xf3, 0xe8, 0xd0, 0x13, 0x77, 0x74, 0xc4, 0x3d, 0xc1, 0xcc, 0xdc, 0x71, 0xe6, 0x24, 0x4f, 0xb6, - 0x14, 0xde, 0xf4, 0xc4, 0x5d, 0x4b, 0xa2, 0xe8, 0x2b, 0xd8, 0x4e, 0x9c, 0x85, 0x7a, 0x17, 0x66, - 0xfe, 0x38, 0x73, 0x52, 0x22, 0x5b, 0xd3, 0xe5, 0xbd, 0x7d, 0x05, 0xdb, 0xc2, 0x9d, 0xf0, 0x60, - 0x26, 0x68, 0xc4, 0x87, 0x81, 0x3f, 0x8a, 0xcc, 0x4d, 0xed, 0x31, 0x86, 0x7b, 0x1a, 0x45, 0x75, - 0xa8, 0x5e, 0x73, 0x4e, 0x3d, 0x77, 0xe2, 0x0a, 0x1a, 0x31, 0x61, 0x16, 0xd4, 0xd6, 0xcb, 0xd7, - 0x9c, 0x77, 0x24, 0xd6, 0x63, 0x42, 0xee, 0x2f, 0x98, 0x89, 0x9b, 0xc0, 0xf5, 0x6f, 0xe8, 0x70, - 0xcc, 0x7c, 0xea, 0x8e, 0xcc, 0xe2, 0x71, 0xe6, 0x24, 0x47, 0xb6, 0x12, 0xbc, 0x39, 0x66, 0xbe, - 0x35, 0x42, 0x47, 0x00, 0xea, 0x0c, 0xca, 0x9d, 0x59, 0x52, 0x2b, 0x96, 0x24, 0xa2, 0x7c, 0xa1, - 0x73, 0x28, 0xab, 0x00, 0xd3, 0xb1, 0xeb, 0x8b, 0xc8, 0x84, 0xe3, 0xec, 0x49, 0xf9, 0xdc, 0x38, - 0xf5, 0x7c, 0x19, 0x6b, 0x22, 0x2d, 0x17, 0xae, 0x2f, 0x48, 0x9a, 0x84, 0x30, 0x14, 0x65, 0x64, - 0xa9, 0xf0, 0xee, 0xcc, 0xb2, 0x9a, 0xf0, 0xe2, 0x74, 0x9e, 0xa5, 0xd3, 0x87, 0x69, 0x39, 0x6d, - 0xf1, 0x48, 0xf4, 0xbd, 0x3b, 0xec, 0x8b, 0xf0, 0x9e, 0x14, 0x46, 0x7a, 0x54, 0xfb, 0x1e, 0x2a, - 0x69, 0x83, 0x4c, 0xd4, 0x2d, 0xbf, 0x57, 0xb9, 0xcb, 0x11, 0xf9, 0x89, 0xf6, 0x20, 0x7f, 0xc7, - 0xbc, 0x19, 0x57, 0xc9, 0xab, 0x10, 0x3d, 0xf8, 0x7e, 0xe3, 0x65, 0xa6, 0xfe, 0x12, 0x76, 0xfb, - 0x21, 0x1b, 0xde, 0xae, 0xe4, 0x7f, 0x35, 0xb3, 0x99, 0x07, 0x99, 0xad, 0xff, 0x09, 0xaa, 0xf1, - 0xa4, 0x9e, 0x60, 0x62, 0x16, 0xa1, 0x5f, 0x42, 0x3e, 0x12, 0x4c, 0x70, 0x45, 0xde, 0x3a, 0x3f, - 0x48, 0x1d, 0x25, 0x45, 0xe4, 0x44, 0xb3, 0x50, 0x0d, 0x8a, 0xd3, 0x90, 0xbb, 0x13, 0x76, 0x93, - 0x6c, 0x6b, 0x3e, 0x46, 0x75, 0xc8, 0xab, 0xc9, 0xea, 0x46, 0x95, 0xcf, 0x2b, 0xe9, 0x30, 0x12, - 0x6d, 0xaa, 0xff, 0x06, 0xb6, 0xd5, 0xb8, 0xcd, 0xf9, 0xc7, 0x6e, 0xed, 0x01, 0x14, 0xd8, 0x44, - 0xa7, 0x5f, 0xdf, 0xdc, 0x4d, 0x36, 0x91, 0x99, 0xaf, 0x8f, 0xc0, 0x58, 0xcc, 0x8f, 0xa6, 0x81, - 0x1f, 0x71, 0x79, 0x1b, 0xa4, 0x73, 0x79, 0x19, 0xe4, 0xcd, 0x99, 0xc8, 0x59, 0x19, 0x35, 0x6b, - 0x2b, 0xc6, 0xdb, 0x9c, 0x77, 0x23, 0x26, 0xd0, 0x73, 0x7d, 0x09, 0xa9, 0x17, 0x0c, 0x6f, 0xe5, - 0xb5, 0x66, 0xf7, 0xb1, 0xfb, 0xaa, 0x84, 0x3b, 0xc1, 0xf0, 0xb6, 0x25, 0xc1, 0xfa, 0xef, 0x75, - 0x79, 0xf5, 0x03, 0xbd, 0xf7, 0xff, 0x3b, 0xbc, 0x8b, 0x10, 0x6c, 0x3c, 0x1e, 0x02, 0x0a, 0xbb, - 0x4b, 0xce, 0xe3, 0x53, 0xa4, 0x23, 0x9b, 0x59, 0x89, 0xec, 0xd7, 0x50, 0xb8, 0x66, 0xae, 0x37, - 0x0b, 0x13, 0xc7, 0x28, 0x95, 0xa6, 0xb6, 0xb6, 0x90, 0x84, 0x52, 0xff, 0x47, 0x01, 0x0a, 0x31, - 0x88, 0xce, 0x21, 0x37, 0x0c, 0x46, 0x49, 0x76, 0x3f, 0x7d, 0x38, 0x2d, 0xf9, 0x6d, 0x06, 0x23, - 0x4e, 0x14, 0x17, 0xfd, 0x16, 0xb6, 0x64, 0x51, 0xf9, 0xdc, 0xa3, 0xb3, 0xe9, 0x88, 0xcd, 0x13, - 0x6a, 0xa6, 0x66, 0x37, 0x35, 0x61, 0xa0, 0xec, 0xa4, 0x3a, 0x4c, 0x0f, 0xd1, 0x21, 0x94, 0xc6, - 0xc2, 0x1b, 0xea, 0x4c, 0xe4, 0xd4, 0x85, 0x2e, 0x4a, 0x40, 0xe5, 0xa0, 0x0e, 0xd5, 0xc0, 0x77, - 0x03, 0x9f, 0x46, 0x63, 0x46, 0xcf, 0xbf, 0xfd, 0x4e, 0xe9, 0x45, 0x85, 0x94, 0x15, 0xd8, 0x1b, - 0xb3, 0xf3, 0x6f, 0xbf, 0x43, 0x9f, 0x41, 0x59, 0x55, 0x2d, 0xff, 0x30, 0x75, 0xc3, 0x7b, 0x25, - 0x14, 0x55, 0xa2, 0x0a, 0x19, 0x2b, 0x44, 0x96, 0xc6, 0xb5, 0xc7, 0x6e, 0x22, 0x25, 0x0e, 0x55, - 0xa2, 0x07, 0xe8, 0x1b, 0xd8, 0x8b, 0x63, 0x40, 0xa3, 0x60, 0x16, 0x0e, 0x39, 0x75, 0xfd, 0x11, - 0xff, 0xa0, 0xa4, 0xa1, 0x4a, 0x50, 0x6c, 0xeb, 0x29, 0x93, 0x25, 0x2d, 0xf5, 0xbf, 0xe5, 0xa1, - 0x9c, 0x0a, 0x00, 0xaa, 0x40, 0x91, 0xe0, 0x1e, 0x26, 0x6f, 0x71, 0xcb, 0xf8, 0x04, 0x9d, 0xc0, - 0x97, 0x96, 0xdd, 0x74, 0x08, 0xc1, 0xcd, 0x3e, 0x75, 0x08, 0x1d, 0xd8, 0x6f, 0x6c, 0xe7, 0x27, - 0x9b, 0x5e, 0x36, 0xde, 0x75, 0xb1, 0xdd, 0xa7, 0x2d, 0xdc, 0x6f, 0x58, 0x9d, 0x9e, 0x91, 0x41, - 0xcf, 0xc0, 0x5c, 0x30, 0x13, 0x73, 0xa3, 0xeb, 0x0c, 0xec, 0xbe, 0xb1, 0x81, 0x3e, 0x83, 0xc3, - 0xb6, 0x65, 0x37, 0x3a, 0x74, 0xc1, 0x69, 0x76, 0xfa, 0x6f, 0x29, 0xfe, 0xf9, 0xd2, 0x22, 0xef, - 0x8c, 0xec, 0x3a, 0xc2, 0x45, 0xbf, 0xd3, 0x4c, 0x3c, 0xe4, 0xd0, 0x53, 0x78, 0xa2, 0x09, 0x7a, - 0x0a, 0xed, 0x3b, 0x0e, 0xed, 0x39, 0x8e, 0x6d, 0xe4, 0xd1, 0x0e, 0x54, 0x2d, 0xfb, 0x6d, 0xa3, - 0x63, 0xb5, 0x28, 0xc1, 0x8d, 0x4e, 0xd7, 0xd8, 0x44, 0xbb, 0xb0, 0xbd, 0xca, 0x2b, 0x48, 0x17, - 0x09, 0xcf, 0xb1, 0x2d, 0xc7, 0xa6, 0x6f, 0x31, 0xe9, 0x59, 0x8e, 0x6d, 0x14, 0xd1, 0x3e, 0xa0, - 0x65, 0xd3, 0x45, 0xb7, 0xd1, 0x34, 0x4a, 0xe8, 0x09, 0xec, 0x2c, 0xe3, 0x6f, 0xf0, 0x3b, 0x03, - 0x90, 0x09, 0x7b, 0x7a, 0x63, 0xf4, 0x15, 0xee, 0x38, 0x3f, 0xd1, 0xae, 0x65, 0x5b, 0xdd, 0x41, - 0xd7, 0x28, 0xa3, 0x3d, 0x30, 0xda, 0x18, 0x53, 0xcb, 0xee, 0x0d, 0xda, 0x6d, 0xab, 0x69, 0x61, - 0xbb, 0x6f, 0x54, 0xf4, 0xca, 0xeb, 0x0e, 0x5e, 0x95, 0x13, 0x9a, 0x17, 0x0d, 0xdb, 0xc6, 0x1d, - 0xda, 0xb2, 0x7a, 0x8d, 0x57, 0x1d, 0xdc, 0x32, 0xb6, 0xd0, 0x11, 0x3c, 0xed, 0xe3, 0xee, 0xa5, - 0x43, 0x1a, 0xe4, 0x1d, 0x4d, 0xec, 0xed, 0x86, 0xd5, 0x19, 0x10, 0x6c, 0x6c, 0xa3, 0xcf, 0xe1, - 0x88, 0xe0, 0x1f, 0x07, 0x16, 0xc1, 0x2d, 0x6a, 0x3b, 0x2d, 0x4c, 0xdb, 0xb8, 0xd1, 0x1f, 0x10, - 0x4c, 0xbb, 0x56, 0xaf, 0x67, 0xd9, 0x3f, 0x18, 0x06, 0xfa, 0x12, 0x8e, 0xe7, 0x94, 0xb9, 0x83, - 0x15, 0xd6, 0x8e, 0x3c, 0x5f, 0x92, 0x52, 0x1b, 0xff, 0xdc, 0xa7, 0x97, 0x18, 0x13, 0x03, 0xa1, - 0x1a, 0xec, 0x2f, 0x96, 0xd7, 0x0b, 0xc4, 0x6b, 0xef, 0x4a, 0xdb, 0x25, 0x26, 0xdd, 0x86, 0x2d, - 0x13, 0xbc, 0x64, 0xdb, 0x93, 0xdb, 0x5e, 0xd8, 0x56, 0xb7, 0xfd, 0x04, 0xed, 0xc1, 0x76, 0xb2, - 0x5a, 0x02, 0xfe, 0xb3, 0x80, 0x0e, 0x00, 0x0d, 0x6c, 0x82, 0x1b, 0x2d, 0x79, 0xf8, 0xb9, 0xe1, - 0x5f, 0x85, 0xd7, 0xb9, 0xe2, 0x86, 0x91, 0xad, 0xff, 0x3d, 0x0b, 0xd5, 0xa5, 0x5a, 0x43, 0xcf, - 0xa0, 0x14, 0xb9, 0x37, 0x3e, 0x13, 0x52, 0x0d, 0xb4, 0x50, 0x2c, 0x00, 0xf5, 0xde, 0x8d, 0x99, - 0xeb, 0x6b, 0x85, 0xd2, 0x0a, 0x5d, 0x52, 0x88, 0xd2, 0xa7, 0x03, 0x28, 0x24, 0xef, 0x65, 0x56, - 0xd5, 0xe5, 0xe6, 0x50, 0xbf, 0x93, 0xcf, 0xa0, 0x24, 0x25, 0x30, 0x12, 0x6c, 0x32, 0x55, 0x25, - 0x5b, 0x25, 0x0b, 0x00, 0x7d, 0x01, 0xd5, 0x09, 0x8f, 0x22, 0x76, 0xc3, 0xa9, 0x2e, 0x3b, 0x50, - 0x8c, 0x4a, 0x0c, 0xb6, 0x55, 0xf5, 0x7d, 0x01, 0x89, 0x0c, 0xc4, 0xa4, 0xbc, 0x26, 0xc5, 0xa0, - 0x26, 0xad, 0x2a, 0xb0, 0x60, 0x71, 0x75, 0xa7, 0x15, 0x58, 0x30, 0xf4, 0x02, 0x76, 0xb4, 0x84, - 0xb8, 0xbe, 0x3b, 0x99, 0x4d, 0xb4, 0x94, 0x14, 0xd4, 0x96, 0xb7, 0x95, 0x94, 0x68, 0x5c, 0x29, - 0xca, 0x53, 0x28, 0x5e, 0xb1, 0x88, 0x4b, 0xf1, 0x8f, 0x4b, 0xbd, 0x20, 0xc7, 0x6d, 0xce, 0xa5, - 0x49, 0x3e, 0x09, 0xa1, 0x14, 0xb1, 0x92, 0x36, 0x5d, 0x73, 0x4e, 0x64, 0x1c, 0xe7, 0x2b, 0xb0, - 0x0f, 0x8b, 0x15, 0xca, 0xa9, 0x15, 0x34, 0xae, 0x56, 0x78, 0x01, 0x3b, 0xfc, 0x83, 0x08, 0x19, - 0x0d, 0xa6, 0xec, 0xfd, 0x8c, 0xd3, 0x11, 0x13, 0xcc, 0xac, 0xa8, 0xe0, 0x6e, 0x2b, 0x83, 0xa3, - 0xf0, 0x16, 0x13, 0xac, 0xfe, 0x0c, 0x6a, 0x84, 0x47, 0x5c, 0x74, 0xdd, 0x28, 0x72, 0x03, 0xbf, - 0x19, 0xf8, 0x22, 0x0c, 0xbc, 0xf8, 0x0d, 0xa9, 0x1f, 0xc1, 0xe1, 0x5a, 0xab, 0x7e, 0x04, 0xe4, - 0xe4, 0x1f, 0x67, 0x3c, 0xbc, 0x5f, 0x3f, 0xf9, 0x1e, 0x0e, 0xd7, 0x5a, 0xe3, 0x17, 0xe4, 0x6b, - 0xc8, 0xfb, 0xc1, 0x88, 0x47, 0x66, 0x46, 0x75, 0x25, 0xfb, 0x29, 0xb9, 0xb6, 0x83, 0x11, 0xbf, - 0x70, 0x23, 0x11, 0x84, 0xf7, 0x44, 0x93, 0x24, 0x7b, 0xca, 0xdc, 0x30, 0x32, 0x37, 0x1e, 0xb0, - 0x2f, 0x99, 0x1b, 0xce, 0xd9, 0x8a, 0x54, 0xff, 0x73, 0x06, 0xca, 0x29, 0x27, 0x68, 0x1f, 0x36, - 0xa7, 0xb3, 0xab, 0xa4, 0x61, 0xa9, 0x90, 0x78, 0x84, 0x9e, 0xc3, 0x96, 0xc7, 0x22, 0x41, 0xa5, - 0xd6, 0x52, 0x99, 0xd2, 0xf8, 0x81, 0x5d, 0x41, 0xd1, 0x29, 0xa0, 0x40, 0x8c, 0x79, 0x48, 0xa3, - 0xd9, 0x70, 0xc8, 0xa3, 0x88, 0x4e, 0xc3, 0xe0, 0x4a, 0xdd, 0xc9, 0x0d, 0xb2, 0xc6, 0xf2, 0x3a, - 0x57, 0xcc, 0x19, 0xf9, 0xfa, 0xbf, 0x33, 0x50, 0x4e, 0x6d, 0x4e, 0xde, 0x5a, 0x79, 0x18, 0x7a, - 0x1d, 0x06, 0x93, 0xa4, 0x16, 0xe6, 0x00, 0x32, 0xa1, 0xa0, 0x06, 0x22, 0x88, 0x0b, 0x21, 0x19, - 0x2e, 0xdf, 0xf6, 0xac, 0xda, 0x60, 0xea, 0xb6, 0x9f, 0xc3, 0xde, 0xc4, 0xf5, 0xe9, 0x94, 0xfb, - 0xcc, 0x73, 0xff, 0xc8, 0x69, 0xd2, 0x89, 0xe4, 0x14, 0x71, 0xad, 0x0d, 0xd5, 0xa1, 0xb2, 0x74, - 0x92, 0xbc, 0x3a, 0xc9, 0x12, 0x86, 0x5e, 0xc2, 0x81, 0x8a, 0x02, 0x13, 0x82, 0x4f, 0xa6, 0x22, - 0x39, 0xe0, 0xf5, 0xcc, 0x53, 0x35, 0x50, 0x24, 0x8f, 0x99, 0x5f, 0xfc, 0x35, 0x03, 0x95, 0x74, - 0x37, 0x86, 0xaa, 0x50, 0xb2, 0x6c, 0xda, 0xee, 0x58, 0x3f, 0x5c, 0xf4, 0x8d, 0x4f, 0xe4, 0xb0, - 0x37, 0x68, 0x36, 0x31, 0x6e, 0xe1, 0x96, 0x91, 0x41, 0x08, 0xb6, 0xa4, 0x90, 0xe0, 0x16, 0xed, - 0x5b, 0x5d, 0xec, 0x0c, 0xe4, 0x1b, 0xb4, 0x0b, 0xdb, 0x31, 0x66, 0x3b, 0x94, 0x38, 0x83, 0x3e, - 0x36, 0xb2, 0xc8, 0x80, 0x4a, 0x0c, 0x62, 0x42, 0x1c, 0x62, 0xe4, 0xa4, 0x70, 0xc6, 0xc8, 0xc3, - 0xf7, 0x2c, 0x79, 0xee, 0xf2, 0xe7, 0x7f, 0xc9, 0xc1, 0xa6, 0xea, 0x5e, 0x42, 0x74, 0x01, 0xe5, - 0x54, 0xcb, 0x8b, 0x8e, 0x3e, 0xda, 0x0a, 0xd7, 0xcc, 0xf5, 0xed, 0xe5, 0x2c, 0xfa, 0x26, 0x83, - 0x5e, 0x43, 0x25, 0xdd, 0xd4, 0xa2, 0x74, 0xb3, 0xb2, 0xa6, 0xdb, 0xfd, 0xa8, 0xaf, 0x37, 0x60, - 0xe0, 0x48, 0xb8, 0x13, 0xd9, 0x9c, 0xc4, 0xed, 0x22, 0xaa, 0xa5, 0xf8, 0x2b, 0x3d, 0x68, 0xed, - 0x70, 0xad, 0x2d, 0xae, 0xab, 0x8e, 0x3e, 0x62, 0xdc, 0xb0, 0x3d, 0x38, 0xe2, 0x72, 0x97, 0x58, - 0xfb, 0xf4, 0x31, 0x73, 0xec, 0x6d, 0x04, 0xbb, 0x6b, 0x14, 0x00, 0xfd, 0x22, 0xbd, 0x83, 0x47, - 0xf5, 0xa3, 0xf6, 0xfc, 0x7f, 0xd1, 0x16, 0xab, 0xac, 0x91, 0x8a, 0xa5, 0x55, 0x1e, 0x17, 0x9a, - 0xa5, 0x55, 0x3e, 0xa2, 0x38, 0xaf, 0x7e, 0xf5, 0xbb, 0xb3, 0x1b, 0x57, 0x8c, 0x67, 0x57, 0xa7, - 0xc3, 0x60, 0x72, 0xe6, 0xb9, 0x37, 0x63, 0xe1, 0xbb, 0xfe, 0x8d, 0xcf, 0xc5, 0x1f, 0x82, 0xf0, - 0xf6, 0xcc, 0xf3, 0x47, 0x67, 0xaa, 0x01, 0x3e, 0x9b, 0xbb, 0xbb, 0xda, 0x54, 0xff, 0x60, 0x7f, - 0xfd, 0xdf, 0x00, 0x00, 0x00, 0xff, 0xff, 0xd5, 0x11, 0xe6, 0x51, 0xf1, 0x0e, 0x00, 0x00, + 0x67, 0x9c, 0x43, 0x2e, 0xa9, 0x1c, 0x73, 0xcf, 0xbf, 0xc8, 0x6f, 0xca, 0x25, 0xf9, 0x05, 0x39, + 0xa6, 0x2a, 0x25, 0xa9, 0x1b, 0x1a, 0x8c, 0x27, 0x39, 0xd1, 0xfa, 0xde, 0xa7, 0x27, 0xe9, 0x3d, + 0xbd, 0x4f, 0x0f, 0xd8, 0x0f, 0x83, 0x99, 0xe0, 0x61, 0x38, 0x1d, 0x9e, 0xe9, 0xaf, 0xd3, 0x69, + 0x18, 0x88, 0x00, 0x95, 0xe6, 0x78, 0xad, 0x14, 0x4e, 0x87, 0x1a, 0xad, 0xff, 0x27, 0x0b, 0xa8, + 0xc7, 0xfd, 0xd1, 0x25, 0xbb, 0x9f, 0x70, 0x5f, 0x10, 0xfe, 0x7e, 0xc6, 0x23, 0x81, 0x10, 0xe4, + 0x46, 0x3c, 0x12, 0x66, 0xe6, 0x38, 0x73, 0x52, 0x21, 0xea, 0x1b, 0x19, 0x90, 0x65, 0x13, 0x61, + 0x6e, 0x1c, 0x67, 0x4e, 0xb2, 0x44, 0x7e, 0xa2, 0xcf, 0xa1, 0x32, 0xd5, 0xf3, 0xe8, 0x98, 0x45, + 0x63, 0x33, 0xab, 0xd8, 0xe5, 0x18, 0xbb, 0x60, 0xd1, 0x18, 0x9d, 0x80, 0x71, 0xed, 0xfa, 0xcc, + 0xa3, 0x43, 0x4f, 0xdc, 0xd1, 0x11, 0xf7, 0x04, 0x33, 0x73, 0xc7, 0x99, 0x93, 0x3c, 0xd9, 0x52, + 0x78, 0xd3, 0x13, 0x77, 0x2d, 0x89, 0xa2, 0xaf, 0x60, 0x3b, 0x71, 0x16, 0xea, 0x5d, 0x98, 0xf9, + 0xe3, 0xcc, 0x49, 0x89, 0x6c, 0x4d, 0x97, 0xf7, 0xf6, 0x15, 0x6c, 0x0b, 0x77, 0xc2, 0x83, 0x99, + 0xa0, 0x11, 0x1f, 0x06, 0xfe, 0x28, 0x32, 0x37, 0xb5, 0xc7, 0x18, 0xee, 0x69, 0x14, 0xd5, 0xa1, + 0x7a, 0xcd, 0x39, 0xf5, 0xdc, 0x89, 0x2b, 0x68, 0xc4, 0x84, 0x59, 0x50, 0x5b, 0x2f, 0x5f, 0x73, + 0xde, 0x91, 0x58, 0x8f, 0x09, 0xb9, 0xbf, 0x60, 0x26, 0x6e, 0x02, 0xd7, 0xbf, 0xa1, 0xc3, 0x31, + 0xf3, 0xa9, 0x3b, 0x32, 0x8b, 0xc7, 0x99, 0x93, 0x1c, 0xd9, 0x4a, 0xf0, 0xe6, 0x98, 0xf9, 0xd6, + 0x08, 0x1d, 0x01, 0xa8, 0x33, 0x28, 0x77, 0x66, 0x49, 0xad, 0x58, 0x92, 0x88, 0xf2, 0x85, 0xce, + 0xa1, 0xac, 0x02, 0x4c, 0xc7, 0xae, 0x2f, 0x22, 0x13, 0x8e, 0xb3, 0x27, 0xe5, 0x73, 0xe3, 0xd4, + 0xf3, 0x65, 0xac, 0x89, 0xb4, 0x5c, 0xb8, 0xbe, 0x20, 0x69, 0x12, 0xc2, 0x50, 0x94, 0x91, 0xa5, + 0xc2, 0xbb, 0x33, 0xcb, 0x6a, 0xc2, 0x8b, 0xd3, 0x79, 0x96, 0x4e, 0x1f, 0xa6, 0xe5, 0xb4, 0xc5, + 0x23, 0xd1, 0xf7, 0xee, 0xb0, 0x2f, 0xc2, 0x7b, 0x52, 0x18, 0xe9, 0x51, 0xed, 0x7b, 0xa8, 0xa4, + 0x0d, 0x32, 0x51, 0xb7, 0xfc, 0x5e, 0xe5, 0x2e, 0x47, 0xe4, 0x27, 0xda, 0x83, 0xfc, 0x1d, 0xf3, + 0x66, 0x5c, 0x25, 0xaf, 0x42, 0xf4, 0xe0, 0xfb, 0x8d, 0x97, 0x99, 0xfa, 0x4b, 0xd8, 0xed, 0x87, + 0x6c, 0x78, 0xbb, 0x92, 0xff, 0xd5, 0xcc, 0x66, 0x1e, 0x64, 0xb6, 0xfe, 0x27, 0xa8, 0xc6, 0x93, + 0x7a, 0x82, 0x89, 0x59, 0x84, 0x7e, 0x09, 0xf9, 0x48, 0x30, 0xc1, 0x15, 0x79, 0xeb, 0xfc, 0x20, + 0x75, 0x94, 0x14, 0x91, 0x13, 0xcd, 0x42, 0x35, 0x28, 0x4e, 0x43, 0xee, 0x4e, 0xd8, 0x4d, 0xb2, + 0xad, 0xf9, 0x18, 0xd5, 0x21, 0xaf, 0x26, 0xab, 0x1b, 0x55, 0x3e, 0xaf, 0xa4, 0xc3, 0x48, 0xb4, + 0xa9, 0xfe, 0x1b, 0xd8, 0x56, 0xe3, 0x36, 0xe7, 0x1f, 0xbb, 0xb5, 0x07, 0x50, 0x60, 0x13, 0x9d, + 0x7e, 0x7d, 0x73, 0x37, 0xd9, 0x44, 0x66, 0xbe, 0x3e, 0x02, 0x63, 0x31, 0x3f, 0x9a, 0x06, 0x7e, + 0xc4, 0xe5, 0x6d, 0x90, 0xce, 0xe5, 0x65, 0x90, 0x37, 0x67, 0x22, 0x67, 0x65, 0xd4, 0xac, 0xad, + 0x18, 0x6f, 0x73, 0xde, 0x8d, 0x98, 0x40, 0xcf, 0xf5, 0x25, 0xa4, 0x5e, 0x30, 0xbc, 0x95, 0xd7, + 0x9a, 0xdd, 0xc7, 0xee, 0xab, 0x12, 0xee, 0x04, 0xc3, 0xdb, 0x96, 0x04, 0xeb, 0xbf, 0xd7, 0xe5, + 0xd5, 0x0f, 0xf4, 0xde, 0xff, 0xef, 0xf0, 0x2e, 0x42, 0xb0, 0xf1, 0x78, 0x08, 0x28, 0xec, 0x2e, + 0x39, 0x8f, 0x4f, 0x91, 0x8e, 0x6c, 0x66, 0x25, 0xb2, 0x5f, 0x43, 0xe1, 0x9a, 0xb9, 0xde, 0x2c, + 0x4c, 0x1c, 0xa3, 0x54, 0x9a, 0xda, 0xda, 0x42, 0x12, 0x4a, 0xfd, 0x1f, 0x05, 0x28, 0xc4, 0x20, + 0x3a, 0x87, 0xdc, 0x30, 0x18, 0x25, 0xd9, 0xfd, 0xf4, 0xe1, 0xb4, 0xe4, 0xb7, 0x19, 0x8c, 0x38, + 0x51, 0x5c, 0xf4, 0x5b, 0xd8, 0x92, 0x45, 0xe5, 0x73, 0x8f, 0xce, 0xa6, 0x23, 0x36, 0x4f, 0xa8, + 0x99, 0x9a, 0xdd, 0xd4, 0x84, 0x81, 0xb2, 0x93, 0xea, 0x30, 0x3d, 0x44, 0x87, 0x50, 0x1a, 0x0b, + 0x6f, 0xa8, 0x33, 0x91, 0x53, 0x17, 0xba, 0x28, 0x01, 0x95, 0x83, 0x3a, 0x54, 0x03, 0xdf, 0x0d, + 0x7c, 0x1a, 0x8d, 0x19, 0x3d, 0xff, 0xf6, 0x3b, 0xa5, 0x17, 0x15, 0x52, 0x56, 0x60, 0x6f, 0xcc, + 0xce, 0xbf, 0xfd, 0x0e, 0x7d, 0x06, 0x65, 0x55, 0xb5, 0xfc, 0xc3, 0xd4, 0x0d, 0xef, 0x95, 0x50, + 0x54, 0x89, 0x2a, 0x64, 0xac, 0x10, 0x59, 0x1a, 0xd7, 0x1e, 0xbb, 0x89, 0x94, 0x38, 0x54, 0x89, + 0x1e, 0xa0, 0x6f, 0x60, 0x2f, 0x8e, 0x01, 0x8d, 0x82, 0x59, 0x38, 0xe4, 0xd4, 0xf5, 0x47, 0xfc, + 0x83, 0x92, 0x86, 0x2a, 0x41, 0xb1, 0xad, 0xa7, 0x4c, 0x96, 0xb4, 0xa0, 0x7d, 0xd8, 0x1c, 0x73, + 0xf7, 0x66, 0xac, 0xa5, 0xa1, 0x4a, 0xe2, 0x51, 0xfd, 0x6f, 0x79, 0x28, 0xa7, 0x02, 0x83, 0x2a, + 0x50, 0x24, 0xb8, 0x87, 0xc9, 0x5b, 0xdc, 0x32, 0x3e, 0x41, 0x27, 0xf0, 0xa5, 0x65, 0x37, 0x1d, + 0x42, 0x70, 0xb3, 0x4f, 0x1d, 0x42, 0x07, 0xf6, 0x1b, 0xdb, 0xf9, 0xc9, 0xa6, 0x97, 0x8d, 0x77, + 0x5d, 0x6c, 0xf7, 0x69, 0x0b, 0xf7, 0x1b, 0x56, 0xa7, 0x67, 0x64, 0xd0, 0x33, 0x30, 0x17, 0xcc, + 0xc4, 0xdc, 0xe8, 0x3a, 0x03, 0xbb, 0x6f, 0x6c, 0xa0, 0xcf, 0xe0, 0xb0, 0x6d, 0xd9, 0x8d, 0x0e, + 0x5d, 0x70, 0x9a, 0x9d, 0xfe, 0x5b, 0x8a, 0x7f, 0xbe, 0xb4, 0xc8, 0x3b, 0x23, 0xbb, 0x8e, 0x70, + 0xd1, 0xef, 0x34, 0x13, 0x0f, 0x39, 0xf4, 0x14, 0x9e, 0x68, 0x82, 0x9e, 0x42, 0xfb, 0x8e, 0x43, + 0x7b, 0x8e, 0x63, 0x1b, 0x79, 0xb4, 0x03, 0x55, 0xcb, 0x7e, 0xdb, 0xe8, 0x58, 0x2d, 0x4a, 0x70, + 0xa3, 0xd3, 0x35, 0x36, 0xd1, 0x2e, 0x6c, 0xaf, 0xf2, 0x0a, 0xd2, 0x45, 0xc2, 0x73, 0x6c, 0xcb, + 0xb1, 0xe9, 0x5b, 0x4c, 0x7a, 0x96, 0x63, 0x1b, 0x45, 0xb4, 0x0f, 0x68, 0xd9, 0x74, 0xd1, 0x6d, + 0x34, 0x8d, 0x12, 0x7a, 0x02, 0x3b, 0xcb, 0xf8, 0x1b, 0xfc, 0xce, 0x00, 0x64, 0xc2, 0x9e, 0xde, + 0x18, 0x7d, 0x85, 0x3b, 0xce, 0x4f, 0xb4, 0x6b, 0xd9, 0x56, 0x77, 0xd0, 0x35, 0xca, 0x68, 0x0f, + 0x8c, 0x36, 0xc6, 0xd4, 0xb2, 0x7b, 0x83, 0x76, 0xdb, 0x6a, 0x5a, 0xd8, 0xee, 0x1b, 0x15, 0xbd, + 0xf2, 0xba, 0x83, 0x57, 0xe5, 0x84, 0xe6, 0x45, 0xc3, 0xb6, 0x71, 0x87, 0xb6, 0xac, 0x5e, 0xe3, + 0x55, 0x07, 0xb7, 0x8c, 0x2d, 0x74, 0x04, 0x4f, 0xfb, 0xb8, 0x7b, 0xe9, 0x90, 0x06, 0x79, 0x47, + 0x13, 0x7b, 0xbb, 0x61, 0x75, 0x06, 0x04, 0x1b, 0xdb, 0xe8, 0x73, 0x38, 0x22, 0xf8, 0xc7, 0x81, + 0x45, 0x70, 0x8b, 0xda, 0x4e, 0x0b, 0xd3, 0x36, 0x6e, 0xf4, 0x07, 0x04, 0xd3, 0xae, 0xd5, 0xeb, + 0x59, 0xf6, 0x0f, 0x86, 0x81, 0xbe, 0x84, 0xe3, 0x39, 0x65, 0xee, 0x60, 0x85, 0xb5, 0x23, 0xcf, + 0x97, 0xa4, 0xd4, 0xc6, 0x3f, 0xf7, 0xe9, 0x25, 0xc6, 0xc4, 0x40, 0xa8, 0x06, 0xfb, 0x8b, 0xe5, + 0xf5, 0x02, 0xf1, 0xda, 0xbb, 0xd2, 0x76, 0x89, 0x49, 0xb7, 0x61, 0xcb, 0x04, 0x2f, 0xd9, 0xf6, + 0xe4, 0xb6, 0x17, 0xb6, 0xd5, 0x6d, 0x3f, 0x41, 0x7b, 0xb0, 0x9d, 0xac, 0x96, 0x80, 0xff, 0x2c, + 0xa0, 0x03, 0x40, 0x03, 0x9b, 0xe0, 0x46, 0x4b, 0x1e, 0x7e, 0x6e, 0xf8, 0x57, 0xe1, 0x75, 0xae, + 0xb8, 0x61, 0x64, 0xeb, 0x7f, 0xcf, 0x42, 0x75, 0xa9, 0x06, 0xd1, 0x33, 0x28, 0x45, 0xee, 0x8d, + 0xcf, 0x84, 0x54, 0x09, 0x2d, 0x20, 0x0b, 0x40, 0xbd, 0x83, 0x63, 0xe6, 0xfa, 0x5a, 0xb9, 0xb4, + 0x72, 0x97, 0x14, 0xa2, 0x74, 0xeb, 0x00, 0x0a, 0xc9, 0x3b, 0x9a, 0x55, 0xf5, 0xba, 0x39, 0xd4, + 0xef, 0xe7, 0x33, 0x28, 0x49, 0x69, 0x8c, 0x04, 0x9b, 0x4c, 0x55, 0x29, 0x57, 0xc9, 0x02, 0x40, + 0x5f, 0x40, 0x75, 0xc2, 0xa3, 0x88, 0xdd, 0x70, 0xaa, 0xcb, 0x11, 0x14, 0xa3, 0x12, 0x83, 0x6d, + 0x55, 0x95, 0x5f, 0x40, 0x22, 0x0f, 0x31, 0x29, 0xaf, 0x49, 0x31, 0xa8, 0x49, 0xab, 0xca, 0x2c, + 0x58, 0x5c, 0xf5, 0x69, 0x65, 0x16, 0x0c, 0xbd, 0x80, 0x1d, 0x2d, 0x2d, 0xae, 0xef, 0x4e, 0x66, + 0x13, 0x2d, 0x31, 0x05, 0xb5, 0xe5, 0x6d, 0x25, 0x31, 0x1a, 0x57, 0x4a, 0xf3, 0x14, 0x8a, 0x57, + 0x2c, 0xe2, 0xf2, 0x51, 0x88, 0x25, 0xa0, 0x20, 0xc7, 0x6d, 0xce, 0xa5, 0x49, 0x3e, 0x15, 0xa1, + 0x14, 0x37, 0x5d, 0xf9, 0x85, 0x6b, 0xce, 0x89, 0x8c, 0xe3, 0x7c, 0x05, 0xf6, 0x61, 0xb1, 0x42, + 0x39, 0xb5, 0x82, 0xc6, 0xd5, 0x0a, 0x2f, 0x60, 0x87, 0x7f, 0x10, 0x21, 0xa3, 0xc1, 0x94, 0xbd, + 0x9f, 0x71, 0x3a, 0x62, 0x82, 0x99, 0x15, 0x15, 0xdc, 0x6d, 0x65, 0x70, 0x14, 0xde, 0x62, 0x82, + 0xd5, 0x9f, 0x41, 0x8d, 0xf0, 0x88, 0x8b, 0xae, 0x1b, 0x45, 0x6e, 0xe0, 0x37, 0x03, 0x5f, 0x84, + 0x81, 0x17, 0xbf, 0x2d, 0xf5, 0x23, 0x38, 0x5c, 0x6b, 0xd5, 0x8f, 0x83, 0x9c, 0xfc, 0xe3, 0x8c, + 0x87, 0xf7, 0xeb, 0x27, 0xdf, 0xc3, 0xe1, 0x5a, 0x6b, 0xfc, 0xb2, 0x7c, 0x0d, 0x79, 0x3f, 0x18, + 0xf1, 0xc8, 0xcc, 0xa8, 0x6e, 0x65, 0x3f, 0x25, 0xe3, 0x76, 0x30, 0xe2, 0x17, 0x6e, 0x24, 0x82, + 0xf0, 0x9e, 0x68, 0x92, 0x64, 0x4f, 0x99, 0x1b, 0x46, 0xe6, 0xc6, 0x03, 0xf6, 0x25, 0x73, 0xc3, + 0x39, 0x5b, 0x91, 0xea, 0x7f, 0xce, 0x40, 0x39, 0xe5, 0x44, 0x0a, 0xea, 0x74, 0x76, 0x95, 0x34, + 0x32, 0x15, 0x12, 0x8f, 0xd0, 0x73, 0xd8, 0xf2, 0x58, 0x24, 0xa8, 0xd4, 0x60, 0x2a, 0x53, 0x1a, + 0x3f, 0xbc, 0x2b, 0x28, 0x3a, 0x05, 0x14, 0x88, 0x31, 0x0f, 0x69, 0x34, 0x1b, 0x0e, 0x79, 0x14, + 0xd1, 0x69, 0x18, 0x5c, 0xa9, 0x3b, 0xb9, 0x41, 0xd6, 0x58, 0x5e, 0xe7, 0x8a, 0x39, 0x23, 0x5f, + 0xff, 0x77, 0x06, 0xca, 0xa9, 0xcd, 0xc9, 0x5b, 0x2b, 0x0f, 0x43, 0xaf, 0xc3, 0x60, 0x92, 0xd4, + 0xc2, 0x1c, 0x40, 0x26, 0x14, 0xd4, 0x40, 0x04, 0x71, 0x21, 0x24, 0xc3, 0xe5, 0xdb, 0x9e, 0x55, + 0x1b, 0x4c, 0xdd, 0xf6, 0x73, 0xd8, 0x9b, 0xb8, 0x3e, 0x9d, 0x72, 0x9f, 0x79, 0xee, 0x1f, 0x39, + 0x4d, 0x3a, 0x94, 0x9c, 0x22, 0xae, 0xb5, 0xa1, 0x3a, 0x54, 0x96, 0x4e, 0x92, 0x57, 0x27, 0x59, + 0xc2, 0xd0, 0x4b, 0x38, 0x50, 0x51, 0x60, 0x42, 0xf0, 0xc9, 0x54, 0x24, 0x07, 0xbc, 0x9e, 0x79, + 0xaa, 0x06, 0x8a, 0xe4, 0x31, 0xf3, 0x8b, 0xbf, 0x66, 0xa0, 0x92, 0xee, 0xd2, 0x50, 0x15, 0x4a, + 0x96, 0x4d, 0xdb, 0x1d, 0xeb, 0x87, 0x8b, 0xbe, 0xf1, 0x89, 0x1c, 0xf6, 0x06, 0xcd, 0x26, 0xc6, + 0x2d, 0xdc, 0x32, 0x32, 0x08, 0xc1, 0x96, 0x14, 0x12, 0xdc, 0xa2, 0x7d, 0xab, 0x8b, 0x9d, 0x81, + 0x7c, 0x83, 0x76, 0x61, 0x3b, 0xc6, 0x6c, 0x87, 0x12, 0x67, 0xd0, 0xc7, 0x46, 0x16, 0x19, 0x50, + 0x89, 0x41, 0x4c, 0x88, 0x43, 0x8c, 0x9c, 0x14, 0xce, 0x18, 0x79, 0xf8, 0x9e, 0x25, 0xcf, 0x5d, + 0xfe, 0xfc, 0x2f, 0x39, 0xd8, 0x54, 0x5d, 0x4d, 0x88, 0x2e, 0xa0, 0x9c, 0x6a, 0x85, 0xd1, 0xd1, + 0x47, 0x5b, 0xe4, 0x9a, 0xb9, 0xbe, 0xed, 0x9c, 0x45, 0xdf, 0x64, 0xd0, 0x6b, 0xa8, 0xa4, 0x9b, + 0x5d, 0x94, 0x6e, 0x62, 0xd6, 0x74, 0xc1, 0x1f, 0xf5, 0xf5, 0x06, 0x0c, 0x1c, 0x09, 0x77, 0x22, + 0x9b, 0x96, 0xb8, 0x8d, 0x44, 0xb5, 0x14, 0x7f, 0xa5, 0x37, 0xad, 0x1d, 0xae, 0xb5, 0xc5, 0x75, + 0xd5, 0xd1, 0x47, 0x8c, 0x1b, 0xb9, 0x07, 0x47, 0x5c, 0xee, 0x1e, 0x6b, 0x9f, 0x3e, 0x66, 0x8e, + 0xbd, 0x8d, 0x60, 0x77, 0x8d, 0x02, 0xa0, 0x5f, 0xa4, 0x77, 0xf0, 0xa8, 0x7e, 0xd4, 0x9e, 0xff, + 0x2f, 0xda, 0x62, 0x95, 0x35, 0x52, 0xb1, 0xb4, 0xca, 0xe3, 0x42, 0xb3, 0xb4, 0xca, 0x47, 0x14, + 0xe7, 0xd5, 0xaf, 0x7e, 0x77, 0x76, 0xe3, 0x8a, 0xf1, 0xec, 0xea, 0x74, 0x18, 0x4c, 0xce, 0x3c, + 0xd9, 0x52, 0xf9, 0xae, 0x7f, 0xe3, 0x73, 0xf1, 0x87, 0x20, 0xbc, 0x3d, 0xf3, 0xfc, 0xd1, 0x99, + 0x6a, 0x8c, 0xcf, 0xe6, 0xee, 0xae, 0x36, 0xd5, 0x3f, 0xdb, 0x5f, 0xff, 0x37, 0x00, 0x00, 0xff, + 0xff, 0x3c, 0xe4, 0x5c, 0x67, 0x09, 0x0f, 0x00, 0x00, } // Reference imports to suppress errors if they are not otherwise used. diff --git a/lnrpc/routerrpc/router.proto b/lnrpc/routerrpc/router.proto index d09b6867..7299a02b 100644 --- a/lnrpc/routerrpc/router.proto +++ b/lnrpc/routerrpc/router.proto @@ -239,6 +239,9 @@ message Failure { the failure message. Position zero is the sender node. **/ uint32 failure_source_index = 8; + + /// A failure type-dependent block height. + uint32 height = 9; } diff --git a/lnrpc/routerrpc/router_server.go b/lnrpc/routerrpc/router_server.go index f2fe0c68..cea63be9 100644 --- a/lnrpc/routerrpc/router_server.go +++ b/lnrpc/routerrpc/router_server.go @@ -324,6 +324,7 @@ func marshallError(sendError error) (*Failure, error) { case *lnwire.FailIncorrectDetails: response.Code = Failure_INCORRECT_OR_UNKNOWN_PAYMENT_DETAILS + response.Height = onionErr.Height() case *lnwire.FailIncorrectPaymentAmount: response.Code = Failure_INCORRECT_PAYMENT_AMOUNT diff --git a/lnwire/onion_error.go b/lnwire/onion_error.go index 51f3bc58..43a3d326 100644 --- a/lnwire/onion_error.go +++ b/lnwire/onion_error.go @@ -333,13 +333,19 @@ func (f *FailIncorrectPaymentAmount) Error() string { type FailIncorrectDetails struct { // amount is the value of the extended HTLC. amount MilliSatoshi + + // height is the block height when the htlc was received. + height uint32 } // NewFailIncorrectDetails makes a new instance of the FailIncorrectDetails -// error bound to the specified HTLC amount. -func NewFailIncorrectDetails(amt MilliSatoshi) *FailIncorrectDetails { +// error bound to the specified HTLC amount and acceptance height. +func NewFailIncorrectDetails(amt MilliSatoshi, + height uint32) *FailIncorrectDetails { + return &FailIncorrectDetails{ amount: amt, + height: height, } } @@ -348,6 +354,11 @@ func (f *FailIncorrectDetails) Amount() MilliSatoshi { return f.amount } +// Height is the block height when the htlc was received. +func (f *FailIncorrectDetails) Height() uint32 { + return f.height +} + // Code returns the failure unique code. // // NOTE: Part of the FailureMessage interface. @@ -360,7 +371,8 @@ func (f *FailIncorrectDetails) Code() FailCode { // NOTE: Implements the error interface. func (f *FailIncorrectDetails) Error() string { return fmt.Sprintf( - "%v(amt=%v)", CodeIncorrectOrUnknownPaymentDetails, f.amount, + "%v(amt=%v, height=%v)", CodeIncorrectOrUnknownPaymentDetails, + f.amount, f.height, ) } @@ -381,6 +393,17 @@ func (f *FailIncorrectDetails) Decode(r io.Reader, pver uint32) error { return err } + // At a later stage, the height field was also tacked on. We need to + // check for io.EOF here as well. + err = ReadElement(r, &f.height) + switch { + case err == io.EOF: + return nil + + case err != nil: + return err + } + return nil } @@ -388,7 +411,7 @@ func (f *FailIncorrectDetails) Decode(r io.Reader, pver uint32) error { // // NOTE: Part of the Serializable interface. func (f *FailIncorrectDetails) Encode(w io.Writer, pver uint32) error { - return WriteElement(w, f.amount) + return WriteElements(w, f.amount, f.height) } // FailFinalExpiryTooSoon is returned if the cltv_expiry is too low, the final diff --git a/lnwire/onion_error_test.go b/lnwire/onion_error_test.go index a29d3ee2..8420be1f 100644 --- a/lnwire/onion_error_test.go +++ b/lnwire/onion_error_test.go @@ -37,7 +37,7 @@ var onionFailures = []FailureMessage{ &FailIncorrectPaymentAmount{}, &FailFinalExpiryTooSoon{}, - NewFailIncorrectDetails(99), + NewFailIncorrectDetails(99, 100), NewInvalidOnionVersion(testOnionHash), NewInvalidOnionHmac(testOnionHash), NewInvalidOnionKey(testOnionHash), @@ -196,6 +196,9 @@ func TestFailIncorrectDetailsOptionalAmount(t *testing.T) { if invalidDetailsErr.amount != 0 { t.Fatalf("expected amount to be zero") } + if invalidDetailsErr.height != 0 { + t.Fatalf("height incorrect") + } } type mockFailIncorrectDetailsNoAmt struct { @@ -216,3 +219,57 @@ func (f *mockFailIncorrectDetailsNoAmt) Decode(r io.Reader, pver uint32) error { func (f *mockFailIncorrectDetailsNoAmt) Encode(w io.Writer, pver uint32) error { return nil } + +// TestFailIncorrectDetailsOptionalHeight tests that we're able to decode an +// FailIncorrectDetails error that doesn't have the optional height. This +// ensures we're able to decode FailIncorrectDetails messages from older nodes. +func TestFailIncorrectDetailsOptionalHeight(t *testing.T) { + t.Parallel() + + onionError := &mockFailIncorrectDetailsNoHeight{ + amount: uint64(123), + } + + var b bytes.Buffer + if err := EncodeFailure(&b, onionError, 0); err != nil { + t.Fatalf("unable to encode failure: %v", err) + } + + onionError2, err := DecodeFailure(bytes.NewReader(b.Bytes()), 0) + if err != nil { + t.Fatalf("unable to decode error: %v", err) + } + + invalidDetailsErr, ok := onionError2.(*FailIncorrectDetails) + if !ok { + t.Fatalf("expected FailIncorrectDetails, but got %T", + onionError2) + } + + if invalidDetailsErr.amount != 123 { + t.Fatalf("amount incorrect") + } + if invalidDetailsErr.height != 0 { + t.Fatalf("height incorrect") + } +} + +type mockFailIncorrectDetailsNoHeight struct { + amount uint64 +} + +func (f *mockFailIncorrectDetailsNoHeight) Code() FailCode { + return CodeIncorrectOrUnknownPaymentDetails +} + +func (f *mockFailIncorrectDetailsNoHeight) Error() string { + return "" +} + +func (f *mockFailIncorrectDetailsNoHeight) Decode(r io.Reader, pver uint32) error { + return nil +} + +func (f *mockFailIncorrectDetailsNoHeight) Encode(w io.Writer, pver uint32) error { + return WriteElement(w, f.amount) +} diff --git a/routing/missioncontrol_store_test.go b/routing/missioncontrol_store_test.go index 86111d0b..9d070f4d 100644 --- a/routing/missioncontrol_store_test.go +++ b/routing/missioncontrol_store_test.go @@ -61,7 +61,7 @@ func TestMissionControlStore(t *testing.T) { result1 := paymentResult{ route: &testRoute, - failure: lnwire.NewFailIncorrectDetails(100), + failure: lnwire.NewFailIncorrectDetails(100, 1000), failureSourceIdx: &failureSourceIdx, id: 99, timeReply: testTime, diff --git a/routing/result_interpretation_test.go b/routing/result_interpretation_test.go index 7d95ad64..8c4498b0 100644 --- a/routing/result_interpretation_test.go +++ b/routing/result_interpretation_test.go @@ -103,7 +103,7 @@ var resultTestCases = []resultTestCase{ name: "fail incorrect details", route: &routeTwoHop, failureSrcIdx: 2, - failure: lnwire.NewFailIncorrectDetails(97), + failure: lnwire.NewFailIncorrectDetails(97, 0), expectedResult: &interpretedResult{ pairResults: map[DirectedNodePair]pairResult{