Merge pull request #3414 from joostjager/report-accept-height

lnwire+htlcswitch: report htlc accept height
This commit is contained in:
Joost Jager 2019-09-16 13:49:29 +02:00 committed by GitHub
commit 95502da7e8
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
13 changed files with 486 additions and 232 deletions

@ -272,7 +272,7 @@ type InvoiceHTLC struct {
// State indicates the state the invoice htlc is currently in. A // State indicates the state the invoice htlc is currently in. A
// cancelled htlc isn't just removed from the invoice htlcs map, because // 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 State HtlcState
} }
@ -1224,9 +1224,9 @@ func (d *DB) updateInvoice(hash lntypes.Hash, invoices, settleIndex *bbolt.Bucke
if !ok { if !ok {
return nil, fmt.Errorf("unknown htlc %v", key) return nil, fmt.Errorf("unknown htlc %v", key)
} }
if htlc.State == HtlcStateSettled { if htlc.State != HtlcStateAccepted {
return nil, fmt.Errorf("cannot cancel a " + return nil, fmt.Errorf("can only cancel " +
"settled htlc") "accepted htlcs")
} }
htlc.State = HtlcStateCancelled htlc.State = HtlcStateCancelled

@ -18,8 +18,9 @@ const (
) )
var ( var (
testResPreimage = lntypes.Preimage{1, 2, 3} testResPreimage = lntypes.Preimage{1, 2, 3}
testResHash = testResPreimage.Hash() testResHash = testResPreimage.Hash()
testResCircuitKey = channeldb.CircuitKey{}
) )
// TestHtlcIncomingResolverFwdPreimageKnown tests resolution of a forwarded htlc // TestHtlcIncomingResolverFwdPreimageKnown tests resolution of a forwarded htlc
@ -92,8 +93,8 @@ func TestHtlcIncomingResolverExitSettle(t *testing.T) {
ctx := newIncomingResolverTestContext(t) ctx := newIncomingResolverTestContext(t)
ctx.registry.notifyEvent = &invoices.HodlEvent{ ctx.registry.notifyEvent = &invoices.HodlEvent{
Hash: testResHash, CircuitKey: testResCircuitKey,
Preimage: &testResPreimage, Preimage: &testResPreimage,
} }
ctx.resolve() ctx.resolve()
@ -116,7 +117,7 @@ func TestHtlcIncomingResolverExitCancel(t *testing.T) {
ctx := newIncomingResolverTestContext(t) ctx := newIncomingResolverTestContext(t)
ctx.registry.notifyEvent = &invoices.HodlEvent{ ctx.registry.notifyEvent = &invoices.HodlEvent{
Hash: testResHash, CircuitKey: testResCircuitKey,
} }
ctx.resolve() ctx.resolve()
ctx.waitForResult(false) ctx.waitForResult(false)
@ -133,8 +134,8 @@ func TestHtlcIncomingResolverExitSettleHodl(t *testing.T) {
notifyData := <-ctx.registry.notifyChan notifyData := <-ctx.registry.notifyChan
notifyData.hodlChan <- invoices.HodlEvent{ notifyData.hodlChan <- invoices.HodlEvent{
Hash: testResHash, CircuitKey: testResCircuitKey,
Preimage: &testResPreimage, Preimage: &testResPreimage,
} }
ctx.waitForResult(true) ctx.waitForResult(true)
@ -162,7 +163,7 @@ func TestHtlcIncomingResolverExitCancelHodl(t *testing.T) {
ctx.resolve() ctx.resolve()
notifyData := <-ctx.registry.notifyChan notifyData := <-ctx.registry.notifyChan
notifyData.hodlChan <- invoices.HodlEvent{ notifyData.hodlChan <- invoices.HodlEvent{
Hash: testResHash, CircuitKey: testResCircuitKey,
} }
ctx.waitForResult(false) ctx.waitForResult(false)
} }

@ -364,9 +364,9 @@ type channelLink struct {
// registry. // registry.
hodlQueue *queue.ConcurrentQueue 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. // resolving those htlcs when we receive a message on hodlQueue.
hodlMap map[lntypes.Hash][]hodlHtlc hodlMap map[channeldb.CircuitKey]hodlHtlc
wg sync.WaitGroup wg sync.WaitGroup
quit chan struct{} quit chan struct{}
@ -391,7 +391,7 @@ func NewChannelLink(cfg ChannelLinkConfig,
logCommitTimer: time.NewTimer(300 * time.Millisecond), logCommitTimer: time.NewTimer(300 * time.Millisecond),
overflowQueue: newPacketQueue(input.MaxHTLCNumber / 2), overflowQueue: newPacketQueue(input.MaxHTLCNumber / 2),
htlcUpdates: make(chan *contractcourt.ContractUpdate), htlcUpdates: make(chan *contractcourt.ContractUpdate),
hodlMap: make(map[lntypes.Hash][]hodlHtlc), hodlMap: make(map[channeldb.CircuitKey]hodlHtlc),
hodlQueue: queue.NewConcurrentQueue(10), hodlQueue: queue.NewConcurrentQueue(10),
quit: make(chan struct{}), quit: make(chan struct{}),
} }
@ -1151,10 +1151,21 @@ func (l *channelLink) processHodlQueue(firstHodlEvent invoices.HodlEvent) error
hodlEvent := firstHodlEvent hodlEvent := firstHodlEvent
loop: loop:
for { 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 return err
} }
// Clean up hodl map.
delete(l.hodlMap, circuitKey)
select { select {
case item := <-l.hodlQueue.ChanOut(): case item := <-l.hodlQueue.ChanOut():
hodlEvent = item.(invoices.HodlEvent) hodlEvent = item.(invoices.HodlEvent)
@ -1171,73 +1182,37 @@ loop:
return nil 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 // processHodlEvent applies a received hodl event to the provided htlc. When
// this function returns without an error, the commit tx should be updated. // this function returns without an error, the commit tx should be updated.
func (l *channelLink) processHodlEvent(hodlEvent invoices.HodlEvent, 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. // Determine required action for the resolution.
var hodlAction func(htlc hodlHtlc) error
if hodlEvent.Preimage != nil { 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(
return l.settleHTLC( *hodlEvent.Preimage, htlc.pd.HtlcIndex,
*hodlEvent.Preimage, htlc.pd.HtlcIndex, htlc.pd.SourceRef,
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
}
} }
// Apply action for all htlcs matching this hash. l.debugf("Received hodl cancel event for %v", circuitKey)
for _, htlc := range htlcs {
if err := hodlAction(htlc); err != nil {
return err
}
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, uint32(hodlEvent.AcceptHeight),
)
l.sendHTLCError(
htlc.pd.HtlcIndex, failure, htlc.obfuscator,
htlc.pd.SourceRef,
)
return nil return nil
} }
@ -2892,7 +2867,7 @@ func (l *channelLink) processExitHop(pd *lnwallet.PaymentDescriptor,
// Cancel htlc if we don't have an invoice for it. // Cancel htlc if we don't have an invoice for it.
case channeldb.ErrInvoiceNotFound: case channeldb.ErrInvoiceNotFound:
failure := lnwire.NewFailIncorrectDetails(pd.Amount) failure := lnwire.NewFailIncorrectDetails(pd.Amount, heightNow)
l.sendHTLCError(pd.HtlcIndex, failure, obfuscator, pd.SourceRef) l.sendHTLCError(pd.HtlcIndex, failure, obfuscator, pd.SourceRef)
return true, nil return true, nil
@ -2913,8 +2888,7 @@ func (l *channelLink) processExitHop(pd *lnwallet.PaymentDescriptor,
if event == nil { if event == nil {
// Save payment descriptor for future reference. // Save payment descriptor for future reference.
hodlHtlcs := l.hodlMap[invoiceHash] l.hodlMap[circuitKey] = htlc
l.hodlMap[invoiceHash] = append(hodlHtlcs, htlc)
return false, nil return false, nil
} }

@ -1803,7 +1803,7 @@ func TestSwitchSendPayment(t *testing.T) {
// the add htlc request with error and sent the htlc fail request // the add htlc request with error and sent the htlc fail request
// back. This request should be forwarded back to alice channel link. // back. This request should be forwarded back to alice channel link.
obfuscator := NewMockObfuscator() obfuscator := NewMockObfuscator()
failure := lnwire.NewFailIncorrectDetails(update.Amount) failure := lnwire.NewFailIncorrectDetails(update.Amount, 100)
reason, err := obfuscator.EncryptFirstHop(failure) reason, err := obfuscator.EncryptFirstHop(failure)
if err != nil { if err != nil {
t.Fatalf("unable obfuscate failure: %v", err) t.Fatalf("unable obfuscate failure: %v", err)

@ -36,8 +36,12 @@ type HodlEvent 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
// Hash is the htlc hash. // CircuitKey is the key of the htlc for which we have a resolution
Hash lntypes.Hash // 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 // InvoiceRegistry is a central registry of all the outstanding invoices
@ -60,13 +64,13 @@ type InvoiceRegistry struct {
// new single invoice subscriptions are carried. // new single invoice subscriptions are carried.
invoiceEvents chan interface{} 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. // 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 // reverseSubscriptions tracks circuit keys subscribed to per
// is used to unsubscribe from all hashes efficiently. // subscriber. This is used to unsubscribe from all hashes efficiently.
hodlReverseSubscriptions map[chan<- interface{}]map[lntypes.Hash]struct{} hodlReverseSubscriptions map[chan<- interface{}]map[channeldb.CircuitKey]struct{}
// finalCltvRejectDelta defines the number of blocks before the expiry // finalCltvRejectDelta defines the number of blocks before the expiry
// of the htlc where we no longer settle it as an exit hop and instead // of the htlc where we no longer settle it as an exit hop and instead
@ -92,8 +96,8 @@ func NewRegistry(cdb *channeldb.DB, finalCltvRejectDelta int32) *InvoiceRegistry
newSubscriptions: make(chan *InvoiceSubscription), newSubscriptions: make(chan *InvoiceSubscription),
subscriptionCancels: make(chan uint32), subscriptionCancels: make(chan uint32),
invoiceEvents: make(chan interface{}, 100), invoiceEvents: make(chan interface{}, 100),
hodlSubscriptions: make(map[lntypes.Hash]map[chan<- interface{}]struct{}), hodlSubscriptions: make(map[channeldb.CircuitKey]map[chan<- interface{}]struct{}),
hodlReverseSubscriptions: make(map[chan<- interface{}]map[lntypes.Hash]struct{}), hodlReverseSubscriptions: make(map[chan<- interface{}]map[channeldb.CircuitKey]struct{}),
finalCltvRejectDelta: finalCltvRejectDelta, finalCltvRejectDelta: finalCltvRejectDelta,
quit: make(chan struct{}), quit: make(chan struct{}),
} }
@ -551,24 +555,33 @@ 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 &HodlEvent{
Hash: rHash, CircuitKey: circuitKey,
AcceptHeight: currentHeight,
}, nil }, 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 { switch invoiceHtlc.State {
case channeldb.HtlcStateCancelled: case channeldb.HtlcStateCancelled:
return &HodlEvent{ return &HodlEvent{
Hash: rHash, CircuitKey: circuitKey,
AcceptHeight: acceptHeight,
}, nil }, nil
case channeldb.HtlcStateSettled: case channeldb.HtlcStateSettled:
return &HodlEvent{ return &HodlEvent{
Hash: rHash, CircuitKey: circuitKey,
Preimage: &invoice.Terms.PaymentPreimage, Preimage: &invoice.Terms.PaymentPreimage,
AcceptHeight: acceptHeight,
}, nil }, nil
case channeldb.HtlcStateAccepted: case channeldb.HtlcStateAccepted:
i.hodlSubscribe(hodlChan, rHash) i.hodlSubscribe(hodlChan, circuitKey)
return nil, nil return nil, nil
default: default:
@ -609,10 +622,23 @@ func (i *InvoiceRegistry) SettleHodlInvoice(preimage lntypes.Preimage) error {
log.Debugf("Invoice(%v): settled with preimage %v", hash, log.Debugf("Invoice(%v): settled with preimage %v", hash,
invoice.Terms.PaymentPreimage) invoice.Terms.PaymentPreimage)
i.notifyHodlSubscribers(HodlEvent{ // In the callback, we marked the invoice as settled. UpdateInvoice will
Hash: hash, // have seen this and should have moved all htlcs that were accepted to
Preimage: &preimage, // 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,
AcceptHeight: int32(htlc.AcceptHeight),
})
}
i.notifyClients(hash, invoice, invoice.Terms.State) i.notifyClients(hash, invoice, invoice.Terms.State)
return nil return nil
@ -640,7 +666,21 @@ func (i *InvoiceRegistry) CancelInvoice(payHash lntypes.Hash) error {
canceledHtlcs := make( canceledHtlcs := make(
map[channeldb.CircuitKey]*channeldb.HtlcAcceptDesc, 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 canceledHtlcs[key] = nil
} }
@ -664,9 +704,22 @@ func (i *InvoiceRegistry) CancelInvoice(payHash lntypes.Hash) error {
} }
log.Debugf("Invoice(%v): canceled", payHash) 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,
AcceptHeight: int32(htlc.AcceptHeight),
})
}
i.notifyClients(payHash, invoice, channeldb.ContractCanceled) i.notifyClients(payHash, invoice, channeldb.ContractCanceled)
return nil return nil
@ -933,7 +986,7 @@ func (i *InvoiceRegistry) SubscribeSingleInvoice(
// notifyHodlSubscribers sends out the hodl event to all current subscribers. // notifyHodlSubscribers sends out the hodl event to all current subscribers.
func (i *InvoiceRegistry) notifyHodlSubscribers(hodlEvent HodlEvent) { func (i *InvoiceRegistry) notifyHodlSubscribers(hodlEvent HodlEvent) {
subscribers, ok := i.hodlSubscriptions[hodlEvent.Hash] subscribers, ok := i.hodlSubscriptions[hodlEvent.CircuitKey]
if !ok { if !ok {
return return
} }
@ -948,31 +1001,34 @@ func (i *InvoiceRegistry) notifyHodlSubscribers(hodlEvent HodlEvent) {
return 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. // hodlSubscribe adds a new invoice subscription.
func (i *InvoiceRegistry) hodlSubscribe(subscriber chan<- interface{}, 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 { if !ok {
subscriptions = make(map[chan<- interface{}]struct{}) subscriptions = make(map[chan<- interface{}]struct{})
i.hodlSubscriptions[hash] = subscriptions i.hodlSubscriptions[circuitKey] = subscriptions
} }
subscriptions[subscriber] = struct{}{} subscriptions[subscriber] = struct{}{}
reverseSubscriptions, ok := i.hodlReverseSubscriptions[subscriber] reverseSubscriptions, ok := i.hodlReverseSubscriptions[subscriber]
if !ok { if !ok {
reverseSubscriptions = make(map[lntypes.Hash]struct{}) reverseSubscriptions = make(map[channeldb.CircuitKey]struct{})
i.hodlReverseSubscriptions[subscriber] = reverseSubscriptions i.hodlReverseSubscriptions[subscriber] = reverseSubscriptions
} }
reverseSubscriptions[hash] = struct{}{} reverseSubscriptions[circuitKey] = struct{}{}
} }
// HodlUnsubscribeAll cancels the subscription. // HodlUnsubscribeAll cancels the subscription.

@ -23,6 +23,8 @@ var (
testHtlcExpiry = uint32(5) testHtlcExpiry = uint32(5)
testInvoiceCltvDelta = uint32(4)
testFinalCltvRejectDelta = int32(4) testFinalCltvRejectDelta = int32(4)
testCurrentHeight = int32(1) testCurrentHeight = int32(1)
@ -121,6 +123,23 @@ 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.
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. // Settle invoice with a slightly higher amount.
amtPaid := lnwire.MilliSatoshi(100500) amtPaid := lnwire.MilliSatoshi(100500)
_, err = registry.NotifyExitHopHtlc( _, 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 // Try to settle again with the same htlc id. We need this idempotent
// behaviour after a restart. // behaviour after a restart.
event, err := registry.NotifyExitHopHtlc( event, err = registry.NotifyExitHopHtlc(
hash, amtPaid, testHtlcExpiry, testCurrentHeight, hash, amtPaid, testHtlcExpiry, testCurrentHeight,
getCircuitKey(0), hodlChan, nil, 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 // Notify arrival of a new htlc paying to this invoice. This should
// succeed. // result in a cancel event.
hodlChan := make(chan interface{}) hodlChan := make(chan interface{})
event, err := registry.NotifyExitHopHtlc( event, err := registry.NotifyExitHopHtlc(
hash, amt, testHtlcExpiry, testCurrentHeight, hash, amt, testHtlcExpiry, testCurrentHeight,
@ -322,10 +341,15 @@ func TestCancelInvoice(t *testing.T) {
if event.Preimage != nil { if event.Preimage != nil {
t.Fatal("expected cancel hodl event") 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. // TestSettleHoldInvoice tests settling of a hold invoice and related
func TestHoldInvoice(t *testing.T) { // notifications.
func TestSettleHoldInvoice(t *testing.T) {
defer timeout(t)() defer timeout(t)()
cdb, cleanup, err := newDB() cdb, cleanup, err := newDB()
@ -462,6 +486,10 @@ func TestHoldInvoice(t *testing.T) {
if *hodlEvent.Preimage != preimage { if *hodlEvent.Preimage != preimage {
t.Fatal("unexpected preimage in hodl event") 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 // We expect a settled notification to be sent out for both all and
// single invoice subscribers. // 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) { func newDB() (*channeldb.DB, func(), error) {
// First, create a temporary directory to be used for the duration of // First, create a temporary directory to be used for the duration of
// this test. // this test.

@ -646,7 +646,9 @@ type Failure struct {
//* //*
//The position in the path of the intermediate or final node that generated //The position in the path of the intermediate or final node that generated
//the failure message. Position zero is the sender node. //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_NoUnkeyedLiteral struct{} `json:"-"`
XXX_unrecognized []byte `json:"-"` XXX_unrecognized []byte `json:"-"`
XXX_sizecache int32 `json:"-"` XXX_sizecache int32 `json:"-"`
@ -726,6 +728,13 @@ func (m *Failure) GetFailureSourceIndex() uint32 {
return 0 return 0
} }
func (m *Failure) GetHeight() uint32 {
if m != nil {
return m.Height
}
return 0
}
type ChannelUpdate struct { type ChannelUpdate struct {
//* //*
//The signature that validates the announced data and proves the ownership //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) } func init() { proto.RegisterFile("routerrpc/router.proto", fileDescriptor_7a0613f69d37b0a5) }
var fileDescriptor_7a0613f69d37b0a5 = []byte{ 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, 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, 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, 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, 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, 0x67, 0x9c, 0x43, 0x2e, 0xa9, 0x1c, 0x73, 0xcf, 0xbf, 0xc8, 0x6f, 0xca, 0x25, 0xf9, 0x05, 0x39,
0x8e, 0xa9, 0x4a, 0x49, 0xea, 0x86, 0x06, 0xe3, 0x49, 0x4e, 0xb4, 0xbe, 0xf7, 0xe9, 0x49, 0x7a, 0xa6, 0x2a, 0x25, 0xa9, 0x1b, 0x1a, 0x8c, 0x27, 0x39, 0xd1, 0xfa, 0xde, 0xa7, 0x27, 0xe9, 0x3d,
0x4f, 0xef, 0xd3, 0x03, 0xf6, 0xc3, 0x60, 0x26, 0x78, 0x18, 0x4e, 0x87, 0x67, 0xfa, 0xeb, 0x74, 0xbd, 0x4f, 0x0f, 0xd8, 0x0f, 0x83, 0x99, 0xe0, 0x61, 0x38, 0x1d, 0x9e, 0xe9, 0xaf, 0xd3, 0x69,
0x1a, 0x06, 0x22, 0x40, 0xa5, 0x39, 0x5e, 0x2b, 0x85, 0xd3, 0xa1, 0x46, 0xeb, 0xff, 0xc9, 0x02, 0x18, 0x88, 0x00, 0x95, 0xe6, 0x78, 0xad, 0x14, 0x4e, 0x87, 0x1a, 0xad, 0xff, 0x27, 0x0b, 0xa8,
0xea, 0x71, 0x7f, 0x74, 0xc9, 0xee, 0x27, 0xdc, 0x17, 0x84, 0xbf, 0x9f, 0xf1, 0x48, 0x20, 0x04, 0xc7, 0xfd, 0xd1, 0x25, 0xbb, 0x9f, 0x70, 0x5f, 0x10, 0xfe, 0x7e, 0xc6, 0x23, 0x81, 0x10, 0xe4,
0xb9, 0x11, 0x8f, 0x84, 0x99, 0x39, 0xce, 0x9c, 0x54, 0x88, 0xfa, 0x46, 0x06, 0x64, 0xd9, 0x44, 0x46, 0x3c, 0x12, 0x66, 0xe6, 0x38, 0x73, 0x52, 0x21, 0xea, 0x1b, 0x19, 0x90, 0x65, 0x13, 0x61,
0x98, 0x1b, 0xc7, 0x99, 0x93, 0x2c, 0x91, 0x9f, 0xe8, 0x73, 0xa8, 0x4c, 0xf5, 0x3c, 0x3a, 0x66, 0x6e, 0x1c, 0x67, 0x4e, 0xb2, 0x44, 0x7e, 0xa2, 0xcf, 0xa1, 0x32, 0xd5, 0xf3, 0xe8, 0x98, 0x45,
0xd1, 0xd8, 0xcc, 0x2a, 0x76, 0x39, 0xc6, 0x2e, 0x58, 0x34, 0x46, 0x27, 0x60, 0x5c, 0xbb, 0x3e, 0x63, 0x33, 0xab, 0xd8, 0xe5, 0x18, 0xbb, 0x60, 0xd1, 0x18, 0x9d, 0x80, 0x71, 0xed, 0xfa, 0xcc,
0xf3, 0xe8, 0xd0, 0x13, 0x77, 0x74, 0xc4, 0x3d, 0xc1, 0xcc, 0xdc, 0x71, 0xe6, 0x24, 0x4f, 0xb6, 0xa3, 0x43, 0x4f, 0xdc, 0xd1, 0x11, 0xf7, 0x04, 0x33, 0x73, 0xc7, 0x99, 0x93, 0x3c, 0xd9, 0x52,
0x14, 0xde, 0xf4, 0xc4, 0x5d, 0x4b, 0xa2, 0xe8, 0x2b, 0xd8, 0x4e, 0x9c, 0x85, 0x7a, 0x17, 0x66, 0x78, 0xd3, 0x13, 0x77, 0x2d, 0x89, 0xa2, 0xaf, 0x60, 0x3b, 0x71, 0x16, 0xea, 0x5d, 0x98, 0xf9,
0xfe, 0x38, 0x73, 0x52, 0x22, 0x5b, 0xd3, 0xe5, 0xbd, 0x7d, 0x05, 0xdb, 0xc2, 0x9d, 0xf0, 0x60, 0xe3, 0xcc, 0x49, 0x89, 0x6c, 0x4d, 0x97, 0xf7, 0xf6, 0x15, 0x6c, 0x0b, 0x77, 0xc2, 0x83, 0x99,
0x26, 0x68, 0xc4, 0x87, 0x81, 0x3f, 0x8a, 0xcc, 0x4d, 0xed, 0x31, 0x86, 0x7b, 0x1a, 0x45, 0x75, 0xa0, 0x11, 0x1f, 0x06, 0xfe, 0x28, 0x32, 0x37, 0xb5, 0xc7, 0x18, 0xee, 0x69, 0x14, 0xd5, 0xa1,
0xa8, 0x5e, 0x73, 0x4e, 0x3d, 0x77, 0xe2, 0x0a, 0x1a, 0x31, 0x61, 0x16, 0xd4, 0xd6, 0xcb, 0xd7, 0x7a, 0xcd, 0x39, 0xf5, 0xdc, 0x89, 0x2b, 0x68, 0xc4, 0x84, 0x59, 0x50, 0x5b, 0x2f, 0x5f, 0x73,
0x9c, 0x77, 0x24, 0xd6, 0x63, 0x42, 0xee, 0x2f, 0x98, 0x89, 0x9b, 0xc0, 0xf5, 0x6f, 0xe8, 0x70, 0xde, 0x91, 0x58, 0x8f, 0x09, 0xb9, 0xbf, 0x60, 0x26, 0x6e, 0x02, 0xd7, 0xbf, 0xa1, 0xc3, 0x31,
0xcc, 0x7c, 0xea, 0x8e, 0xcc, 0xe2, 0x71, 0xe6, 0x24, 0x47, 0xb6, 0x12, 0xbc, 0x39, 0x66, 0xbe, 0xf3, 0xa9, 0x3b, 0x32, 0x8b, 0xc7, 0x99, 0x93, 0x1c, 0xd9, 0x4a, 0xf0, 0xe6, 0x98, 0xf9, 0xd6,
0x35, 0x42, 0x47, 0x00, 0xea, 0x0c, 0xca, 0x9d, 0x59, 0x52, 0x2b, 0x96, 0x24, 0xa2, 0x7c, 0xa1, 0x08, 0x1d, 0x01, 0xa8, 0x33, 0x28, 0x77, 0x66, 0x49, 0xad, 0x58, 0x92, 0x88, 0xf2, 0x85, 0xce,
0x73, 0x28, 0xab, 0x00, 0xd3, 0xb1, 0xeb, 0x8b, 0xc8, 0x84, 0xe3, 0xec, 0x49, 0xf9, 0xdc, 0x38, 0xa1, 0xac, 0x02, 0x4c, 0xc7, 0xae, 0x2f, 0x22, 0x13, 0x8e, 0xb3, 0x27, 0xe5, 0x73, 0xe3, 0xd4,
0xf5, 0x7c, 0x19, 0x6b, 0x22, 0x2d, 0x17, 0xae, 0x2f, 0x48, 0x9a, 0x84, 0x30, 0x14, 0x65, 0x64, 0xf3, 0x65, 0xac, 0x89, 0xb4, 0x5c, 0xb8, 0xbe, 0x20, 0x69, 0x12, 0xc2, 0x50, 0x94, 0x91, 0xa5,
0xa9, 0xf0, 0xee, 0xcc, 0xb2, 0x9a, 0xf0, 0xe2, 0x74, 0x9e, 0xa5, 0xd3, 0x87, 0x69, 0x39, 0x6d, 0xc2, 0xbb, 0x33, 0xcb, 0x6a, 0xc2, 0x8b, 0xd3, 0x79, 0x96, 0x4e, 0x1f, 0xa6, 0xe5, 0xb4, 0xc5,
0xf1, 0x48, 0xf4, 0xbd, 0x3b, 0xec, 0x8b, 0xf0, 0x9e, 0x14, 0x46, 0x7a, 0x54, 0xfb, 0x1e, 0x2a, 0x23, 0xd1, 0xf7, 0xee, 0xb0, 0x2f, 0xc2, 0x7b, 0x52, 0x18, 0xe9, 0x51, 0xed, 0x7b, 0xa8, 0xa4,
0x69, 0x83, 0x4c, 0xd4, 0x2d, 0xbf, 0x57, 0xb9, 0xcb, 0x11, 0xf9, 0x89, 0xf6, 0x20, 0x7f, 0xc7, 0x0d, 0x32, 0x51, 0xb7, 0xfc, 0x5e, 0xe5, 0x2e, 0x47, 0xe4, 0x27, 0xda, 0x83, 0xfc, 0x1d, 0xf3,
0xbc, 0x19, 0x57, 0xc9, 0xab, 0x10, 0x3d, 0xf8, 0x7e, 0xe3, 0x65, 0xa6, 0xfe, 0x12, 0x76, 0xfb, 0x66, 0x5c, 0x25, 0xaf, 0x42, 0xf4, 0xe0, 0xfb, 0x8d, 0x97, 0x99, 0xfa, 0x4b, 0xd8, 0xed, 0x87,
0x21, 0x1b, 0xde, 0xae, 0xe4, 0x7f, 0x35, 0xb3, 0x99, 0x07, 0x99, 0xad, 0xff, 0x09, 0xaa, 0xf1, 0x6c, 0x78, 0xbb, 0x92, 0xff, 0xd5, 0xcc, 0x66, 0x1e, 0x64, 0xb6, 0xfe, 0x27, 0xa8, 0xc6, 0x93,
0xa4, 0x9e, 0x60, 0x62, 0x16, 0xa1, 0x5f, 0x42, 0x3e, 0x12, 0x4c, 0x70, 0x45, 0xde, 0x3a, 0x3f, 0x7a, 0x82, 0x89, 0x59, 0x84, 0x7e, 0x09, 0xf9, 0x48, 0x30, 0xc1, 0x15, 0x79, 0xeb, 0xfc, 0x20,
0x48, 0x1d, 0x25, 0x45, 0xe4, 0x44, 0xb3, 0x50, 0x0d, 0x8a, 0xd3, 0x90, 0xbb, 0x13, 0x76, 0x93, 0x75, 0x94, 0x14, 0x91, 0x13, 0xcd, 0x42, 0x35, 0x28, 0x4e, 0x43, 0xee, 0x4e, 0xd8, 0x4d, 0xb2,
0x6c, 0x6b, 0x3e, 0x46, 0x75, 0xc8, 0xab, 0xc9, 0xea, 0x46, 0x95, 0xcf, 0x2b, 0xe9, 0x30, 0x12, 0xad, 0xf9, 0x18, 0xd5, 0x21, 0xaf, 0x26, 0xab, 0x1b, 0x55, 0x3e, 0xaf, 0xa4, 0xc3, 0x48, 0xb4,
0x6d, 0xaa, 0xff, 0x06, 0xb6, 0xd5, 0xb8, 0xcd, 0xf9, 0xc7, 0x6e, 0xed, 0x01, 0x14, 0xd8, 0x44, 0xa9, 0xfe, 0x1b, 0xd8, 0x56, 0xe3, 0x36, 0xe7, 0x1f, 0xbb, 0xb5, 0x07, 0x50, 0x60, 0x13, 0x9d,
0xa7, 0x5f, 0xdf, 0xdc, 0x4d, 0x36, 0x91, 0x99, 0xaf, 0x8f, 0xc0, 0x58, 0xcc, 0x8f, 0xa6, 0x81, 0x7e, 0x7d, 0x73, 0x37, 0xd9, 0x44, 0x66, 0xbe, 0x3e, 0x02, 0x63, 0x31, 0x3f, 0x9a, 0x06, 0x7e,
0x1f, 0x71, 0x79, 0x1b, 0xa4, 0x73, 0x79, 0x19, 0xe4, 0xcd, 0x99, 0xc8, 0x59, 0x19, 0x35, 0x6b, 0xc4, 0xe5, 0x6d, 0x90, 0xce, 0xe5, 0x65, 0x90, 0x37, 0x67, 0x22, 0x67, 0x65, 0xd4, 0xac, 0xad,
0x2b, 0xc6, 0xdb, 0x9c, 0x77, 0x23, 0x26, 0xd0, 0x73, 0x7d, 0x09, 0xa9, 0x17, 0x0c, 0x6f, 0xe5, 0x18, 0x6f, 0x73, 0xde, 0x8d, 0x98, 0x40, 0xcf, 0xf5, 0x25, 0xa4, 0x5e, 0x30, 0xbc, 0x95, 0xd7,
0xb5, 0x66, 0xf7, 0xb1, 0xfb, 0xaa, 0x84, 0x3b, 0xc1, 0xf0, 0xb6, 0x25, 0xc1, 0xfa, 0xef, 0x75, 0x9a, 0xdd, 0xc7, 0xee, 0xab, 0x12, 0xee, 0x04, 0xc3, 0xdb, 0x96, 0x04, 0xeb, 0xbf, 0xd7, 0xe5,
0x79, 0xf5, 0x03, 0xbd, 0xf7, 0xff, 0x3b, 0xbc, 0x8b, 0x10, 0x6c, 0x3c, 0x1e, 0x02, 0x0a, 0xbb, 0xd5, 0x0f, 0xf4, 0xde, 0xff, 0xef, 0xf0, 0x2e, 0x42, 0xb0, 0xf1, 0x78, 0x08, 0x28, 0xec, 0x2e,
0x4b, 0xce, 0xe3, 0x53, 0xa4, 0x23, 0x9b, 0x59, 0x89, 0xec, 0xd7, 0x50, 0xb8, 0x66, 0xae, 0x37, 0x39, 0x8f, 0x4f, 0x91, 0x8e, 0x6c, 0x66, 0x25, 0xb2, 0x5f, 0x43, 0xe1, 0x9a, 0xb9, 0xde, 0x2c,
0x0b, 0x13, 0xc7, 0x28, 0x95, 0xa6, 0xb6, 0xb6, 0x90, 0x84, 0x52, 0xff, 0x47, 0x01, 0x0a, 0x31, 0x4c, 0x1c, 0xa3, 0x54, 0x9a, 0xda, 0xda, 0x42, 0x12, 0x4a, 0xfd, 0x1f, 0x05, 0x28, 0xc4, 0x20,
0x88, 0xce, 0x21, 0x37, 0x0c, 0x46, 0x49, 0x76, 0x3f, 0x7d, 0x38, 0x2d, 0xf9, 0x6d, 0x06, 0x23, 0x3a, 0x87, 0xdc, 0x30, 0x18, 0x25, 0xd9, 0xfd, 0xf4, 0xe1, 0xb4, 0xe4, 0xb7, 0x19, 0x8c, 0x38,
0x4e, 0x14, 0x17, 0xfd, 0x16, 0xb6, 0x64, 0x51, 0xf9, 0xdc, 0xa3, 0xb3, 0xe9, 0x88, 0xcd, 0x13, 0x51, 0x5c, 0xf4, 0x5b, 0xd8, 0x92, 0x45, 0xe5, 0x73, 0x8f, 0xce, 0xa6, 0x23, 0x36, 0x4f, 0xa8,
0x6a, 0xa6, 0x66, 0x37, 0x35, 0x61, 0xa0, 0xec, 0xa4, 0x3a, 0x4c, 0x0f, 0xd1, 0x21, 0x94, 0xc6, 0x99, 0x9a, 0xdd, 0xd4, 0x84, 0x81, 0xb2, 0x93, 0xea, 0x30, 0x3d, 0x44, 0x87, 0x50, 0x1a, 0x0b,
0xc2, 0x1b, 0xea, 0x4c, 0xe4, 0xd4, 0x85, 0x2e, 0x4a, 0x40, 0xe5, 0xa0, 0x0e, 0xd5, 0xc0, 0x77, 0x6f, 0xa8, 0x33, 0x91, 0x53, 0x17, 0xba, 0x28, 0x01, 0x95, 0x83, 0x3a, 0x54, 0x03, 0xdf, 0x0d,
0x03, 0x9f, 0x46, 0x63, 0x46, 0xcf, 0xbf, 0xfd, 0x4e, 0xe9, 0x45, 0x85, 0x94, 0x15, 0xd8, 0x1b, 0x7c, 0x1a, 0x8d, 0x19, 0x3d, 0xff, 0xf6, 0x3b, 0xa5, 0x17, 0x15, 0x52, 0x56, 0x60, 0x6f, 0xcc,
0xb3, 0xf3, 0x6f, 0xbf, 0x43, 0x9f, 0x41, 0x59, 0x55, 0x2d, 0xff, 0x30, 0x75, 0xc3, 0x7b, 0x25, 0xce, 0xbf, 0xfd, 0x0e, 0x7d, 0x06, 0x65, 0x55, 0xb5, 0xfc, 0xc3, 0xd4, 0x0d, 0xef, 0x95, 0x50,
0x14, 0x55, 0xa2, 0x0a, 0x19, 0x2b, 0x44, 0x96, 0xc6, 0xb5, 0xc7, 0x6e, 0x22, 0x25, 0x0e, 0x55, 0x54, 0x89, 0x2a, 0x64, 0xac, 0x10, 0x59, 0x1a, 0xd7, 0x1e, 0xbb, 0x89, 0x94, 0x38, 0x54, 0x89,
0xa2, 0x07, 0xe8, 0x1b, 0xd8, 0x8b, 0x63, 0x40, 0xa3, 0x60, 0x16, 0x0e, 0x39, 0x75, 0xfd, 0x11, 0x1e, 0xa0, 0x6f, 0x60, 0x2f, 0x8e, 0x01, 0x8d, 0x82, 0x59, 0x38, 0xe4, 0xd4, 0xf5, 0x47, 0xfc,
0xff, 0xa0, 0xa4, 0xa1, 0x4a, 0x50, 0x6c, 0xeb, 0x29, 0x93, 0x25, 0x2d, 0xf5, 0xbf, 0xe5, 0xa1, 0x83, 0x92, 0x86, 0x2a, 0x41, 0xb1, 0xad, 0xa7, 0x4c, 0x96, 0xb4, 0xa0, 0x7d, 0xd8, 0x1c, 0x73,
0x9c, 0x0a, 0x00, 0xaa, 0x40, 0x91, 0xe0, 0x1e, 0x26, 0x6f, 0x71, 0xcb, 0xf8, 0x04, 0x9d, 0xc0, 0xf7, 0x66, 0xac, 0xa5, 0xa1, 0x4a, 0xe2, 0x51, 0xfd, 0x6f, 0x79, 0x28, 0xa7, 0x02, 0x83, 0x2a,
0x97, 0x96, 0xdd, 0x74, 0x08, 0xc1, 0xcd, 0x3e, 0x75, 0x08, 0x1d, 0xd8, 0x6f, 0x6c, 0xe7, 0x27, 0x50, 0x24, 0xb8, 0x87, 0xc9, 0x5b, 0xdc, 0x32, 0x3e, 0x41, 0x27, 0xf0, 0xa5, 0x65, 0x37, 0x1d,
0x9b, 0x5e, 0x36, 0xde, 0x75, 0xb1, 0xdd, 0xa7, 0x2d, 0xdc, 0x6f, 0x58, 0x9d, 0x9e, 0x91, 0x41, 0x42, 0x70, 0xb3, 0x4f, 0x1d, 0x42, 0x07, 0xf6, 0x1b, 0xdb, 0xf9, 0xc9, 0xa6, 0x97, 0x8d, 0x77,
0xcf, 0xc0, 0x5c, 0x30, 0x13, 0x73, 0xa3, 0xeb, 0x0c, 0xec, 0xbe, 0xb1, 0x81, 0x3e, 0x83, 0xc3, 0x5d, 0x6c, 0xf7, 0x69, 0x0b, 0xf7, 0x1b, 0x56, 0xa7, 0x67, 0x64, 0xd0, 0x33, 0x30, 0x17, 0xcc,
0xb6, 0x65, 0x37, 0x3a, 0x74, 0xc1, 0x69, 0x76, 0xfa, 0x6f, 0x29, 0xfe, 0xf9, 0xd2, 0x22, 0xef, 0xc4, 0xdc, 0xe8, 0x3a, 0x03, 0xbb, 0x6f, 0x6c, 0xa0, 0xcf, 0xe0, 0xb0, 0x6d, 0xd9, 0x8d, 0x0e,
0x8c, 0xec, 0x3a, 0xc2, 0x45, 0xbf, 0xd3, 0x4c, 0x3c, 0xe4, 0xd0, 0x53, 0x78, 0xa2, 0x09, 0x7a, 0x5d, 0x70, 0x9a, 0x9d, 0xfe, 0x5b, 0x8a, 0x7f, 0xbe, 0xb4, 0xc8, 0x3b, 0x23, 0xbb, 0x8e, 0x70,
0x0a, 0xed, 0x3b, 0x0e, 0xed, 0x39, 0x8e, 0x6d, 0xe4, 0xd1, 0x0e, 0x54, 0x2d, 0xfb, 0x6d, 0xa3, 0xd1, 0xef, 0x34, 0x13, 0x0f, 0x39, 0xf4, 0x14, 0x9e, 0x68, 0x82, 0x9e, 0x42, 0xfb, 0x8e, 0x43,
0x63, 0xb5, 0x28, 0xc1, 0x8d, 0x4e, 0xd7, 0xd8, 0x44, 0xbb, 0xb0, 0xbd, 0xca, 0x2b, 0x48, 0x17, 0x7b, 0x8e, 0x63, 0x1b, 0x79, 0xb4, 0x03, 0x55, 0xcb, 0x7e, 0xdb, 0xe8, 0x58, 0x2d, 0x4a, 0x70,
0x09, 0xcf, 0xb1, 0x2d, 0xc7, 0xa6, 0x6f, 0x31, 0xe9, 0x59, 0x8e, 0x6d, 0x14, 0xd1, 0x3e, 0xa0, 0xa3, 0xd3, 0x35, 0x36, 0xd1, 0x2e, 0x6c, 0xaf, 0xf2, 0x0a, 0xd2, 0x45, 0xc2, 0x73, 0x6c, 0xcb,
0x65, 0xd3, 0x45, 0xb7, 0xd1, 0x34, 0x4a, 0xe8, 0x09, 0xec, 0x2c, 0xe3, 0x6f, 0xf0, 0x3b, 0x03, 0xb1, 0xe9, 0x5b, 0x4c, 0x7a, 0x96, 0x63, 0x1b, 0x45, 0xb4, 0x0f, 0x68, 0xd9, 0x74, 0xd1, 0x6d,
0x90, 0x09, 0x7b, 0x7a, 0x63, 0xf4, 0x15, 0xee, 0x38, 0x3f, 0xd1, 0xae, 0x65, 0x5b, 0xdd, 0x41, 0x34, 0x8d, 0x12, 0x7a, 0x02, 0x3b, 0xcb, 0xf8, 0x1b, 0xfc, 0xce, 0x00, 0x64, 0xc2, 0x9e, 0xde,
0xd7, 0x28, 0xa3, 0x3d, 0x30, 0xda, 0x18, 0x53, 0xcb, 0xee, 0x0d, 0xda, 0x6d, 0xab, 0x69, 0x61, 0x18, 0x7d, 0x85, 0x3b, 0xce, 0x4f, 0xb4, 0x6b, 0xd9, 0x56, 0x77, 0xd0, 0x35, 0xca, 0x68, 0x0f,
0xbb, 0x6f, 0x54, 0xf4, 0xca, 0xeb, 0x0e, 0x5e, 0x95, 0x13, 0x9a, 0x17, 0x0d, 0xdb, 0xc6, 0x1d, 0x8c, 0x36, 0xc6, 0xd4, 0xb2, 0x7b, 0x83, 0x76, 0xdb, 0x6a, 0x5a, 0xd8, 0xee, 0x1b, 0x15, 0xbd,
0xda, 0xb2, 0x7a, 0x8d, 0x57, 0x1d, 0xdc, 0x32, 0xb6, 0xd0, 0x11, 0x3c, 0xed, 0xe3, 0xee, 0xa5, 0xf2, 0xba, 0x83, 0x57, 0xe5, 0x84, 0xe6, 0x45, 0xc3, 0xb6, 0x71, 0x87, 0xb6, 0xac, 0x5e, 0xe3,
0x43, 0x1a, 0xe4, 0x1d, 0x4d, 0xec, 0xed, 0x86, 0xd5, 0x19, 0x10, 0x6c, 0x6c, 0xa3, 0xcf, 0xe1, 0x55, 0x07, 0xb7, 0x8c, 0x2d, 0x74, 0x04, 0x4f, 0xfb, 0xb8, 0x7b, 0xe9, 0x90, 0x06, 0x79, 0x47,
0x88, 0xe0, 0x1f, 0x07, 0x16, 0xc1, 0x2d, 0x6a, 0x3b, 0x2d, 0x4c, 0xdb, 0xb8, 0xd1, 0x1f, 0x10, 0x13, 0x7b, 0xbb, 0x61, 0x75, 0x06, 0x04, 0x1b, 0xdb, 0xe8, 0x73, 0x38, 0x22, 0xf8, 0xc7, 0x81,
0x4c, 0xbb, 0x56, 0xaf, 0x67, 0xd9, 0x3f, 0x18, 0x06, 0xfa, 0x12, 0x8e, 0xe7, 0x94, 0xb9, 0x83, 0x45, 0x70, 0x8b, 0xda, 0x4e, 0x0b, 0xd3, 0x36, 0x6e, 0xf4, 0x07, 0x04, 0xd3, 0xae, 0xd5, 0xeb,
0x15, 0xd6, 0x8e, 0x3c, 0x5f, 0x92, 0x52, 0x1b, 0xff, 0xdc, 0xa7, 0x97, 0x18, 0x13, 0x03, 0xa1, 0x59, 0xf6, 0x0f, 0x86, 0x81, 0xbe, 0x84, 0xe3, 0x39, 0x65, 0xee, 0x60, 0x85, 0xb5, 0x23, 0xcf,
0x1a, 0xec, 0x2f, 0x96, 0xd7, 0x0b, 0xc4, 0x6b, 0xef, 0x4a, 0xdb, 0x25, 0x26, 0xdd, 0x86, 0x2d, 0x97, 0xa4, 0xd4, 0xc6, 0x3f, 0xf7, 0xe9, 0x25, 0xc6, 0xc4, 0x40, 0xa8, 0x06, 0xfb, 0x8b, 0xe5,
0x13, 0xbc, 0x64, 0xdb, 0x93, 0xdb, 0x5e, 0xd8, 0x56, 0xb7, 0xfd, 0x04, 0xed, 0xc1, 0x76, 0xb2, 0xf5, 0x02, 0xf1, 0xda, 0xbb, 0xd2, 0x76, 0x89, 0x49, 0xb7, 0x61, 0xcb, 0x04, 0x2f, 0xd9, 0xf6,
0x5a, 0x02, 0xfe, 0xb3, 0x80, 0x0e, 0x00, 0x0d, 0x6c, 0x82, 0x1b, 0x2d, 0x79, 0xf8, 0xb9, 0xe1, 0xe4, 0xb6, 0x17, 0xb6, 0xd5, 0x6d, 0x3f, 0x41, 0x7b, 0xb0, 0x9d, 0xac, 0x96, 0x80, 0xff, 0x2c,
0x5f, 0x85, 0xd7, 0xb9, 0xe2, 0x86, 0x91, 0xad, 0xff, 0x3d, 0x0b, 0xd5, 0xa5, 0x5a, 0x43, 0xcf, 0xa0, 0x03, 0x40, 0x03, 0x9b, 0xe0, 0x46, 0x4b, 0x1e, 0x7e, 0x6e, 0xf8, 0x57, 0xe1, 0x75, 0xae,
0xa0, 0x14, 0xb9, 0x37, 0x3e, 0x13, 0x52, 0x0d, 0xb4, 0x50, 0x2c, 0x00, 0xf5, 0xde, 0x8d, 0x99, 0xb8, 0x61, 0x64, 0xeb, 0x7f, 0xcf, 0x42, 0x75, 0xa9, 0x06, 0xd1, 0x33, 0x28, 0x45, 0xee, 0x8d,
0xeb, 0x6b, 0x85, 0xd2, 0x0a, 0x5d, 0x52, 0x88, 0xd2, 0xa7, 0x03, 0x28, 0x24, 0xef, 0x65, 0x56, 0xcf, 0x84, 0x54, 0x09, 0x2d, 0x20, 0x0b, 0x40, 0xbd, 0x83, 0x63, 0xe6, 0xfa, 0x5a, 0xb9, 0xb4,
0xd5, 0xe5, 0xe6, 0x50, 0xbf, 0x93, 0xcf, 0xa0, 0x24, 0x25, 0x30, 0x12, 0x6c, 0x32, 0x55, 0x25, 0x72, 0x97, 0x14, 0xa2, 0x74, 0xeb, 0x00, 0x0a, 0xc9, 0x3b, 0x9a, 0x55, 0xf5, 0xba, 0x39, 0xd4,
0x5b, 0x25, 0x0b, 0x00, 0x7d, 0x01, 0xd5, 0x09, 0x8f, 0x22, 0x76, 0xc3, 0xa9, 0x2e, 0x3b, 0x50, 0xef, 0xe7, 0x33, 0x28, 0x49, 0x69, 0x8c, 0x04, 0x9b, 0x4c, 0x55, 0x29, 0x57, 0xc9, 0x02, 0x40,
0x8c, 0x4a, 0x0c, 0xb6, 0x55, 0xf5, 0x7d, 0x01, 0x89, 0x0c, 0xc4, 0xa4, 0xbc, 0x26, 0xc5, 0xa0, 0x5f, 0x40, 0x75, 0xc2, 0xa3, 0x88, 0xdd, 0x70, 0xaa, 0xcb, 0x11, 0x14, 0xa3, 0x12, 0x83, 0x6d,
0x26, 0xad, 0x2a, 0xb0, 0x60, 0x71, 0x75, 0xa7, 0x15, 0x58, 0x30, 0xf4, 0x02, 0x76, 0xb4, 0x84, 0x55, 0x95, 0x5f, 0x40, 0x22, 0x0f, 0x31, 0x29, 0xaf, 0x49, 0x31, 0xa8, 0x49, 0xab, 0xca, 0x2c,
0xb8, 0xbe, 0x3b, 0x99, 0x4d, 0xb4, 0x94, 0x14, 0xd4, 0x96, 0xb7, 0x95, 0x94, 0x68, 0x5c, 0x29, 0x58, 0x5c, 0xf5, 0x69, 0x65, 0x16, 0x0c, 0xbd, 0x80, 0x1d, 0x2d, 0x2d, 0xae, 0xef, 0x4e, 0x66,
0xca, 0x53, 0x28, 0x5e, 0xb1, 0x88, 0x4b, 0xf1, 0x8f, 0x4b, 0xbd, 0x20, 0xc7, 0x6d, 0xce, 0xa5, 0x13, 0x2d, 0x31, 0x05, 0xb5, 0xe5, 0x6d, 0x25, 0x31, 0x1a, 0x57, 0x4a, 0xf3, 0x14, 0x8a, 0x57,
0x49, 0x3e, 0x09, 0xa1, 0x14, 0xb1, 0x92, 0x36, 0x5d, 0x73, 0x4e, 0x64, 0x1c, 0xe7, 0x2b, 0xb0, 0x2c, 0xe2, 0xf2, 0x51, 0x88, 0x25, 0xa0, 0x20, 0xc7, 0x6d, 0xce, 0xa5, 0x49, 0x3e, 0x15, 0xa1,
0x0f, 0x8b, 0x15, 0xca, 0xa9, 0x15, 0x34, 0xae, 0x56, 0x78, 0x01, 0x3b, 0xfc, 0x83, 0x08, 0x19, 0x14, 0x37, 0x5d, 0xf9, 0x85, 0x6b, 0xce, 0x89, 0x8c, 0xe3, 0x7c, 0x05, 0xf6, 0x61, 0xb1, 0x42,
0x0d, 0xa6, 0xec, 0xfd, 0x8c, 0xd3, 0x11, 0x13, 0xcc, 0xac, 0xa8, 0xe0, 0x6e, 0x2b, 0x83, 0xa3, 0x39, 0xb5, 0x82, 0xc6, 0xd5, 0x0a, 0x2f, 0x60, 0x87, 0x7f, 0x10, 0x21, 0xa3, 0xc1, 0x94, 0xbd,
0xf0, 0x16, 0x13, 0xac, 0xfe, 0x0c, 0x6a, 0x84, 0x47, 0x5c, 0x74, 0xdd, 0x28, 0x72, 0x03, 0xbf, 0x9f, 0x71, 0x3a, 0x62, 0x82, 0x99, 0x15, 0x15, 0xdc, 0x6d, 0x65, 0x70, 0x14, 0xde, 0x62, 0x82,
0x19, 0xf8, 0x22, 0x0c, 0xbc, 0xf8, 0x0d, 0xa9, 0x1f, 0xc1, 0xe1, 0x5a, 0xab, 0x7e, 0x04, 0xe4, 0xd5, 0x9f, 0x41, 0x8d, 0xf0, 0x88, 0x8b, 0xae, 0x1b, 0x45, 0x6e, 0xe0, 0x37, 0x03, 0x5f, 0x84,
0xe4, 0x1f, 0x67, 0x3c, 0xbc, 0x5f, 0x3f, 0xf9, 0x1e, 0x0e, 0xd7, 0x5a, 0xe3, 0x17, 0xe4, 0x6b, 0x81, 0x17, 0xbf, 0x2d, 0xf5, 0x23, 0x38, 0x5c, 0x6b, 0xd5, 0x8f, 0x83, 0x9c, 0xfc, 0xe3, 0x8c,
0xc8, 0xfb, 0xc1, 0x88, 0x47, 0x66, 0x46, 0x75, 0x25, 0xfb, 0x29, 0xb9, 0xb6, 0x83, 0x11, 0xbf, 0x87, 0xf7, 0xeb, 0x27, 0xdf, 0xc3, 0xe1, 0x5a, 0x6b, 0xfc, 0xb2, 0x7c, 0x0d, 0x79, 0x3f, 0x18,
0x70, 0x23, 0x11, 0x84, 0xf7, 0x44, 0x93, 0x24, 0x7b, 0xca, 0xdc, 0x30, 0x32, 0x37, 0x1e, 0xb0, 0xf1, 0xc8, 0xcc, 0xa8, 0x6e, 0x65, 0x3f, 0x25, 0xe3, 0x76, 0x30, 0xe2, 0x17, 0x6e, 0x24, 0x82,
0x2f, 0x99, 0x1b, 0xce, 0xd9, 0x8a, 0x54, 0xff, 0x73, 0x06, 0xca, 0x29, 0x27, 0x68, 0x1f, 0x36, 0xf0, 0x9e, 0x68, 0x92, 0x64, 0x4f, 0x99, 0x1b, 0x46, 0xe6, 0xc6, 0x03, 0xf6, 0x25, 0x73, 0xc3,
0xa7, 0xb3, 0xab, 0xa4, 0x61, 0xa9, 0x90, 0x78, 0x84, 0x9e, 0xc3, 0x96, 0xc7, 0x22, 0x41, 0xa5, 0x39, 0x5b, 0x91, 0xea, 0x7f, 0xce, 0x40, 0x39, 0xe5, 0x44, 0x0a, 0xea, 0x74, 0x76, 0x95, 0x34,
0xd6, 0x52, 0x99, 0xd2, 0xf8, 0x81, 0x5d, 0x41, 0xd1, 0x29, 0xa0, 0x40, 0x8c, 0x79, 0x48, 0xa3, 0x32, 0x15, 0x12, 0x8f, 0xd0, 0x73, 0xd8, 0xf2, 0x58, 0x24, 0xa8, 0xd4, 0x60, 0x2a, 0x53, 0x1a,
0xd9, 0x70, 0xc8, 0xa3, 0x88, 0x4e, 0xc3, 0xe0, 0x4a, 0xdd, 0xc9, 0x0d, 0xb2, 0xc6, 0xf2, 0x3a, 0x3f, 0xbc, 0x2b, 0x28, 0x3a, 0x05, 0x14, 0x88, 0x31, 0x0f, 0x69, 0x34, 0x1b, 0x0e, 0x79, 0x14,
0x57, 0xcc, 0x19, 0xf9, 0xfa, 0xbf, 0x33, 0x50, 0x4e, 0x6d, 0x4e, 0xde, 0x5a, 0x79, 0x18, 0x7a, 0xd1, 0x69, 0x18, 0x5c, 0xa9, 0x3b, 0xb9, 0x41, 0xd6, 0x58, 0x5e, 0xe7, 0x8a, 0x39, 0x23, 0x5f,
0x1d, 0x06, 0x93, 0xa4, 0x16, 0xe6, 0x00, 0x32, 0xa1, 0xa0, 0x06, 0x22, 0x88, 0x0b, 0x21, 0x19, 0xff, 0x77, 0x06, 0xca, 0xa9, 0xcd, 0xc9, 0x5b, 0x2b, 0x0f, 0x43, 0xaf, 0xc3, 0x60, 0x92, 0xd4,
0x2e, 0xdf, 0xf6, 0xac, 0xda, 0x60, 0xea, 0xb6, 0x9f, 0xc3, 0xde, 0xc4, 0xf5, 0xe9, 0x94, 0xfb, 0xc2, 0x1c, 0x40, 0x26, 0x14, 0xd4, 0x40, 0x04, 0x71, 0x21, 0x24, 0xc3, 0xe5, 0xdb, 0x9e, 0x55,
0xcc, 0x73, 0xff, 0xc8, 0x69, 0xd2, 0x89, 0xe4, 0x14, 0x71, 0xad, 0x0d, 0xd5, 0xa1, 0xb2, 0x74, 0x1b, 0x4c, 0xdd, 0xf6, 0x73, 0xd8, 0x9b, 0xb8, 0x3e, 0x9d, 0x72, 0x9f, 0x79, 0xee, 0x1f, 0x39,
0x92, 0xbc, 0x3a, 0xc9, 0x12, 0x86, 0x5e, 0xc2, 0x81, 0x8a, 0x02, 0x13, 0x82, 0x4f, 0xa6, 0x22, 0x4d, 0x3a, 0x94, 0x9c, 0x22, 0xae, 0xb5, 0xa1, 0x3a, 0x54, 0x96, 0x4e, 0x92, 0x57, 0x27, 0x59,
0x39, 0xe0, 0xf5, 0xcc, 0x53, 0x35, 0x50, 0x24, 0x8f, 0x99, 0x5f, 0xfc, 0x35, 0x03, 0x95, 0x74, 0xc2, 0xd0, 0x4b, 0x38, 0x50, 0x51, 0x60, 0x42, 0xf0, 0xc9, 0x54, 0x24, 0x07, 0xbc, 0x9e, 0x79,
0x37, 0x86, 0xaa, 0x50, 0xb2, 0x6c, 0xda, 0xee, 0x58, 0x3f, 0x5c, 0xf4, 0x8d, 0x4f, 0xe4, 0xb0, 0xaa, 0x06, 0x8a, 0xe4, 0x31, 0xf3, 0x8b, 0xbf, 0x66, 0xa0, 0x92, 0xee, 0xd2, 0x50, 0x15, 0x4a,
0x37, 0x68, 0x36, 0x31, 0x6e, 0xe1, 0x96, 0x91, 0x41, 0x08, 0xb6, 0xa4, 0x90, 0xe0, 0x16, 0xed, 0x96, 0x4d, 0xdb, 0x1d, 0xeb, 0x87, 0x8b, 0xbe, 0xf1, 0x89, 0x1c, 0xf6, 0x06, 0xcd, 0x26, 0xc6,
0x5b, 0x5d, 0xec, 0x0c, 0xe4, 0x1b, 0xb4, 0x0b, 0xdb, 0x31, 0x66, 0x3b, 0x94, 0x38, 0x83, 0x3e, 0x2d, 0xdc, 0x32, 0x32, 0x08, 0xc1, 0x96, 0x14, 0x12, 0xdc, 0xa2, 0x7d, 0xab, 0x8b, 0x9d, 0x81,
0x36, 0xb2, 0xc8, 0x80, 0x4a, 0x0c, 0x62, 0x42, 0x1c, 0x62, 0xe4, 0xa4, 0x70, 0xc6, 0xc8, 0xc3, 0x7c, 0x83, 0x76, 0x61, 0x3b, 0xc6, 0x6c, 0x87, 0x12, 0x67, 0xd0, 0xc7, 0x46, 0x16, 0x19, 0x50,
0xf7, 0x2c, 0x79, 0xee, 0xf2, 0xe7, 0x7f, 0xc9, 0xc1, 0xa6, 0xea, 0x5e, 0x42, 0x74, 0x01, 0xe5, 0x89, 0x41, 0x4c, 0x88, 0x43, 0x8c, 0x9c, 0x14, 0xce, 0x18, 0x79, 0xf8, 0x9e, 0x25, 0xcf, 0x5d,
0x54, 0xcb, 0x8b, 0x8e, 0x3e, 0xda, 0x0a, 0xd7, 0xcc, 0xf5, 0xed, 0xe5, 0x2c, 0xfa, 0x26, 0x83, 0xfe, 0xfc, 0x2f, 0x39, 0xd8, 0x54, 0x5d, 0x4d, 0x88, 0x2e, 0xa0, 0x9c, 0x6a, 0x85, 0xd1, 0xd1,
0x5e, 0x43, 0x25, 0xdd, 0xd4, 0xa2, 0x74, 0xb3, 0xb2, 0xa6, 0xdb, 0xfd, 0xa8, 0xaf, 0x37, 0x60, 0x47, 0x5b, 0xe4, 0x9a, 0xb9, 0xbe, 0xed, 0x9c, 0x45, 0xdf, 0x64, 0xd0, 0x6b, 0xa8, 0xa4, 0x9b,
0xe0, 0x48, 0xb8, 0x13, 0xd9, 0x9c, 0xc4, 0xed, 0x22, 0xaa, 0xa5, 0xf8, 0x2b, 0x3d, 0x68, 0xed, 0x5d, 0x94, 0x6e, 0x62, 0xd6, 0x74, 0xc1, 0x1f, 0xf5, 0xf5, 0x06, 0x0c, 0x1c, 0x09, 0x77, 0x22,
0x70, 0xad, 0x2d, 0xae, 0xab, 0x8e, 0x3e, 0x62, 0xdc, 0xb0, 0x3d, 0x38, 0xe2, 0x72, 0x97, 0x58, 0x9b, 0x96, 0xb8, 0x8d, 0x44, 0xb5, 0x14, 0x7f, 0xa5, 0x37, 0xad, 0x1d, 0xae, 0xb5, 0xc5, 0x75,
0xfb, 0xf4, 0x31, 0x73, 0xec, 0x6d, 0x04, 0xbb, 0x6b, 0x14, 0x00, 0xfd, 0x22, 0xbd, 0x83, 0x47, 0xd5, 0xd1, 0x47, 0x8c, 0x1b, 0xb9, 0x07, 0x47, 0x5c, 0xee, 0x1e, 0x6b, 0x9f, 0x3e, 0x66, 0x8e,
0xf5, 0xa3, 0xf6, 0xfc, 0x7f, 0xd1, 0x16, 0xab, 0xac, 0x91, 0x8a, 0xa5, 0x55, 0x1e, 0x17, 0x9a, 0xbd, 0x8d, 0x60, 0x77, 0x8d, 0x02, 0xa0, 0x5f, 0xa4, 0x77, 0xf0, 0xa8, 0x7e, 0xd4, 0x9e, 0xff,
0xa5, 0x55, 0x3e, 0xa2, 0x38, 0xaf, 0x7e, 0xf5, 0xbb, 0xb3, 0x1b, 0x57, 0x8c, 0x67, 0x57, 0xa7, 0x2f, 0xda, 0x62, 0x95, 0x35, 0x52, 0xb1, 0xb4, 0xca, 0xe3, 0x42, 0xb3, 0xb4, 0xca, 0x47, 0x14,
0xc3, 0x60, 0x72, 0xe6, 0xb9, 0x37, 0x63, 0xe1, 0xbb, 0xfe, 0x8d, 0xcf, 0xc5, 0x1f, 0x82, 0xf0, 0xe7, 0xd5, 0xaf, 0x7e, 0x77, 0x76, 0xe3, 0x8a, 0xf1, 0xec, 0xea, 0x74, 0x18, 0x4c, 0xce, 0x3c,
0xf6, 0xcc, 0xf3, 0x47, 0x67, 0xaa, 0x01, 0x3e, 0x9b, 0xbb, 0xbb, 0xda, 0x54, 0xff, 0x60, 0x7f, 0xd9, 0x52, 0xf9, 0xae, 0x7f, 0xe3, 0x73, 0xf1, 0x87, 0x20, 0xbc, 0x3d, 0xf3, 0xfc, 0xd1, 0x99,
0xfd, 0xdf, 0x00, 0x00, 0x00, 0xff, 0xff, 0xd5, 0x11, 0xe6, 0x51, 0xf1, 0x0e, 0x00, 0x00, 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. // Reference imports to suppress errors if they are not otherwise used.

@ -239,6 +239,9 @@ message Failure {
the failure message. Position zero is the sender node. the failure message. Position zero is the sender node.
**/ **/
uint32 failure_source_index = 8; uint32 failure_source_index = 8;
/// A failure type-dependent block height.
uint32 height = 9;
} }

@ -324,6 +324,7 @@ func marshallError(sendError error) (*Failure, error) {
case *lnwire.FailIncorrectDetails: case *lnwire.FailIncorrectDetails:
response.Code = Failure_INCORRECT_OR_UNKNOWN_PAYMENT_DETAILS response.Code = Failure_INCORRECT_OR_UNKNOWN_PAYMENT_DETAILS
response.Height = onionErr.Height()
case *lnwire.FailIncorrectPaymentAmount: case *lnwire.FailIncorrectPaymentAmount:
response.Code = Failure_INCORRECT_PAYMENT_AMOUNT response.Code = Failure_INCORRECT_PAYMENT_AMOUNT

@ -333,13 +333,19 @@ func (f *FailIncorrectPaymentAmount) Error() string {
type FailIncorrectDetails struct { type FailIncorrectDetails struct {
// amount is the value of the extended HTLC. // amount is the value of the extended HTLC.
amount MilliSatoshi amount MilliSatoshi
// height is the block height when the htlc was received.
height uint32
} }
// NewFailIncorrectDetails makes a new instance of the FailIncorrectDetails // NewFailIncorrectDetails makes a new instance of the FailIncorrectDetails
// error bound to the specified HTLC amount. // error bound to the specified HTLC amount and acceptance height.
func NewFailIncorrectDetails(amt MilliSatoshi) *FailIncorrectDetails { func NewFailIncorrectDetails(amt MilliSatoshi,
height uint32) *FailIncorrectDetails {
return &FailIncorrectDetails{ return &FailIncorrectDetails{
amount: amt, amount: amt,
height: height,
} }
} }
@ -348,6 +354,11 @@ func (f *FailIncorrectDetails) Amount() MilliSatoshi {
return f.amount 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. // Code returns the failure unique code.
// //
// NOTE: Part of the FailureMessage interface. // NOTE: Part of the FailureMessage interface.
@ -360,7 +371,8 @@ func (f *FailIncorrectDetails) Code() FailCode {
// NOTE: Implements the error interface. // NOTE: Implements the error interface.
func (f *FailIncorrectDetails) Error() string { func (f *FailIncorrectDetails) Error() string {
return fmt.Sprintf( 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 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 return nil
} }
@ -388,7 +411,7 @@ func (f *FailIncorrectDetails) Decode(r io.Reader, pver uint32) error {
// //
// NOTE: Part of the Serializable interface. // NOTE: Part of the Serializable interface.
func (f *FailIncorrectDetails) Encode(w io.Writer, pver uint32) error { 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 // FailFinalExpiryTooSoon is returned if the cltv_expiry is too low, the final

@ -4,6 +4,7 @@ import (
"bufio" "bufio"
"bytes" "bytes"
"encoding/binary" "encoding/binary"
"io"
"reflect" "reflect"
"testing" "testing"
@ -36,7 +37,7 @@ var onionFailures = []FailureMessage{
&FailIncorrectPaymentAmount{}, &FailIncorrectPaymentAmount{},
&FailFinalExpiryTooSoon{}, &FailFinalExpiryTooSoon{},
NewFailIncorrectDetails(99), NewFailIncorrectDetails(99, 100),
NewInvalidOnionVersion(testOnionHash), NewInvalidOnionVersion(testOnionHash),
NewInvalidOnionHmac(testOnionHash), NewInvalidOnionHmac(testOnionHash),
NewInvalidOnionKey(testOnionHash), NewInvalidOnionKey(testOnionHash),
@ -174,10 +175,7 @@ func TestWriteOnionErrorChanUpdate(t *testing.T) {
func TestFailIncorrectDetailsOptionalAmount(t *testing.T) { func TestFailIncorrectDetailsOptionalAmount(t *testing.T) {
t.Parallel() t.Parallel()
// Creation an error that is a non-pointer will allow us to skip the onionError := &mockFailIncorrectDetailsNoAmt{}
// type assertion for the Serializable interface. As a result, the
// amount body won't be written.
onionError := &FailIncorrectDetails{}
var b bytes.Buffer var b bytes.Buffer
if err := EncodeFailure(&b, onionError, 0); err != nil { if err := EncodeFailure(&b, onionError, 0); err != nil {
@ -189,8 +187,89 @@ func TestFailIncorrectDetailsOptionalAmount(t *testing.T) {
t.Fatalf("unable to decode error: %v", err) t.Fatalf("unable to decode error: %v", err)
} }
if !reflect.DeepEqual(onionError, onionError2) { invalidDetailsErr, ok := onionError2.(*FailIncorrectDetails)
t.Fatalf("expected %v, got %v", spew.Sdump(onionError), if !ok {
spew.Sdump(onionError2)) t.Fatalf("expected FailIncorrectDetails, but got %T",
onionError2)
}
if invalidDetailsErr.amount != 0 {
t.Fatalf("expected amount to be zero")
}
if invalidDetailsErr.height != 0 {
t.Fatalf("height incorrect")
} }
} }
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
}
// 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)
}

@ -61,7 +61,7 @@ func TestMissionControlStore(t *testing.T) {
result1 := paymentResult{ result1 := paymentResult{
route: &testRoute, route: &testRoute,
failure: lnwire.NewFailIncorrectDetails(100), failure: lnwire.NewFailIncorrectDetails(100, 1000),
failureSourceIdx: &failureSourceIdx, failureSourceIdx: &failureSourceIdx,
id: 99, id: 99,
timeReply: testTime, timeReply: testTime,

@ -103,7 +103,7 @@ var resultTestCases = []resultTestCase{
name: "fail incorrect details", name: "fail incorrect details",
route: &routeTwoHop, route: &routeTwoHop,
failureSrcIdx: 2, failureSrcIdx: 2,
failure: lnwire.NewFailIncorrectDetails(97), failure: lnwire.NewFailIncorrectDetails(97, 0),
expectedResult: &interpretedResult{ expectedResult: &interpretedResult{
pairResults: map[DirectedNodePair]pairResult{ pairResults: map[DirectedNodePair]pairResult{