From d98a22879f7d16004d5b1c40777fc0888269a34e Mon Sep 17 00:00:00 2001 From: Joost Jager Date: Mon, 12 Aug 2019 17:28:36 +0200 Subject: [PATCH 01/16] invoices/test: clarify test parameters --- invoices/invoiceregistry_test.go | 29 ++++++++++++++--------------- 1 file changed, 14 insertions(+), 15 deletions(-) diff --git a/invoices/invoiceregistry_test.go b/invoices/invoiceregistry_test.go index c72db33e5..990bcdc46 100644 --- a/invoices/invoiceregistry_test.go +++ b/invoices/invoiceregistry_test.go @@ -21,15 +21,17 @@ var ( hash = preimage.Hash() - testInvoiceExpiry = uint32(3) + testHtlcExpiry = uint32(5) - testCurrentHeight = int32(0) + testInvoiceCltvDelta = uint32(4) - testFinalCltvRejectDelta = int32(3) + testFinalCltvRejectDelta = int32(4) + + testCurrentHeight = int32(1) ) func decodeExpiry(payReq string) (uint32, error) { - return uint32(testInvoiceExpiry), nil + return uint32(testInvoiceCltvDelta), nil } var ( @@ -119,7 +121,7 @@ func TestSettleInvoice(t *testing.T) { // Settle invoice with a slightly higher amount. amtPaid := lnwire.MilliSatoshi(100500) _, err = registry.NotifyExitHopHtlc( - hash, amtPaid, testInvoiceExpiry, 0, hodlChan, nil, + hash, amtPaid, testHtlcExpiry, testCurrentHeight, hodlChan, nil, ) if err != nil { t.Fatal(err) @@ -154,8 +156,7 @@ func TestSettleInvoice(t *testing.T) { // Try to settle again. We need this idempotent behaviour after a // restart. event, err := registry.NotifyExitHopHtlc( - hash, amtPaid, testInvoiceExpiry, testCurrentHeight, hodlChan, - nil, + hash, amtPaid, testHtlcExpiry, testCurrentHeight, hodlChan, nil, ) if err != nil { t.Fatalf("unexpected NotifyExitHopHtlc error: %v", err) @@ -168,7 +169,7 @@ func TestSettleInvoice(t *testing.T) { // cancel event because after a restart the amount should still be the // same. New HTLCs with a different amount should be rejected. event, err = registry.NotifyExitHopHtlc( - hash, amtPaid+600, testInvoiceExpiry, testCurrentHeight, + hash, amtPaid+600, testHtlcExpiry, testCurrentHeight, hodlChan, nil, ) if err != nil { @@ -181,7 +182,7 @@ func TestSettleInvoice(t *testing.T) { // Try to settle again with a lower amount. This should show the same // behaviour as settling with a higher amount. event, err = registry.NotifyExitHopHtlc( - hash, amtPaid-600, testInvoiceExpiry, testCurrentHeight, + hash, amtPaid-600, testHtlcExpiry, testCurrentHeight, hodlChan, nil, ) if err != nil { @@ -305,7 +306,7 @@ func TestCancelInvoice(t *testing.T) { // succeed. hodlChan := make(chan interface{}) event, err := registry.NotifyExitHopHtlc( - hash, amt, testInvoiceExpiry, testCurrentHeight, hodlChan, nil, + hash, amt, testHtlcExpiry, testCurrentHeight, hodlChan, nil, ) if err != nil { t.Fatal("expected settlement of a canceled invoice to succeed") @@ -381,8 +382,7 @@ func TestHoldInvoice(t *testing.T) { // NotifyExitHopHtlc without a preimage present in the invoice registry // should be possible. event, err := registry.NotifyExitHopHtlc( - hash, amtPaid, testInvoiceExpiry, testCurrentHeight, hodlChan, - nil, + hash, amtPaid, testHtlcExpiry, testCurrentHeight, hodlChan, nil, ) if err != nil { t.Fatalf("expected settle to succeed but got %v", err) @@ -393,8 +393,7 @@ func TestHoldInvoice(t *testing.T) { // Test idempotency. event, err = registry.NotifyExitHopHtlc( - hash, amtPaid, testInvoiceExpiry, testCurrentHeight, hodlChan, - nil, + hash, amtPaid, testHtlcExpiry, testCurrentHeight, hodlChan, nil, ) if err != nil { t.Fatalf("expected settle to succeed but got %v", err) @@ -490,7 +489,7 @@ func TestUnknownInvoice(t *testing.T) { hodlChan := make(chan interface{}) amt := lnwire.MilliSatoshi(100000) _, err := registry.NotifyExitHopHtlc( - hash, amt, testInvoiceExpiry, testCurrentHeight, hodlChan, nil, + hash, amt, testHtlcExpiry, testCurrentHeight, hodlChan, nil, ) if err != channeldb.ErrInvoiceNotFound { t.Fatal("expected invoice not found error") From 762609a16973124580dd648bdee27cb5b8e44246 Mon Sep 17 00:00:00 2001 From: Joost Jager Date: Wed, 14 Aug 2019 17:48:34 +0200 Subject: [PATCH 02/16] channeldb: fix suppressed error --- channeldb/invoices.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/channeldb/invoices.go b/channeldb/invoices.go index 2fb490e95..339761df7 100644 --- a/channeldb/invoices.go +++ b/channeldb/invoices.go @@ -855,7 +855,7 @@ func putInvoice(invoices, invoiceIndex, addIndex *bbolt.Bucket, // Finally, serialize the invoice itself to be written to the disk. var buf bytes.Buffer if err := serializeInvoice(&buf, i); err != nil { - return 0, nil + return 0, err } if err := invoices.Put(invoiceKey[:], buf.Bytes()); err != nil { From 43bad4af9fa50d879678c00ab7dc939bd4ef7063 Mon Sep 17 00:00:00 2001 From: Joost Jager Date: Thu, 8 Aug 2019 16:25:25 +0200 Subject: [PATCH 03/16] invoices: always check htlc amt with invoice amount Previously a check was made for accepted and settled invoices against the paid amount. This opens up a probe vector where an attacker can pay to an invoice with an amt that is higher than the invoice amount and find out if the invoice is already paid or not. --- invoices/invoiceregistry.go | 13 ++++--------- invoices/invoiceregistry_test.go | 14 +++++++------- 2 files changed, 11 insertions(+), 16 deletions(-) diff --git a/invoices/invoiceregistry.go b/invoices/invoiceregistry.go index a5570a3c8..2cbba30eb 100644 --- a/invoices/invoiceregistry.go +++ b/invoices/invoiceregistry.go @@ -437,10 +437,10 @@ func (i *InvoiceRegistry) checkHtlcParameters(invoice *channeldb.Invoice, return channeldb.ErrInvoiceAlreadyCanceled } - // If a payment has already been made, we only accept more payments if - // the amount is the exact same. This prevents probing with small - // amounts on settled invoices to find out the receiver node. - if invoice.AmtPaid != 0 && amtPaid != invoice.AmtPaid { + // If an invoice amount is specified, check that enough is paid. Also + // check this for duplicate payments if the invoice is already settled + // or accepted. + if invoice.Terms.Value > 0 && amtPaid < invoice.Terms.Value { return ErrInvoiceAmountTooLow } @@ -468,11 +468,6 @@ func (i *InvoiceRegistry) checkHtlcParameters(invoice *channeldb.Invoice, return ErrInvoiceExpiryTooSoon } - // If an invoice amount is specified, check that enough is paid. - if invoice.Terms.Value > 0 && amtPaid < invoice.Terms.Value { - return ErrInvoiceAmountTooLow - } - return nil } diff --git a/invoices/invoiceregistry_test.go b/invoices/invoiceregistry_test.go index 990bcdc46..d8b8a5fe3 100644 --- a/invoices/invoiceregistry_test.go +++ b/invoices/invoiceregistry_test.go @@ -165,9 +165,9 @@ func TestSettleInvoice(t *testing.T) { t.Fatal("expected settle event") } - // Try to settle again with a higher amount. This should result in a - // cancel event because after a restart the amount should still be the - // same. New HTLCs with a different amount should be rejected. + // Try to settle again with a higher amount. This payment should also be + // accepted, to prevent any change in behaviour for a paid invoice that + // may open up a probe vector. event, err = registry.NotifyExitHopHtlc( hash, amtPaid+600, testHtlcExpiry, testCurrentHeight, hodlChan, nil, @@ -175,12 +175,12 @@ func TestSettleInvoice(t *testing.T) { if err != nil { t.Fatalf("unexpected NotifyExitHopHtlc error: %v", err) } - if event.Preimage != nil { - t.Fatal("expected cancel event") + if event.Preimage == nil { + t.Fatal("expected settle event") } - // Try to settle again with a lower amount. This should show the same - // behaviour as settling with a higher amount. + // Try to settle again with a lower amount. This should fail just as it + // would have failed if it were the first payment. event, err = registry.NotifyExitHopHtlc( hash, amtPaid-600, testHtlcExpiry, testCurrentHeight, hodlChan, nil, From 4fe846af6e3918374334d2970ab3a2cb332dee25 Mon Sep 17 00:00:00 2001 From: Joost Jager Date: Tue, 20 Aug 2019 15:51:34 +0200 Subject: [PATCH 04/16] invoices/test: extend hodl invoice test Add logic to specifically exercise the replay behavior of invoice registry for hodl invoices. --- invoices/invoiceregistry_test.go | 35 ++++++++++++++++++++++++++++++-- 1 file changed, 33 insertions(+), 2 deletions(-) diff --git a/invoices/invoiceregistry_test.go b/invoices/invoiceregistry_test.go index d8b8a5fe3..ed3ec0771 100644 --- a/invoices/invoiceregistry_test.go +++ b/invoices/invoiceregistry_test.go @@ -388,7 +388,7 @@ func TestHoldInvoice(t *testing.T) { t.Fatalf("expected settle to succeed but got %v", err) } if event != nil { - t.Fatalf("unexpect direct settle") + t.Fatalf("expected htlc to be held") } // Test idempotency. @@ -399,7 +399,34 @@ func TestHoldInvoice(t *testing.T) { t.Fatalf("expected settle to succeed but got %v", err) } if event != nil { - t.Fatalf("unexpect direct settle") + t.Fatalf("expected htlc to be held") + } + + // Test replay at a higher height. We expect the same result because it + // is a replay. + event, err = registry.NotifyExitHopHtlc( + hash, amtPaid, testHtlcExpiry, testCurrentHeight+10, + getCircuitKey(0), hodlChan, + ) + if err != nil { + t.Fatalf("expected settle to succeed but got %v", err) + } + if event != nil { + t.Fatalf("expected htlc to be held") + } + + // Test a new htlc coming in that doesn't meet the final cltv delta + // requirement. It should be rejected, but because invoice registry + // doesn't track individual htlcs it is accepted. + event, err = registry.NotifyExitHopHtlc( + hash, amtPaid, 1, testCurrentHeight, + getCircuitKey(1), hodlChan, + ) + if err != nil { + t.Fatalf("expected settle to succeed but got %v", err) + } + if event != nil { + t.Fatalf("expected htlc to be held") } // We expect the accepted state to be sent to the single invoice @@ -432,6 +459,10 @@ func TestHoldInvoice(t *testing.T) { t.Fatalf("expected state ContractSettled, but got %v", settledInvoice.Terms.State) } + if settledInvoice.AmtPaid != amtPaid { + t.Fatalf("expected amount to be %v, but got %v", + amtPaid, settledInvoice.AmtPaid) + } update = <-subscription.Updates if update.Terms.State != channeldb.ContractSettled { From 5871d69bde477be9bf3a6431d210f8aafc7f0859 Mon Sep 17 00:00:00 2001 From: Joost Jager Date: Thu, 8 Aug 2019 15:29:07 +0200 Subject: [PATCH 05/16] cnct: convert supplement functions to methods --- contractcourt/channel_arbitrator.go | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/contractcourt/channel_arbitrator.go b/contractcourt/channel_arbitrator.go index 7bb1899c2..34d75b336 100644 --- a/contractcourt/channel_arbitrator.go +++ b/contractcourt/channel_arbitrator.go @@ -479,7 +479,7 @@ func (c *ChannelArbitrator) relaunchResolvers() error { "resolvers", c.cfg.ChanPoint, len(unresolvedContracts)) for _, resolver := range unresolvedContracts { - supplementResolver(resolver, htlcMap) + c.supplementResolver(resolver, htlcMap) } c.launchResolvers(unresolvedContracts) @@ -489,24 +489,24 @@ func (c *ChannelArbitrator) relaunchResolvers() error { // supplementResolver takes a resolver as it is restored from the log and fills // in missing data from the htlcMap. -func supplementResolver(resolver ContractResolver, +func (c *ChannelArbitrator) supplementResolver(resolver ContractResolver, htlcMap map[wire.OutPoint]*channeldb.HTLC) error { switch r := resolver.(type) { case *htlcSuccessResolver: - return supplementSuccessResolver(r, htlcMap) + return c.supplementSuccessResolver(r, htlcMap) case *htlcIncomingContestResolver: - return supplementSuccessResolver( + return c.supplementSuccessResolver( &r.htlcSuccessResolver, htlcMap, ) case *htlcTimeoutResolver: - return supplementTimeoutResolver(r, htlcMap) + return c.supplementTimeoutResolver(r, htlcMap) case *htlcOutgoingContestResolver: - return supplementTimeoutResolver( + return c.supplementTimeoutResolver( &r.htlcTimeoutResolver, htlcMap, ) } @@ -516,7 +516,7 @@ func supplementResolver(resolver ContractResolver, // supplementSuccessResolver takes a htlcSuccessResolver as it is restored from // the log and fills in missing data from the htlcMap. -func supplementSuccessResolver(r *htlcSuccessResolver, +func (c *ChannelArbitrator) supplementSuccessResolver(r *htlcSuccessResolver, htlcMap map[wire.OutPoint]*channeldb.HTLC) error { res := r.htlcResolution @@ -533,7 +533,7 @@ func supplementSuccessResolver(r *htlcSuccessResolver, // supplementTimeoutResolver takes a htlcSuccessResolver as it is restored from // the log and fills in missing data from the htlcMap. -func supplementTimeoutResolver(r *htlcTimeoutResolver, +func (c *ChannelArbitrator) supplementTimeoutResolver(r *htlcTimeoutResolver, htlcMap map[wire.OutPoint]*channeldb.HTLC) error { res := r.htlcResolution From 05e6b62cb20af56c06544e4702fef3e804214c2c Mon Sep 17 00:00:00 2001 From: Joost Jager Date: Thu, 8 Aug 2019 15:48:31 +0200 Subject: [PATCH 06/16] cnct+htlcswitch+invoices: report circuit key to invoice registry Currently the invoice registry cannot tell apart the htlcs that pay to an invoice. Because htlcs may also be replayed on startup, it isn't possible to determine the total amount paid to an invoice. This commit is a first step towards fixing that. It reports the circuit keys of htlcs to the invoice registry, which forms the basis for accurate invoice accounting. --- contractcourt/channel_arbitrator.go | 28 ++++++++++-- .../htlc_incoming_contest_resolver.go | 5 ++- contractcourt/interfaces.go | 2 +- contractcourt/mock_registry_test.go | 3 +- htlcswitch/interfaces.go | 2 +- htlcswitch/link.go | 7 ++- htlcswitch/mock.go | 5 ++- invoices/invoiceregistry.go | 7 +-- invoices/invoiceregistry_test.go | 45 ++++++++++++------- 9 files changed, 76 insertions(+), 28 deletions(-) diff --git a/contractcourt/channel_arbitrator.go b/contractcourt/channel_arbitrator.go index 34d75b336..e05a5863b 100644 --- a/contractcourt/channel_arbitrator.go +++ b/contractcourt/channel_arbitrator.go @@ -498,9 +498,7 @@ func (c *ChannelArbitrator) supplementResolver(resolver ContractResolver, return c.supplementSuccessResolver(r, htlcMap) case *htlcIncomingContestResolver: - return c.supplementSuccessResolver( - &r.htlcSuccessResolver, htlcMap, - ) + return c.supplementIncomingContestResolver(r, htlcMap) case *htlcTimeoutResolver: return c.supplementTimeoutResolver(r, htlcMap) @@ -514,6 +512,30 @@ func (c *ChannelArbitrator) supplementResolver(resolver ContractResolver, return nil } +// supplementSuccessResolver takes a htlcIncomingContestResolver as it is +// restored from the log and fills in missing data from the htlcMap. +func (c *ChannelArbitrator) supplementIncomingContestResolver( + r *htlcIncomingContestResolver, + htlcMap map[wire.OutPoint]*channeldb.HTLC) error { + + res := r.htlcResolution + htlcPoint := res.HtlcPoint() + htlc, ok := htlcMap[htlcPoint] + if !ok { + return errors.New( + "htlc for incoming contest resolver unavailable", + ) + } + + r.htlcAmt = htlc.Amt + r.circuitKey = channeldb.CircuitKey{ + ChanID: c.cfg.ShortChanID, + HtlcID: htlc.HtlcIndex, + } + + return nil +} + // supplementSuccessResolver takes a htlcSuccessResolver as it is restored from // the log and fills in missing data from the htlcMap. func (c *ChannelArbitrator) supplementSuccessResolver(r *htlcSuccessResolver, diff --git a/contractcourt/htlc_incoming_contest_resolver.go b/contractcourt/htlc_incoming_contest_resolver.go index 54bccab52..bda680987 100644 --- a/contractcourt/htlc_incoming_contest_resolver.go +++ b/contractcourt/htlc_incoming_contest_resolver.go @@ -27,6 +27,9 @@ type htlcIncomingContestResolver struct { // successfully. htlcExpiry uint32 + // circuitKey describes the incoming htlc that is being resolved. + circuitKey channeldb.CircuitKey + // htlcSuccessResolver is the inner resolver that may be utilized if we // learn of the preimage. htlcSuccessResolver @@ -166,7 +169,7 @@ func (h *htlcIncomingContestResolver) Resolve() (ContractResolver, error) { // identical to HTLC resolution in the link. event, err := h.Registry.NotifyExitHopHtlc( h.payHash, h.htlcAmt, h.htlcExpiry, currentHeight, - hodlChan, nil, + h.circuitKey, hodlChan, nil, ) switch err { case channeldb.ErrInvoiceNotFound: diff --git a/contractcourt/interfaces.go b/contractcourt/interfaces.go index 3333e6b6b..e8c22c97d 100644 --- a/contractcourt/interfaces.go +++ b/contractcourt/interfaces.go @@ -22,7 +22,7 @@ type Registry interface { // the resolution is sent on the passed in hodlChan later. NotifyExitHopHtlc(payHash lntypes.Hash, paidAmount lnwire.MilliSatoshi, expiry uint32, currentHeight int32, - hodlChan chan<- interface{}, + circuitKey channeldb.CircuitKey, hodlChan chan<- interface{}, eob []byte) (*invoices.HodlEvent, error) // HodlUnsubscribeAll unsubscribes from all hodl events. diff --git a/contractcourt/mock_registry_test.go b/contractcourt/mock_registry_test.go index 288ea5ba6..071651c67 100644 --- a/contractcourt/mock_registry_test.go +++ b/contractcourt/mock_registry_test.go @@ -23,7 +23,8 @@ type mockRegistry struct { func (r *mockRegistry) NotifyExitHopHtlc(payHash lntypes.Hash, paidAmount lnwire.MilliSatoshi, expiry uint32, currentHeight int32, - hodlChan chan<- interface{}, eob []byte) (*invoices.HodlEvent, error) { + circuitKey channeldb.CircuitKey, hodlChan chan<- interface{}, + eob []byte) (*invoices.HodlEvent, error) { r.notifyChan <- notifyExitHopData{ hodlChan: hodlChan, diff --git a/htlcswitch/interfaces.go b/htlcswitch/interfaces.go index 52a4c194b..379ef674b 100644 --- a/htlcswitch/interfaces.go +++ b/htlcswitch/interfaces.go @@ -28,7 +28,7 @@ type InvoiceDatabase interface { // for decoding purposes. NotifyExitHopHtlc(payHash lntypes.Hash, paidAmount lnwire.MilliSatoshi, expiry uint32, currentHeight int32, - hodlChan chan<- interface{}, + circuitKey channeldb.CircuitKey, hodlChan chan<- interface{}, eob []byte) (*invoices.HodlEvent, error) // CancelInvoice attempts to cancel the invoice corresponding to the diff --git a/htlcswitch/link.go b/htlcswitch/link.go index 7d14b7af6..3b4d10920 100644 --- a/htlcswitch/link.go +++ b/htlcswitch/link.go @@ -2878,9 +2878,14 @@ func (l *channelLink) processExitHop(pd *lnwallet.PaymentDescriptor, // receive back a resolution event. invoiceHash := lntypes.Hash(pd.RHash) + circuitKey := channeldb.CircuitKey{ + ChanID: l.ShortChanID(), + HtlcID: pd.HtlcIndex, + } + event, err := l.cfg.Registry.NotifyExitHopHtlc( invoiceHash, pd.Amount, pd.Timeout, int32(heightNow), - l.hodlQueue.ChanIn(), eob, + circuitKey, l.hodlQueue.ChanIn(), eob, ) switch err { diff --git a/htlcswitch/mock.go b/htlcswitch/mock.go index 073e825dd..46be963b9 100644 --- a/htlcswitch/mock.go +++ b/htlcswitch/mock.go @@ -793,10 +793,11 @@ func (i *mockInvoiceRegistry) SettleHodlInvoice(preimage lntypes.Preimage) error func (i *mockInvoiceRegistry) NotifyExitHopHtlc(rhash lntypes.Hash, amt lnwire.MilliSatoshi, expiry uint32, currentHeight int32, - hodlChan chan<- interface{}, eob []byte) (*invoices.HodlEvent, error) { + circuitKey channeldb.CircuitKey, hodlChan chan<- interface{}, + eob []byte) (*invoices.HodlEvent, error) { event, err := i.registry.NotifyExitHopHtlc( - rhash, amt, expiry, currentHeight, hodlChan, eob, + rhash, amt, expiry, currentHeight, circuitKey, hodlChan, eob, ) if err != nil { return nil, err diff --git a/invoices/invoiceregistry.go b/invoices/invoiceregistry.go index 2cbba30eb..d1c3da0ca 100644 --- a/invoices/invoiceregistry.go +++ b/invoices/invoiceregistry.go @@ -484,14 +484,15 @@ func (i *InvoiceRegistry) checkHtlcParameters(invoice *channeldb.Invoice, // prevent deadlock. func (i *InvoiceRegistry) NotifyExitHopHtlc(rHash lntypes.Hash, amtPaid lnwire.MilliSatoshi, expiry uint32, currentHeight int32, - hodlChan chan<- interface{}, eob []byte) (*HodlEvent, error) { + circuitKey channeldb.CircuitKey, hodlChan chan<- interface{}, + eob []byte) (*HodlEvent, error) { i.Lock() defer i.Unlock() debugLog := func(s string) { - log.Debugf("Invoice(%x): %v, amt=%v, expiry=%v", - rHash[:], s, amtPaid, expiry) + log.Debugf("Invoice(%x): %v, amt=%v, expiry=%v, circuit=%v", + rHash[:], s, amtPaid, expiry, circuitKey) } // If this isn't a debug invoice, then we'll attempt to settle an diff --git a/invoices/invoiceregistry_test.go b/invoices/invoiceregistry_test.go index ed3ec0771..0394b8bfd 100644 --- a/invoices/invoiceregistry_test.go +++ b/invoices/invoiceregistry_test.go @@ -64,6 +64,15 @@ func newTestContext(t *testing.T) (*InvoiceRegistry, func()) { } } +func getCircuitKey(htlcID uint64) channeldb.CircuitKey { + return channeldb.CircuitKey{ + ChanID: lnwire.ShortChannelID{ + BlockHeight: 1, TxIndex: 2, TxPosition: 3, + }, + HtlcID: htlcID, + } +} + // TestSettleInvoice tests settling of an invoice and related notifications. func TestSettleInvoice(t *testing.T) { registry, cleanup := newTestContext(t) @@ -121,7 +130,8 @@ func TestSettleInvoice(t *testing.T) { // Settle invoice with a slightly higher amount. amtPaid := lnwire.MilliSatoshi(100500) _, err = registry.NotifyExitHopHtlc( - hash, amtPaid, testHtlcExpiry, testCurrentHeight, hodlChan, nil, + hash, amtPaid, testHtlcExpiry, testCurrentHeight, + getCircuitKey(0), hodlChan, nil, ) if err != nil { t.Fatal(err) @@ -153,10 +163,11 @@ func TestSettleInvoice(t *testing.T) { t.Fatal("no update received") } - // Try to settle again. We need this idempotent behaviour after a - // restart. + // Try to settle again with the same htlc id. We need this idempotent + // behaviour after a restart. event, err := registry.NotifyExitHopHtlc( - hash, amtPaid, testHtlcExpiry, testCurrentHeight, hodlChan, nil, + hash, amtPaid, testHtlcExpiry, testCurrentHeight, + getCircuitKey(0), hodlChan, nil, ) if err != nil { t.Fatalf("unexpected NotifyExitHopHtlc error: %v", err) @@ -165,12 +176,12 @@ func TestSettleInvoice(t *testing.T) { t.Fatal("expected settle event") } - // Try to settle again with a higher amount. This payment should also be - // accepted, to prevent any change in behaviour for a paid invoice that - // may open up a probe vector. + // Try to settle again with a new higher-valued htlc. This payment + // should also be accepted, to prevent any change in behaviour for a + // paid invoice that may open up a probe vector. event, err = registry.NotifyExitHopHtlc( hash, amtPaid+600, testHtlcExpiry, testCurrentHeight, - hodlChan, nil, + getCircuitKey(1), hodlChan, nil, ) if err != nil { t.Fatalf("unexpected NotifyExitHopHtlc error: %v", err) @@ -183,7 +194,7 @@ func TestSettleInvoice(t *testing.T) { // would have failed if it were the first payment. event, err = registry.NotifyExitHopHtlc( hash, amtPaid-600, testHtlcExpiry, testCurrentHeight, - hodlChan, nil, + getCircuitKey(2), hodlChan, nil, ) if err != nil { t.Fatalf("unexpected NotifyExitHopHtlc error: %v", err) @@ -306,7 +317,8 @@ func TestCancelInvoice(t *testing.T) { // succeed. hodlChan := make(chan interface{}) event, err := registry.NotifyExitHopHtlc( - hash, amt, testHtlcExpiry, testCurrentHeight, hodlChan, nil, + hash, amt, testHtlcExpiry, testCurrentHeight, + getCircuitKey(0), hodlChan, nil, ) if err != nil { t.Fatal("expected settlement of a canceled invoice to succeed") @@ -382,7 +394,8 @@ func TestHoldInvoice(t *testing.T) { // NotifyExitHopHtlc without a preimage present in the invoice registry // should be possible. event, err := registry.NotifyExitHopHtlc( - hash, amtPaid, testHtlcExpiry, testCurrentHeight, hodlChan, nil, + hash, amtPaid, testHtlcExpiry, testCurrentHeight, + getCircuitKey(0), hodlChan, nil, ) if err != nil { t.Fatalf("expected settle to succeed but got %v", err) @@ -393,7 +406,8 @@ func TestHoldInvoice(t *testing.T) { // Test idempotency. event, err = registry.NotifyExitHopHtlc( - hash, amtPaid, testHtlcExpiry, testCurrentHeight, hodlChan, nil, + hash, amtPaid, testHtlcExpiry, testCurrentHeight, + getCircuitKey(0), hodlChan, nil, ) if err != nil { t.Fatalf("expected settle to succeed but got %v", err) @@ -406,7 +420,7 @@ func TestHoldInvoice(t *testing.T) { // is a replay. event, err = registry.NotifyExitHopHtlc( hash, amtPaid, testHtlcExpiry, testCurrentHeight+10, - getCircuitKey(0), hodlChan, + getCircuitKey(0), hodlChan, nil, ) if err != nil { t.Fatalf("expected settle to succeed but got %v", err) @@ -420,7 +434,7 @@ func TestHoldInvoice(t *testing.T) { // doesn't track individual htlcs it is accepted. event, err = registry.NotifyExitHopHtlc( hash, amtPaid, 1, testCurrentHeight, - getCircuitKey(1), hodlChan, + getCircuitKey(1), hodlChan, nil, ) if err != nil { t.Fatalf("expected settle to succeed but got %v", err) @@ -520,7 +534,8 @@ func TestUnknownInvoice(t *testing.T) { hodlChan := make(chan interface{}) amt := lnwire.MilliSatoshi(100000) _, err := registry.NotifyExitHopHtlc( - hash, amt, testHtlcExpiry, testCurrentHeight, hodlChan, nil, + hash, amt, testHtlcExpiry, testCurrentHeight, + getCircuitKey(0), hodlChan, nil, ) if err != channeldb.ErrInvoiceNotFound { t.Fatal("expected invoice not found error") From 061b34b9248f5aa33aec159b8ac575ed5d61272e Mon Sep 17 00:00:00 2001 From: Joost Jager Date: Mon, 12 Aug 2019 14:58:59 +0200 Subject: [PATCH 07/16] channeldb: add int64 to codec --- channeldb/codec.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/channeldb/codec.go b/channeldb/codec.go index e9afe0e15..508879339 100644 --- a/channeldb/codec.go +++ b/channeldb/codec.go @@ -108,7 +108,7 @@ func WriteElement(w io.Writer, element interface{}) error { return err } - case uint64: + case int64, uint64: if err := binary.Write(w, byteOrder, e); err != nil { return err } @@ -280,7 +280,7 @@ func ReadElement(r io.Reader, element interface{}) error { return err } - case *uint64: + case *int64, *uint64: if err := binary.Read(r, byteOrder, e); err != nil { return err } From 4105142c96863f43e36a9ff3718482efbb052b1f Mon Sep 17 00:00:00 2001 From: Joost Jager Date: Thu, 8 Aug 2019 16:58:28 +0200 Subject: [PATCH 08/16] channeldb+invoices: add invoice htlcs This commit adds a set of htlcs to the Invoice struct and serializes/deserializes this set to/from disk. It is a preparation for accurate invoice accounting across restarts of lnd. A migration is added for the invoice htlcs. In addition to these changes, separate final cltv delta and expiry invoice fields are created and populated. Previously it was required to decode this from the stored payment request. The reason to create a combined commit is to prevent multiple migrations. --- channeldb/db.go | 5 + channeldb/invoice_test.go | 24 ++ channeldb/invoices.go | 207 ++++++++++++++++ .../migration_09_legacy_serialization.go | 4 +- channeldb/migration_11_invoices.go | 225 ++++++++++++++++++ channeldb/migration_11_invoices_test.go | 166 +++++++++++++ channeldb/migrations.go | 4 +- lnrpc/invoicesrpc/addinvoice.go | 2 + 8 files changed, 633 insertions(+), 4 deletions(-) create mode 100644 channeldb/migration_11_invoices.go create mode 100644 channeldb/migration_11_invoices_test.go diff --git a/channeldb/db.go b/channeldb/db.go index 4c96123fb..c53b7a231 100644 --- a/channeldb/db.go +++ b/channeldb/db.go @@ -110,6 +110,11 @@ var ( number: 10, migration: migrateRouteSerialization, }, + { + // Add invoice htlc and cltv delta fields. + number: 11, + migration: migrateInvoices, + }, } // Big endian is the preferred byte order, due to cursor scans over diff --git a/channeldb/invoice_test.go b/channeldb/invoice_test.go index 288edfefa..580fe3d3f 100644 --- a/channeldb/invoice_test.go +++ b/channeldb/invoice_test.go @@ -10,6 +10,26 @@ import ( "github.com/lightningnetwork/lnd/lnwire" ) +var ( + testCircuitKey = CircuitKey{ + ChanID: lnwire.ShortChannelID{ + BlockHeight: 1, TxIndex: 2, TxPosition: 3, + }, + HtlcID: 4, + } + + testHtlcs = map[CircuitKey]*InvoiceHTLC{ + testCircuitKey: { + State: HtlcStateCancelled, + AcceptTime: time.Unix(1, 0), + AcceptHeight: 100, + ResolveTime: time.Unix(2, 0), + Amt: 5200, + Expiry: 150, + }, + } +) + func randInvoice(value lnwire.MilliSatoshi) (*Invoice, error) { var pre [32]byte if _, err := rand.Read(pre[:]); err != nil { @@ -24,6 +44,9 @@ func randInvoice(value lnwire.MilliSatoshi) (*Invoice, error) { PaymentPreimage: pre, Value: value, }, + Htlcs: testHtlcs, + FinalCltvDelta: 50, + Expiry: 4000, } i.Memo = []byte("memo") i.Receipt = []byte("receipt") @@ -59,6 +82,7 @@ func TestInvoiceWorkflow(t *testing.T) { // Use single second precision to avoid false positive test // failures due to the monotonic time component. CreationDate: time.Unix(time.Now().Unix(), 0), + Htlcs: testHtlcs, } fakeInvoice.Memo = []byte("memo") fakeInvoice.Receipt = []byte("receipt") diff --git a/channeldb/invoices.go b/channeldb/invoices.go index 339761df7..18dbf75fa 100644 --- a/channeldb/invoices.go +++ b/channeldb/invoices.go @@ -12,6 +12,7 @@ import ( "github.com/coreos/bbolt" "github.com/lightningnetwork/lnd/lntypes" "github.com/lightningnetwork/lnd/lnwire" + "github.com/lightningnetwork/lnd/tlv" ) var ( @@ -92,6 +93,17 @@ const ( // TODO(halseth): determine the max length payment request when field // lengths are final. MaxPaymentRequestSize = 4096 + + // A set of tlv type definitions used to serialize invoice htlcs to the + // database. + chanIDType tlv.Type = 1 + htlcIDType tlv.Type = 3 + amtType tlv.Type = 5 + acceptHeightType tlv.Type = 7 + acceptTimeType tlv.Type = 9 + resolveTimeType tlv.Type = 11 + expiryHeightType tlv.Type = 13 + stateType tlv.Type = 15 ) // ContractState describes the state the invoice is in. @@ -172,6 +184,13 @@ type Invoice struct { // for this invoice can be stored. PaymentRequest []byte + // FinalCltvDelta is the minimum required number of blocks before htlc + // expiry when the invoice is accepted. + FinalCltvDelta int32 + + // Expiry defines how long after creation this invoice should expire. + Expiry time.Duration + // CreationDate is the exact time the invoice was created. CreationDate time.Time @@ -209,6 +228,52 @@ type Invoice struct { // that the invoice originally didn't specify an amount, or the sender // overpaid. AmtPaid lnwire.MilliSatoshi + + // Htlcs records all htlcs that paid to this invoice. Some of these + // htlcs may have been marked as cancelled. + Htlcs map[CircuitKey]*InvoiceHTLC +} + +// HtlcState defines the states an htlc paying to an invoice can be in. +type HtlcState uint8 + +const ( + // HtlcStateAccepted indicates the htlc is locked-in, but not resolved. + HtlcStateAccepted HtlcState = iota + + // HtlcStateCancelled indicates the htlc is cancelled back to the + // sender. + HtlcStateCancelled + + // HtlcStateSettled indicates the htlc is settled. + HtlcStateSettled +) + +// InvoiceHTLC contains details about an htlc paying to this invoice. +type InvoiceHTLC struct { + // Amt is the amount that is carried by this htlc. + Amt lnwire.MilliSatoshi + + // AcceptHeight is the block height at which the invoice registry + // decided to accept this htlc as a payment to the invoice. At this + // height, the invoice cltv delay must have been met. + AcceptHeight uint32 + + // AcceptTime is the wall clock time at which the invoice registry + // decided to accept the htlc. + AcceptTime time.Time + + // ResolveTime is the wall clock time at which the invoice registry + // decided to settle the htlc. + ResolveTime time.Time + + // Expiry is the expiry height of this htlc. + Expiry uint32 + + // 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. + State HtlcState } func validateInvoice(i *Invoice) error { @@ -865,6 +930,11 @@ func putInvoice(invoices, invoiceIndex, addIndex *bbolt.Bucket, return nextAddSeqNo, nil } +// serializeInvoice serializes an invoice to a writer. +// +// Note: this function is in use for a migration. Before making changes that +// would modify the on disk format, make a copy of the original code and store +// it with the migration. func serializeInvoice(w io.Writer, i *Invoice) error { if err := wire.WriteVarBytes(w, 0, i.Memo[:]); err != nil { return err @@ -876,6 +946,14 @@ func serializeInvoice(w io.Writer, i *Invoice) error { return err } + if err := binary.Write(w, byteOrder, i.FinalCltvDelta); err != nil { + return err + } + + if err := binary.Write(w, byteOrder, int64(i.Expiry)); err != nil { + return err + } + birthBytes, err := i.CreationDate.MarshalBinary() if err != nil { return err @@ -918,6 +996,57 @@ func serializeInvoice(w io.Writer, i *Invoice) error { return err } + if err := serializeHtlcs(w, i.Htlcs); err != nil { + return err + } + + return nil +} + +// serializeHtlcs serializes a map containing circuit keys and invoice htlcs to +// a writer. +func serializeHtlcs(w io.Writer, htlcs map[CircuitKey]*InvoiceHTLC) error { + for key, htlc := range htlcs { + // Encode the htlc in a tlv stream. + chanID := key.ChanID.ToUint64() + amt := uint64(htlc.Amt) + acceptTime := uint64(htlc.AcceptTime.UnixNano()) + resolveTime := uint64(htlc.ResolveTime.UnixNano()) + state := uint8(htlc.State) + + tlvStream, err := tlv.NewStream( + tlv.MakePrimitiveRecord(chanIDType, &chanID), + tlv.MakePrimitiveRecord(htlcIDType, &key.HtlcID), + tlv.MakePrimitiveRecord(amtType, &amt), + tlv.MakePrimitiveRecord( + acceptHeightType, &htlc.AcceptHeight, + ), + tlv.MakePrimitiveRecord(acceptTimeType, &acceptTime), + tlv.MakePrimitiveRecord(resolveTimeType, &resolveTime), + tlv.MakePrimitiveRecord(expiryHeightType, &htlc.Expiry), + tlv.MakePrimitiveRecord(stateType, &state), + ) + if err != nil { + return err + } + + var b bytes.Buffer + if err := tlvStream.Encode(&b); err != nil { + return err + } + + // Write the length of the tlv stream followed by the stream + // bytes. + err = binary.Write(w, byteOrder, uint64(b.Len())) + if err != nil { + return err + } + + if _, err := w.Write(b.Bytes()); err != nil { + return err + } + } + return nil } @@ -951,6 +1080,16 @@ func deserializeInvoice(r io.Reader) (Invoice, error) { return invoice, err } + if err := binary.Read(r, byteOrder, &invoice.FinalCltvDelta); err != nil { + return invoice, err + } + + var expiry int64 + if err := binary.Read(r, byteOrder, &expiry); err != nil { + return invoice, err + } + invoice.Expiry = time.Duration(expiry) + birthBytes, err := wire.ReadVarBytes(r, 0, 300, "birth") if err != nil { return invoice, err @@ -990,9 +1129,77 @@ func deserializeInvoice(r io.Reader) (Invoice, error) { return invoice, err } + invoice.Htlcs, err = deserializeHtlcs(r) + if err != nil { + return Invoice{}, err + } + return invoice, nil } +// deserializeHtlcs reads a list of invoice htlcs from a reader and returns it +// as a map. +func deserializeHtlcs(r io.Reader) (map[CircuitKey]*InvoiceHTLC, error) { + htlcs := make(map[CircuitKey]*InvoiceHTLC, 0) + + for { + // Read the length of the tlv stream for this htlc. + var streamLen uint64 + if err := binary.Read(r, byteOrder, &streamLen); err != nil { + if err == io.EOF { + break + } + + return nil, err + } + + streamBytes := make([]byte, streamLen) + if _, err := r.Read(streamBytes); err != nil { + return nil, err + } + streamReader := bytes.NewReader(streamBytes) + + // Decode the contents into the htlc fields. + var ( + htlc InvoiceHTLC + key CircuitKey + chanID uint64 + state uint8 + acceptTime, resolveTime uint64 + amt uint64 + ) + tlvStream, err := tlv.NewStream( + tlv.MakePrimitiveRecord(chanIDType, &chanID), + tlv.MakePrimitiveRecord(htlcIDType, &key.HtlcID), + tlv.MakePrimitiveRecord(amtType, &amt), + tlv.MakePrimitiveRecord( + acceptHeightType, &htlc.AcceptHeight, + ), + tlv.MakePrimitiveRecord(acceptTimeType, &acceptTime), + tlv.MakePrimitiveRecord(resolveTimeType, &resolveTime), + tlv.MakePrimitiveRecord(expiryHeightType, &htlc.Expiry), + tlv.MakePrimitiveRecord(stateType, &state), + ) + if err != nil { + return nil, err + } + + if err := tlvStream.Decode(streamReader); err != nil { + return nil, err + } + + key.ChanID = lnwire.NewShortChanIDFromInt(chanID) + htlc.AcceptTime = time.Unix(0, int64(acceptTime)) + htlc.ResolveTime = time.Unix(0, int64(resolveTime)) + htlc.State = HtlcState(state) + htlc.Amt = lnwire.MilliSatoshi(amt) + + htlcs[key] = &htlc + } + + return htlcs, nil +} + func acceptOrSettleInvoice(invoices, settleIndex *bbolt.Bucket, invoiceNum []byte, amtPaid lnwire.MilliSatoshi, checkHtlcParameters func(invoice *Invoice) error) ( diff --git a/channeldb/migration_09_legacy_serialization.go b/channeldb/migration_09_legacy_serialization.go index 52e765ed2..56e36ab1a 100644 --- a/channeldb/migration_09_legacy_serialization.go +++ b/channeldb/migration_09_legacy_serialization.go @@ -177,7 +177,7 @@ func fetchPaymentStatusTx(tx *bbolt.Tx, paymentHash [32]byte) (PaymentStatus, er func serializeOutgoingPayment(w io.Writer, p *outgoingPayment) error { var scratch [8]byte - if err := serializeInvoice(w, &p.Invoice); err != nil { + if err := serializeInvoiceLegacy(w, &p.Invoice); err != nil { return err } @@ -218,7 +218,7 @@ func deserializeOutgoingPayment(r io.Reader) (*outgoingPayment, error) { p := &outgoingPayment{} - inv, err := deserializeInvoice(r) + inv, err := deserializeInvoiceLegacy(r) if err != nil { return nil, err } diff --git a/channeldb/migration_11_invoices.go b/channeldb/migration_11_invoices.go new file mode 100644 index 000000000..e242309ba --- /dev/null +++ b/channeldb/migration_11_invoices.go @@ -0,0 +1,225 @@ +package channeldb + +import ( + "bytes" + "encoding/binary" + "fmt" + "io" + + bitcoinCfg "github.com/btcsuite/btcd/chaincfg" + "github.com/btcsuite/btcd/wire" + "github.com/coreos/bbolt" + "github.com/lightningnetwork/lnd/lnwire" + "github.com/lightningnetwork/lnd/zpay32" + litecoinCfg "github.com/ltcsuite/ltcd/chaincfg" +) + +// migrateInvoices adds invoice htlcs and a separate cltv delta field to the +// invoices. +func migrateInvoices(tx *bbolt.Tx) error { + log.Infof("Migrating invoices to new invoice format") + + invoiceB := tx.Bucket(invoiceBucket) + if invoiceB == nil { + return nil + } + + // Iterate through the entire key space of the top-level invoice bucket. + // If key with a non-nil value stores the next invoice ID which maps to + // the corresponding invoice. Store those keys first, because it isn't + // safe to modify the bucket inside a ForEach loop. + var invoiceKeys [][]byte + err := invoiceB.ForEach(func(k, v []byte) error { + if v == nil { + return nil + } + + invoiceKeys = append(invoiceKeys, k) + + return nil + }) + if err != nil { + return err + } + + nets := []*bitcoinCfg.Params{ + &bitcoinCfg.MainNetParams, &bitcoinCfg.SimNetParams, + &bitcoinCfg.RegressionNetParams, &bitcoinCfg.TestNet3Params, + } + + ltcNets := []*litecoinCfg.Params{ + &litecoinCfg.MainNetParams, &litecoinCfg.SimNetParams, + &litecoinCfg.RegressionNetParams, &litecoinCfg.TestNet4Params, + } + for _, net := range ltcNets { + var convertedNet bitcoinCfg.Params + convertedNet.Bech32HRPSegwit = net.Bech32HRPSegwit + nets = append(nets, &convertedNet) + } + + // Iterate over all stored keys and migrate the invoices. + for _, k := range invoiceKeys { + v := invoiceB.Get(k) + + // Deserialize the invoice with the deserializing function that + // was in use for this version of the database. + invoiceReader := bytes.NewReader(v) + invoice, err := deserializeInvoiceLegacy(invoiceReader) + if err != nil { + return err + } + + // Try to decode the payment request for every possible net to + // avoid passing a the active network to channeldb. This would + // be a layering violation, while this migration is only running + // once and will likely be removed in the future. + var payReq *zpay32.Invoice + for _, net := range nets { + payReq, err = zpay32.Decode( + string(invoice.PaymentRequest), net, + ) + if err == nil { + break + } + } + if payReq == nil { + return fmt.Errorf("cannot decode payreq") + } + invoice.FinalCltvDelta = int32(payReq.MinFinalCLTVExpiry()) + invoice.Expiry = payReq.Expiry() + + // Serialize the invoice in the new format and use it to replace + // the old invoice in the database. + var buf bytes.Buffer + if err := serializeInvoice(&buf, &invoice); err != nil { + return err + } + + err = invoiceB.Put(k, buf.Bytes()) + if err != nil { + return err + } + } + + log.Infof("Migration of invoices completed!") + return nil +} + +func deserializeInvoiceLegacy(r io.Reader) (Invoice, error) { + var err error + invoice := Invoice{} + + // TODO(roasbeef): use read full everywhere + invoice.Memo, err = wire.ReadVarBytes(r, 0, MaxMemoSize, "") + if err != nil { + return invoice, err + } + invoice.Receipt, err = wire.ReadVarBytes(r, 0, MaxReceiptSize, "") + if err != nil { + return invoice, err + } + + invoice.PaymentRequest, err = wire.ReadVarBytes(r, 0, MaxPaymentRequestSize, "") + if err != nil { + return invoice, err + } + + birthBytes, err := wire.ReadVarBytes(r, 0, 300, "birth") + if err != nil { + return invoice, err + } + if err := invoice.CreationDate.UnmarshalBinary(birthBytes); err != nil { + return invoice, err + } + + settledBytes, err := wire.ReadVarBytes(r, 0, 300, "settled") + if err != nil { + return invoice, err + } + if err := invoice.SettleDate.UnmarshalBinary(settledBytes); err != nil { + return invoice, err + } + + if _, err := io.ReadFull(r, invoice.Terms.PaymentPreimage[:]); err != nil { + return invoice, err + } + var scratch [8]byte + if _, err := io.ReadFull(r, scratch[:]); err != nil { + return invoice, err + } + invoice.Terms.Value = lnwire.MilliSatoshi(byteOrder.Uint64(scratch[:])) + + if err := binary.Read(r, byteOrder, &invoice.Terms.State); err != nil { + return invoice, err + } + + if err := binary.Read(r, byteOrder, &invoice.AddIndex); err != nil { + return invoice, err + } + if err := binary.Read(r, byteOrder, &invoice.SettleIndex); err != nil { + return invoice, err + } + if err := binary.Read(r, byteOrder, &invoice.AmtPaid); err != nil { + return invoice, err + } + + return invoice, nil +} + +// serializeInvoiceLegacy serializes an invoice in the format of the previous db +// version. +func serializeInvoiceLegacy(w io.Writer, i *Invoice) error { + if err := wire.WriteVarBytes(w, 0, i.Memo[:]); err != nil { + return err + } + if err := wire.WriteVarBytes(w, 0, i.Receipt[:]); err != nil { + return err + } + if err := wire.WriteVarBytes(w, 0, i.PaymentRequest[:]); err != nil { + return err + } + + birthBytes, err := i.CreationDate.MarshalBinary() + if err != nil { + return err + } + + if err := wire.WriteVarBytes(w, 0, birthBytes); err != nil { + return err + } + + settleBytes, err := i.SettleDate.MarshalBinary() + if err != nil { + return err + } + + if err := wire.WriteVarBytes(w, 0, settleBytes); err != nil { + return err + } + + if _, err := w.Write(i.Terms.PaymentPreimage[:]); err != nil { + return err + } + + var scratch [8]byte + byteOrder.PutUint64(scratch[:], uint64(i.Terms.Value)) + if _, err := w.Write(scratch[:]); err != nil { + return err + } + + if err := binary.Write(w, byteOrder, i.Terms.State); err != nil { + return err + } + + if err := binary.Write(w, byteOrder, i.AddIndex); err != nil { + return err + } + if err := binary.Write(w, byteOrder, i.SettleIndex); err != nil { + return err + } + if err := binary.Write(w, byteOrder, int64(i.AmtPaid)); err != nil { + return err + } + + return nil +} diff --git a/channeldb/migration_11_invoices_test.go b/channeldb/migration_11_invoices_test.go new file mode 100644 index 000000000..9739af80a --- /dev/null +++ b/channeldb/migration_11_invoices_test.go @@ -0,0 +1,166 @@ +package channeldb + +import ( + "bytes" + "fmt" + "testing" + "time" + + "github.com/btcsuite/btcd/btcec" + bitcoinCfg "github.com/btcsuite/btcd/chaincfg" + "github.com/coreos/bbolt" + "github.com/lightningnetwork/lnd/zpay32" + litecoinCfg "github.com/ltcsuite/ltcd/chaincfg" +) + +var ( + testPrivKeyBytes = []byte{ + 0x2b, 0xd8, 0x06, 0xc9, 0x7f, 0x0e, 0x00, 0xaf, + 0x1a, 0x1f, 0xc3, 0x32, 0x8f, 0xa7, 0x63, 0xa9, + 0x26, 0x97, 0x23, 0xc8, 0xdb, 0x8f, 0xac, 0x4f, + 0x93, 0xaf, 0x71, 0xdb, 0x18, 0x6d, 0x6e, 0x90, + } + + testCltvDelta = int32(50) +) + +// TestMigrateInvoices checks that invoices are migrated correctly. +func TestMigrateInvoices(t *testing.T) { + t.Parallel() + + payReqBtc, err := getPayReq(&bitcoinCfg.MainNetParams) + if err != nil { + t.Fatal(err) + } + + var ltcNetParams bitcoinCfg.Params + ltcNetParams.Bech32HRPSegwit = litecoinCfg.MainNetParams.Bech32HRPSegwit + payReqLtc, err := getPayReq(<cNetParams) + if err != nil { + t.Fatal(err) + } + + invoices := []Invoice{ + { + PaymentRequest: []byte(payReqBtc), + }, + { + PaymentRequest: []byte(payReqLtc), + }, + } + + beforeMigrationFunc := func(d *DB) { + err := d.Update(func(tx *bbolt.Tx) error { + invoicesBucket, err := tx.CreateBucketIfNotExists( + invoiceBucket, + ) + if err != nil { + return err + } + + invoiceNum := uint32(1) + for _, invoice := range invoices { + var invoiceKey [4]byte + byteOrder.PutUint32(invoiceKey[:], invoiceNum) + invoiceNum++ + + var buf bytes.Buffer + err := serializeInvoiceLegacy(&buf, &invoice) + if err != nil { + return err + } + + err = invoicesBucket.Put( + invoiceKey[:], buf.Bytes(), + ) + if err != nil { + return err + } + } + + return nil + }) + if err != nil { + t.Fatal(err) + } + } + + // Verify that all invoices were migrated. + afterMigrationFunc := func(d *DB) { + meta, err := d.FetchMeta(nil) + if err != nil { + t.Fatal(err) + } + + if meta.DbVersionNumber != 1 { + t.Fatal("migration 'invoices' wasn't applied") + } + + dbInvoices, err := d.FetchAllInvoices(false) + if err != nil { + t.Fatalf("unable to fetch invoices: %v", err) + } + + if len(invoices) != len(dbInvoices) { + t.Fatalf("expected %d invoices, got %d", len(invoices), + len(dbInvoices)) + } + + for _, dbInvoice := range dbInvoices { + if dbInvoice.FinalCltvDelta != testCltvDelta { + t.Fatal("incorrect final cltv delta") + } + if dbInvoice.Expiry != 3600*time.Second { + t.Fatal("incorrect expiry") + } + if len(dbInvoice.Htlcs) != 0 { + t.Fatal("expected no htlcs after migration") + } + } + } + + applyMigration(t, + beforeMigrationFunc, + afterMigrationFunc, + migrateInvoices, + false) +} + +// signDigestCompact generates a test signature to be used in the generation of +// test payment requests. +func signDigestCompact(hash []byte) ([]byte, error) { + // Should the signature reference a compressed public key or not. + isCompressedKey := true + + privKey, _ := btcec.PrivKeyFromBytes(btcec.S256(), testPrivKeyBytes) + + // btcec.SignCompact returns a pubkey-recoverable signature + sig, err := btcec.SignCompact( + btcec.S256(), privKey, hash, isCompressedKey, + ) + if err != nil { + return nil, fmt.Errorf("can't sign the hash: %v", err) + } + + return sig, nil +} + +// getPayReq creates a payment request for the given net. +func getPayReq(net *bitcoinCfg.Params) (string, error) { + options := []func(*zpay32.Invoice){ + zpay32.CLTVExpiry(uint64(testCltvDelta)), + zpay32.Description("test"), + } + + payReq, err := zpay32.NewInvoice( + net, [32]byte{}, time.Unix(1, 0), options..., + ) + if err != nil { + return "", err + } + return payReq.Encode( + zpay32.MessageSigner{ + SignCompact: signDigestCompact, + }, + ) +} diff --git a/channeldb/migrations.go b/channeldb/migrations.go index d875dc8db..3423a6d77 100644 --- a/channeldb/migrations.go +++ b/channeldb/migrations.go @@ -168,7 +168,7 @@ func migrateInvoiceTimeSeries(tx *bbolt.Tx) error { invoiceBytesCopy = append(invoiceBytesCopy, padding...) invoiceReader := bytes.NewReader(invoiceBytesCopy) - invoice, err := deserializeInvoice(invoiceReader) + invoice, err := deserializeInvoiceLegacy(invoiceReader) if err != nil { return fmt.Errorf("unable to decode invoice: %v", err) } @@ -227,7 +227,7 @@ func migrateInvoiceTimeSeries(tx *bbolt.Tx) error { // We've fully migrated an invoice, so we'll now update the // invoice in-place. var b bytes.Buffer - if err := serializeInvoice(&b, &invoice); err != nil { + if err := serializeInvoiceLegacy(&b, &invoice); err != nil { return err } diff --git a/lnrpc/invoicesrpc/addinvoice.go b/lnrpc/invoicesrpc/addinvoice.go index c79b60b81..055b564ce 100644 --- a/lnrpc/invoicesrpc/addinvoice.go +++ b/lnrpc/invoicesrpc/addinvoice.go @@ -394,6 +394,8 @@ func AddInvoice(ctx context.Context, cfg *AddInvoiceConfig, Memo: []byte(invoice.Memo), Receipt: invoice.Receipt, PaymentRequest: []byte(payReqString), + FinalCltvDelta: int32(payReq.MinFinalCLTVExpiry()), + Expiry: payReq.Expiry(), Terms: channeldb.ContractTerm{ Value: amtMSat, PaymentPreimage: paymentPreimage, From c1345a411743fb3d8a62f7b26d5f4f5fde1d4c0e Mon Sep 17 00:00:00 2001 From: Joost Jager Date: Tue, 20 Aug 2019 14:54:39 +0200 Subject: [PATCH 09/16] multi: use separate cltv expiry field from invoice Now that the Invoice struct contains the decoded final cltv delta value, the decoding of payment requests can be removed from the invoice registry. --- contractcourt/channel_arbitrator.go | 2 +- contractcourt/interfaces.go | 6 ++--- contractcourt/mock_registry_test.go | 4 ++-- htlcswitch/interfaces.go | 6 ++--- htlcswitch/link_test.go | 16 ++++++------- htlcswitch/mock.go | 10 ++++---- htlcswitch/test_utils.go | 1 + invoices/invoiceregistry.go | 37 ++++++----------------------- invoices/invoiceregistry_test.go | 10 +++----- lnrpc/invoicesrpc/utils.go | 11 ++------- rpcserver.go | 2 +- server.go | 12 +--------- 12 files changed, 34 insertions(+), 83 deletions(-) diff --git a/contractcourt/channel_arbitrator.go b/contractcourt/channel_arbitrator.go index e05a5863b..8fd9f9407 100644 --- a/contractcourt/channel_arbitrator.go +++ b/contractcourt/channel_arbitrator.go @@ -1348,7 +1348,7 @@ func (c *ChannelArbitrator) isPreimageAvailable(hash lntypes.Hash) (bool, // than the invoice cltv delta. We don't want to go to chain only to // have the incoming contest resolver decide that we don't want to // settle this invoice. - invoice, _, err := c.cfg.Registry.LookupInvoice(hash) + invoice, err := c.cfg.Registry.LookupInvoice(hash) switch err { case nil: case channeldb.ErrInvoiceNotFound, channeldb.ErrNoInvoicesCreated: diff --git a/contractcourt/interfaces.go b/contractcourt/interfaces.go index e8c22c97d..c928f4289 100644 --- a/contractcourt/interfaces.go +++ b/contractcourt/interfaces.go @@ -10,10 +10,8 @@ import ( // Registry is an interface which represents the invoice registry. type Registry interface { // LookupInvoice attempts to look up an invoice according to its 32 - // byte payment hash. This method should also reutrn the min final CLTV - // delta for this invoice. We'll use this to ensure that the HTLC - // extended to us gives us enough time to settle as we prescribe. - LookupInvoice(lntypes.Hash) (channeldb.Invoice, uint32, error) + // byte payment hash. + LookupInvoice(lntypes.Hash) (channeldb.Invoice, error) // NotifyExitHopHtlc attempts to mark an invoice as settled. If the // invoice is a debug invoice, then this method is a noop as debug diff --git a/contractcourt/mock_registry_test.go b/contractcourt/mock_registry_test.go index 071651c67..e45022893 100644 --- a/contractcourt/mock_registry_test.go +++ b/contractcourt/mock_registry_test.go @@ -39,8 +39,8 @@ func (r *mockRegistry) NotifyExitHopHtlc(payHash lntypes.Hash, func (r *mockRegistry) HodlUnsubscribeAll(subscriber chan<- interface{}) {} -func (r *mockRegistry) LookupInvoice(lntypes.Hash) (channeldb.Invoice, uint32, +func (r *mockRegistry) LookupInvoice(lntypes.Hash) (channeldb.Invoice, error) { - return channeldb.Invoice{}, 0, channeldb.ErrInvoiceNotFound + return channeldb.Invoice{}, channeldb.ErrInvoiceNotFound } diff --git a/htlcswitch/interfaces.go b/htlcswitch/interfaces.go index 379ef674b..d4bcc0a80 100644 --- a/htlcswitch/interfaces.go +++ b/htlcswitch/interfaces.go @@ -14,10 +14,8 @@ import ( // which may search, lookup and settle invoices. type InvoiceDatabase interface { // LookupInvoice attempts to look up an invoice according to its 32 - // byte payment hash. This method should also reutrn the min final CLTV - // delta for this invoice. We'll use this to ensure that the HTLC - // extended to us gives us enough time to settle as we prescribe. - LookupInvoice(lntypes.Hash) (channeldb.Invoice, uint32, error) + // byte payment hash. + LookupInvoice(lntypes.Hash) (channeldb.Invoice, error) // NotifyExitHopHtlc attempts to mark an invoice as settled. If the // invoice is a debug invoice, then this method is a noop as debug diff --git a/htlcswitch/link_test.go b/htlcswitch/link_test.go index d567e9528..8d17dbb66 100644 --- a/htlcswitch/link_test.go +++ b/htlcswitch/link_test.go @@ -238,7 +238,7 @@ func TestChannelLinkSingleHopPayment(t *testing.T) { // Check that alice invoice was settled and bandwidth of HTLC // links was changed. - invoice, _, err := receiver.registry.LookupInvoice(rhash) + invoice, err := receiver.registry.LookupInvoice(rhash) if err != nil { t.Fatalf("unable to get invoice: %v", err) } @@ -498,7 +498,7 @@ func testChannelLinkMultiHopPayment(t *testing.T, // Check that Carol invoice was settled and bandwidth of HTLC // links were changed. - invoice, _, err := receiver.registry.LookupInvoice(rhash) + invoice, err := receiver.registry.LookupInvoice(rhash) if err != nil { t.Fatalf("unable to get invoice: %v", err) } @@ -912,7 +912,7 @@ func TestUpdateForwardingPolicy(t *testing.T) { // Carol's invoice should now be shown as settled as the payment // succeeded. - invoice, _, err := n.carolServer.registry.LookupInvoice(payResp) + invoice, err := n.carolServer.registry.LookupInvoice(payResp) if err != nil { t.Fatalf("unable to get invoice: %v", err) } @@ -1030,7 +1030,7 @@ func TestChannelLinkMultiHopInsufficientPayment(t *testing.T) { // Check that alice invoice wasn't settled and bandwidth of htlc // links hasn't been changed. - invoice, _, err := receiver.registry.LookupInvoice(rhash) + invoice, err := receiver.registry.LookupInvoice(rhash) if err != nil { t.Fatalf("unable to get invoice: %v", err) } @@ -1215,7 +1215,7 @@ func TestChannelLinkMultiHopUnknownNextHop(t *testing.T) { // Check that alice invoice wasn't settled and bandwidth of htlc // links hasn't been changed. - invoice, _, err := receiver.registry.LookupInvoice(rhash) + invoice, err := receiver.registry.LookupInvoice(rhash) if err != nil { t.Fatalf("unable to get invoice: %v", err) } @@ -1330,7 +1330,7 @@ func TestChannelLinkMultiHopDecodeError(t *testing.T) { // Check that alice invoice wasn't settled and bandwidth of htlc // links hasn't been changed. - invoice, _, err := receiver.registry.LookupInvoice(rhash) + invoice, err := receiver.registry.LookupInvoice(rhash) if err != nil { t.Fatalf("unable to get invoice: %v", err) } @@ -3455,7 +3455,7 @@ func TestChannelRetransmission(t *testing.T) { // Check that alice invoice wasn't settled and // bandwidth of htlc links hasn't been changed. - invoice, _, err = receiver.registry.LookupInvoice(rhash) + invoice, err = receiver.registry.LookupInvoice(rhash) if err != nil { err = errors.Errorf("unable to get invoice: %v", err) continue @@ -3974,7 +3974,7 @@ func TestChannelLinkAcceptOverpay(t *testing.T) { // Even though we sent 2x what was asked for, Carol should still have // accepted the payment and marked it as settled. - invoice, _, err := receiver.registry.LookupInvoice(rhash) + invoice, err := receiver.registry.LookupInvoice(rhash) if err != nil { t.Fatalf("unable to get invoice: %v", err) } diff --git a/htlcswitch/mock.go b/htlcswitch/mock.go index 46be963b9..34651b5e3 100644 --- a/htlcswitch/mock.go +++ b/htlcswitch/mock.go @@ -768,13 +768,9 @@ func newMockRegistry(minDelta uint32) *mockInvoiceRegistry { panic(err) } - decodeExpiry := func(invoice string) (uint32, error) { - return testInvoiceCltvExpiry, nil - } - finalCltvRejectDelta := int32(5) - registry := invoices.NewRegistry(cdb, decodeExpiry, finalCltvRejectDelta) + registry := invoices.NewRegistry(cdb, finalCltvRejectDelta) registry.Start() return &mockInvoiceRegistry{ @@ -783,7 +779,9 @@ func newMockRegistry(minDelta uint32) *mockInvoiceRegistry { } } -func (i *mockInvoiceRegistry) LookupInvoice(rHash lntypes.Hash) (channeldb.Invoice, uint32, error) { +func (i *mockInvoiceRegistry) LookupInvoice(rHash lntypes.Hash) ( + channeldb.Invoice, error) { + return i.registry.LookupInvoice(rHash) } diff --git a/htlcswitch/test_utils.go b/htlcswitch/test_utils.go index 212e20e3a..d0fe1abbb 100644 --- a/htlcswitch/test_utils.go +++ b/htlcswitch/test_utils.go @@ -557,6 +557,7 @@ func generatePaymentWithPreimage(invoiceAmt, htlcAmt lnwire.MilliSatoshi, Value: invoiceAmt, PaymentPreimage: preimage, }, + FinalCltvDelta: testInvoiceCltvExpiry, } htlc := &lnwire.UpdateAddHTLC{ diff --git a/invoices/invoiceregistry.go b/invoices/invoiceregistry.go index d1c3da0ca..cfe8cb60b 100644 --- a/invoices/invoiceregistry.go +++ b/invoices/invoiceregistry.go @@ -58,10 +58,6 @@ type InvoiceRegistry struct { // new single invoice subscriptions are carried. invoiceEvents chan interface{} - // decodeFinalCltvExpiry is a function used to decode the final expiry - // value from the payment request. - decodeFinalCltvExpiry func(invoice string) (uint32, error) - // subscriptions is a map from a payment hash to a list of subscribers. // It is used for efficient notification of links. hodlSubscriptions map[lntypes.Hash]map[chan<- interface{}]struct{} @@ -85,8 +81,7 @@ type InvoiceRegistry struct { // wraps the persistent on-disk invoice storage with an additional in-memory // layer. The in-memory layer is in place such that debug invoices can be added // which are volatile yet available system wide within the daemon. -func NewRegistry(cdb *channeldb.DB, decodeFinalCltvExpiry func(invoice string) ( - uint32, error), finalCltvRejectDelta int32) *InvoiceRegistry { +func NewRegistry(cdb *channeldb.DB, finalCltvRejectDelta int32) *InvoiceRegistry { return &InvoiceRegistry{ cdb: cdb, @@ -97,7 +92,6 @@ func NewRegistry(cdb *channeldb.DB, decodeFinalCltvExpiry func(invoice string) ( invoiceEvents: make(chan interface{}, 100), hodlSubscriptions: make(map[lntypes.Hash]map[chan<- interface{}]struct{}), hodlReverseSubscriptions: make(map[chan<- interface{}]map[lntypes.Hash]struct{}), - decodeFinalCltvExpiry: decodeFinalCltvExpiry, finalCltvRejectDelta: finalCltvRejectDelta, quit: make(chan struct{}), } @@ -404,26 +398,15 @@ func (i *InvoiceRegistry) AddInvoice(invoice *channeldb.Invoice, } // LookupInvoice looks up an invoice by its payment hash (R-Hash), if found -// then we're able to pull the funds pending within an HTLC. We'll also return -// what the expected min final CLTV delta is, pre-parsed from the payment -// request. This may be used by callers to determine if an HTLC is well formed -// according to the cltv delta. +// then we're able to pull the funds pending within an HTLC. // // TODO(roasbeef): ignore if settled? -func (i *InvoiceRegistry) LookupInvoice(rHash lntypes.Hash) (channeldb.Invoice, uint32, error) { +func (i *InvoiceRegistry) LookupInvoice(rHash lntypes.Hash) (channeldb.Invoice, + error) { + // We'll check the database to see if there's an existing matching // invoice. - invoice, err := i.cdb.LookupInvoice(rHash) - if err != nil { - return channeldb.Invoice{}, 0, err - } - - expiry, err := i.decodeFinalCltvExpiry(string(invoice.PaymentRequest)) - if err != nil { - return channeldb.Invoice{}, 0, err - } - - return invoice, expiry, nil + return i.cdb.LookupInvoice(rHash) } // checkHtlcParameters is a callback used inside invoice db transactions to @@ -454,17 +437,11 @@ func (i *InvoiceRegistry) checkHtlcParameters(invoice *channeldb.Invoice, return channeldb.ErrInvoiceAlreadySettled } - // The invoice is still open. Check the expiry. - expiry, err := i.decodeFinalCltvExpiry(string(invoice.PaymentRequest)) - if err != nil { - return err - } - if htlcExpiry < uint32(currentHeight+i.finalCltvRejectDelta) { return ErrInvoiceExpiryTooSoon } - if htlcExpiry < uint32(currentHeight)+expiry { + if htlcExpiry < uint32(currentHeight+invoice.FinalCltvDelta) { return ErrInvoiceExpiryTooSoon } diff --git a/invoices/invoiceregistry_test.go b/invoices/invoiceregistry_test.go index 0394b8bfd..c7873d5bb 100644 --- a/invoices/invoiceregistry_test.go +++ b/invoices/invoiceregistry_test.go @@ -30,10 +30,6 @@ var ( testCurrentHeight = int32(1) ) -func decodeExpiry(payReq string) (uint32, error) { - return uint32(testInvoiceCltvDelta), nil -} - var ( testInvoice = &channeldb.Invoice{ Terms: channeldb.ContractTerm{ @@ -50,7 +46,7 @@ func newTestContext(t *testing.T) (*InvoiceRegistry, func()) { } // Instantiate and start the invoice registry. - registry := NewRegistry(cdb, decodeExpiry, testFinalCltvRejectDelta) + registry := NewRegistry(cdb, testFinalCltvRejectDelta) err = registry.Start() if err != nil { @@ -204,7 +200,7 @@ func TestSettleInvoice(t *testing.T) { } // Check that settled amount remains unchanged. - inv, _, err := registry.LookupInvoice(hash) + inv, err := registry.LookupInvoice(hash) if err != nil { t.Fatal(err) } @@ -337,7 +333,7 @@ func TestHoldInvoice(t *testing.T) { defer cleanup() // Instantiate and start the invoice registry. - registry := NewRegistry(cdb, decodeExpiry, testFinalCltvRejectDelta) + registry := NewRegistry(cdb, testFinalCltvRejectDelta) err = registry.Start() if err != nil { diff --git a/lnrpc/invoicesrpc/utils.go b/lnrpc/invoicesrpc/utils.go index da7db52c4..7d39d3324 100644 --- a/lnrpc/invoicesrpc/utils.go +++ b/lnrpc/invoicesrpc/utils.go @@ -36,13 +36,6 @@ func CreateRPCInvoice(invoice *channeldb.Invoice, settleDate = invoice.SettleDate.Unix() } - // Expiry time will default to 3600 seconds if not specified - // explicitly. - expiry := int64(decoded.Expiry().Seconds()) - - // The expiry will default to 9 blocks if not specified explicitly. - cltvExpiry := decoded.MinFinalCLTVExpiry() - // Convert between the `lnrpc` and `routing` types. routeHints := CreateRPCRouteHints(decoded.RouteHints) @@ -77,8 +70,8 @@ func CreateRPCInvoice(invoice *channeldb.Invoice, Settled: isSettled, PaymentRequest: paymentRequest, DescriptionHash: descHash, - Expiry: expiry, - CltvExpiry: cltvExpiry, + Expiry: int64(invoice.Expiry.Seconds()), + CltvExpiry: uint64(invoice.FinalCltvDelta), FallbackAddr: fallbackAddr, RouteHints: routeHints, AddIndex: invoice.AddIndex, diff --git a/rpcserver.go b/rpcserver.go index 5cb959c5f..5df883565 100644 --- a/rpcserver.go +++ b/rpcserver.go @@ -3493,7 +3493,7 @@ func (r *rpcServer) LookupInvoice(ctx context.Context, rpcsLog.Tracef("[lookupinvoice] searching for invoice %x", payHash[:]) - invoice, _, err := r.server.invoices.LookupInvoice(payHash) + invoice, err := r.server.invoices.LookupInvoice(payHash) if err != nil { return nil, err } diff --git a/server.go b/server.go index 7a37e7695..8775742a2 100644 --- a/server.go +++ b/server.go @@ -55,7 +55,6 @@ import ( "github.com/lightningnetwork/lnd/watchtower/wtclient" "github.com/lightningnetwork/lnd/watchtower/wtdb" "github.com/lightningnetwork/lnd/watchtower/wtpolicy" - "github.com/lightningnetwork/lnd/zpay32" ) const ( @@ -347,14 +346,6 @@ func newServer(listenAddrs []net.Addr, chanDB *channeldb.DB, readBufferPool, cfg.Workers.Read, pool.DefaultWorkerTimeout, ) - decodeFinalCltvExpiry := func(payReq string) (uint32, error) { - invoice, err := zpay32.Decode(payReq, activeNetParams.Params) - if err != nil { - return 0, err - } - return uint32(invoice.MinFinalCLTVExpiry()), nil - } - s := &server{ chanDB: chanDB, cc: cc, @@ -364,8 +355,7 @@ func newServer(listenAddrs []net.Addr, chanDB *channeldb.DB, chansToRestore: chansToRestore, invoices: invoices.NewRegistry( - chanDB, decodeFinalCltvExpiry, - defaultFinalCltvRejectDelta, + chanDB, defaultFinalCltvRejectDelta, ), channelNotifier: channelnotifier.New(chanDB), From ad3522f1a6cb79a9001ebe082b54cca41eabc590 Mon Sep 17 00:00:00 2001 From: Joost Jager Date: Fri, 9 Aug 2019 13:40:34 +0200 Subject: [PATCH 10/16] channeldb+invoices: move invoice accept or settle logic into registry As the logic around invoice mutations gets more complex, the friction caused by having this logic split between invoice registry and channeldb becomes more apparent. This commit brings a clearer separation of concerns by centralizing the accept/settle logic in the invoice registry. The original AcceptOrSettle method is renamed to UpdateInvoice because the update to perform is controlled by the callback. --- channeldb/invoice_test.go | 39 +++++++------ channeldb/invoices.go | 111 ++++++++++++++++++++++++++---------- invoices/invoiceregistry.go | 43 +++++++++----- 3 files changed, 132 insertions(+), 61 deletions(-) diff --git a/channeldb/invoice_test.go b/channeldb/invoice_test.go index 580fe3d3f..7ea19afd4 100644 --- a/channeldb/invoice_test.go +++ b/channeldb/invoice_test.go @@ -123,9 +123,7 @@ func TestInvoiceWorkflow(t *testing.T) { // now have the settled bit toggle to true and a non-default // SettledDate payAmt := fakeInvoice.Terms.Value * 2 - _, err = db.AcceptOrSettleInvoice( - paymentHash, payAmt, checkHtlcParameters, - ) + _, err = db.UpdateInvoice(paymentHash, getUpdateInvoice(payAmt)) if err != nil { t.Fatalf("unable to settle invoice: %v", err) } @@ -288,8 +286,8 @@ func TestInvoiceAddTimeSeries(t *testing.T) { paymentHash := invoice.Terms.PaymentPreimage.Hash() - _, err := db.AcceptOrSettleInvoice( - paymentHash, 0, checkHtlcParameters, + _, err := db.UpdateInvoice( + paymentHash, getUpdateInvoice(0), ) if err != nil { t.Fatalf("unable to settle invoice: %v", err) @@ -371,8 +369,8 @@ func TestDuplicateSettleInvoice(t *testing.T) { } // With the invoice in the DB, we'll now attempt to settle the invoice. - dbInvoice, err := db.AcceptOrSettleInvoice( - payHash, amt, checkHtlcParameters, + dbInvoice, err := db.UpdateInvoice( + payHash, getUpdateInvoice(amt), ) if err != nil { t.Fatalf("unable to settle invoice: %v", err) @@ -393,8 +391,8 @@ func TestDuplicateSettleInvoice(t *testing.T) { // If we try to settle the invoice again, then we should get the very // same invoice back, but with an error this time. - dbInvoice, err = db.AcceptOrSettleInvoice( - payHash, amt, checkHtlcParameters, + dbInvoice, err = db.UpdateInvoice( + payHash, getUpdateInvoice(amt), ) if err != ErrInvoiceAlreadySettled { t.Fatalf("expected ErrInvoiceAlreadySettled") @@ -440,8 +438,8 @@ func TestQueryInvoices(t *testing.T) { // We'll only settle half of all invoices created. if i%2 == 0 { - _, err := db.AcceptOrSettleInvoice( - paymentHash, i, checkHtlcParameters, + _, err := db.UpdateInvoice( + paymentHash, getUpdateInvoice(i), ) if err != nil { t.Fatalf("unable to settle invoice: %v", err) @@ -685,10 +683,19 @@ func TestQueryInvoices(t *testing.T) { } } -func checkHtlcParameters(invoice *Invoice) error { - if invoice.Terms.State == ContractSettled { - return ErrInvoiceAlreadySettled - } +// getUpdateInvoice returns an invoice update callback that, when called, +// settles the invoice with the given amount. +func getUpdateInvoice(amt lnwire.MilliSatoshi) InvoiceUpdateCallback { + return func(invoice *Invoice) (*InvoiceUpdateDesc, error) { + if invoice.Terms.State == ContractSettled { + return nil, ErrInvoiceAlreadySettled + } - return nil + update := &InvoiceUpdateDesc{ + State: ContractSettled, + AmtPaid: amt, + } + + return update, nil + } } diff --git a/channeldb/invoices.go b/channeldb/invoices.go index 18dbf75fa..195d82ffe 100644 --- a/channeldb/invoices.go +++ b/channeldb/invoices.go @@ -276,6 +276,20 @@ type InvoiceHTLC struct { State HtlcState } +// InvoiceUpdateDesc describes the changes that should be applied to the +// invoice. +type InvoiceUpdateDesc struct { + // State is the new state that this invoice should progress to. + State ContractState + + // AmtPaid is the updated amount that has been paid to this invoice. + AmtPaid lnwire.MilliSatoshi +} + +// InvoiceUpdateCallback is a callback used in the db transaction to update the +// invoice. +type InvoiceUpdateCallback = func(invoice *Invoice) (*InvoiceUpdateDesc, error) + func validateInvoice(i *Invoice) error { if len(i.Memo) > MaxMemoSize { return fmt.Errorf("max length a memo is %v, and invoice "+ @@ -689,21 +703,17 @@ func (d *DB) QueryInvoices(q InvoiceQuery) (InvoiceSlice, error) { return resp, nil } -// AcceptOrSettleInvoice attempts to mark an invoice corresponding to the passed -// payment hash as settled. If an invoice matching the passed payment hash -// doesn't existing within the database, then the action will fail with a "not -// found" error. +// UpdateInvoice attempts to update an invoice corresponding to the passed +// payment hash. If an invoice matching the passed payment hash doesn't exist +// within the database, then the action will fail with a "not found" error. // -// When the preimage for the invoice is unknown (hold invoice), the invoice is -// marked as accepted. -// -// TODO: Store invoice cltv as separate field in database so that it doesn't -// need to be decoded from the payment request. -func (d *DB) AcceptOrSettleInvoice(paymentHash [32]byte, - amtPaid lnwire.MilliSatoshi, - checkHtlcParameters func(invoice *Invoice) error) (*Invoice, error) { +// The update is performed inside the same database transaction that fetches the +// invoice and is therefore atomic. The fields to update are controlled by the +// supplied callback. +func (d *DB) UpdateInvoice(paymentHash lntypes.Hash, + callback InvoiceUpdateCallback) (*Invoice, error) { - var settledInvoice *Invoice + var updatedInvoice *Invoice err := d.Update(func(tx *bbolt.Tx) error { invoices, err := tx.CreateBucketIfNotExists(invoiceBucket) if err != nil { @@ -729,15 +739,14 @@ func (d *DB) AcceptOrSettleInvoice(paymentHash [32]byte, return ErrInvoiceNotFound } - settledInvoice, err = acceptOrSettleInvoice( - invoices, settleIndex, invoiceNum, amtPaid, - checkHtlcParameters, + updatedInvoice, err = updateInvoice( + invoices, settleIndex, invoiceNum, callback, ) return err }) - return settledInvoice, err + return updatedInvoice, err } // SettleHoldInvoice sets the preimage of a hodl invoice and marks the invoice @@ -1200,35 +1209,75 @@ func deserializeHtlcs(r io.Reader) (map[CircuitKey]*InvoiceHTLC, error) { return htlcs, nil } -func acceptOrSettleInvoice(invoices, settleIndex *bbolt.Bucket, - invoiceNum []byte, amtPaid lnwire.MilliSatoshi, - checkHtlcParameters func(invoice *Invoice) error) ( - *Invoice, error) { +// copySlice allocates a new slice and copies the source into it. +func copySlice(src []byte) []byte { + dest := make([]byte, len(src)) + copy(dest, src) + return dest +} + +// copyInvoice makes a deep copy of the supplied invoice. +func copyInvoice(src *Invoice) *Invoice { + dest := Invoice{ + Memo: copySlice(src.Memo), + Receipt: copySlice(src.Receipt), + PaymentRequest: copySlice(src.PaymentRequest), + FinalCltvDelta: src.FinalCltvDelta, + CreationDate: src.CreationDate, + SettleDate: src.SettleDate, + Terms: src.Terms, + AddIndex: src.AddIndex, + SettleIndex: src.SettleIndex, + AmtPaid: src.AmtPaid, + Htlcs: make( + map[CircuitKey]*InvoiceHTLC, len(src.Htlcs), + ), + } + + for k, v := range src.Htlcs { + dest.Htlcs[k] = v + } + + return &dest +} + +// updateInvoice fetches the invoice, obtains the update descriptor from the +// callback and applies the updates in a single db transaction. +func updateInvoice(invoices, settleIndex *bbolt.Bucket, invoiceNum []byte, + callback InvoiceUpdateCallback) (*Invoice, error) { invoice, err := fetchInvoice(invoiceNum, invoices) if err != nil { return nil, err } - // If the invoice is still open, check the htlc parameters. - if err := checkHtlcParameters(&invoice); err != nil { + preUpdateState := invoice.Terms.State + + // Create deep copy to prevent any accidental modification in the + // callback. + copy := copyInvoice(&invoice) + + // Call the callback and obtain the update descriptor. + update, err := callback(copy) + if err != nil { return &invoice, err } - // Check to see if we can settle or this is an hold invoice and we need - // to wait for the preimage. - holdInvoice := invoice.Terms.PaymentPreimage == UnknownPreimage - if holdInvoice { - invoice.Terms.State = ContractAccepted - } else { + // Update invoice state and amount. + invoice.Terms.State = update.State + invoice.AmtPaid = update.AmtPaid + + // If invoice moved to the settled state, update settle index and settle + // time. + if preUpdateState != invoice.Terms.State && + invoice.Terms.State == ContractSettled { + err := setSettleFields(settleIndex, invoiceNum, &invoice) if err != nil { return nil, err } } - invoice.AmtPaid = amtPaid - var buf bytes.Buffer if err := serializeInvoice(&buf, &invoice); err != nil { return nil, err diff --git a/invoices/invoiceregistry.go b/invoices/invoiceregistry.go index cfe8cb60b..d2729d154 100644 --- a/invoices/invoiceregistry.go +++ b/invoices/invoiceregistry.go @@ -409,22 +409,23 @@ func (i *InvoiceRegistry) LookupInvoice(rHash lntypes.Hash) (channeldb.Invoice, return i.cdb.LookupInvoice(rHash) } -// checkHtlcParameters is a callback used inside invoice db transactions to +// updateInvoice is a callback used inside invoice db transactions to // atomically check-and-update an invoice. -func (i *InvoiceRegistry) checkHtlcParameters(invoice *channeldb.Invoice, - amtPaid lnwire.MilliSatoshi, htlcExpiry uint32, currentHeight int32) error { +func (i *InvoiceRegistry) updateInvoice(invoice *channeldb.Invoice, + amtPaid lnwire.MilliSatoshi, htlcExpiry uint32, currentHeight int32) ( + *channeldb.InvoiceUpdateDesc, error) { // If the invoice is already canceled, there is no further checking to // do. if invoice.Terms.State == channeldb.ContractCanceled { - return channeldb.ErrInvoiceAlreadyCanceled + return nil, channeldb.ErrInvoiceAlreadyCanceled } // If an invoice amount is specified, check that enough is paid. Also // check this for duplicate payments if the invoice is already settled // or accepted. if invoice.Terms.Value > 0 && amtPaid < invoice.Terms.Value { - return ErrInvoiceAmountTooLow + return nil, ErrInvoiceAmountTooLow } // Return early in case the invoice was already accepted or settled. We @@ -432,20 +433,32 @@ func (i *InvoiceRegistry) checkHtlcParameters(invoice *channeldb.Invoice, // just restarting. switch invoice.Terms.State { case channeldb.ContractAccepted: - return channeldb.ErrInvoiceAlreadyAccepted + return nil, channeldb.ErrInvoiceAlreadyAccepted case channeldb.ContractSettled: - return channeldb.ErrInvoiceAlreadySettled + return nil, channeldb.ErrInvoiceAlreadySettled } if htlcExpiry < uint32(currentHeight+i.finalCltvRejectDelta) { - return ErrInvoiceExpiryTooSoon + return nil, ErrInvoiceExpiryTooSoon } if htlcExpiry < uint32(currentHeight+invoice.FinalCltvDelta) { - return ErrInvoiceExpiryTooSoon + return nil, ErrInvoiceExpiryTooSoon } - return nil + update := channeldb.InvoiceUpdateDesc{ + AmtPaid: amtPaid, + } + + // Check to see if we can settle or this is an hold invoice and we need + // to wait for the preimage. + holdInvoice := invoice.Terms.PaymentPreimage == channeldb.UnknownPreimage + if holdInvoice { + update.State = channeldb.ContractAccepted + } else { + update.State = channeldb.ContractSettled + } + return &update, nil } // NotifyExitHopHtlc attempts to mark an invoice as settled. If the invoice is a @@ -474,10 +487,12 @@ func (i *InvoiceRegistry) NotifyExitHopHtlc(rHash lntypes.Hash, // If this isn't a debug invoice, then we'll attempt to settle an // invoice matching this rHash on disk (if one exists). - invoice, err := i.cdb.AcceptOrSettleInvoice( - rHash, amtPaid, - func(inv *channeldb.Invoice) error { - return i.checkHtlcParameters( + invoice, err := i.cdb.UpdateInvoice( + rHash, + func(inv *channeldb.Invoice) (*channeldb.InvoiceUpdateDesc, + error) { + + return i.updateInvoice( inv, amtPaid, expiry, currentHeight, ) }, From 416bc8c68c6231ed3df304ae9a1731861a295b15 Mon Sep 17 00:00:00 2001 From: Joost Jager Date: Wed, 21 Aug 2019 15:24:21 +0200 Subject: [PATCH 11/16] channeldb+invoices: move hold invoice settle logic into registry This commit is a continuation of the centralization of invoice state transition logic in the invoice registry. --- channeldb/invoice_test.go | 5 +- channeldb/invoices.go | 92 +++++-------------------------------- invoices/invoiceregistry.go | 24 +++++++++- 3 files changed, 37 insertions(+), 84 deletions(-) diff --git a/channeldb/invoice_test.go b/channeldb/invoice_test.go index 7ea19afd4..0e5afd1e2 100644 --- a/channeldb/invoice_test.go +++ b/channeldb/invoice_test.go @@ -692,8 +692,9 @@ func getUpdateInvoice(amt lnwire.MilliSatoshi) InvoiceUpdateCallback { } update := &InvoiceUpdateDesc{ - State: ContractSettled, - AmtPaid: amt, + Preimage: invoice.Terms.PaymentPreimage, + State: ContractSettled, + AmtPaid: amt, } return update, nil diff --git a/channeldb/invoices.go b/channeldb/invoices.go index 195d82ffe..de480f368 100644 --- a/channeldb/invoices.go +++ b/channeldb/invoices.go @@ -284,6 +284,9 @@ type InvoiceUpdateDesc struct { // AmtPaid is the updated amount that has been paid to this invoice. AmtPaid lnwire.MilliSatoshi + + // Preimage must be set to the preimage when state is settled. + Preimage lntypes.Preimage } // InvoiceUpdateCallback is a callback used in the db transaction to update the @@ -740,47 +743,8 @@ func (d *DB) UpdateInvoice(paymentHash lntypes.Hash, } updatedInvoice, err = updateInvoice( - invoices, settleIndex, invoiceNum, callback, - ) - - return err - }) - - return updatedInvoice, err -} - -// SettleHoldInvoice sets the preimage of a hodl invoice and marks the invoice -// as settled. -func (d *DB) SettleHoldInvoice(preimage lntypes.Preimage) (*Invoice, error) { - var updatedInvoice *Invoice - hash := preimage.Hash() - err := d.Update(func(tx *bbolt.Tx) error { - invoices, err := tx.CreateBucketIfNotExists(invoiceBucket) - if err != nil { - return err - } - invoiceIndex, err := invoices.CreateBucketIfNotExists( - invoiceIndexBucket, - ) - if err != nil { - return err - } - settleIndex, err := invoices.CreateBucketIfNotExists( - settleIndexBucket, - ) - if err != nil { - return err - } - - // Check the invoice index to see if an invoice paying to this - // hash exists within the DB. - invoiceNum := invoiceIndex.Get(hash[:]) - if invoiceNum == nil { - return ErrInvoiceNotFound - } - - updatedInvoice, err = settleHoldInvoice( - invoices, settleIndex, invoiceNum, preimage, + paymentHash, invoices, settleIndex, invoiceNum, + callback, ) return err @@ -1243,8 +1207,8 @@ func copyInvoice(src *Invoice) *Invoice { // updateInvoice fetches the invoice, obtains the update descriptor from the // callback and applies the updates in a single db transaction. -func updateInvoice(invoices, settleIndex *bbolt.Bucket, invoiceNum []byte, - callback InvoiceUpdateCallback) (*Invoice, error) { +func updateInvoice(hash lntypes.Hash, invoices, settleIndex *bbolt.Bucket, + invoiceNum []byte, callback InvoiceUpdateCallback) (*Invoice, error) { invoice, err := fetchInvoice(invoiceNum, invoices) if err != nil { @@ -1272,6 +1236,11 @@ func updateInvoice(invoices, settleIndex *bbolt.Bucket, invoiceNum []byte, if preUpdateState != invoice.Terms.State && invoice.Terms.State == ContractSettled { + if update.Preimage.Hash() != hash { + return nil, fmt.Errorf("preimage does not match") + } + invoice.Terms.PaymentPreimage = update.Preimage + err := setSettleFields(settleIndex, invoiceNum, &invoice) if err != nil { return nil, err @@ -1314,43 +1283,6 @@ func setSettleFields(settleIndex *bbolt.Bucket, invoiceNum []byte, return nil } -func settleHoldInvoice(invoices, settleIndex *bbolt.Bucket, - invoiceNum []byte, preimage lntypes.Preimage) (*Invoice, - error) { - - invoice, err := fetchInvoice(invoiceNum, invoices) - if err != nil { - return nil, err - } - - switch invoice.Terms.State { - case ContractOpen: - return &invoice, ErrInvoiceStillOpen - case ContractCanceled: - return &invoice, ErrInvoiceAlreadyCanceled - case ContractSettled: - return &invoice, ErrInvoiceAlreadySettled - } - - invoice.Terms.PaymentPreimage = preimage - - err = setSettleFields(settleIndex, invoiceNum, &invoice) - if err != nil { - return nil, err - } - - var buf bytes.Buffer - if err := serializeInvoice(&buf, &invoice); err != nil { - return nil, err - } - - if err := invoices.Put(invoiceNum[:], buf.Bytes()); err != nil { - return nil, err - } - - return &invoice, nil -} - func cancelInvoice(invoices *bbolt.Bucket, invoiceNum []byte) ( *Invoice, error) { diff --git a/invoices/invoiceregistry.go b/invoices/invoiceregistry.go index d2729d154..1681bae5e 100644 --- a/invoices/invoiceregistry.go +++ b/invoices/invoiceregistry.go @@ -456,6 +456,7 @@ func (i *InvoiceRegistry) updateInvoice(invoice *channeldb.Invoice, if holdInvoice { update.State = channeldb.ContractAccepted } else { + update.Preimage = invoice.Terms.PaymentPreimage update.State = channeldb.ContractSettled } return &update, nil @@ -587,13 +588,32 @@ func (i *InvoiceRegistry) SettleHodlInvoice(preimage lntypes.Preimage) error { i.Lock() defer i.Unlock() - invoice, err := i.cdb.SettleHoldInvoice(preimage) + updateInvoice := func(invoice *channeldb.Invoice) ( + *channeldb.InvoiceUpdateDesc, error) { + + switch invoice.Terms.State { + case channeldb.ContractOpen: + return nil, channeldb.ErrInvoiceStillOpen + case channeldb.ContractCanceled: + return nil, channeldb.ErrInvoiceAlreadyCanceled + case channeldb.ContractSettled: + return nil, channeldb.ErrInvoiceAlreadySettled + } + + return &channeldb.InvoiceUpdateDesc{ + AmtPaid: invoice.AmtPaid, + State: channeldb.ContractSettled, + Preimage: preimage, + }, nil + } + + hash := preimage.Hash() + invoice, err := i.cdb.UpdateInvoice(hash, updateInvoice) if err != nil { log.Errorf("SettleHodlInvoice with preimage %v: %v", preimage, err) return err } - hash := preimage.Hash() log.Debugf("Invoice(%v): settled with preimage %v", hash, invoice.Terms.PaymentPreimage) From 144856757dd692769ca1752dd3a97aed887abff6 Mon Sep 17 00:00:00 2001 From: Joost Jager Date: Fri, 9 Aug 2019 15:30:33 +0200 Subject: [PATCH 12/16] channeldb+invoices: move invoice cancel logic into registry This commit is a continuation of the centralization of invoice state transition logic in the invoice registry. --- channeldb/invoices.go | 63 ------------------------------------- invoices/invoiceregistry.go | 19 ++++++++++- 2 files changed, 18 insertions(+), 64 deletions(-) diff --git a/channeldb/invoices.go b/channeldb/invoices.go index de480f368..ff4d60f63 100644 --- a/channeldb/invoices.go +++ b/channeldb/invoices.go @@ -753,37 +753,6 @@ func (d *DB) UpdateInvoice(paymentHash lntypes.Hash, return updatedInvoice, err } -// CancelInvoice attempts to cancel the invoice corresponding to the passed -// payment hash. -func (d *DB) CancelInvoice(paymentHash lntypes.Hash) (*Invoice, error) { - var canceledInvoice *Invoice - err := d.Update(func(tx *bbolt.Tx) error { - invoices, err := tx.CreateBucketIfNotExists(invoiceBucket) - if err != nil { - return err - } - invoiceIndex, err := invoices.CreateBucketIfNotExists( - invoiceIndexBucket, - ) - if err != nil { - return err - } - - // Check the invoice index to see if an invoice paying to this - // hash exists within the DB. - invoiceNum := invoiceIndex.Get(paymentHash[:]) - if invoiceNum == nil { - return ErrInvoiceNotFound - } - - canceledInvoice, err = cancelInvoice(invoices, invoiceNum) - - return err - }) - - return canceledInvoice, err -} - // InvoicesSettledSince can be used by callers to catch up any settled invoices // they missed within the settled invoice time series. We'll return all known // settled invoice that have a settle index higher than the passed @@ -1282,35 +1251,3 @@ func setSettleFields(settleIndex *bbolt.Bucket, invoiceNum []byte, return nil } - -func cancelInvoice(invoices *bbolt.Bucket, invoiceNum []byte) ( - *Invoice, error) { - - invoice, err := fetchInvoice(invoiceNum, invoices) - if err != nil { - return nil, err - } - - switch invoice.Terms.State { - case ContractSettled: - return &invoice, ErrInvoiceAlreadySettled - case ContractCanceled: - return &invoice, ErrInvoiceAlreadyCanceled - } - - invoice.Terms.State = ContractCanceled - - // Set AmtPaid back to 0, in case the invoice was already accepted. - invoice.AmtPaid = 0 - - var buf bytes.Buffer - if err := serializeInvoice(&buf, &invoice); err != nil { - return nil, err - } - - if err := invoices.Put(invoiceNum[:], buf.Bytes()); err != nil { - return nil, err - } - - return &invoice, nil -} diff --git a/invoices/invoiceregistry.go b/invoices/invoiceregistry.go index 1681bae5e..1d258e5bf 100644 --- a/invoices/invoiceregistry.go +++ b/invoices/invoiceregistry.go @@ -634,7 +634,24 @@ func (i *InvoiceRegistry) CancelInvoice(payHash lntypes.Hash) error { log.Debugf("Invoice(%v): canceling invoice", payHash) - invoice, err := i.cdb.CancelInvoice(payHash) + updateInvoice := func(invoice *channeldb.Invoice) ( + *channeldb.InvoiceUpdateDesc, error) { + + switch invoice.Terms.State { + case channeldb.ContractSettled: + return nil, channeldb.ErrInvoiceAlreadySettled + case channeldb.ContractCanceled: + return nil, channeldb.ErrInvoiceAlreadyCanceled + } + + // Move invoice to the canceled state. + return &channeldb.InvoiceUpdateDesc{ + AmtPaid: 0, + State: channeldb.ContractCanceled, + }, nil + } + + invoice, err := i.cdb.UpdateInvoice(payHash, updateInvoice) // Implement idempotency by returning success if the invoice was already // canceled. From c8fa51f8652420b40a22b69cec919c27d86857ff Mon Sep 17 00:00:00 2001 From: Joost Jager Date: Thu, 15 Aug 2019 19:33:30 +0200 Subject: [PATCH 13/16] invoices: refactor invoice update callback This commit refactors the invoice registry accept/settle logic so that it doesn't rely anymore on a set of error values to indirectly communicate from the update callback to the main function what action is required on the htlc. --- invoices/invoiceregistry.go | 244 +++++++++++++++++------------------- 1 file changed, 113 insertions(+), 131 deletions(-) diff --git a/invoices/invoiceregistry.go b/invoices/invoiceregistry.go index 1d258e5bf..4fbd683e8 100644 --- a/invoices/invoiceregistry.go +++ b/invoices/invoiceregistry.go @@ -2,7 +2,6 @@ package invoices import ( "errors" - "fmt" "sync" "sync/atomic" @@ -25,6 +24,9 @@ var ( // ErrShuttingDown is returned when an operation failed because the // invoice registry is shutting down. ErrShuttingDown = errors.New("invoice registry shutting down") + + // errNoUpdate is returned when no invoice updated is required. + errNoUpdate = errors.New("no update needed") ) // HodlEvent describes how an htlc should be resolved. If HodlEvent.Preimage is @@ -409,59 +411,6 @@ func (i *InvoiceRegistry) LookupInvoice(rHash lntypes.Hash) (channeldb.Invoice, return i.cdb.LookupInvoice(rHash) } -// updateInvoice is a callback used inside invoice db transactions to -// atomically check-and-update an invoice. -func (i *InvoiceRegistry) updateInvoice(invoice *channeldb.Invoice, - amtPaid lnwire.MilliSatoshi, htlcExpiry uint32, currentHeight int32) ( - *channeldb.InvoiceUpdateDesc, error) { - - // If the invoice is already canceled, there is no further checking to - // do. - if invoice.Terms.State == channeldb.ContractCanceled { - return nil, channeldb.ErrInvoiceAlreadyCanceled - } - - // If an invoice amount is specified, check that enough is paid. Also - // check this for duplicate payments if the invoice is already settled - // or accepted. - if invoice.Terms.Value > 0 && amtPaid < invoice.Terms.Value { - return nil, ErrInvoiceAmountTooLow - } - - // Return early in case the invoice was already accepted or settled. We - // don't want to check the expiry again, because it may be that we are - // just restarting. - switch invoice.Terms.State { - case channeldb.ContractAccepted: - return nil, channeldb.ErrInvoiceAlreadyAccepted - case channeldb.ContractSettled: - return nil, channeldb.ErrInvoiceAlreadySettled - } - - if htlcExpiry < uint32(currentHeight+i.finalCltvRejectDelta) { - return nil, ErrInvoiceExpiryTooSoon - } - - if htlcExpiry < uint32(currentHeight+invoice.FinalCltvDelta) { - return nil, ErrInvoiceExpiryTooSoon - } - - update := channeldb.InvoiceUpdateDesc{ - AmtPaid: amtPaid, - } - - // Check to see if we can settle or this is an hold invoice and we need - // to wait for the preimage. - holdInvoice := invoice.Terms.PaymentPreimage == channeldb.UnknownPreimage - if holdInvoice { - update.State = channeldb.ContractAccepted - } else { - update.Preimage = invoice.Terms.PaymentPreimage - update.State = channeldb.ContractSettled - } - return &update, nil -} - // NotifyExitHopHtlc attempts to mark an invoice as settled. If the invoice is a // debug invoice, then this method is a noop as debug invoices are never fully // settled. The return value describes how the htlc should be resolved. @@ -486,100 +435,133 @@ func (i *InvoiceRegistry) NotifyExitHopHtlc(rHash lntypes.Hash, rHash[:], s, amtPaid, expiry, circuitKey) } - // If this isn't a debug invoice, then we'll attempt to settle an - // invoice matching this rHash on disk (if one exists). - invoice, err := i.cdb.UpdateInvoice( - rHash, - func(inv *channeldb.Invoice) (*channeldb.InvoiceUpdateDesc, - error) { - - return i.updateInvoice( - inv, amtPaid, expiry, currentHeight, - ) - }, + const ( + actionCancel = iota + actionSettle + actionHold ) + + // If no action is set, cancel the htlc. + action := actionCancel + + updateInvoice := func(inv *channeldb.Invoice) ( + *channeldb.InvoiceUpdateDesc, error) { + + // If the invoice is already canceled, there is no + // further checking to do. + if inv.Terms.State == channeldb.ContractCanceled { + debugLog("invoice already canceled") + return nil, errNoUpdate + } + + // If an invoice amount is specified, check that enough + // is paid. Also check this for duplicate payments if + // the invoice is already settled or accepted. + if inv.Terms.Value > 0 && amtPaid < inv.Terms.Value { + debugLog("amount too low") + return nil, errNoUpdate + } + + // Return early in case the invoice was already accepted or + // settled. We don't want to check the expiry again, because it + // may be that we are just restarting. + // + // NOTE: Though our recovery and forwarding logic is + // predominately batched, settling invoices happens iteratively. + // We may reject one of two payments for the same rhash at + // first, but then restart and reject both after seeing that the + // invoice has been settled. Without any record of which one + // settles first, it is ambiguous as to which one actually + // settled the invoice. Thus, by accepting all payments, we + // eliminate the race condition that can lead to this + // inconsistency. + // + // TODO(conner): track ownership of settlements to properly + // recover from failures? or add batch invoice settlement + switch inv.Terms.State { + case channeldb.ContractAccepted: + debugLog("accepting duplicate payment to accepted invoice") + action = actionHold + return nil, errNoUpdate + + // If invoice is already settled, settle htlc. This means we + // accept more payments to the same invoice hash. + case channeldb.ContractSettled: + debugLog("accepting duplicate payment to settled invoice") + action = actionSettle + return nil, errNoUpdate + } + + // The invoice is still open. Check the expiry. + if expiry < uint32(currentHeight+i.finalCltvRejectDelta) { + debugLog("expiry too soon") + return nil, errNoUpdate + } + + if expiry < uint32(currentHeight+inv.FinalCltvDelta) { + debugLog("expiry too soon") + return nil, errNoUpdate + } + + update := channeldb.InvoiceUpdateDesc{ + AmtPaid: amtPaid, + } + + // Check to see if we can settle or this is an hold invoice and + // we need to wait for the preimage. + holdInvoice := inv.Terms.PaymentPreimage == channeldb.UnknownPreimage + if holdInvoice { + debugLog("accepted") + action = actionHold + update.State = channeldb.ContractAccepted + } else { + debugLog("settled") + action = actionSettle + update.Preimage = inv.Terms.PaymentPreimage + update.State = channeldb.ContractSettled + } + return &update, nil + } + + // We'll attempt to settle an invoice matching this rHash on disk (if + // one exists). The callback will set the resolution action that is + // returned to the link or contract resolver. + invoice, err := i.cdb.UpdateInvoice(rHash, updateInvoice) switch err { - // If invoice is already settled, settle htlc. This means we accept more - // payments to the same invoice hash. - // - // NOTE: Though our recovery and forwarding logic is predominately - // batched, settling invoices happens iteratively. We may reject one of - // two payments for the same rhash at first, but then restart and reject - // both after seeing that the invoice has been settled. Without any - // record of which one settles first, it is ambiguous as to which one - // actually settled the invoice. Thus, by accepting all payments, we - // eliminate the race condition that can lead to this inconsistency. - // - // TODO(conner): track ownership of settlements to properly recover from - // failures? or add batch invoice settlement - case channeldb.ErrInvoiceAlreadySettled: - debugLog("accepting duplicate payment to settled invoice") + // Invoice was updated, notify clients. + case nil: + i.notifyClients(rHash, invoice, invoice.Terms.State) + // No invoice update in the database was performed, no action required. + case errNoUpdate: + + // Log and return other unexpected errors. + default: + debugLog(err.Error()) + + return nil, err + } + + switch action { + + case actionSettle: return &HodlEvent{ Hash: rHash, Preimage: &invoice.Terms.PaymentPreimage, }, nil - // If invoice is already canceled, cancel htlc. - case channeldb.ErrInvoiceAlreadyCanceled: - debugLog("invoice already canceled") - + case actionCancel: return &HodlEvent{ Hash: rHash, }, nil - // If invoice is already accepted, add this htlc to the list of - // subscribers. - case channeldb.ErrInvoiceAlreadyAccepted: - debugLog("accepting duplicate payment to accepted invoice") - + case actionHold: i.hodlSubscribe(hodlChan, rHash) return nil, nil - // If there are not enough blocks left, cancel the htlc. - case ErrInvoiceExpiryTooSoon: - debugLog("expiry too soon") - - return &HodlEvent{ - Hash: rHash, - }, nil - - // If there are not enough blocks left, cancel the htlc. - case ErrInvoiceAmountTooLow: - debugLog("amount too low") - - return &HodlEvent{ - Hash: rHash, - }, nil - - // If this call settled the invoice, settle the htlc. Otherwise - // subscribe for a future hodl event. - case nil: - i.notifyClients(rHash, invoice, invoice.Terms.State) - switch invoice.Terms.State { - case channeldb.ContractSettled: - debugLog("settled") - - return &HodlEvent{ - Hash: rHash, - Preimage: &invoice.Terms.PaymentPreimage, - }, nil - case channeldb.ContractAccepted: - debugLog("accepted") - - // Subscribe to updates to this invoice. - i.hodlSubscribe(hodlChan, rHash) - return nil, nil - default: - return nil, fmt.Errorf("unexpected invoice state %v", - invoice.Terms.State) - } - default: - debugLog(err.Error()) - - return nil, err + panic("unknown action") } } From 53eea09b639d03fdbed88d133b424310624800a8 Mon Sep 17 00:00:00 2001 From: Joost Jager Date: Fri, 30 Aug 2019 13:10:29 +0200 Subject: [PATCH 14/16] channeldb: add now function Needed for time control in unit tests. --- channeldb/db.go | 2 ++ channeldb/invoice_test.go | 1 + channeldb/invoices.go | 12 +++++++----- 3 files changed, 10 insertions(+), 5 deletions(-) diff --git a/channeldb/db.go b/channeldb/db.go index c53b7a231..fcb81aa8b 100644 --- a/channeldb/db.go +++ b/channeldb/db.go @@ -129,6 +129,7 @@ type DB struct { *bbolt.DB dbPath string graph *ChannelGraph + now func() time.Time } // Open opens an existing channeldb. Any necessary schemas migrations due to @@ -162,6 +163,7 @@ func Open(dbPath string, modifiers ...OptionModifier) (*DB, error) { chanDB := &DB{ DB: bdb, dbPath: dbPath, + now: time.Now, } chanDB.graph = newChannelGraph( chanDB, opts.RejectCacheSize, opts.ChannelCacheSize, diff --git a/channeldb/invoice_test.go b/channeldb/invoice_test.go index 0e5afd1e2..3ed1f8ebc 100644 --- a/channeldb/invoice_test.go +++ b/channeldb/invoice_test.go @@ -354,6 +354,7 @@ func TestDuplicateSettleInvoice(t *testing.T) { if err != nil { t.Fatalf("unable to make test db: %v", err) } + db.now = func() time.Time { return time.Unix(1, 0) } // We'll start out by creating an invoice and writing it to the DB. amt := lnwire.NewMSatFromSatoshis(1000) diff --git a/channeldb/invoices.go b/channeldb/invoices.go index ff4d60f63..df218af16 100644 --- a/channeldb/invoices.go +++ b/channeldb/invoices.go @@ -742,7 +742,7 @@ func (d *DB) UpdateInvoice(paymentHash lntypes.Hash, return ErrInvoiceNotFound } - updatedInvoice, err = updateInvoice( + updatedInvoice, err = d.updateInvoice( paymentHash, invoices, settleIndex, invoiceNum, callback, ) @@ -1176,7 +1176,7 @@ func copyInvoice(src *Invoice) *Invoice { // updateInvoice fetches the invoice, obtains the update descriptor from the // callback and applies the updates in a single db transaction. -func updateInvoice(hash lntypes.Hash, invoices, settleIndex *bbolt.Bucket, +func (d *DB) updateInvoice(hash lntypes.Hash, invoices, settleIndex *bbolt.Bucket, invoiceNum []byte, callback InvoiceUpdateCallback) (*Invoice, error) { invoice, err := fetchInvoice(invoiceNum, invoices) @@ -1210,7 +1210,9 @@ func updateInvoice(hash lntypes.Hash, invoices, settleIndex *bbolt.Bucket, } invoice.Terms.PaymentPreimage = update.Preimage - err := setSettleFields(settleIndex, invoiceNum, &invoice) + err := setSettleFields( + settleIndex, invoiceNum, &invoice, d.now(), + ) if err != nil { return nil, err } @@ -1229,7 +1231,7 @@ func updateInvoice(hash lntypes.Hash, invoices, settleIndex *bbolt.Bucket, } func setSettleFields(settleIndex *bbolt.Bucket, invoiceNum []byte, - invoice *Invoice) error { + invoice *Invoice, now time.Time) error { // Now that we know the invoice hasn't already been settled, we'll // update the settle index so we can place this settle event in the @@ -1246,7 +1248,7 @@ func setSettleFields(settleIndex *bbolt.Bucket, invoiceNum []byte, } invoice.Terms.State = ContractSettled - invoice.SettleDate = time.Now() + invoice.SettleDate = now invoice.SettleIndex = nextSettleSeqNo return nil From d6d9ec6aa542e64573fcaca7b3b2bed2ae3d9dc0 Mon Sep 17 00:00:00 2001 From: Joost Jager Date: Fri, 9 Aug 2019 15:09:57 +0200 Subject: [PATCH 15/16] invoices: replay awareness Previously the invoice registry wasn't aware of replayed htlcs. This was dealt with by keeping the invoice accept/settle logic idempotent, so that a replay wouldn't have an effect. This mechanism has two limitations: 1. No accurate tracking of the total amount paid to an invoice. The total amount couldn't just be increased with every htlc received, because it could be a replay which would lead to counting the htlc amount multiple times. Therefore the total amount was set to the amount of the first htlc that was received, even though there may have been multiple htlcs paying to the invoice. 2. Impossible to check htlc expiry consistently for hodl invoices. When an htlc is new, its expiry needs to be checked against the invoice cltv delta. But for a replay, that check must be skipped. The htlc was accepted in time, the invoice was moved to the accepted state and a replay some blocks later shouldn't lead to that htlc being cancelled. Because the invoice registry couldn't recognize replays, it stopped checking htlc expiry heights when the invoice reached the accepted state. This prevents hold htlcs from being cancelled after a restart. But unfortunately this also caused additional htlcs to be accepted on an already accepted invoice without their expiry being checked. In this commit, the invoice registry starts to persistently track htlcs so that replays can be recognized. For replays, an htlc resolution action is returned early. This fixes both limitations mentioned above. --- channeldb/invoice_test.go | 41 +++----- channeldb/invoices.go | 79 +++++++++++++-- contractcourt/channel_arbitrator.go | 6 ++ invoices/invoiceregistry.go | 152 ++++++++++++++++------------ invoices/invoiceregistry_test.go | 14 +-- 5 files changed, 188 insertions(+), 104 deletions(-) diff --git a/channeldb/invoice_test.go b/channeldb/invoice_test.go index 3ed1f8ebc..4b5dda87e 100644 --- a/channeldb/invoice_test.go +++ b/channeldb/invoice_test.go @@ -10,26 +10,6 @@ import ( "github.com/lightningnetwork/lnd/lnwire" ) -var ( - testCircuitKey = CircuitKey{ - ChanID: lnwire.ShortChannelID{ - BlockHeight: 1, TxIndex: 2, TxPosition: 3, - }, - HtlcID: 4, - } - - testHtlcs = map[CircuitKey]*InvoiceHTLC{ - testCircuitKey: { - State: HtlcStateCancelled, - AcceptTime: time.Unix(1, 0), - AcceptHeight: 100, - ResolveTime: time.Unix(2, 0), - Amt: 5200, - Expiry: 150, - }, - } -) - func randInvoice(value lnwire.MilliSatoshi) (*Invoice, error) { var pre [32]byte if _, err := rand.Read(pre[:]); err != nil { @@ -44,9 +24,8 @@ func randInvoice(value lnwire.MilliSatoshi) (*Invoice, error) { PaymentPreimage: pre, Value: value, }, - Htlcs: testHtlcs, - FinalCltvDelta: 50, - Expiry: 4000, + Htlcs: map[CircuitKey]*InvoiceHTLC{}, + Expiry: 4000, } i.Memo = []byte("memo") i.Receipt = []byte("receipt") @@ -82,7 +61,7 @@ func TestInvoiceWorkflow(t *testing.T) { // Use single second precision to avoid false positive test // failures due to the monotonic time component. CreationDate: time.Unix(time.Now().Unix(), 0), - Htlcs: testHtlcs, + Htlcs: map[CircuitKey]*InvoiceHTLC{}, } fakeInvoice.Memo = []byte("memo") fakeInvoice.Receipt = []byte("receipt") @@ -383,6 +362,14 @@ func TestDuplicateSettleInvoice(t *testing.T) { invoice.Terms.State = ContractSettled invoice.AmtPaid = amt invoice.SettleDate = dbInvoice.SettleDate + invoice.Htlcs = map[CircuitKey]*InvoiceHTLC{ + {}: { + Amt: amt, + AcceptTime: time.Unix(1, 0), + ResolveTime: time.Unix(1, 0), + State: HtlcStateSettled, + }, + } // We should get back the exact same invoice that we just inserted. if !reflect.DeepEqual(dbInvoice, invoice) { @@ -695,7 +682,11 @@ func getUpdateInvoice(amt lnwire.MilliSatoshi) InvoiceUpdateCallback { update := &InvoiceUpdateDesc{ Preimage: invoice.Terms.PaymentPreimage, State: ContractSettled, - AmtPaid: amt, + Htlcs: map[CircuitKey]*HtlcAcceptDesc{ + {}: { + Amt: amt, + }, + }, } return update, nil diff --git a/channeldb/invoices.go b/channeldb/invoices.go index df218af16..ad09da4f1 100644 --- a/channeldb/invoices.go +++ b/channeldb/invoices.go @@ -276,14 +276,28 @@ type InvoiceHTLC struct { State HtlcState } +// HtlcAcceptDesc describes the details of a newly accepted htlc. +type HtlcAcceptDesc struct { + // AcceptHeight is the block height at which this htlc was accepted. + AcceptHeight int32 + + // Amt is the amount that is carried by this htlc. + Amt lnwire.MilliSatoshi + + // Expiry is the expiry height of this htlc. + Expiry uint32 +} + // InvoiceUpdateDesc describes the changes that should be applied to the // invoice. type InvoiceUpdateDesc struct { // State is the new state that this invoice should progress to. State ContractState - // AmtPaid is the updated amount that has been paid to this invoice. - AmtPaid lnwire.MilliSatoshi + // Htlcs describes the changes that need to be made to the invoice htlcs + // in the database. Htlc map entries with their value set should be + // added. If the map value is nil, the htlc should be cancelled. + Htlcs map[CircuitKey]*HtlcAcceptDesc // Preimage must be set to the preimage when state is settled. Preimage lntypes.Preimage @@ -1196,9 +1210,52 @@ func (d *DB) updateInvoice(hash lntypes.Hash, invoices, settleIndex *bbolt.Bucke return &invoice, err } - // Update invoice state and amount. + // Update invoice state. invoice.Terms.State = update.State - invoice.AmtPaid = update.AmtPaid + + now := d.now() + + // Update htlc set. + for key, htlcUpdate := range update.Htlcs { + htlc, ok := invoice.Htlcs[key] + + // No update means the htlc needs to be cancelled. + if htlcUpdate == nil { + if !ok { + return nil, fmt.Errorf("unknown htlc %v", key) + } + if htlc.State == HtlcStateSettled { + return nil, fmt.Errorf("cannot cancel a " + + "settled htlc") + } + + htlc.State = HtlcStateCancelled + htlc.ResolveTime = now + invoice.AmtPaid -= htlc.Amt + + continue + } + + // Add new htlc paying to the invoice. + if ok { + return nil, fmt.Errorf("htlc %v already exists", key) + } + htlc = &InvoiceHTLC{ + Amt: htlcUpdate.Amt, + Expiry: htlcUpdate.Expiry, + AcceptHeight: uint32(htlcUpdate.AcceptHeight), + AcceptTime: now, + } + if preUpdateState == ContractSettled { + htlc.State = HtlcStateSettled + htlc.ResolveTime = now + } else { + htlc.State = HtlcStateAccepted + } + + invoice.Htlcs[key] = htlc + invoice.AmtPaid += htlc.Amt + } // If invoice moved to the settled state, update settle index and settle // time. @@ -1210,9 +1267,17 @@ func (d *DB) updateInvoice(hash lntypes.Hash, invoices, settleIndex *bbolt.Bucke } invoice.Terms.PaymentPreimage = update.Preimage - err := setSettleFields( - settleIndex, invoiceNum, &invoice, d.now(), - ) + // Settle all accepted htlcs. + for _, htlc := range invoice.Htlcs { + if htlc.State != HtlcStateAccepted { + continue + } + + htlc.State = HtlcStateSettled + htlc.ResolveTime = now + } + + err := setSettleFields(settleIndex, invoiceNum, &invoice, now) if err != nil { return nil, err } diff --git a/contractcourt/channel_arbitrator.go b/contractcourt/channel_arbitrator.go index 8fd9f9407..b90707686 100644 --- a/contractcourt/channel_arbitrator.go +++ b/contractcourt/channel_arbitrator.go @@ -1745,9 +1745,15 @@ func (c *ChannelArbitrator) prepContractResolutions( continue } + circuitKey := channeldb.CircuitKey{ + HtlcID: htlc.HtlcIndex, + ChanID: c.cfg.ShortChanID, + } + resKit.Quit = make(chan struct{}) resolver := &htlcIncomingContestResolver{ htlcExpiry: htlc.RefundTimeout, + circuitKey: circuitKey, htlcSuccessResolver: htlcSuccessResolver{ htlcResolution: resolution, broadcastHeight: height, diff --git a/invoices/invoiceregistry.go b/invoices/invoiceregistry.go index 4fbd683e8..b1dc4b4d9 100644 --- a/invoices/invoiceregistry.go +++ b/invoices/invoiceregistry.go @@ -27,6 +27,10 @@ var ( // errNoUpdate is returned when no invoice updated is required. errNoUpdate = errors.New("no update needed") + + // errReplayedHtlc is returned if the htlc is already recorded on the + // invoice. + errReplayedHtlc = errors.New("replayed htlc") ) // HodlEvent describes how an htlc should be resolved. If HodlEvent.Preimage is @@ -435,20 +439,34 @@ func (i *InvoiceRegistry) NotifyExitHopHtlc(rHash lntypes.Hash, rHash[:], s, amtPaid, expiry, circuitKey) } - const ( - actionCancel = iota - actionSettle - actionHold - ) - - // If no action is set, cancel the htlc. - action := actionCancel + // Default is to not update subscribers after the invoice update. + updateSubscribers := false updateInvoice := func(inv *channeldb.Invoice) ( *channeldb.InvoiceUpdateDesc, error) { - // If the invoice is already canceled, there is no - // further checking to do. + // Don't update the invoice when this is a replayed htlc. + htlc, ok := inv.Htlcs[circuitKey] + if ok { + switch htlc.State { + case channeldb.HtlcStateCancelled: + debugLog("replayed htlc to canceled invoice") + + case channeldb.HtlcStateAccepted: + debugLog("replayed htlc to accepted invoice") + + case channeldb.HtlcStateSettled: + debugLog("replayed htlc to settled invoice") + + default: + return nil, errors.New("unexpected htlc state") + } + + return nil, errNoUpdate + } + + // If the invoice is already canceled, there is no further + // checking to do. if inv.Terms.State == channeldb.ContractCanceled { debugLog("invoice already canceled") return nil, errNoUpdate @@ -462,36 +480,6 @@ func (i *InvoiceRegistry) NotifyExitHopHtlc(rHash lntypes.Hash, return nil, errNoUpdate } - // Return early in case the invoice was already accepted or - // settled. We don't want to check the expiry again, because it - // may be that we are just restarting. - // - // NOTE: Though our recovery and forwarding logic is - // predominately batched, settling invoices happens iteratively. - // We may reject one of two payments for the same rhash at - // first, but then restart and reject both after seeing that the - // invoice has been settled. Without any record of which one - // settles first, it is ambiguous as to which one actually - // settled the invoice. Thus, by accepting all payments, we - // eliminate the race condition that can lead to this - // inconsistency. - // - // TODO(conner): track ownership of settlements to properly - // recover from failures? or add batch invoice settlement - switch inv.Terms.State { - case channeldb.ContractAccepted: - debugLog("accepting duplicate payment to accepted invoice") - action = actionHold - return nil, errNoUpdate - - // If invoice is already settled, settle htlc. This means we - // accept more payments to the same invoice hash. - case channeldb.ContractSettled: - debugLog("accepting duplicate payment to settled invoice") - action = actionSettle - return nil, errNoUpdate - } - // The invoice is still open. Check the expiry. if expiry < uint32(currentHeight+i.finalCltvRejectDelta) { debugLog("expiry too soon") @@ -503,8 +491,31 @@ func (i *InvoiceRegistry) NotifyExitHopHtlc(rHash lntypes.Hash, return nil, errNoUpdate } + // Record HTLC in the invoice database. + newHtlcs := map[channeldb.CircuitKey]*channeldb.HtlcAcceptDesc{ + circuitKey: { + Amt: amtPaid, + Expiry: expiry, + AcceptHeight: currentHeight, + }, + } + update := channeldb.InvoiceUpdateDesc{ - AmtPaid: amtPaid, + Htlcs: newHtlcs, + } + + // Don't update invoice state if we are accepting a duplicate + // payment. We do accept or settle the HTLC. + switch inv.Terms.State { + case channeldb.ContractAccepted: + debugLog("accepting duplicate payment to accepted invoice") + update.State = channeldb.ContractAccepted + return &update, nil + + case channeldb.ContractSettled: + debugLog("accepting duplicate payment to settled invoice") + update.State = channeldb.ContractSettled + return &update, nil } // Check to see if we can settle or this is an hold invoice and @@ -512,14 +523,15 @@ func (i *InvoiceRegistry) NotifyExitHopHtlc(rHash lntypes.Hash, holdInvoice := inv.Terms.PaymentPreimage == channeldb.UnknownPreimage if holdInvoice { debugLog("accepted") - action = actionHold update.State = channeldb.ContractAccepted } else { debugLog("settled") - action = actionSettle update.Preimage = inv.Terms.PaymentPreimage update.State = channeldb.ContractSettled } + + updateSubscribers = true + return &update, nil } @@ -527,36 +539,39 @@ func (i *InvoiceRegistry) NotifyExitHopHtlc(rHash lntypes.Hash, // one exists). The callback will set the resolution action that is // returned to the link or contract resolver. invoice, err := i.cdb.UpdateInvoice(rHash, updateInvoice) - switch err { - - // Invoice was updated, notify clients. - case nil: - i.notifyClients(rHash, invoice, invoice.Terms.State) - - // No invoice update in the database was performed, no action required. - case errNoUpdate: - - // Log and return other unexpected errors. - default: + if err != nil && err != errNoUpdate { debugLog(err.Error()) return nil, err } - switch action { + if updateSubscribers { + i.notifyClients(rHash, invoice, invoice.Terms.State) + } - case actionSettle: + // Inspect latest htlc state on the invoice. + invoiceHtlc, ok := invoice.Htlcs[circuitKey] + + // If it isn't recorded, cancel htlc. + if !ok { + return &HodlEvent{ + Hash: rHash, + }, nil + } + + switch invoiceHtlc.State { + case channeldb.HtlcStateCancelled: + return &HodlEvent{ + Hash: rHash, + }, nil + + case channeldb.HtlcStateSettled: return &HodlEvent{ Hash: rHash, Preimage: &invoice.Terms.PaymentPreimage, }, nil - case actionCancel: - return &HodlEvent{ - Hash: rHash, - }, nil - - case actionHold: + case channeldb.HtlcStateAccepted: i.hodlSubscribe(hodlChan, rHash) return nil, nil @@ -583,7 +598,6 @@ func (i *InvoiceRegistry) SettleHodlInvoice(preimage lntypes.Preimage) error { } return &channeldb.InvoiceUpdateDesc{ - AmtPaid: invoice.AmtPaid, State: channeldb.ContractSettled, Preimage: preimage, }, nil @@ -626,10 +640,18 @@ func (i *InvoiceRegistry) CancelInvoice(payHash lntypes.Hash) error { return nil, channeldb.ErrInvoiceAlreadyCanceled } + // Mark individual held htlcs as cancelled. + canceledHtlcs := make( + map[channeldb.CircuitKey]*channeldb.HtlcAcceptDesc, + ) + for key := range invoice.Htlcs { + canceledHtlcs[key] = nil + } + // Move invoice to the canceled state. return &channeldb.InvoiceUpdateDesc{ - AmtPaid: 0, - State: channeldb.ContractCanceled, + Htlcs: canceledHtlcs, + State: channeldb.ContractCanceled, }, nil } diff --git a/invoices/invoiceregistry_test.go b/invoices/invoiceregistry_test.go index c7873d5bb..c8ae8e0e9 100644 --- a/invoices/invoiceregistry_test.go +++ b/invoices/invoiceregistry_test.go @@ -199,13 +199,14 @@ func TestSettleInvoice(t *testing.T) { t.Fatal("expected cancel event") } - // Check that settled amount remains unchanged. + // Check that settled amount is equal to the sum of values of the htlcs + // 0 and 1. inv, err := registry.LookupInvoice(hash) if err != nil { t.Fatal(err) } - if inv.AmtPaid != amtPaid { - t.Fatal("expected amount to be unchanged") + if inv.AmtPaid != amtPaid+amtPaid+600 { + t.Fatal("amount incorrect") } // Try to cancel. @@ -426,8 +427,7 @@ func TestHoldInvoice(t *testing.T) { } // Test a new htlc coming in that doesn't meet the final cltv delta - // requirement. It should be rejected, but because invoice registry - // doesn't track individual htlcs it is accepted. + // requirement. It should be rejected. event, err = registry.NotifyExitHopHtlc( hash, amtPaid, 1, testCurrentHeight, getCircuitKey(1), hodlChan, nil, @@ -435,8 +435,8 @@ func TestHoldInvoice(t *testing.T) { if err != nil { t.Fatalf("expected settle to succeed but got %v", err) } - if event != nil { - t.Fatalf("expected htlc to be held") + if event == nil || event.Preimage != nil { + t.Fatalf("expected htlc to be cancelled") } // We expect the accepted state to be sent to the single invoice From 45cd76e9eb168cd7fd70644f2a73860c65bfd0db Mon Sep 17 00:00:00 2001 From: Joost Jager Date: Fri, 9 Aug 2019 17:28:08 +0200 Subject: [PATCH 16/16] lnrpc+invoicesrpc: report invoice htlcs --- lnrpc/invoicesrpc/utils.go | 33 + lnrpc/rpc.pb.go | 1243 ++++++++++++++++++++---------------- lnrpc/rpc.proto | 36 ++ lnrpc/rpc.swagger.json | 61 ++ 4 files changed, 827 insertions(+), 546 deletions(-) diff --git a/lnrpc/invoicesrpc/utils.go b/lnrpc/invoicesrpc/utils.go index 7d39d3324..d9253d57d 100644 --- a/lnrpc/invoicesrpc/utils.go +++ b/lnrpc/invoicesrpc/utils.go @@ -60,6 +60,38 @@ func CreateRPCInvoice(invoice *channeldb.Invoice, invoice.Terms.State) } + rpcHtlcs := make([]*lnrpc.InvoiceHTLC, 0, len(invoice.Htlcs)) + for key, htlc := range invoice.Htlcs { + var state lnrpc.InvoiceHTLCState + switch htlc.State { + case channeldb.HtlcStateAccepted: + state = lnrpc.InvoiceHTLCState_ACCEPTED + case channeldb.HtlcStateSettled: + state = lnrpc.InvoiceHTLCState_SETTLED + case channeldb.HtlcStateCancelled: + state = lnrpc.InvoiceHTLCState_CANCELLED + default: + return nil, fmt.Errorf("unknown state %v", htlc.State) + } + + rpcHtlc := lnrpc.InvoiceHTLC{ + ChanId: key.ChanID.ToUint64(), + HtlcIndex: key.HtlcID, + AcceptHeight: int32(htlc.AcceptHeight), + AcceptTime: htlc.AcceptTime.Unix(), + ExpiryHeight: int32(htlc.Expiry), + AmtMsat: uint64(htlc.Amt), + State: state, + } + + // Only report resolved times if htlc is resolved. + if htlc.State != channeldb.HtlcStateAccepted { + rpcHtlc.ResolveTime = htlc.ResolveTime.Unix() + } + + rpcHtlcs = append(rpcHtlcs, &rpcHtlc) + } + rpcInvoice := &lnrpc.Invoice{ Memo: string(invoice.Memo[:]), Receipt: invoice.Receipt[:], @@ -81,6 +113,7 @@ func CreateRPCInvoice(invoice *channeldb.Invoice, AmtPaidMsat: int64(invoice.AmtPaid), AmtPaid: int64(invoice.AmtPaid), State: state, + Htlcs: rpcHtlcs, } if preimage != channeldb.UnknownPreimage { diff --git a/lnrpc/rpc.pb.go b/lnrpc/rpc.pb.go index aec2dedda..14c9cc665 100644 --- a/lnrpc/rpc.pb.go +++ b/lnrpc/rpc.pb.go @@ -59,6 +59,34 @@ func (AddressType) EnumDescriptor() ([]byte, []int) { return fileDescriptor_77a6da22d6a3feb1, []int{0} } +type InvoiceHTLCState int32 + +const ( + InvoiceHTLCState_ACCEPTED InvoiceHTLCState = 0 + InvoiceHTLCState_SETTLED InvoiceHTLCState = 1 + InvoiceHTLCState_CANCELLED InvoiceHTLCState = 2 +) + +var InvoiceHTLCState_name = map[int32]string{ + 0: "ACCEPTED", + 1: "SETTLED", + 2: "CANCELLED", +} + +var InvoiceHTLCState_value = map[string]int32{ + "ACCEPTED": 0, + "SETTLED": 1, + "CANCELLED": 2, +} + +func (x InvoiceHTLCState) String() string { + return proto.EnumName(InvoiceHTLCState_name, int32(x)) +} + +func (InvoiceHTLCState) EnumDescriptor() ([]byte, []int) { + return fileDescriptor_77a6da22d6a3feb1, []int{1} +} + type ChannelCloseSummary_ClosureType int32 const ( @@ -220,7 +248,7 @@ func (x Payment_PaymentStatus) String() string { } func (Payment_PaymentStatus) EnumDescriptor() ([]byte, []int) { - return fileDescriptor_77a6da22d6a3feb1, []int{99, 0} + return fileDescriptor_77a6da22d6a3feb1, []int{100, 0} } type GenSeedRequest struct { @@ -6604,10 +6632,12 @@ type Invoice struct { AmtPaidMsat int64 `protobuf:"varint,20,opt,name=amt_paid_msat,proto3" json:"amt_paid_msat,omitempty"` //* //The state the invoice is in. - State Invoice_InvoiceState `protobuf:"varint,21,opt,name=state,proto3,enum=lnrpc.Invoice_InvoiceState" json:"state,omitempty"` - XXX_NoUnkeyedLiteral struct{} `json:"-"` - XXX_unrecognized []byte `json:"-"` - XXX_sizecache int32 `json:"-"` + State Invoice_InvoiceState `protobuf:"varint,21,opt,name=state,proto3,enum=lnrpc.Invoice_InvoiceState" json:"state,omitempty"` + /// List of HTLCs paying to this invoice [EXPERIMENTAL]. + Htlcs []*InvoiceHTLC `protobuf:"bytes,22,rep,name=htlcs,proto3" json:"htlcs,omitempty"` + XXX_NoUnkeyedLiteral struct{} `json:"-"` + XXX_unrecognized []byte `json:"-"` + XXX_sizecache int32 `json:"-"` } func (m *Invoice) Reset() { *m = Invoice{} } @@ -6785,6 +6815,117 @@ func (m *Invoice) GetState() Invoice_InvoiceState { return Invoice_OPEN } +func (m *Invoice) GetHtlcs() []*InvoiceHTLC { + if m != nil { + return m.Htlcs + } + return nil +} + +/// Details of an HTLC that paid to an invoice +type InvoiceHTLC struct { + /// Short channel id over which the htlc was received. + ChanId uint64 `protobuf:"varint,1,opt,name=chan_id,proto3" json:"chan_id,omitempty"` + /// Index identifying the htlc on the channel. + HtlcIndex uint64 `protobuf:"varint,2,opt,name=htlc_index,proto3" json:"htlc_index,omitempty"` + /// The amount of the htlc in msat. + AmtMsat uint64 `protobuf:"varint,3,opt,name=amt_msat,proto3" json:"amt_msat,omitempty"` + /// Block height at which this htlc was accepted. + AcceptHeight int32 `protobuf:"varint,4,opt,name=accept_height,proto3" json:"accept_height,omitempty"` + /// Time at which this htlc was accepted. + AcceptTime int64 `protobuf:"varint,5,opt,name=accept_time,proto3" json:"accept_time,omitempty"` + /// Time at which this htlc was settled or cancelled. + ResolveTime int64 `protobuf:"varint,6,opt,name=resolve_time,proto3" json:"resolve_time,omitempty"` + /// Block height at which this htlc expires. + ExpiryHeight int32 `protobuf:"varint,7,opt,name=expiry_height,proto3" json:"expiry_height,omitempty"` + /// Current state the htlc is in. + State InvoiceHTLCState `protobuf:"varint,8,opt,name=state,proto3,enum=lnrpc.InvoiceHTLCState" json:"state,omitempty"` + XXX_NoUnkeyedLiteral struct{} `json:"-"` + XXX_unrecognized []byte `json:"-"` + XXX_sizecache int32 `json:"-"` +} + +func (m *InvoiceHTLC) Reset() { *m = InvoiceHTLC{} } +func (m *InvoiceHTLC) String() string { return proto.CompactTextString(m) } +func (*InvoiceHTLC) ProtoMessage() {} +func (*InvoiceHTLC) Descriptor() ([]byte, []int) { + return fileDescriptor_77a6da22d6a3feb1, []int{94} +} + +func (m *InvoiceHTLC) XXX_Unmarshal(b []byte) error { + return xxx_messageInfo_InvoiceHTLC.Unmarshal(m, b) +} +func (m *InvoiceHTLC) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + return xxx_messageInfo_InvoiceHTLC.Marshal(b, m, deterministic) +} +func (m *InvoiceHTLC) XXX_Merge(src proto.Message) { + xxx_messageInfo_InvoiceHTLC.Merge(m, src) +} +func (m *InvoiceHTLC) XXX_Size() int { + return xxx_messageInfo_InvoiceHTLC.Size(m) +} +func (m *InvoiceHTLC) XXX_DiscardUnknown() { + xxx_messageInfo_InvoiceHTLC.DiscardUnknown(m) +} + +var xxx_messageInfo_InvoiceHTLC proto.InternalMessageInfo + +func (m *InvoiceHTLC) GetChanId() uint64 { + if m != nil { + return m.ChanId + } + return 0 +} + +func (m *InvoiceHTLC) GetHtlcIndex() uint64 { + if m != nil { + return m.HtlcIndex + } + return 0 +} + +func (m *InvoiceHTLC) GetAmtMsat() uint64 { + if m != nil { + return m.AmtMsat + } + return 0 +} + +func (m *InvoiceHTLC) GetAcceptHeight() int32 { + if m != nil { + return m.AcceptHeight + } + return 0 +} + +func (m *InvoiceHTLC) GetAcceptTime() int64 { + if m != nil { + return m.AcceptTime + } + return 0 +} + +func (m *InvoiceHTLC) GetResolveTime() int64 { + if m != nil { + return m.ResolveTime + } + return 0 +} + +func (m *InvoiceHTLC) GetExpiryHeight() int32 { + if m != nil { + return m.ExpiryHeight + } + return 0 +} + +func (m *InvoiceHTLC) GetState() InvoiceHTLCState { + if m != nil { + return m.State + } + return InvoiceHTLCState_ACCEPTED +} + type AddInvoiceResponse struct { RHash []byte `protobuf:"bytes,1,opt,name=r_hash,proto3" json:"r_hash,omitempty"` //* @@ -6807,7 +6948,7 @@ func (m *AddInvoiceResponse) Reset() { *m = AddInvoiceResponse{} } func (m *AddInvoiceResponse) String() string { return proto.CompactTextString(m) } func (*AddInvoiceResponse) ProtoMessage() {} func (*AddInvoiceResponse) Descriptor() ([]byte, []int) { - return fileDescriptor_77a6da22d6a3feb1, []int{94} + return fileDescriptor_77a6da22d6a3feb1, []int{95} } func (m *AddInvoiceResponse) XXX_Unmarshal(b []byte) error { @@ -6865,7 +7006,7 @@ func (m *PaymentHash) Reset() { *m = PaymentHash{} } func (m *PaymentHash) String() string { return proto.CompactTextString(m) } func (*PaymentHash) ProtoMessage() {} func (*PaymentHash) Descriptor() ([]byte, []int) { - return fileDescriptor_77a6da22d6a3feb1, []int{95} + return fileDescriptor_77a6da22d6a3feb1, []int{96} } func (m *PaymentHash) XXX_Unmarshal(b []byte) error { @@ -6922,7 +7063,7 @@ func (m *ListInvoiceRequest) Reset() { *m = ListInvoiceRequest{} } func (m *ListInvoiceRequest) String() string { return proto.CompactTextString(m) } func (*ListInvoiceRequest) ProtoMessage() {} func (*ListInvoiceRequest) Descriptor() ([]byte, []int) { - return fileDescriptor_77a6da22d6a3feb1, []int{96} + return fileDescriptor_77a6da22d6a3feb1, []int{97} } func (m *ListInvoiceRequest) XXX_Unmarshal(b []byte) error { @@ -6993,7 +7134,7 @@ func (m *ListInvoiceResponse) Reset() { *m = ListInvoiceResponse{} } func (m *ListInvoiceResponse) String() string { return proto.CompactTextString(m) } func (*ListInvoiceResponse) ProtoMessage() {} func (*ListInvoiceResponse) Descriptor() ([]byte, []int) { - return fileDescriptor_77a6da22d6a3feb1, []int{97} + return fileDescriptor_77a6da22d6a3feb1, []int{98} } func (m *ListInvoiceResponse) XXX_Unmarshal(b []byte) error { @@ -7057,7 +7198,7 @@ func (m *InvoiceSubscription) Reset() { *m = InvoiceSubscription{} } func (m *InvoiceSubscription) String() string { return proto.CompactTextString(m) } func (*InvoiceSubscription) ProtoMessage() {} func (*InvoiceSubscription) Descriptor() ([]byte, []int) { - return fileDescriptor_77a6da22d6a3feb1, []int{98} + return fileDescriptor_77a6da22d6a3feb1, []int{99} } func (m *InvoiceSubscription) XXX_Unmarshal(b []byte) error { @@ -7126,7 +7267,7 @@ func (m *Payment) Reset() { *m = Payment{} } func (m *Payment) String() string { return proto.CompactTextString(m) } func (*Payment) ProtoMessage() {} func (*Payment) Descriptor() ([]byte, []int) { - return fileDescriptor_77a6da22d6a3feb1, []int{99} + return fileDescriptor_77a6da22d6a3feb1, []int{100} } func (m *Payment) XXX_Unmarshal(b []byte) error { @@ -7248,7 +7389,7 @@ func (m *ListPaymentsRequest) Reset() { *m = ListPaymentsRequest{} } func (m *ListPaymentsRequest) String() string { return proto.CompactTextString(m) } func (*ListPaymentsRequest) ProtoMessage() {} func (*ListPaymentsRequest) Descriptor() ([]byte, []int) { - return fileDescriptor_77a6da22d6a3feb1, []int{100} + return fileDescriptor_77a6da22d6a3feb1, []int{101} } func (m *ListPaymentsRequest) XXX_Unmarshal(b []byte) error { @@ -7288,7 +7429,7 @@ func (m *ListPaymentsResponse) Reset() { *m = ListPaymentsResponse{} } func (m *ListPaymentsResponse) String() string { return proto.CompactTextString(m) } func (*ListPaymentsResponse) ProtoMessage() {} func (*ListPaymentsResponse) Descriptor() ([]byte, []int) { - return fileDescriptor_77a6da22d6a3feb1, []int{101} + return fileDescriptor_77a6da22d6a3feb1, []int{102} } func (m *ListPaymentsResponse) XXX_Unmarshal(b []byte) error { @@ -7326,7 +7467,7 @@ func (m *DeleteAllPaymentsRequest) Reset() { *m = DeleteAllPaymentsReque func (m *DeleteAllPaymentsRequest) String() string { return proto.CompactTextString(m) } func (*DeleteAllPaymentsRequest) ProtoMessage() {} func (*DeleteAllPaymentsRequest) Descriptor() ([]byte, []int) { - return fileDescriptor_77a6da22d6a3feb1, []int{102} + return fileDescriptor_77a6da22d6a3feb1, []int{103} } func (m *DeleteAllPaymentsRequest) XXX_Unmarshal(b []byte) error { @@ -7357,7 +7498,7 @@ func (m *DeleteAllPaymentsResponse) Reset() { *m = DeleteAllPaymentsResp func (m *DeleteAllPaymentsResponse) String() string { return proto.CompactTextString(m) } func (*DeleteAllPaymentsResponse) ProtoMessage() {} func (*DeleteAllPaymentsResponse) Descriptor() ([]byte, []int) { - return fileDescriptor_77a6da22d6a3feb1, []int{103} + return fileDescriptor_77a6da22d6a3feb1, []int{104} } func (m *DeleteAllPaymentsResponse) XXX_Unmarshal(b []byte) error { @@ -7389,7 +7530,7 @@ func (m *AbandonChannelRequest) Reset() { *m = AbandonChannelRequest{} } func (m *AbandonChannelRequest) String() string { return proto.CompactTextString(m) } func (*AbandonChannelRequest) ProtoMessage() {} func (*AbandonChannelRequest) Descriptor() ([]byte, []int) { - return fileDescriptor_77a6da22d6a3feb1, []int{104} + return fileDescriptor_77a6da22d6a3feb1, []int{105} } func (m *AbandonChannelRequest) XXX_Unmarshal(b []byte) error { @@ -7427,7 +7568,7 @@ func (m *AbandonChannelResponse) Reset() { *m = AbandonChannelResponse{} func (m *AbandonChannelResponse) String() string { return proto.CompactTextString(m) } func (*AbandonChannelResponse) ProtoMessage() {} func (*AbandonChannelResponse) Descriptor() ([]byte, []int) { - return fileDescriptor_77a6da22d6a3feb1, []int{105} + return fileDescriptor_77a6da22d6a3feb1, []int{106} } func (m *AbandonChannelResponse) XXX_Unmarshal(b []byte) error { @@ -7460,7 +7601,7 @@ func (m *DebugLevelRequest) Reset() { *m = DebugLevelRequest{} } func (m *DebugLevelRequest) String() string { return proto.CompactTextString(m) } func (*DebugLevelRequest) ProtoMessage() {} func (*DebugLevelRequest) Descriptor() ([]byte, []int) { - return fileDescriptor_77a6da22d6a3feb1, []int{106} + return fileDescriptor_77a6da22d6a3feb1, []int{107} } func (m *DebugLevelRequest) XXX_Unmarshal(b []byte) error { @@ -7506,7 +7647,7 @@ func (m *DebugLevelResponse) Reset() { *m = DebugLevelResponse{} } func (m *DebugLevelResponse) String() string { return proto.CompactTextString(m) } func (*DebugLevelResponse) ProtoMessage() {} func (*DebugLevelResponse) Descriptor() ([]byte, []int) { - return fileDescriptor_77a6da22d6a3feb1, []int{107} + return fileDescriptor_77a6da22d6a3feb1, []int{108} } func (m *DebugLevelResponse) XXX_Unmarshal(b []byte) error { @@ -7546,7 +7687,7 @@ func (m *PayReqString) Reset() { *m = PayReqString{} } func (m *PayReqString) String() string { return proto.CompactTextString(m) } func (*PayReqString) ProtoMessage() {} func (*PayReqString) Descriptor() ([]byte, []int) { - return fileDescriptor_77a6da22d6a3feb1, []int{108} + return fileDescriptor_77a6da22d6a3feb1, []int{109} } func (m *PayReqString) XXX_Unmarshal(b []byte) error { @@ -7594,7 +7735,7 @@ func (m *PayReq) Reset() { *m = PayReq{} } func (m *PayReq) String() string { return proto.CompactTextString(m) } func (*PayReq) ProtoMessage() {} func (*PayReq) Descriptor() ([]byte, []int) { - return fileDescriptor_77a6da22d6a3feb1, []int{109} + return fileDescriptor_77a6da22d6a3feb1, []int{110} } func (m *PayReq) XXX_Unmarshal(b []byte) error { @@ -7695,7 +7836,7 @@ func (m *FeeReportRequest) Reset() { *m = FeeReportRequest{} } func (m *FeeReportRequest) String() string { return proto.CompactTextString(m) } func (*FeeReportRequest) ProtoMessage() {} func (*FeeReportRequest) Descriptor() ([]byte, []int) { - return fileDescriptor_77a6da22d6a3feb1, []int{110} + return fileDescriptor_77a6da22d6a3feb1, []int{111} } func (m *FeeReportRequest) XXX_Unmarshal(b []byte) error { @@ -7734,7 +7875,7 @@ func (m *ChannelFeeReport) Reset() { *m = ChannelFeeReport{} } func (m *ChannelFeeReport) String() string { return proto.CompactTextString(m) } func (*ChannelFeeReport) ProtoMessage() {} func (*ChannelFeeReport) Descriptor() ([]byte, []int) { - return fileDescriptor_77a6da22d6a3feb1, []int{111} + return fileDescriptor_77a6da22d6a3feb1, []int{112} } func (m *ChannelFeeReport) XXX_Unmarshal(b []byte) error { @@ -7801,7 +7942,7 @@ func (m *FeeReportResponse) Reset() { *m = FeeReportResponse{} } func (m *FeeReportResponse) String() string { return proto.CompactTextString(m) } func (*FeeReportResponse) ProtoMessage() {} func (*FeeReportResponse) Descriptor() ([]byte, []int) { - return fileDescriptor_77a6da22d6a3feb1, []int{112} + return fileDescriptor_77a6da22d6a3feb1, []int{113} } func (m *FeeReportResponse) XXX_Unmarshal(b []byte) error { @@ -7870,7 +8011,7 @@ func (m *PolicyUpdateRequest) Reset() { *m = PolicyUpdateRequest{} } func (m *PolicyUpdateRequest) String() string { return proto.CompactTextString(m) } func (*PolicyUpdateRequest) ProtoMessage() {} func (*PolicyUpdateRequest) Descriptor() ([]byte, []int) { - return fileDescriptor_77a6da22d6a3feb1, []int{113} + return fileDescriptor_77a6da22d6a3feb1, []int{114} } func (m *PolicyUpdateRequest) XXX_Unmarshal(b []byte) error { @@ -7967,7 +8108,7 @@ func (m *PolicyUpdateResponse) Reset() { *m = PolicyUpdateResponse{} } func (m *PolicyUpdateResponse) String() string { return proto.CompactTextString(m) } func (*PolicyUpdateResponse) ProtoMessage() {} func (*PolicyUpdateResponse) Descriptor() ([]byte, []int) { - return fileDescriptor_77a6da22d6a3feb1, []int{114} + return fileDescriptor_77a6da22d6a3feb1, []int{115} } func (m *PolicyUpdateResponse) XXX_Unmarshal(b []byte) error { @@ -8006,7 +8147,7 @@ func (m *ForwardingHistoryRequest) Reset() { *m = ForwardingHistoryReque func (m *ForwardingHistoryRequest) String() string { return proto.CompactTextString(m) } func (*ForwardingHistoryRequest) ProtoMessage() {} func (*ForwardingHistoryRequest) Descriptor() ([]byte, []int) { - return fileDescriptor_77a6da22d6a3feb1, []int{115} + return fileDescriptor_77a6da22d6a3feb1, []int{116} } func (m *ForwardingHistoryRequest) XXX_Unmarshal(b []byte) error { @@ -8079,7 +8220,7 @@ func (m *ForwardingEvent) Reset() { *m = ForwardingEvent{} } func (m *ForwardingEvent) String() string { return proto.CompactTextString(m) } func (*ForwardingEvent) ProtoMessage() {} func (*ForwardingEvent) Descriptor() ([]byte, []int) { - return fileDescriptor_77a6da22d6a3feb1, []int{116} + return fileDescriptor_77a6da22d6a3feb1, []int{117} } func (m *ForwardingEvent) XXX_Unmarshal(b []byte) error { @@ -8163,7 +8304,7 @@ func (m *ForwardingHistoryResponse) Reset() { *m = ForwardingHistoryResp func (m *ForwardingHistoryResponse) String() string { return proto.CompactTextString(m) } func (*ForwardingHistoryResponse) ProtoMessage() {} func (*ForwardingHistoryResponse) Descriptor() ([]byte, []int) { - return fileDescriptor_77a6da22d6a3feb1, []int{117} + return fileDescriptor_77a6da22d6a3feb1, []int{118} } func (m *ForwardingHistoryResponse) XXX_Unmarshal(b []byte) error { @@ -8210,7 +8351,7 @@ func (m *ExportChannelBackupRequest) Reset() { *m = ExportChannelBackupR func (m *ExportChannelBackupRequest) String() string { return proto.CompactTextString(m) } func (*ExportChannelBackupRequest) ProtoMessage() {} func (*ExportChannelBackupRequest) Descriptor() ([]byte, []int) { - return fileDescriptor_77a6da22d6a3feb1, []int{118} + return fileDescriptor_77a6da22d6a3feb1, []int{119} } func (m *ExportChannelBackupRequest) XXX_Unmarshal(b []byte) error { @@ -8256,7 +8397,7 @@ func (m *ChannelBackup) Reset() { *m = ChannelBackup{} } func (m *ChannelBackup) String() string { return proto.CompactTextString(m) } func (*ChannelBackup) ProtoMessage() {} func (*ChannelBackup) Descriptor() ([]byte, []int) { - return fileDescriptor_77a6da22d6a3feb1, []int{119} + return fileDescriptor_77a6da22d6a3feb1, []int{120} } func (m *ChannelBackup) XXX_Unmarshal(b []byte) error { @@ -8309,7 +8450,7 @@ func (m *MultiChanBackup) Reset() { *m = MultiChanBackup{} } func (m *MultiChanBackup) String() string { return proto.CompactTextString(m) } func (*MultiChanBackup) ProtoMessage() {} func (*MultiChanBackup) Descriptor() ([]byte, []int) { - return fileDescriptor_77a6da22d6a3feb1, []int{120} + return fileDescriptor_77a6da22d6a3feb1, []int{121} } func (m *MultiChanBackup) XXX_Unmarshal(b []byte) error { @@ -8354,7 +8495,7 @@ func (m *ChanBackupExportRequest) Reset() { *m = ChanBackupExportRequest func (m *ChanBackupExportRequest) String() string { return proto.CompactTextString(m) } func (*ChanBackupExportRequest) ProtoMessage() {} func (*ChanBackupExportRequest) Descriptor() ([]byte, []int) { - return fileDescriptor_77a6da22d6a3feb1, []int{121} + return fileDescriptor_77a6da22d6a3feb1, []int{122} } func (m *ChanBackupExportRequest) XXX_Unmarshal(b []byte) error { @@ -8393,7 +8534,7 @@ func (m *ChanBackupSnapshot) Reset() { *m = ChanBackupSnapshot{} } func (m *ChanBackupSnapshot) String() string { return proto.CompactTextString(m) } func (*ChanBackupSnapshot) ProtoMessage() {} func (*ChanBackupSnapshot) Descriptor() ([]byte, []int) { - return fileDescriptor_77a6da22d6a3feb1, []int{122} + return fileDescriptor_77a6da22d6a3feb1, []int{123} } func (m *ChanBackupSnapshot) XXX_Unmarshal(b []byte) error { @@ -8441,7 +8582,7 @@ func (m *ChannelBackups) Reset() { *m = ChannelBackups{} } func (m *ChannelBackups) String() string { return proto.CompactTextString(m) } func (*ChannelBackups) ProtoMessage() {} func (*ChannelBackups) Descriptor() ([]byte, []int) { - return fileDescriptor_77a6da22d6a3feb1, []int{123} + return fileDescriptor_77a6da22d6a3feb1, []int{124} } func (m *ChannelBackups) XXX_Unmarshal(b []byte) error { @@ -8483,7 +8624,7 @@ func (m *RestoreChanBackupRequest) Reset() { *m = RestoreChanBackupReque func (m *RestoreChanBackupRequest) String() string { return proto.CompactTextString(m) } func (*RestoreChanBackupRequest) ProtoMessage() {} func (*RestoreChanBackupRequest) Descriptor() ([]byte, []int) { - return fileDescriptor_77a6da22d6a3feb1, []int{124} + return fileDescriptor_77a6da22d6a3feb1, []int{125} } func (m *RestoreChanBackupRequest) XXX_Unmarshal(b []byte) error { @@ -8559,7 +8700,7 @@ func (m *RestoreBackupResponse) Reset() { *m = RestoreBackupResponse{} } func (m *RestoreBackupResponse) String() string { return proto.CompactTextString(m) } func (*RestoreBackupResponse) ProtoMessage() {} func (*RestoreBackupResponse) Descriptor() ([]byte, []int) { - return fileDescriptor_77a6da22d6a3feb1, []int{125} + return fileDescriptor_77a6da22d6a3feb1, []int{126} } func (m *RestoreBackupResponse) XXX_Unmarshal(b []byte) error { @@ -8590,7 +8731,7 @@ func (m *ChannelBackupSubscription) Reset() { *m = ChannelBackupSubscrip func (m *ChannelBackupSubscription) String() string { return proto.CompactTextString(m) } func (*ChannelBackupSubscription) ProtoMessage() {} func (*ChannelBackupSubscription) Descriptor() ([]byte, []int) { - return fileDescriptor_77a6da22d6a3feb1, []int{126} + return fileDescriptor_77a6da22d6a3feb1, []int{127} } func (m *ChannelBackupSubscription) XXX_Unmarshal(b []byte) error { @@ -8621,7 +8762,7 @@ func (m *VerifyChanBackupResponse) Reset() { *m = VerifyChanBackupRespon func (m *VerifyChanBackupResponse) String() string { return proto.CompactTextString(m) } func (*VerifyChanBackupResponse) ProtoMessage() {} func (*VerifyChanBackupResponse) Descriptor() ([]byte, []int) { - return fileDescriptor_77a6da22d6a3feb1, []int{127} + return fileDescriptor_77a6da22d6a3feb1, []int{128} } func (m *VerifyChanBackupResponse) XXX_Unmarshal(b []byte) error { @@ -8644,6 +8785,7 @@ var xxx_messageInfo_VerifyChanBackupResponse proto.InternalMessageInfo func init() { proto.RegisterEnum("lnrpc.AddressType", AddressType_name, AddressType_value) + proto.RegisterEnum("lnrpc.InvoiceHTLCState", InvoiceHTLCState_name, InvoiceHTLCState_value) proto.RegisterEnum("lnrpc.ChannelCloseSummary_ClosureType", ChannelCloseSummary_ClosureType_name, ChannelCloseSummary_ClosureType_value) proto.RegisterEnum("lnrpc.Peer_SyncType", Peer_SyncType_name, Peer_SyncType_value) proto.RegisterEnum("lnrpc.ChannelEventUpdate_UpdateType", ChannelEventUpdate_UpdateType_name, ChannelEventUpdate_UpdateType_value) @@ -8753,6 +8895,7 @@ func init() { proto.RegisterType((*HopHint)(nil), "lnrpc.HopHint") proto.RegisterType((*RouteHint)(nil), "lnrpc.RouteHint") proto.RegisterType((*Invoice)(nil), "lnrpc.Invoice") + proto.RegisterType((*InvoiceHTLC)(nil), "lnrpc.InvoiceHTLC") proto.RegisterType((*AddInvoiceResponse)(nil), "lnrpc.AddInvoiceResponse") proto.RegisterType((*PaymentHash)(nil), "lnrpc.PaymentHash") proto.RegisterType((*ListInvoiceRequest)(nil), "lnrpc.ListInvoiceRequest") @@ -8792,515 +8935,523 @@ func init() { func init() { proto.RegisterFile("rpc.proto", fileDescriptor_77a6da22d6a3feb1) } var fileDescriptor_77a6da22d6a3feb1 = []byte{ - // 8125 bytes of a gzipped FileDescriptorProto + // 8246 bytes of a gzipped FileDescriptorProto 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xbc, 0x7d, 0x6d, 0x6c, 0x24, 0xd9, 0xb5, 0x90, 0xab, 0x3f, 0xec, 0xee, 0xd3, 0xed, 0xee, 0xf6, 0xb5, 0xc7, 0xee, 0xe9, 0x9d, 0x9d, - 0x9d, 0xad, 0xcc, 0xdb, 0x99, 0x4c, 0xf6, 0xd9, 0xb3, 0x93, 0x64, 0x99, 0xb7, 0xf3, 0xc2, 0xc3, - 0x63, 0x7b, 0xc6, 0x93, 0x78, 0x3d, 0x4e, 0xd9, 0x93, 0x21, 0xc9, 0x43, 0x9d, 0x72, 0xf7, 0x75, - 0xbb, 0x32, 0xd5, 0x55, 0x9d, 0xaa, 0x6a, 0x7b, 0x3a, 0xcb, 0xf2, 0x03, 0x21, 0x84, 0x9e, 0x84, - 0x50, 0x40, 0x20, 0x40, 0x20, 0xa4, 0x04, 0x21, 0x9e, 0xf8, 0x01, 0xfc, 0x00, 0x81, 0x14, 0x89, - 0x9f, 0xfc, 0x42, 0x08, 0xbd, 0x9f, 0x48, 0x3c, 0x21, 0x90, 0x50, 0xc4, 0x3f, 0x24, 0xfe, 0xa3, - 0x7b, 0xee, 0x47, 0xdd, 0x5b, 0x55, 0x3d, 0x9e, 0x49, 0xc2, 0xfb, 0xe5, 0xbe, 0xe7, 0x9c, 0xba, - 0x9f, 0xe7, 0x9c, 0x7b, 0xce, 0xb9, 0xe7, 0x5e, 0x43, 0x3d, 0x9a, 0x0c, 0x36, 0x27, 0x51, 0x98, - 0x84, 0xa4, 0xea, 0x07, 0xd1, 0x64, 0xd0, 0xbb, 0x31, 0x0a, 0xc3, 0x91, 0x4f, 0xb7, 0xdc, 0x89, - 0xb7, 0xe5, 0x06, 0x41, 0x98, 0xb8, 0x89, 0x17, 0x06, 0x31, 0x27, 0xb2, 0x7f, 0x04, 0xad, 0xa7, - 0x34, 0x38, 0xa6, 0x74, 0xe8, 0xd0, 0x9f, 0x4c, 0x69, 0x9c, 0x90, 0xaf, 0xc1, 0x8a, 0x4b, 0x7f, - 0x4a, 0xe9, 0xb0, 0x3f, 0x71, 0xe3, 0x78, 0x72, 0x1e, 0xb9, 0x31, 0xed, 0x5a, 0xb7, 0xac, 0xbb, - 0x4d, 0xa7, 0xc3, 0x11, 0x47, 0x0a, 0x4e, 0x3e, 0x84, 0x66, 0xcc, 0x48, 0x69, 0x90, 0x44, 0xe1, - 0x64, 0xd6, 0x2d, 0x21, 0x5d, 0x83, 0xc1, 0xf6, 0x38, 0xc8, 0xf6, 0xa1, 0xad, 0x5a, 0x88, 0x27, - 0x61, 0x10, 0x53, 0x72, 0x1f, 0xd6, 0x06, 0xde, 0xe4, 0x9c, 0x46, 0x7d, 0xfc, 0x78, 0x1c, 0xd0, - 0x71, 0x18, 0x78, 0x83, 0xae, 0x75, 0xab, 0x7c, 0xb7, 0xee, 0x10, 0x8e, 0x63, 0x5f, 0x7c, 0x2e, - 0x30, 0xe4, 0x0e, 0xb4, 0x69, 0xc0, 0xe1, 0x74, 0x88, 0x5f, 0x89, 0xa6, 0x5a, 0x29, 0x98, 0x7d, - 0x60, 0xff, 0x8d, 0x12, 0xac, 0x3c, 0x0b, 0xbc, 0xe4, 0xa5, 0xeb, 0xfb, 0x34, 0x91, 0x63, 0xba, - 0x03, 0xed, 0x4b, 0x04, 0xe0, 0x98, 0x2e, 0xc3, 0x68, 0x28, 0x46, 0xd4, 0xe2, 0xe0, 0x23, 0x01, - 0x9d, 0xdb, 0xb3, 0xd2, 0xdc, 0x9e, 0x15, 0x4e, 0x57, 0x79, 0xce, 0x74, 0xdd, 0x81, 0x76, 0x44, - 0x07, 0xe1, 0x05, 0x8d, 0x66, 0xfd, 0x4b, 0x2f, 0x18, 0x86, 0x97, 0xdd, 0xca, 0x2d, 0xeb, 0x6e, - 0xd5, 0x69, 0x49, 0xf0, 0x4b, 0x84, 0x92, 0xc7, 0xd0, 0x1e, 0x9c, 0xbb, 0x41, 0x40, 0xfd, 0xfe, - 0xa9, 0x3b, 0x78, 0x35, 0x9d, 0xc4, 0xdd, 0xea, 0x2d, 0xeb, 0x6e, 0xe3, 0xc1, 0xf5, 0x4d, 0x5c, - 0xd5, 0xcd, 0x9d, 0x73, 0x37, 0x78, 0x8c, 0x98, 0xe3, 0xc0, 0x9d, 0xc4, 0xe7, 0x61, 0xe2, 0xb4, - 0xc4, 0x17, 0x1c, 0x1c, 0xdb, 0x6b, 0x40, 0xf4, 0x99, 0xe0, 0x73, 0x6f, 0xff, 0x0b, 0x0b, 0x56, - 0x5f, 0x04, 0x7e, 0x38, 0x78, 0xf5, 0x6b, 0x4e, 0x51, 0xc1, 0x18, 0x4a, 0x6f, 0x3b, 0x86, 0xf2, - 0xbb, 0x8e, 0x61, 0x1d, 0xd6, 0xcc, 0xce, 0x8a, 0x51, 0x50, 0xb8, 0xc6, 0xbe, 0x1e, 0x51, 0xd9, - 0x2d, 0x39, 0x8c, 0xaf, 0x42, 0x67, 0x30, 0x8d, 0x22, 0x1a, 0xe4, 0xc6, 0xd1, 0x16, 0x70, 0x35, - 0x90, 0x0f, 0xa1, 0x19, 0xd0, 0xcb, 0x94, 0x4c, 0xf0, 0x6e, 0x40, 0x2f, 0x25, 0x89, 0xdd, 0x85, - 0xf5, 0x6c, 0x33, 0xa2, 0x03, 0xff, 0xdd, 0x82, 0xca, 0x8b, 0xe4, 0x75, 0x48, 0x36, 0xa1, 0x92, - 0xcc, 0x26, 0x5c, 0x42, 0x5a, 0x0f, 0x88, 0x18, 0xda, 0xf6, 0x70, 0x18, 0xd1, 0x38, 0x3e, 0x99, - 0x4d, 0xa8, 0xd3, 0x74, 0x79, 0xa1, 0xcf, 0xe8, 0x48, 0x17, 0x96, 0x44, 0x19, 0x1b, 0xac, 0x3b, - 0xb2, 0x48, 0x6e, 0x02, 0xb8, 0xe3, 0x70, 0x1a, 0x24, 0xfd, 0xd8, 0x4d, 0x70, 0xaa, 0xca, 0x8e, - 0x06, 0x21, 0x37, 0xa0, 0x3e, 0x79, 0xd5, 0x8f, 0x07, 0x91, 0x37, 0x49, 0x90, 0x6d, 0xea, 0x4e, - 0x0a, 0x20, 0x5f, 0x83, 0x5a, 0x38, 0x4d, 0x26, 0xa1, 0x17, 0x24, 0x82, 0x55, 0xda, 0xa2, 0x2f, - 0xcf, 0xa7, 0xc9, 0x11, 0x03, 0x3b, 0x8a, 0x80, 0xdc, 0x86, 0xe5, 0x41, 0x18, 0x9c, 0x79, 0xd1, - 0x98, 0x2b, 0x83, 0xee, 0x22, 0xb6, 0x66, 0x02, 0xed, 0x7f, 0x5f, 0x82, 0xc6, 0x49, 0xe4, 0x06, - 0xb1, 0x3b, 0x60, 0x00, 0xd6, 0xf5, 0xe4, 0x75, 0xff, 0xdc, 0x8d, 0xcf, 0x71, 0xb4, 0x75, 0x47, - 0x16, 0xc9, 0x3a, 0x2c, 0xf2, 0x8e, 0xe2, 0x98, 0xca, 0x8e, 0x28, 0x91, 0x8f, 0x61, 0x25, 0x98, - 0x8e, 0xfb, 0x66, 0x5b, 0x65, 0xe4, 0x96, 0x3c, 0x82, 0x4d, 0xc0, 0x29, 0x5b, 0x6b, 0xde, 0x04, - 0x1f, 0xa1, 0x06, 0x21, 0x36, 0x34, 0x45, 0x89, 0x7a, 0xa3, 0x73, 0x3e, 0xcc, 0xaa, 0x63, 0xc0, - 0x58, 0x1d, 0x89, 0x37, 0xa6, 0xfd, 0x38, 0x71, 0xc7, 0x13, 0x31, 0x2c, 0x0d, 0x82, 0xf8, 0x30, - 0x71, 0xfd, 0xfe, 0x19, 0xa5, 0x71, 0x77, 0x49, 0xe0, 0x15, 0x84, 0x7c, 0x04, 0xad, 0x21, 0x8d, - 0x93, 0xbe, 0x58, 0x14, 0x1a, 0x77, 0x6b, 0x28, 0xfa, 0x19, 0x28, 0xab, 0x27, 0x72, 0x2f, 0xfb, - 0x6c, 0x02, 0xe8, 0xeb, 0x6e, 0x9d, 0xf7, 0x35, 0x85, 0x30, 0xce, 0x79, 0x4a, 0x13, 0x6d, 0xf6, - 0x62, 0xc1, 0xa1, 0xf6, 0x01, 0x10, 0x0d, 0xbc, 0x4b, 0x13, 0xd7, 0xf3, 0x63, 0xf2, 0x29, 0x34, - 0x13, 0x8d, 0x18, 0x55, 0x61, 0x43, 0xb1, 0x93, 0xf6, 0x81, 0x63, 0xd0, 0xd9, 0x4f, 0xa1, 0xf6, - 0x84, 0xd2, 0x03, 0x6f, 0xec, 0x25, 0x64, 0x1d, 0xaa, 0x67, 0xde, 0x6b, 0xca, 0x19, 0xbe, 0xbc, - 0xbf, 0xe0, 0xf0, 0x22, 0xe9, 0xc1, 0xd2, 0x84, 0x46, 0x03, 0x2a, 0x97, 0x67, 0x7f, 0xc1, 0x91, - 0x80, 0xc7, 0x4b, 0x50, 0xf5, 0xd9, 0xc7, 0xf6, 0xaf, 0xca, 0xd0, 0x38, 0xa6, 0x81, 0x12, 0x24, - 0x02, 0x15, 0x36, 0x64, 0x21, 0x3c, 0xf8, 0x9b, 0x7c, 0x00, 0x0d, 0x9c, 0x86, 0x38, 0x89, 0xbc, - 0x60, 0x24, 0xf8, 0x17, 0x18, 0xe8, 0x18, 0x21, 0xa4, 0x03, 0x65, 0x77, 0x2c, 0x79, 0x97, 0xfd, - 0x64, 0x42, 0x36, 0x71, 0x67, 0x63, 0x26, 0x8f, 0x6a, 0x55, 0x9b, 0x4e, 0x43, 0xc0, 0xf6, 0xd9, - 0xb2, 0x6e, 0xc2, 0xaa, 0x4e, 0x22, 0x6b, 0xaf, 0x62, 0xed, 0x2b, 0x1a, 0xa5, 0x68, 0xe4, 0x0e, - 0xb4, 0x25, 0x7d, 0xc4, 0x3b, 0x8b, 0xeb, 0x5c, 0x77, 0x5a, 0x02, 0x2c, 0x87, 0x70, 0x17, 0x3a, - 0x67, 0x5e, 0xe0, 0xfa, 0xfd, 0x81, 0x9f, 0x5c, 0xf4, 0x87, 0xd4, 0x4f, 0x5c, 0x5c, 0xf1, 0xaa, - 0xd3, 0x42, 0xf8, 0x8e, 0x9f, 0x5c, 0xec, 0x32, 0x28, 0xf9, 0x18, 0xea, 0x67, 0x94, 0xf6, 0x71, - 0x26, 0xba, 0x35, 0x43, 0x7a, 0xe4, 0xec, 0x3a, 0xb5, 0x33, 0x39, 0xcf, 0x77, 0xa1, 0x13, 0x4e, - 0x93, 0x51, 0xe8, 0x05, 0xa3, 0x3e, 0xd3, 0x57, 0x7d, 0x6f, 0x88, 0x1c, 0x50, 0x71, 0x5a, 0x12, - 0xce, 0xb4, 0xc6, 0xb3, 0x21, 0x79, 0x1f, 0x00, 0xdb, 0xe6, 0x15, 0xc3, 0x2d, 0xeb, 0xee, 0xb2, - 0x53, 0x67, 0x10, 0x5e, 0xd1, 0x67, 0x50, 0xc3, 0xf9, 0x4c, 0xfc, 0x8b, 0x6e, 0x03, 0x17, 0xfc, - 0x03, 0xd1, 0xaa, 0xb6, 0x12, 0x9b, 0xbb, 0x34, 0x4e, 0x4e, 0xfc, 0x0b, 0xb6, 0x9f, 0xce, 0x9c, - 0xa5, 0x21, 0x2f, 0xf5, 0x3e, 0x83, 0xa6, 0x8e, 0x60, 0x53, 0xff, 0x8a, 0xce, 0x70, 0xb9, 0x2a, - 0x0e, 0xfb, 0x49, 0xd6, 0xa0, 0x7a, 0xe1, 0xfa, 0x53, 0x2a, 0x14, 0x1b, 0x2f, 0x7c, 0x56, 0x7a, - 0x68, 0xd9, 0xff, 0xce, 0x82, 0x26, 0x6f, 0x41, 0x6c, 0xc8, 0xb7, 0x61, 0x59, 0x4e, 0x29, 0x8d, - 0xa2, 0x30, 0x12, 0xf2, 0x6d, 0x02, 0xc9, 0x3d, 0xe8, 0x48, 0xc0, 0x24, 0xa2, 0xde, 0xd8, 0x1d, - 0xc9, 0xba, 0x73, 0x70, 0xf2, 0x20, 0xad, 0x31, 0x0a, 0xa7, 0x09, 0x15, 0xaa, 0xbf, 0x29, 0xc6, - 0xe7, 0x30, 0x98, 0x63, 0x92, 0x30, 0xf9, 0x2e, 0xe0, 0x15, 0x03, 0x66, 0xff, 0xcc, 0x02, 0xc2, - 0xba, 0x7e, 0x12, 0xf2, 0x2a, 0xc4, 0x52, 0x67, 0xd9, 0xcc, 0x7a, 0x6b, 0x36, 0x2b, 0xcd, 0x63, - 0x33, 0x1b, 0xaa, 0xbc, 0xe7, 0x95, 0x82, 0x9e, 0x73, 0xd4, 0xb7, 0x2b, 0xb5, 0x72, 0xa7, 0x62, - 0xff, 0xdc, 0x82, 0xe6, 0x0e, 0xdf, 0xb7, 0x50, 0xd1, 0x92, 0xfb, 0x40, 0xce, 0xa6, 0xc1, 0x90, - 0xf1, 0x47, 0xf2, 0xda, 0x1b, 0xf6, 0x4f, 0x67, 0x09, 0x8d, 0x79, 0x9f, 0xf6, 0x17, 0x9c, 0x02, - 0x1c, 0xf9, 0x18, 0x3a, 0x06, 0x34, 0x4e, 0x22, 0xde, 0xb3, 0xfd, 0x05, 0x27, 0x87, 0x61, 0x13, - 0xc5, 0x54, 0xf9, 0x34, 0xe9, 0x7b, 0xc1, 0x90, 0xbe, 0xc6, 0xb9, 0x5d, 0x76, 0x0c, 0xd8, 0xe3, - 0x16, 0x34, 0xf5, 0xef, 0xec, 0x1f, 0x43, 0x4d, 0x6e, 0x04, 0xa8, 0x04, 0x33, 0xfd, 0x72, 0x34, - 0x08, 0xe9, 0x41, 0xcd, 0xec, 0x85, 0x53, 0x7b, 0x97, 0xb6, 0xed, 0x3f, 0x0f, 0x9d, 0x03, 0xa6, - 0x8d, 0x03, 0x2f, 0x18, 0x89, 0x9d, 0x90, 0x6d, 0x11, 0x93, 0xe9, 0xa9, 0x64, 0xd1, 0xba, 0x23, - 0x4a, 0x4c, 0xcf, 0x9c, 0x87, 0x71, 0x22, 0xda, 0xc1, 0xdf, 0xf6, 0x7f, 0xb4, 0x80, 0xec, 0xc5, - 0x89, 0x37, 0x76, 0x13, 0xfa, 0x84, 0xaa, 0x45, 0x7e, 0x0e, 0x4d, 0x56, 0xdb, 0x49, 0xb8, 0xcd, - 0xf7, 0x1a, 0xae, 0x23, 0xbf, 0x26, 0x16, 0x26, 0xff, 0xc1, 0xa6, 0x4e, 0xcd, 0xc5, 0xc7, 0xa8, - 0x80, 0xe9, 0xb3, 0xc4, 0x8d, 0x46, 0x34, 0xc1, 0x8d, 0x48, 0x98, 0x31, 0xc0, 0x41, 0x3b, 0x61, - 0x70, 0xd6, 0xfb, 0x03, 0x58, 0xc9, 0xd5, 0xa1, 0x4b, 0x5a, 0xbd, 0x40, 0xd2, 0xca, 0xba, 0xa4, - 0x0d, 0x60, 0xd5, 0xe8, 0x97, 0x90, 0xb7, 0x2e, 0x2c, 0x31, 0x7d, 0xc3, 0xf6, 0x79, 0xd4, 0xd5, - 0x8e, 0x2c, 0x92, 0x07, 0xb0, 0x76, 0x46, 0x69, 0xe4, 0x26, 0x58, 0xec, 0x4f, 0x68, 0x84, 0x6b, - 0x22, 0x6a, 0x2e, 0xc4, 0xd9, 0xff, 0xc3, 0x82, 0x36, 0x93, 0x89, 0xcf, 0xdd, 0x60, 0x26, 0xe7, - 0xea, 0xa0, 0x70, 0xae, 0xee, 0x6a, 0xea, 0x45, 0xa3, 0x7e, 0xd7, 0x89, 0x2a, 0x67, 0x27, 0x8a, - 0xdc, 0x82, 0xa6, 0xd1, 0xdd, 0x2a, 0xdf, 0x58, 0x63, 0x37, 0x39, 0xa2, 0xd1, 0xe3, 0x59, 0x42, - 0x7f, 0xf3, 0xa9, 0xfc, 0x08, 0x3a, 0x69, 0xb7, 0xc5, 0x3c, 0x12, 0xa8, 0x30, 0xc6, 0x14, 0x15, - 0xe0, 0x6f, 0xfb, 0x1f, 0x59, 0x9c, 0x70, 0x27, 0xf4, 0xd4, 0xa6, 0xcb, 0x08, 0xd9, 0xde, 0x2d, - 0x09, 0xd9, 0xef, 0xb9, 0x46, 0xcb, 0x6f, 0x3e, 0x58, 0x72, 0x1d, 0x6a, 0x31, 0x0d, 0x86, 0x7d, - 0xd7, 0xf7, 0x71, 0x6f, 0xaa, 0x39, 0x4b, 0xac, 0xbc, 0xed, 0xfb, 0xf6, 0x1d, 0x58, 0xd1, 0x7a, - 0xf7, 0x86, 0x71, 0x1c, 0x02, 0x39, 0xf0, 0xe2, 0xe4, 0x45, 0x10, 0x4f, 0xb4, 0x3d, 0xed, 0x3d, - 0xa8, 0x8f, 0xbd, 0x00, 0x7b, 0xc6, 0x25, 0xb7, 0xea, 0xd4, 0xc6, 0x5e, 0xc0, 0xfa, 0x15, 0x23, - 0xd2, 0x7d, 0x2d, 0x90, 0x25, 0x81, 0x74, 0x5f, 0x23, 0xd2, 0x7e, 0x08, 0xab, 0x46, 0x7d, 0xa2, - 0xe9, 0x0f, 0xa1, 0x3a, 0x4d, 0x5e, 0x87, 0xd2, 0xe2, 0x68, 0x08, 0x0e, 0x61, 0xb6, 0xad, 0xc3, - 0x31, 0xf6, 0x23, 0x58, 0x39, 0xa4, 0x97, 0x42, 0x90, 0x65, 0x47, 0x3e, 0xba, 0xd2, 0xee, 0x45, - 0xbc, 0xbd, 0x09, 0x44, 0xff, 0x38, 0x15, 0x00, 0x69, 0x05, 0x5b, 0x86, 0x15, 0x6c, 0x7f, 0x04, - 0xe4, 0xd8, 0x1b, 0x05, 0x9f, 0xd3, 0x38, 0x76, 0x47, 0x4a, 0xf4, 0x3b, 0x50, 0x1e, 0xc7, 0x23, - 0xa1, 0xaa, 0xd8, 0x4f, 0xfb, 0xeb, 0xb0, 0x6a, 0xd0, 0x89, 0x8a, 0x6f, 0x40, 0x3d, 0xf6, 0x46, - 0x81, 0x9b, 0x4c, 0x23, 0x2a, 0xaa, 0x4e, 0x01, 0xf6, 0x13, 0x58, 0xfb, 0x1e, 0x8d, 0xbc, 0xb3, - 0xd9, 0x55, 0xd5, 0x9b, 0xf5, 0x94, 0xb2, 0xf5, 0xec, 0xc1, 0xb5, 0x4c, 0x3d, 0xa2, 0x79, 0xce, - 0xbe, 0x62, 0x25, 0x6b, 0x0e, 0x2f, 0x68, 0xba, 0xaf, 0xa4, 0xeb, 0x3e, 0xfb, 0x05, 0x90, 0x9d, - 0x30, 0x08, 0xe8, 0x20, 0x39, 0xa2, 0x34, 0x4a, 0x1d, 0xf0, 0x94, 0x57, 0x1b, 0x0f, 0x36, 0xc4, - 0xcc, 0x66, 0x15, 0xaa, 0x60, 0x62, 0x02, 0x95, 0x09, 0x8d, 0xc6, 0x58, 0x71, 0xcd, 0xc1, 0xdf, - 0xf6, 0x35, 0x58, 0x35, 0xaa, 0x15, 0x2e, 0xcb, 0x27, 0x70, 0x6d, 0xd7, 0x8b, 0x07, 0xf9, 0x06, - 0xbb, 0xb0, 0x34, 0x99, 0x9e, 0xf6, 0x53, 0x49, 0x94, 0x45, 0x66, 0xc5, 0x66, 0x3f, 0x11, 0x95, - 0xfd, 0x75, 0x0b, 0x2a, 0xfb, 0x27, 0x07, 0x3b, 0x6c, 0xaf, 0xf0, 0x82, 0x41, 0x38, 0x66, 0x7b, - 0x29, 0x1f, 0xb4, 0x2a, 0xcf, 0x95, 0xb0, 0x1b, 0x50, 0xc7, 0x2d, 0x98, 0x19, 0xee, 0xc2, 0x57, - 0x4e, 0x01, 0xcc, 0x69, 0xa0, 0xaf, 0x27, 0x5e, 0x84, 0x5e, 0x81, 0xb4, 0xf5, 0x2b, 0xb8, 0xcd, - 0xe4, 0x11, 0xf6, 0x2f, 0x17, 0x61, 0x49, 0x6c, 0xbe, 0xd8, 0xde, 0x20, 0xf1, 0x2e, 0xa8, 0xe8, - 0x89, 0x28, 0x31, 0xf3, 0x26, 0xa2, 0xe3, 0x30, 0xa1, 0x7d, 0x63, 0x19, 0x4c, 0x20, 0x3a, 0x45, - 0xc2, 0x5f, 0xe5, 0x6e, 0x54, 0x99, 0x53, 0x19, 0x40, 0x36, 0x59, 0xd2, 0xe6, 0xab, 0xa0, 0xad, - 0x25, 0x8b, 0x6c, 0x26, 0x06, 0xee, 0xc4, 0x1d, 0x78, 0xc9, 0x4c, 0xa8, 0x04, 0x55, 0x66, 0x75, - 0xfb, 0xe1, 0xc0, 0x65, 0x9e, 0xb0, 0xef, 0x06, 0x03, 0x2a, 0x1d, 0x2e, 0x03, 0xc8, 0x9c, 0x0f, - 0xd1, 0x25, 0x49, 0xc6, 0x1d, 0x94, 0x0c, 0x94, 0xed, 0xdf, 0x83, 0x70, 0x3c, 0xf6, 0x12, 0xe6, - 0xb3, 0xa0, 0xbd, 0x5a, 0x76, 0x34, 0x08, 0x77, 0xef, 0xb0, 0x74, 0xc9, 0x67, 0xaf, 0x2e, 0xdd, - 0x3b, 0x0d, 0xc8, 0x6a, 0x61, 0xbb, 0x0e, 0x53, 0x63, 0xaf, 0x2e, 0xd1, 0x38, 0x2d, 0x3b, 0x1a, - 0x84, 0xad, 0xc3, 0x34, 0x88, 0x69, 0x92, 0xf8, 0x74, 0xa8, 0x3a, 0xd4, 0x40, 0xb2, 0x3c, 0x82, - 0xdc, 0x87, 0x55, 0xee, 0x46, 0xc5, 0x6e, 0x12, 0xc6, 0xe7, 0x5e, 0xdc, 0x8f, 0x99, 0xc3, 0xd1, - 0x44, 0xfa, 0x22, 0x14, 0x79, 0x08, 0x1b, 0x19, 0x70, 0x44, 0x07, 0xd4, 0xbb, 0xa0, 0xc3, 0xee, - 0x32, 0x7e, 0x35, 0x0f, 0x4d, 0x6e, 0x41, 0x83, 0x79, 0x8f, 0xd3, 0xc9, 0xd0, 0x65, 0x06, 0x4c, - 0x0b, 0xd7, 0x41, 0x07, 0x91, 0x4f, 0x60, 0x79, 0x42, 0xb9, 0xf5, 0x73, 0x9e, 0xf8, 0x83, 0xb8, - 0xdb, 0x36, 0xb4, 0x1b, 0xe3, 0x5c, 0xc7, 0xa4, 0x60, 0x4c, 0x39, 0x88, 0xd1, 0x4d, 0x70, 0x67, - 0xdd, 0x8e, 0x30, 0xd5, 0x25, 0x00, 0x65, 0x24, 0xf2, 0x2e, 0xdc, 0x84, 0x76, 0x57, 0xb8, 0x42, - 0x17, 0x45, 0xf6, 0x9d, 0x17, 0x78, 0x89, 0xe7, 0x26, 0x61, 0xd4, 0x25, 0x88, 0x4b, 0x01, 0x6c, - 0x12, 0x91, 0x3f, 0xe2, 0xc4, 0x4d, 0xa6, 0x71, 0xff, 0xcc, 0x77, 0x47, 0x71, 0x77, 0x95, 0xdb, - 0x9c, 0x39, 0x04, 0xf9, 0x14, 0xd6, 0x39, 0x47, 0x20, 0x2a, 0xa2, 0x31, 0x8d, 0x2e, 0xb8, 0x99, - 0xb0, 0x86, 0x33, 0x32, 0x07, 0xcb, 0xa6, 0x52, 0xb0, 0x48, 0xee, 0xc3, 0x6b, 0x7c, 0x2a, 0xe7, - 0xa0, 0xed, 0x7f, 0x62, 0xf1, 0x6d, 0x41, 0x88, 0x90, 0x52, 0xef, 0x1f, 0x40, 0x83, 0x0b, 0x4f, - 0x3f, 0x0c, 0xfc, 0x99, 0x90, 0x27, 0xe0, 0xa0, 0xe7, 0x81, 0x3f, 0x23, 0x5f, 0x81, 0x65, 0x2f, - 0xd0, 0x49, 0xb8, 0x06, 0x6a, 0x4a, 0x20, 0x12, 0x7d, 0x00, 0x8d, 0xc9, 0xf4, 0xd4, 0xf7, 0x06, - 0x9c, 0xa4, 0xcc, 0x6b, 0xe1, 0x20, 0x24, 0x60, 0x76, 0x3b, 0x9f, 0x47, 0x4e, 0x51, 0x41, 0x8a, - 0x86, 0x80, 0x31, 0x12, 0xfb, 0x31, 0xac, 0x99, 0x1d, 0x14, 0xaa, 0xf6, 0x1e, 0xd4, 0x84, 0x64, - 0xc6, 0xc2, 0x79, 0x6a, 0x69, 0x71, 0xa5, 0x80, 0xfa, 0x8e, 0xc2, 0xdb, 0xff, 0xb6, 0x02, 0xab, - 0x02, 0xba, 0xe3, 0x87, 0x31, 0x3d, 0x9e, 0x8e, 0xc7, 0x6e, 0x54, 0x20, 0xf2, 0xd6, 0x15, 0x22, - 0x5f, 0x32, 0x45, 0x9e, 0x09, 0xe2, 0xb9, 0xeb, 0x05, 0xdc, 0xe9, 0xe0, 0xfa, 0x42, 0x83, 0x90, - 0xbb, 0xd0, 0x1e, 0xf8, 0x61, 0xcc, 0x8d, 0x70, 0x3d, 0xac, 0x91, 0x05, 0xe7, 0x55, 0x54, 0xb5, - 0x48, 0x45, 0xe9, 0x2a, 0x66, 0x31, 0xa3, 0x62, 0x6c, 0x68, 0xb2, 0x4a, 0xa9, 0xd4, 0x98, 0x4b, - 0xdc, 0x30, 0xd7, 0x61, 0xac, 0x3f, 0x59, 0x81, 0xe6, 0xda, 0xa3, 0x5d, 0x24, 0xce, 0xde, 0x98, - 0xa2, 0x46, 0xd6, 0xa8, 0xeb, 0x42, 0x9c, 0xf3, 0x28, 0xf2, 0x84, 0xf9, 0xba, 0xac, 0x2d, 0x34, - 0x0b, 0x00, 0xcd, 0x82, 0x8f, 0xcc, 0x15, 0xd1, 0xe7, 0x7e, 0x93, 0x15, 0xa6, 0x11, 0x45, 0x53, - 0x41, 0xfb, 0xd2, 0xfe, 0x23, 0x0b, 0x1a, 0x1a, 0x8e, 0x5c, 0x83, 0x95, 0x9d, 0xe7, 0xcf, 0x8f, - 0xf6, 0x9c, 0xed, 0x93, 0x67, 0xdf, 0xdb, 0xeb, 0xef, 0x1c, 0x3c, 0x3f, 0xde, 0xeb, 0x2c, 0x30, - 0xf0, 0xc1, 0xf3, 0x9d, 0xed, 0x83, 0xfe, 0x93, 0xe7, 0xce, 0x8e, 0x04, 0x5b, 0x64, 0x1d, 0x88, - 0xb3, 0xf7, 0xf9, 0xf3, 0x93, 0x3d, 0x03, 0x5e, 0x22, 0x1d, 0x68, 0x3e, 0x76, 0xf6, 0xb6, 0x77, - 0xf6, 0x05, 0xa4, 0x4c, 0xd6, 0xa0, 0xf3, 0xe4, 0xc5, 0xe1, 0xee, 0xb3, 0xc3, 0xa7, 0xfd, 0x9d, - 0xed, 0xc3, 0x9d, 0xbd, 0x83, 0xbd, 0xdd, 0x4e, 0x85, 0x2c, 0x43, 0x7d, 0xfb, 0xf1, 0xf6, 0xe1, - 0xee, 0xf3, 0xc3, 0xbd, 0xdd, 0x4e, 0xd5, 0xfe, 0x6f, 0x16, 0x5c, 0xc3, 0x5e, 0x0f, 0xb3, 0x02, - 0x72, 0x0b, 0x1a, 0x83, 0x30, 0x9c, 0x30, 0x73, 0x3c, 0xdd, 0x70, 0x74, 0x10, 0x63, 0x7e, 0x2e, - 0xae, 0x67, 0x61, 0x34, 0xa0, 0x42, 0x3e, 0x00, 0x41, 0x4f, 0x18, 0x84, 0x31, 0xbf, 0x58, 0x5e, - 0x4e, 0xc1, 0xc5, 0xa3, 0xc1, 0x61, 0x9c, 0x64, 0x1d, 0x16, 0x4f, 0x23, 0xea, 0x0e, 0xce, 0x85, - 0x64, 0x88, 0x12, 0xf9, 0x6a, 0xea, 0x2f, 0x0e, 0xd8, 0xec, 0xfb, 0x74, 0x88, 0x1c, 0x53, 0x73, - 0xda, 0x02, 0xbe, 0x23, 0xc0, 0x4c, 0x3f, 0xb9, 0xa7, 0x6e, 0x30, 0x0c, 0x03, 0x3a, 0x14, 0xc6, - 0x68, 0x0a, 0xb0, 0x8f, 0x60, 0x3d, 0x3b, 0x3e, 0x21, 0x5f, 0x9f, 0x6a, 0xf2, 0xc5, 0x6d, 0xc3, - 0xde, 0xfc, 0xd5, 0xd4, 0x64, 0xed, 0x4f, 0x4b, 0x50, 0x61, 0xa6, 0xc2, 0x7c, 0xb3, 0x42, 0xb7, - 0xfe, 0xca, 0xb9, 0x18, 0x28, 0xba, 0xa0, 0x7c, 0xf3, 0xe0, 0x1b, 0xac, 0x06, 0x49, 0xf1, 0x11, - 0x1d, 0x5c, 0xe0, 0x88, 0x15, 0x9e, 0x41, 0x98, 0x80, 0x30, 0xd3, 0x1c, 0xbf, 0x16, 0x02, 0x22, - 0xcb, 0x12, 0x87, 0x5f, 0x2e, 0xa5, 0x38, 0xfc, 0xae, 0x0b, 0x4b, 0x5e, 0x70, 0x1a, 0x4e, 0x83, - 0x21, 0x0a, 0x44, 0xcd, 0x91, 0x45, 0x8c, 0xba, 0xa2, 0xa0, 0x7a, 0x63, 0xc9, 0xfe, 0x29, 0x80, - 0x3c, 0x80, 0x7a, 0x3c, 0x0b, 0x06, 0x3a, 0xcf, 0xaf, 0x89, 0x59, 0x62, 0x73, 0xb0, 0x79, 0x3c, - 0x0b, 0x06, 0xc8, 0xe1, 0x29, 0x99, 0xfd, 0x07, 0x50, 0x93, 0x60, 0xc6, 0x96, 0x2f, 0x0e, 0xbf, - 0x73, 0xf8, 0xfc, 0xe5, 0x61, 0xff, 0xf8, 0xfb, 0x87, 0x3b, 0x9d, 0x05, 0xd2, 0x86, 0xc6, 0xf6, - 0x0e, 0x72, 0x3a, 0x02, 0x2c, 0x46, 0x72, 0xb4, 0x7d, 0x7c, 0xac, 0x20, 0x25, 0x9b, 0x30, 0xf7, - 0x3a, 0x46, 0x7b, 0x4c, 0x45, 0x15, 0x3f, 0x85, 0x15, 0x0d, 0x96, 0xda, 0xf6, 0x13, 0x06, 0xc8, - 0xd8, 0xf6, 0x68, 0xc8, 0x71, 0x8c, 0xdd, 0x81, 0xd6, 0x53, 0x9a, 0x3c, 0x0b, 0xce, 0x42, 0x59, - 0xd3, 0xff, 0xaa, 0x40, 0x5b, 0x81, 0x44, 0x45, 0x77, 0xa1, 0xed, 0x0d, 0x69, 0x90, 0x78, 0xc9, - 0xac, 0x6f, 0x78, 0xf1, 0x59, 0x30, 0x33, 0x80, 0x5d, 0xdf, 0x73, 0x65, 0x70, 0x9b, 0x17, 0x98, - 0x57, 0xcb, 0x76, 0x67, 0xb9, 0xe1, 0x2a, 0xbe, 0xe2, 0xc1, 0x83, 0x42, 0x1c, 0xd3, 0x40, 0x0c, - 0x2e, 0xb6, 0x18, 0xf5, 0x09, 0x37, 0x04, 0x8b, 0x50, 0x6c, 0xa9, 0x78, 0x4d, 0x6c, 0xc8, 0x55, - 0xbe, 0x83, 0x2b, 0x40, 0x2e, 0x7a, 0xbc, 0xc8, 0xf5, 0x63, 0x36, 0x7a, 0xac, 0x45, 0xa0, 0x6b, - 0xb9, 0x08, 0x34, 0xd3, 0x9f, 0xb3, 0x60, 0x40, 0x87, 0xfd, 0x24, 0xec, 0xa3, 0x9e, 0x47, 0x96, - 0xa8, 0x39, 0x59, 0x30, 0xb9, 0x01, 0x4b, 0x09, 0x8d, 0x93, 0x80, 0xf2, 0xb0, 0x5f, 0xed, 0x71, - 0xa9, 0x6b, 0x39, 0x12, 0xc4, 0xac, 0xf6, 0x69, 0xe4, 0xc5, 0xdd, 0x26, 0xc6, 0x96, 0xf1, 0x37, - 0xf9, 0x06, 0x5c, 0x3b, 0xa5, 0x71, 0xd2, 0x3f, 0xa7, 0xee, 0x90, 0x46, 0xc8, 0x5e, 0x3c, 0x88, - 0xcd, 0x8d, 0xa1, 0x62, 0x24, 0x63, 0xdc, 0x0b, 0x1a, 0xc5, 0x5e, 0x18, 0xa0, 0x19, 0x54, 0x77, - 0x64, 0x91, 0xd5, 0xc7, 0x06, 0xaf, 0x36, 0x69, 0x35, 0x83, 0x6d, 0x1c, 0x78, 0x31, 0x92, 0xdc, - 0x86, 0x45, 0x1c, 0x40, 0xdc, 0xed, 0x20, 0xcf, 0x34, 0x53, 0x99, 0xf7, 0x02, 0x47, 0xe0, 0xd8, - 0x2a, 0x0f, 0x42, 0x3f, 0x8c, 0xd0, 0x16, 0xaa, 0x3b, 0xbc, 0x60, 0xce, 0xce, 0x28, 0x72, 0x27, - 0xe7, 0xc2, 0x1e, 0xca, 0x82, 0xbf, 0x5d, 0xa9, 0x35, 0x3a, 0x4d, 0xfb, 0xcf, 0x41, 0x15, 0xab, - 0xc5, 0xea, 0x70, 0x32, 0x2d, 0x51, 0x1d, 0x42, 0xbb, 0xb0, 0x14, 0xd0, 0xe4, 0x32, 0x8c, 0x5e, - 0xc9, 0x93, 0x12, 0x51, 0xb4, 0x7f, 0x8a, 0x7e, 0x93, 0x3a, 0x39, 0x78, 0x81, 0x46, 0x1f, 0xf3, - 0x7e, 0xf9, 0x52, 0xc5, 0xe7, 0xae, 0x70, 0xe5, 0x6a, 0x08, 0x38, 0x3e, 0x77, 0x99, 0xae, 0x35, - 0x56, 0x9f, 0x7b, 0xc7, 0x0d, 0x84, 0xed, 0xf3, 0xc5, 0xbf, 0x0d, 0x2d, 0x79, 0x26, 0x11, 0xf7, - 0x7d, 0x7a, 0x96, 0xc8, 0xd8, 0x56, 0x30, 0x1d, 0xa3, 0x0b, 0x7d, 0x40, 0xcf, 0x12, 0xfb, 0x10, - 0x56, 0x84, 0xfe, 0x7b, 0x3e, 0xa1, 0xb2, 0xe9, 0xdf, 0x2b, 0xb2, 0x23, 0x1a, 0x0f, 0x56, 0x4d, - 0x85, 0xc9, 0x4f, 0x61, 0x4c, 0x4a, 0xdb, 0x01, 0xa2, 0xeb, 0x53, 0x51, 0xa1, 0xd8, 0xcc, 0x65, - 0xf4, 0x4e, 0x0c, 0xc7, 0x80, 0xb1, 0xf9, 0x89, 0xa7, 0x83, 0x81, 0x3c, 0x49, 0xaa, 0x39, 0xb2, - 0x68, 0xff, 0x73, 0x0b, 0x56, 0xb1, 0x36, 0x69, 0x09, 0x89, 0x3d, 0xeb, 0xe1, 0x3b, 0x74, 0xb3, - 0x39, 0xd0, 0x23, 0x9a, 0x6b, 0x50, 0xd5, 0x77, 0x31, 0x5e, 0x78, 0xf7, 0x48, 0x49, 0x25, 0x1b, - 0x29, 0xb1, 0xff, 0xbe, 0x05, 0x2b, 0x7c, 0x23, 0x41, 0x3b, 0x58, 0x0c, 0xff, 0xf7, 0x61, 0x99, - 0x5b, 0x04, 0x42, 0x2b, 0x88, 0x8e, 0xa6, 0xaa, 0x15, 0xa1, 0x9c, 0x78, 0x7f, 0xc1, 0x31, 0x89, - 0xc9, 0x23, 0xb4, 0xca, 0x82, 0x3e, 0x42, 0x0b, 0xce, 0x1c, 0xcd, 0xb9, 0xde, 0x5f, 0x70, 0x34, - 0xf2, 0xc7, 0x35, 0x58, 0xe4, 0x4e, 0x84, 0xfd, 0x14, 0x96, 0x8d, 0x86, 0x8c, 0x28, 0x4d, 0x93, - 0x47, 0x69, 0x72, 0xe1, 0xd0, 0x52, 0x41, 0x38, 0xf4, 0x5f, 0x97, 0x81, 0x30, 0x66, 0xc9, 0xac, - 0x06, 0xf3, 0x62, 0xc2, 0xa1, 0xe1, 0x93, 0x36, 0x1d, 0x1d, 0x44, 0x36, 0x81, 0x68, 0x45, 0x19, - 0xb1, 0xe6, 0x5b, 0x66, 0x01, 0x86, 0xa9, 0x59, 0x61, 0x71, 0x08, 0xdb, 0x40, 0x78, 0xdf, 0x7c, - 0xda, 0x0b, 0x71, 0x6c, 0x57, 0x9c, 0x4c, 0xe3, 0x73, 0xf4, 0x15, 0x84, 0xd7, 0x2a, 0xcb, 0xd9, - 0xf5, 0x5d, 0xbc, 0x72, 0x7d, 0x97, 0x72, 0x91, 0x30, 0xcd, 0x6f, 0xaa, 0x99, 0x7e, 0xd3, 0x6d, - 0x58, 0x1e, 0x33, 0x3b, 0x39, 0xf1, 0x07, 0xfd, 0x31, 0x6b, 0x5d, 0x38, 0xa9, 0x06, 0x90, 0xdc, - 0x83, 0x8e, 0x74, 0x5d, 0x94, 0x73, 0xc6, 0xcf, 0x51, 0x72, 0x70, 0xa6, 0xff, 0xd3, 0xd8, 0x58, - 0x03, 0x3b, 0x9b, 0x02, 0x98, 0x27, 0x16, 0x33, 0x0e, 0xe9, 0x4f, 0x03, 0x71, 0xec, 0x48, 0x87, - 0xe8, 0x9e, 0xd6, 0x9c, 0x3c, 0xc2, 0xfe, 0xdb, 0x16, 0x74, 0xd8, 0x9a, 0x19, 0x6c, 0xf9, 0x19, - 0xa0, 0x54, 0xbc, 0x25, 0x57, 0x1a, 0xb4, 0xe4, 0x21, 0xd4, 0xb1, 0x1c, 0x4e, 0x68, 0x20, 0x78, - 0xb2, 0x6b, 0xf2, 0x64, 0xaa, 0x4f, 0xf6, 0x17, 0x9c, 0x94, 0x58, 0xe3, 0xc8, 0xff, 0x6c, 0x41, - 0x43, 0xb4, 0xf2, 0x6b, 0xc7, 0x5e, 0x7a, 0xda, 0x39, 0x31, 0xe7, 0xa4, 0xf4, 0x58, 0xf8, 0x2e, - 0xb4, 0xc7, 0x6e, 0x32, 0x8d, 0xd8, 0x7e, 0x6e, 0xc4, 0x5d, 0xb2, 0x60, 0xb6, 0x39, 0xa3, 0xea, - 0x8c, 0xfb, 0x89, 0xe7, 0xf7, 0x25, 0x56, 0x9c, 0xc8, 0x16, 0xa1, 0x98, 0x06, 0x89, 0x13, 0x77, - 0x44, 0xc5, 0xbe, 0xcb, 0x0b, 0x76, 0x17, 0xd6, 0xc5, 0x80, 0x32, 0xf6, 0xb5, 0xfd, 0x2f, 0x97, - 0x61, 0x23, 0x87, 0x52, 0xf9, 0x23, 0x22, 0xa0, 0xe0, 0x7b, 0xe3, 0xd3, 0x50, 0x39, 0x27, 0x96, - 0x1e, 0x6b, 0x30, 0x50, 0x64, 0x04, 0xd7, 0xa4, 0x81, 0xc1, 0xe6, 0x34, 0xdd, 0x0c, 0x4b, 0xb8, - 0xcb, 0x7d, 0x62, 0x2e, 0x61, 0xb6, 0x41, 0x09, 0xd7, 0x85, 0xb8, 0xb8, 0x3e, 0x72, 0x0e, 0x5d, - 0x65, 0xc9, 0x08, 0x65, 0xad, 0x59, 0x3b, 0xac, 0xad, 0x8f, 0xaf, 0x68, 0xcb, 0x30, 0xc7, 0x9d, - 0xb9, 0xb5, 0x91, 0x19, 0xdc, 0x94, 0x38, 0xd4, 0xc6, 0xf9, 0xf6, 0x2a, 0x6f, 0x35, 0x36, 0x74, - 0x34, 0xcc, 0x46, 0xaf, 0xa8, 0x98, 0xfc, 0x18, 0xd6, 0x2f, 0x5d, 0x2f, 0x91, 0xdd, 0xd2, 0x6c, - 0x8b, 0x2a, 0x36, 0xf9, 0xe0, 0x8a, 0x26, 0x5f, 0xf2, 0x8f, 0x8d, 0x2d, 0x6a, 0x4e, 0x8d, 0xbd, - 0x5f, 0x96, 0xa0, 0x65, 0xd6, 0xc3, 0xd8, 0x54, 0xc8, 0xbe, 0xd4, 0x81, 0xd2, 0x1a, 0xcd, 0x80, - 0xf3, 0xfe, 0x7d, 0xa9, 0xc8, 0xbf, 0xd7, 0xbd, 0xea, 0xf2, 0x55, 0x81, 0xbb, 0xca, 0xdb, 0x05, - 0xee, 0xaa, 0x85, 0x81, 0xbb, 0xf9, 0xf1, 0x9d, 0xc5, 0x5f, 0x37, 0xbe, 0xb3, 0xf4, 0xc6, 0xf8, - 0x4e, 0xef, 0xff, 0x5a, 0x40, 0xf2, 0xdc, 0x4b, 0x9e, 0xf2, 0x90, 0x46, 0x40, 0x7d, 0xa1, 0xc4, - 0x7e, 0xf7, 0xed, 0x24, 0x40, 0xae, 0x96, 0xfc, 0x9a, 0x89, 0xa2, 0x9e, 0xc4, 0xa1, 0x9b, 0x57, - 0xcb, 0x4e, 0x11, 0x2a, 0x13, 0xbc, 0xac, 0x5c, 0x1d, 0xbc, 0xac, 0x5e, 0x1d, 0xbc, 0x5c, 0xcc, - 0x06, 0x2f, 0x7b, 0x7f, 0xcd, 0x82, 0xd5, 0x02, 0x36, 0xfb, 0xed, 0x0d, 0x9c, 0x31, 0x86, 0xa1, - 0x7d, 0x4a, 0x82, 0x31, 0x74, 0x60, 0xef, 0x2f, 0xc3, 0xb2, 0x21, 0x5a, 0xbf, 0xbd, 0xf6, 0xb3, - 0x16, 0x22, 0xe7, 0x6c, 0x03, 0xd6, 0xfb, 0xdf, 0x25, 0x20, 0x79, 0xf1, 0xfe, 0x33, 0xed, 0x43, - 0x7e, 0x9e, 0xca, 0x05, 0xf3, 0xf4, 0xff, 0x75, 0xe7, 0xf9, 0x18, 0x56, 0x44, 0x66, 0x9a, 0x16, - 0xc8, 0xe2, 0x1c, 0x93, 0x47, 0x30, 0x1b, 0xd9, 0x8c, 0x1c, 0xd7, 0x8c, 0x4c, 0x1c, 0x6d, 0xfb, - 0xcd, 0x04, 0x90, 0xed, 0x1e, 0x74, 0xc5, 0x0c, 0xed, 0x5d, 0xd0, 0x20, 0x39, 0x9e, 0x9e, 0xf2, - 0xd4, 0x2c, 0x2f, 0x0c, 0xec, 0x7f, 0x53, 0x56, 0x66, 0x3e, 0x22, 0x85, 0x41, 0xf1, 0x0d, 0x68, - 0xea, 0xdb, 0x87, 0x58, 0x8e, 0x4c, 0x1c, 0x93, 0x99, 0x12, 0x3a, 0x15, 0xd9, 0x85, 0x16, 0x2a, - 0xc9, 0xa1, 0xfa, 0xae, 0x84, 0xdf, 0xbd, 0x21, 0x3e, 0xb3, 0xbf, 0xe0, 0x64, 0xbe, 0x21, 0xdf, - 0x82, 0x96, 0xe9, 0xfc, 0x09, 0xab, 0xa4, 0xc8, 0x1b, 0x60, 0x9f, 0x9b, 0xc4, 0x64, 0x1b, 0x3a, - 0x59, 0xef, 0x51, 0x64, 0x4a, 0xcc, 0xa9, 0x20, 0x47, 0x4e, 0x1e, 0x8a, 0x23, 0xc4, 0x2a, 0xc6, - 0x4d, 0x6e, 0x9b, 0x9f, 0x69, 0xd3, 0xb4, 0xc9, 0xff, 0x68, 0x87, 0x8a, 0x7f, 0x08, 0x90, 0xc2, - 0x48, 0x07, 0x9a, 0xcf, 0x8f, 0xf6, 0x0e, 0xfb, 0x3b, 0xfb, 0xdb, 0x87, 0x87, 0x7b, 0x07, 0x9d, - 0x05, 0x42, 0xa0, 0x85, 0x61, 0xbe, 0x5d, 0x05, 0xb3, 0x18, 0x4c, 0x04, 0x56, 0x24, 0xac, 0x44, - 0xd6, 0xa0, 0xf3, 0xec, 0x30, 0x03, 0x2d, 0x3f, 0xae, 0x2b, 0xf9, 0xb0, 0xd7, 0x61, 0x8d, 0x67, - 0x1e, 0x3e, 0xe6, 0xec, 0x21, 0xad, 0x93, 0x7f, 0x6c, 0xc1, 0xb5, 0x0c, 0x22, 0x4d, 0xa5, 0xe1, - 0x06, 0x88, 0x69, 0x95, 0x98, 0x40, 0x3c, 0x16, 0x90, 0xb6, 0x66, 0x46, 0x83, 0xe4, 0x11, 0x8c, - 0xe7, 0x35, 0xdb, 0x34, 0x23, 0x49, 0x45, 0x28, 0x7b, 0x83, 0xe7, 0x47, 0x62, 0x26, 0xa5, 0xd1, - 0xf1, 0x33, 0x9e, 0xd1, 0xa8, 0x23, 0xd2, 0x23, 0x59, 0xb3, 0xcb, 0xb2, 0xc8, 0xdc, 0x0a, 0xc3, - 0xd8, 0x31, 0xfb, 0x5b, 0x88, 0xb3, 0xff, 0xa8, 0x02, 0xe4, 0xbb, 0x53, 0x1a, 0xcd, 0x30, 0x5f, - 0x46, 0x45, 0x4d, 0x37, 0xb2, 0x31, 0xc1, 0xc5, 0xc9, 0xf4, 0xf4, 0x3b, 0x74, 0x26, 0x33, 0xc7, - 0x4a, 0x69, 0xe6, 0x58, 0x51, 0xf6, 0x56, 0xe5, 0xea, 0xec, 0xad, 0xea, 0x55, 0xd9, 0x5b, 0x5f, - 0x81, 0x65, 0x6f, 0x14, 0x84, 0x4c, 0xe6, 0x99, 0x9d, 0x10, 0x77, 0x17, 0x6f, 0x95, 0x99, 0x6f, - 0x2d, 0x80, 0x87, 0x0c, 0x46, 0x1e, 0xa5, 0x44, 0x74, 0x38, 0xc2, 0x4c, 0x41, 0x5d, 0x0b, 0xec, - 0x0d, 0x47, 0xf4, 0x20, 0x1c, 0xb8, 0x49, 0x18, 0x61, 0x60, 0x47, 0x7e, 0xcc, 0xe0, 0x31, 0xb9, - 0x0d, 0xad, 0x38, 0x9c, 0x32, 0xcb, 0x49, 0x8e, 0x95, 0x47, 0x92, 0x9a, 0x1c, 0x7a, 0xc4, 0x47, - 0xbc, 0x09, 0xab, 0xd3, 0x98, 0xf6, 0xc7, 0x5e, 0x1c, 0xb3, 0xdd, 0x71, 0x10, 0x06, 0x49, 0x14, - 0xfa, 0x22, 0x9e, 0xb4, 0x32, 0x8d, 0xe9, 0xe7, 0x1c, 0xb3, 0xc3, 0x11, 0xe4, 0x1b, 0x69, 0x97, - 0x26, 0xae, 0x17, 0xc5, 0x5d, 0xc0, 0x2e, 0xc9, 0x91, 0xb2, 0x7e, 0x1f, 0xb9, 0x5e, 0xa4, 0xfa, - 0xc2, 0x0a, 0x31, 0xd9, 0xce, 0xa5, 0x98, 0xc9, 0x98, 0x7c, 0x7e, 0x75, 0x7e, 0xfb, 0x99, 0x66, - 0x22, 0x41, 0x6a, 0x13, 0x6a, 0xb2, 0x7b, 0xcc, 0x89, 0x3e, 0x8b, 0xc2, 0xb1, 0x74, 0xa2, 0xd9, - 0x6f, 0xd2, 0x82, 0x52, 0x12, 0x8a, 0x8f, 0x4b, 0x49, 0x68, 0x7f, 0x1f, 0x1a, 0xda, 0x0c, 0x63, - 0x16, 0x9d, 0x30, 0xd8, 0x84, 0xf7, 0x5d, 0xe1, 0xfe, 0x51, 0x40, 0xfd, 0x67, 0x43, 0xf2, 0x35, - 0x58, 0x19, 0x7a, 0x11, 0xc5, 0x84, 0xc8, 0x7e, 0x44, 0x2f, 0x68, 0x14, 0xcb, 0x38, 0x45, 0x47, - 0x21, 0x1c, 0x0e, 0xb7, 0x1f, 0xc1, 0xaa, 0x31, 0x70, 0x25, 0xb5, 0x8b, 0x98, 0xd1, 0x25, 0x43, - 0xa5, 0x66, 0xb6, 0x97, 0xc0, 0xd9, 0xff, 0xac, 0x0c, 0xe5, 0xfd, 0x70, 0xa2, 0x1f, 0x08, 0x59, - 0xe6, 0x81, 0x90, 0x30, 0x38, 0xfb, 0xca, 0x9e, 0x14, 0x56, 0x81, 0x01, 0x24, 0xf7, 0xa0, 0xe5, - 0x8e, 0x93, 0x7e, 0x12, 0x32, 0x03, 0xfb, 0xd2, 0x8d, 0x86, 0x5c, 0x94, 0x91, 0x95, 0x32, 0x18, - 0xb2, 0x06, 0x65, 0x65, 0x27, 0x21, 0x01, 0x2b, 0x32, 0xef, 0x0e, 0x8f, 0xc2, 0x67, 0x22, 0xce, - 0x29, 0x4a, 0x4c, 0x53, 0x98, 0xdf, 0x73, 0xd7, 0x9a, 0xef, 0x76, 0x45, 0x28, 0x66, 0xfc, 0x32, - 0xe1, 0x19, 0xa7, 0xb6, 0xa4, 0x2a, 0xeb, 0x11, 0xfc, 0x9a, 0x19, 0xc1, 0xbf, 0x05, 0x8d, 0xc4, - 0xbf, 0xe8, 0x4f, 0xdc, 0x99, 0x1f, 0xba, 0x43, 0xc1, 0xb4, 0x3a, 0x88, 0xfc, 0x3e, 0xa7, 0x60, - 0x1b, 0x6c, 0x34, 0x94, 0xcc, 0x2a, 0x77, 0xa8, 0xfd, 0x70, 0xb2, 0x79, 0xe2, 0x5f, 0x38, 0x1c, - 0xc9, 0xf9, 0x4d, 0x27, 0xef, 0x7d, 0x0b, 0xda, 0x19, 0xfc, 0x3b, 0x25, 0x38, 0xfe, 0xca, 0x82, - 0x2a, 0x2e, 0x1d, 0x33, 0x2c, 0xb8, 0xe6, 0x55, 0x47, 0x56, 0x58, 0xc3, 0xb2, 0x93, 0x05, 0x13, - 0xdb, 0xc8, 0x0c, 0x2e, 0xa9, 0xf9, 0xd6, 0xb3, 0x83, 0x6f, 0x41, 0x9d, 0x97, 0x54, 0x96, 0x2b, - 0x92, 0xa4, 0x40, 0x72, 0x13, 0x2a, 0xe7, 0xe1, 0x44, 0xfa, 0x5e, 0x90, 0x8e, 0xd7, 0x41, 0x78, - 0xda, 0x1f, 0x56, 0x1f, 0x9f, 0x75, 0x6e, 0xdf, 0x66, 0xc1, 0xcc, 0xa7, 0x50, 0xd5, 0xea, 0xab, - 0x98, 0x81, 0xda, 0x2f, 0xa0, 0xcd, 0x84, 0x4b, 0x0b, 0xe1, 0xcf, 0xd7, 0xb2, 0x5f, 0x65, 0x9b, - 0xf6, 0xc0, 0x9f, 0x0e, 0xa9, 0xee, 0x01, 0x63, 0x88, 0x56, 0xc0, 0xa5, 0xed, 0x67, 0xff, 0x2b, - 0x8b, 0x0b, 0x2d, 0xab, 0x97, 0xdc, 0x85, 0x0a, 0xd3, 0x95, 0x99, 0x80, 0x87, 0x4a, 0x49, 0x61, - 0x74, 0x0e, 0x52, 0x30, 0x93, 0x10, 0x83, 0xa8, 0x7a, 0xed, 0x3c, 0x84, 0x9a, 0xba, 0x8f, 0x6a, - 0x64, 0x19, 0xaf, 0x2b, 0x03, 0x25, 0x9b, 0xda, 0x09, 0x54, 0xc5, 0xd0, 0xbf, 0xd2, 0x46, 0x18, - 0x8e, 0xa8, 0x76, 0xf2, 0xf4, 0xc7, 0x16, 0x2c, 0x1b, 0x7d, 0x62, 0x6c, 0xea, 0xbb, 0x71, 0x22, - 0xd2, 0x02, 0xc4, 0xca, 0xeb, 0x20, 0x9d, 0xc5, 0x4b, 0x26, 0x8b, 0xab, 0x93, 0x8c, 0xb2, 0x7e, - 0x92, 0x71, 0x1f, 0xea, 0x69, 0x6a, 0xb8, 0xd9, 0x29, 0xd6, 0xa2, 0x4c, 0xce, 0x49, 0x89, 0xd2, - 0x58, 0x79, 0x55, 0x8b, 0x95, 0xdb, 0x8f, 0xa0, 0xa1, 0xd1, 0xeb, 0xb1, 0x6e, 0xcb, 0x88, 0x75, - 0xab, 0xcc, 0xb5, 0x52, 0x9a, 0xb9, 0x66, 0xff, 0xac, 0x04, 0xcb, 0x8c, 0xbd, 0xbd, 0x60, 0x74, - 0x14, 0xfa, 0xde, 0x60, 0x86, 0x6c, 0x25, 0x39, 0x59, 0xec, 0x95, 0x92, 0xcd, 0x4d, 0x30, 0x93, - 0x77, 0x19, 0x61, 0x13, 0xca, 0x49, 0x95, 0x99, 0xf6, 0x62, 0xb2, 0x7f, 0xea, 0xc6, 0x42, 0x21, - 0x08, 0x5b, 0xdd, 0x00, 0x32, 0x1d, 0xc3, 0x00, 0x98, 0x87, 0x38, 0xf6, 0x7c, 0xdf, 0xe3, 0xb4, - 0xdc, 0x93, 0x2b, 0x42, 0xb1, 0x36, 0x87, 0x5e, 0xec, 0x9e, 0xa6, 0xa7, 0x94, 0xaa, 0x8c, 0x61, - 0x40, 0xf7, 0xb5, 0x16, 0x06, 0x5c, 0x44, 0x01, 0x37, 0x81, 0xd9, 0x85, 0x5c, 0xca, 0x2d, 0xa4, - 0xfd, 0x1f, 0x4a, 0xd0, 0xd0, 0xd8, 0x42, 0x1c, 0xcd, 0x9b, 0x9b, 0x86, 0x06, 0x91, 0x78, 0x23, - 0x2e, 0xa0, 0x41, 0xc8, 0x6d, 0xb3, 0x45, 0x3c, 0x0a, 0x40, 0x61, 0x37, 0xd8, 0xe7, 0x06, 0xd4, - 0x19, 0xdb, 0x7f, 0x82, 0x41, 0x08, 0x71, 0x27, 0x43, 0x01, 0x24, 0xf6, 0x01, 0x62, 0xab, 0x29, - 0x16, 0x01, 0x6f, 0x3c, 0xcc, 0x7f, 0x08, 0x4d, 0x51, 0x0d, 0xae, 0x2f, 0x0e, 0x38, 0x15, 0x3c, - 0x63, 0xed, 0x1d, 0x83, 0x52, 0x7e, 0xf9, 0x40, 0x7e, 0x59, 0xbb, 0xea, 0x4b, 0x49, 0x69, 0x3f, - 0x55, 0x39, 0x12, 0x4f, 0x23, 0x77, 0x72, 0x2e, 0x95, 0xc9, 0x7d, 0x58, 0x95, 0x3a, 0x63, 0x1a, - 0xb8, 0x41, 0x10, 0x4e, 0x83, 0x01, 0x95, 0x09, 0x6e, 0x45, 0x28, 0x7b, 0xa8, 0xd2, 0xa1, 0xb1, - 0x22, 0x72, 0x0f, 0xaa, 0xdc, 0xd2, 0xe2, 0x7b, 0x6b, 0xb1, 0xfa, 0xe0, 0x24, 0xe4, 0x2e, 0x54, - 0xb9, 0xc1, 0x55, 0x9a, 0x2b, 0xf0, 0x9c, 0xc0, 0xbe, 0x07, 0x6d, 0xcc, 0xb2, 0x37, 0xf5, 0x9e, - 0xb9, 0x2f, 0x2f, 0x0e, 0x30, 0x0f, 0xdf, 0x5e, 0x03, 0x72, 0xc8, 0xe5, 0x49, 0x3f, 0xe9, 0xfc, - 0x55, 0x19, 0x1a, 0x1a, 0x98, 0xe9, 0x25, 0x3c, 0x9e, 0xea, 0x0f, 0x3d, 0x77, 0x4c, 0x13, 0x1a, - 0x09, 0x19, 0xca, 0x40, 0x19, 0x9d, 0x7b, 0x31, 0xea, 0x87, 0xd3, 0xa4, 0x3f, 0xa4, 0xa3, 0x88, - 0xf2, 0x0d, 0x88, 0x6d, 0xdd, 0x06, 0x94, 0xd1, 0x31, 0x2e, 0xd6, 0xe8, 0xf8, 0x81, 0x52, 0x06, - 0x2a, 0xcf, 0x2d, 0xf9, 0x1c, 0x55, 0xd2, 0x73, 0x4b, 0x3e, 0x23, 0x59, 0x8d, 0x5a, 0x2d, 0xd0, - 0xa8, 0x9f, 0xc2, 0x3a, 0xd7, 0x9d, 0x42, 0x6b, 0xf4, 0x33, 0x8c, 0x35, 0x07, 0x4b, 0xee, 0x41, - 0x87, 0xf5, 0x59, 0x8a, 0x45, 0xec, 0xfd, 0x94, 0xcb, 0x96, 0xe5, 0xe4, 0xe0, 0x8c, 0x16, 0x83, - 0xe9, 0x3a, 0x2d, 0x4f, 0x1e, 0xc9, 0xc1, 0x91, 0xd6, 0x7d, 0x6d, 0xd2, 0xd6, 0x05, 0x6d, 0x06, - 0x4e, 0x1e, 0xc2, 0xc6, 0x98, 0x0e, 0x3d, 0xd7, 0xac, 0x02, 0x63, 0x5b, 0x3c, 0x27, 0x6d, 0x1e, - 0x9a, 0xb5, 0xc2, 0x66, 0xe1, 0xa7, 0xe1, 0xf8, 0xd4, 0xe3, 0x1b, 0x1a, 0x0f, 0xfb, 0x57, 0x9c, - 0x1c, 0xdc, 0x5e, 0x86, 0xc6, 0x71, 0x12, 0x4e, 0xe4, 0xd2, 0xb7, 0xa0, 0xc9, 0x8b, 0x22, 0x9d, - 0xf1, 0x3d, 0xb8, 0x8e, 0xbc, 0x7a, 0x12, 0x4e, 0x42, 0x3f, 0x1c, 0xcd, 0x0c, 0xe7, 0xfd, 0x3f, - 0x59, 0xb0, 0x6a, 0x60, 0x53, 0xef, 0x1d, 0x23, 0x8d, 0x32, 0x0f, 0x8d, 0xb3, 0xf7, 0x8a, 0xb6, - 0x1d, 0x70, 0x42, 0x7e, 0xa8, 0xf3, 0x42, 0xa4, 0xa6, 0x6d, 0xa7, 0xd7, 0xe2, 0xe4, 0x87, 0x9c, - 0xd7, 0xbb, 0x79, 0x5e, 0x17, 0xdf, 0xcb, 0x5b, 0x71, 0xb2, 0x8a, 0x6f, 0x89, 0x54, 0x9f, 0xa1, - 0x18, 0x74, 0xd9, 0x4c, 0xcf, 0xd0, 0x83, 0x3d, 0xb2, 0x07, 0x03, 0x05, 0x8c, 0xed, 0x9f, 0x5b, - 0x00, 0x69, 0xef, 0x30, 0x41, 0x44, 0x6d, 0x69, 0xfc, 0x0a, 0xa6, 0xb6, 0x7d, 0x7d, 0x08, 0x4d, - 0x75, 0xc6, 0x9f, 0xee, 0x92, 0x0d, 0x09, 0x63, 0x56, 0xc5, 0x1d, 0x68, 0x8f, 0xfc, 0xf0, 0x14, - 0xad, 0x17, 0xcc, 0x8f, 0x8d, 0x45, 0x52, 0x67, 0x8b, 0x83, 0x9f, 0x08, 0x68, 0xba, 0xa5, 0x56, - 0xf4, 0x2d, 0xb5, 0x78, 0x83, 0xfc, 0x9b, 0x25, 0x75, 0xd0, 0x9a, 0xce, 0xc4, 0x5c, 0x09, 0x27, - 0x0f, 0x72, 0xea, 0x7c, 0xce, 0xb9, 0x26, 0x3a, 0x0e, 0x47, 0x57, 0xc6, 0x7d, 0x1f, 0x41, 0x2b, - 0xe2, 0xba, 0x52, 0x2a, 0xd2, 0xca, 0x1b, 0x14, 0xe9, 0x72, 0x64, 0xec, 0xc6, 0x5f, 0x85, 0x8e, - 0x3b, 0xbc, 0xa0, 0x51, 0xe2, 0x61, 0x1c, 0x0c, 0x4d, 0x27, 0x3e, 0xb8, 0xb6, 0x06, 0x47, 0x0b, - 0xe5, 0x0e, 0xb4, 0x45, 0x7a, 0xad, 0xa2, 0x14, 0x97, 0x99, 0x52, 0x30, 0x23, 0xb4, 0x7f, 0x21, - 0xcf, 0x74, 0xcd, 0x95, 0x9d, 0x3f, 0x23, 0xfa, 0xe8, 0x4a, 0x99, 0xd1, 0x7d, 0x45, 0x9c, 0xaf, - 0x0e, 0x65, 0xb0, 0xad, 0xac, 0x25, 0x8b, 0x0d, 0xc5, 0x79, 0xb8, 0x39, 0xa5, 0x95, 0xb7, 0x99, - 0x52, 0xfb, 0x4f, 0x2c, 0x58, 0xda, 0x0f, 0x27, 0xfb, 0x22, 0x6d, 0x0e, 0xc5, 0x43, 0xe5, 0xb5, - 0xcb, 0xe2, 0x1b, 0x12, 0xea, 0x0a, 0x2d, 0x90, 0xe5, 0xac, 0x05, 0xf2, 0x17, 0xe0, 0x3d, 0x0c, - 0xf5, 0x46, 0xe1, 0x24, 0x8c, 0x98, 0x88, 0xba, 0x3e, 0x37, 0x37, 0xc2, 0x20, 0x39, 0x97, 0x2a, - 0xf4, 0x4d, 0x24, 0x18, 0x7f, 0xf1, 0x93, 0x8b, 0x3e, 0x77, 0x9b, 0x84, 0xc5, 0xc4, 0x35, 0x6b, - 0x1e, 0x61, 0xff, 0x1e, 0xd4, 0xd1, 0x9b, 0xc0, 0x61, 0x7d, 0x0c, 0xf5, 0xf3, 0x70, 0xd2, 0x3f, - 0xf7, 0x82, 0x44, 0x8a, 0x7c, 0x2b, 0x35, 0xf3, 0xf7, 0x71, 0x42, 0x14, 0x81, 0xfd, 0x77, 0x17, - 0x61, 0xe9, 0x59, 0x70, 0x11, 0x7a, 0x03, 0x3c, 0x3f, 0x1e, 0xd3, 0x71, 0x28, 0xb3, 0xfc, 0xd9, - 0x6f, 0x72, 0x03, 0x96, 0x30, 0xad, 0x75, 0xc2, 0x99, 0xb6, 0xc9, 0xf3, 0x44, 0x04, 0x08, 0x6f, - 0x19, 0xa6, 0x77, 0xad, 0xb8, 0x50, 0x69, 0x10, 0xe6, 0x06, 0x46, 0xfa, 0x5d, 0x29, 0x51, 0x4a, - 0x3d, 0xa3, 0xaa, 0x76, 0x8b, 0x82, 0xb5, 0x25, 0xd2, 0xfc, 0x78, 0x1e, 0x18, 0x6f, 0x4b, 0x80, - 0xd0, 0x75, 0x8d, 0x28, 0x0f, 0xd5, 0x2b, 0x23, 0x8b, 0xb9, 0xae, 0x3a, 0x90, 0x19, 0x62, 0xfc, - 0x03, 0x4e, 0xc3, 0x37, 0x00, 0x1d, 0xc4, 0x4c, 0xd1, 0xec, 0xf5, 0x3c, 0x7e, 0x3d, 0x32, 0x0b, - 0x66, 0xfa, 0x7b, 0x48, 0x95, 0x9a, 0xe5, 0xe3, 0x00, 0x7e, 0x9f, 0x2c, 0x0b, 0xd7, 0x1c, 0x5e, - 0x9e, 0x81, 0x2c, 0x1d, 0x5e, 0xc6, 0x30, 0xae, 0xef, 0x9f, 0xba, 0x83, 0x57, 0x78, 0x3b, 0x13, - 0x4f, 0x74, 0xeb, 0x8e, 0x09, 0xc4, 0x64, 0xbd, 0x74, 0x55, 0x31, 0xa3, 0xa6, 0xe2, 0xe8, 0x20, - 0xf2, 0x00, 0x1a, 0xe8, 0xe4, 0x8b, 0x75, 0x6d, 0xe1, 0xba, 0x76, 0xf4, 0x28, 0x00, 0xae, 0xac, - 0x4e, 0xa4, 0x9f, 0x6d, 0xb7, 0x73, 0x39, 0xc1, 0xee, 0x70, 0x28, 0x52, 0x02, 0x3a, 0x3c, 0x60, - 0xa1, 0x00, 0x6c, 0x47, 0x17, 0x13, 0xc6, 0x09, 0x56, 0x90, 0xc0, 0x80, 0x91, 0x9b, 0x50, 0x63, - 0x1e, 0xde, 0xc4, 0xf5, 0x86, 0x98, 0x44, 0xc3, 0x1d, 0x4d, 0x05, 0x63, 0x75, 0xc8, 0xdf, 0xb8, - 0x55, 0xae, 0xe2, 0xac, 0x18, 0x30, 0x36, 0x37, 0xaa, 0x3c, 0x4e, 0x93, 0x88, 0x4d, 0x20, 0xf9, - 0x04, 0x0f, 0x66, 0x13, 0x8a, 0x99, 0xc2, 0xad, 0x07, 0xef, 0x89, 0x31, 0x0b, 0xa6, 0x95, 0x7f, - 0x8f, 0x19, 0x89, 0xc3, 0x29, 0xed, 0x6d, 0x68, 0xea, 0x60, 0x52, 0x83, 0xca, 0xf3, 0xa3, 0xbd, - 0xc3, 0xce, 0x02, 0x69, 0xc0, 0xd2, 0xf1, 0xde, 0xc9, 0xc9, 0xc1, 0xde, 0x6e, 0xc7, 0x22, 0x4d, - 0xa8, 0xa9, 0xcc, 0xca, 0x12, 0x2b, 0x6d, 0xef, 0xec, 0xec, 0x1d, 0x9d, 0xec, 0xed, 0x76, 0xca, - 0x76, 0x02, 0x64, 0x7b, 0x38, 0x14, 0xb5, 0xa8, 0x30, 0x4c, 0xca, 0xcf, 0x96, 0xc1, 0xcf, 0x05, - 0x3c, 0x55, 0x2a, 0xe6, 0xa9, 0x37, 0xce, 0xbc, 0xbd, 0x07, 0x8d, 0x23, 0xed, 0x4a, 0x20, 0x8a, - 0x97, 0xbc, 0x0c, 0x28, 0xc4, 0x52, 0x83, 0x68, 0xdd, 0x29, 0xe9, 0xdd, 0xb1, 0xff, 0xa9, 0xc5, - 0xef, 0xe6, 0xa8, 0xee, 0xf3, 0xb6, 0x6d, 0x68, 0xaa, 0x58, 0x68, 0x9a, 0x34, 0x6d, 0xc0, 0x18, - 0x0d, 0x76, 0xa5, 0x1f, 0x9e, 0x9d, 0xc5, 0x54, 0xa6, 0x38, 0x1a, 0x30, 0x69, 0xd7, 0x30, 0x4b, - 0xc9, 0xe3, 0x2d, 0xc4, 0x22, 0xd5, 0x31, 0x07, 0x67, 0x5a, 0x5e, 0x84, 0xbc, 0x64, 0x72, 0xa7, - 0x2a, 0xab, 0xdc, 0xee, 0xec, 0x2c, 0xdf, 0x83, 0x9a, 0xaa, 0xd7, 0x54, 0x60, 0x92, 0x52, 0xe1, - 0x99, 0xa2, 0x44, 0x7f, 0xc7, 0xe8, 0x34, 0x57, 0xda, 0x79, 0x04, 0xd9, 0x04, 0x72, 0xe6, 0x45, - 0x59, 0xf2, 0x32, 0x92, 0x17, 0x60, 0xec, 0x97, 0xb0, 0x2a, 0x19, 0x49, 0x33, 0xb8, 0xcc, 0x45, - 0xb4, 0xae, 0x12, 0x9f, 0x52, 0x5e, 0x7c, 0xec, 0x3f, 0x2d, 0xc3, 0x92, 0x58, 0xe9, 0xdc, 0xb5, - 0x52, 0xbe, 0xce, 0x06, 0x8c, 0x74, 0x8d, 0x6b, 0x67, 0x28, 0x6b, 0x42, 0x69, 0xe6, 0xd4, 0x62, - 0xb9, 0x48, 0x2d, 0x12, 0xa8, 0x4c, 0xdc, 0xe4, 0x1c, 0x23, 0x02, 0x75, 0x07, 0x7f, 0xcb, 0xc8, - 0x5d, 0xd5, 0x8c, 0xdc, 0x15, 0x5d, 0xa2, 0xe5, 0x3b, 0x7e, 0xfe, 0x12, 0xed, 0x0d, 0xa8, 0x63, - 0x27, 0xb4, 0x83, 0xde, 0x14, 0xc0, 0xb8, 0x97, 0x17, 0x50, 0xb6, 0xc5, 0x2d, 0x90, 0x14, 0xf2, - 0x0e, 0x8a, 0xf8, 0x1b, 0xb0, 0xc8, 0xaf, 0x21, 0x88, 0x14, 0xd6, 0x1b, 0xf2, 0xb0, 0x8b, 0xd3, - 0xc9, 0xbf, 0x3c, 0x17, 0xc6, 0x11, 0xb4, 0xfa, 0x25, 0xc6, 0x86, 0x79, 0x89, 0x51, 0x8f, 0x29, - 0x36, 0xcd, 0x98, 0xa2, 0xfd, 0x04, 0x96, 0x8d, 0xea, 0x98, 0xca, 0x10, 0x29, 0xb0, 0x9d, 0x05, - 0xb2, 0x0c, 0xf5, 0x67, 0x87, 0xfd, 0x27, 0x07, 0xcf, 0x9e, 0xee, 0x9f, 0x74, 0x2c, 0x56, 0x3c, - 0x7e, 0xb1, 0xb3, 0xb3, 0xb7, 0xb7, 0x8b, 0x2a, 0x04, 0x60, 0xf1, 0xc9, 0xf6, 0xb3, 0x03, 0x54, - 0x20, 0xbb, 0x9c, 0xb7, 0x45, 0x5d, 0xea, 0x80, 0xe1, 0x77, 0x81, 0x48, 0x97, 0x14, 0x53, 0x61, - 0x26, 0x3e, 0x4d, 0x64, 0x76, 0xf6, 0x8a, 0xc0, 0x3c, 0x53, 0x08, 0x79, 0xb9, 0x20, 0xad, 0x25, - 0x15, 0x11, 0x31, 0x49, 0x59, 0x11, 0x11, 0xa4, 0x8e, 0xc2, 0xdb, 0x3d, 0xe8, 0xee, 0x52, 0x56, - 0xdb, 0xb6, 0xef, 0x67, 0xba, 0xc3, 0xfc, 0x8a, 0x02, 0x9c, 0x70, 0x3a, 0xbe, 0x0b, 0xd7, 0xb6, - 0x79, 0x22, 0xf6, 0x6f, 0x2b, 0x4f, 0xcf, 0xee, 0xc2, 0x7a, 0xb6, 0x4a, 0xd1, 0xd8, 0x13, 0x58, - 0xd9, 0xa5, 0xa7, 0xd3, 0xd1, 0x01, 0xbd, 0x48, 0x1b, 0x22, 0x50, 0x89, 0xcf, 0xc3, 0x4b, 0x31, - 0x3f, 0xf8, 0x9b, 0xbc, 0x0f, 0xe0, 0x33, 0x9a, 0x7e, 0x3c, 0xa1, 0x03, 0x79, 0xf5, 0x0d, 0x21, - 0xc7, 0x13, 0x3a, 0xb0, 0x3f, 0x05, 0xa2, 0xd7, 0x23, 0xe6, 0x8b, 0x99, 0x05, 0xd3, 0xd3, 0x7e, - 0x3c, 0x8b, 0x13, 0x3a, 0x96, 0x77, 0xfa, 0x74, 0x90, 0x7d, 0x07, 0x9a, 0x47, 0xee, 0xcc, 0xa1, - 0x3f, 0x11, 0xd7, 0xab, 0x37, 0x60, 0x69, 0xe2, 0xce, 0x18, 0x0b, 0xaa, 0x18, 0x25, 0xa2, 0xed, - 0xff, 0x53, 0x82, 0x45, 0x4e, 0xc9, 0x6a, 0x1d, 0xd2, 0x38, 0xf1, 0x02, 0x94, 0x34, 0x59, 0xab, - 0x06, 0xca, 0xc9, 0x76, 0xa9, 0x40, 0xb6, 0x85, 0x03, 0x2d, 0xaf, 0x11, 0x09, 0x01, 0x36, 0x60, - 0x4c, 0xd2, 0xd2, 0x84, 0x5b, 0x1e, 0xc9, 0x4a, 0x01, 0x99, 0x68, 0x7b, 0x6a, 0x7c, 0xf0, 0xfe, - 0x49, 0xb5, 0x25, 0xc4, 0x58, 0x07, 0x15, 0x9a, 0x38, 0x4b, 0x5c, 0xda, 0x73, 0x26, 0x4e, 0xce, - 0x94, 0xa9, 0xbd, 0x85, 0x29, 0xc3, 0xbd, 0xea, 0x37, 0x99, 0x32, 0xf0, 0x16, 0xa6, 0x8c, 0x4d, - 0xa0, 0x83, 0xf7, 0x93, 0x99, 0xb1, 0x2c, 0x79, 0xf7, 0x1f, 0x58, 0xd0, 0x11, 0x5c, 0xa4, 0x70, - 0xe4, 0x43, 0xc3, 0x29, 0x28, 0xbc, 0x2e, 0x73, 0x1b, 0x96, 0xd1, 0x54, 0x57, 0x2a, 0x40, 0x9c, - 0x81, 0x18, 0x40, 0x36, 0x0e, 0x99, 0xae, 0x31, 0xf6, 0x7c, 0xb1, 0x28, 0x3a, 0x48, 0x6a, 0x91, - 0xc8, 0x15, 0x89, 0xa3, 0x96, 0xa3, 0xca, 0xf6, 0x2f, 0x2d, 0x58, 0xd1, 0x3a, 0x2c, 0xb8, 0xf0, - 0x11, 0x48, 0x69, 0xe0, 0x41, 0x7c, 0x2e, 0xb9, 0x1b, 0xa6, 0xd8, 0xa4, 0x9f, 0x19, 0xc4, 0xb8, - 0x98, 0xee, 0x0c, 0x3b, 0x18, 0x4f, 0xc7, 0x62, 0x57, 0xd1, 0x41, 0x8c, 0x91, 0x2e, 0x29, 0x7d, - 0xa5, 0x48, 0xf8, 0xbe, 0x66, 0xc0, 0x30, 0x9c, 0xc9, 0x5c, 0x0c, 0x45, 0x54, 0x11, 0xe1, 0x4c, - 0x1d, 0x68, 0xff, 0x57, 0x0b, 0x56, 0xb9, 0xaf, 0x28, 0xfc, 0x73, 0x75, 0x13, 0x73, 0x91, 0xbb, - 0xcc, 0x5c, 0x22, 0xf7, 0x17, 0x1c, 0x51, 0x26, 0xdf, 0x7c, 0x4b, 0xff, 0x56, 0x65, 0xb3, 0xce, - 0x59, 0x8b, 0x72, 0xd1, 0x5a, 0xbc, 0x61, 0xa6, 0x8b, 0x22, 0xcb, 0xd5, 0xc2, 0xc8, 0xf2, 0xe3, - 0x25, 0xa8, 0xc6, 0x83, 0x70, 0x42, 0xed, 0x75, 0x58, 0x33, 0x07, 0x27, 0x54, 0xd0, 0xcf, 0x2d, - 0xe8, 0x3e, 0xe1, 0x67, 0x4f, 0x5e, 0x30, 0xda, 0xf7, 0xe2, 0x24, 0x8c, 0xd4, 0x85, 0xf5, 0x9b, - 0x00, 0x71, 0xe2, 0x46, 0x09, 0xbf, 0x68, 0x21, 0xe2, 0xb5, 0x29, 0x84, 0xf5, 0x91, 0x06, 0x43, - 0x8e, 0xe5, 0x6b, 0xa3, 0xca, 0x39, 0xa3, 0x4a, 0x78, 0xb3, 0x86, 0x69, 0xf2, 0x11, 0xcf, 0xee, - 0x66, 0xc6, 0x13, 0xbd, 0x40, 0xbd, 0xce, 0xdd, 0xc4, 0x0c, 0xd4, 0xfe, 0x2f, 0x16, 0xb4, 0xd3, - 0x4e, 0x62, 0x16, 0x82, 0xa9, 0x1d, 0x84, 0x3d, 0x92, 0x6a, 0x07, 0x19, 0x49, 0xf6, 0x98, 0x81, - 0x22, 0xfa, 0xa6, 0x41, 0x50, 0x62, 0x45, 0x29, 0x9c, 0x4a, 0x8b, 0x4f, 0x07, 0xf1, 0x5c, 0x4d, - 0x66, 0x1a, 0x09, 0x33, 0x4f, 0x94, 0xf0, 0x9e, 0xcc, 0x38, 0xc1, 0xaf, 0x78, 0x54, 0x5c, 0x16, - 0x49, 0x87, 0xdb, 0x16, 0x4b, 0xfc, 0x30, 0x8c, 0xd9, 0x15, 0xfa, 0x9e, 0x5b, 0xe3, 0xf3, 0xa3, - 0xf6, 0xdc, 0xbf, 0x65, 0xc1, 0xf5, 0x82, 0x89, 0x17, 0x52, 0xb3, 0x0b, 0x2b, 0x67, 0x0a, 0x29, - 0x27, 0x87, 0x8b, 0xce, 0xba, 0x3c, 0x46, 0x37, 0x27, 0xc4, 0xc9, 0x7f, 0xa0, 0x0c, 0x45, 0x3e, - 0xdd, 0x46, 0x36, 0x74, 0x1e, 0x61, 0x1f, 0x41, 0x6f, 0xef, 0x35, 0x13, 0xc2, 0x1d, 0xfd, 0xbd, - 0x27, 0xc9, 0x0b, 0x0f, 0x72, 0x4a, 0xe6, 0xea, 0xc8, 0xc3, 0x19, 0x2c, 0x1b, 0x75, 0x91, 0xaf, - 0xbf, 0x6d, 0x25, 0xba, 0xbc, 0xc8, 0xb5, 0xe2, 0x0f, 0x56, 0xc9, 0x9c, 0x6c, 0x0d, 0x64, 0x5f, - 0x40, 0xfb, 0xf3, 0xa9, 0x9f, 0x78, 0xe9, 0xe3, 0x55, 0xe4, 0x9b, 0xe2, 0x23, 0xac, 0x42, 0x4e, - 0x5d, 0x61, 0x53, 0x3a, 0x1d, 0x9b, 0xb1, 0x31, 0xab, 0xa9, 0x9f, 0x6f, 0x31, 0x8f, 0xb0, 0xaf, - 0xc3, 0x46, 0xda, 0x24, 0x9f, 0x3b, 0xa9, 0xa8, 0x7f, 0x61, 0xf1, 0xe4, 0x22, 0xf3, 0x2d, 0x2d, - 0xf2, 0x14, 0x56, 0x63, 0x2f, 0x18, 0xf9, 0x54, 0xaf, 0x27, 0x16, 0x33, 0x71, 0xcd, 0xec, 0x9e, - 0x78, 0x6f, 0xcb, 0x29, 0xfa, 0x82, 0x31, 0x48, 0x71, 0x47, 0x53, 0x06, 0xc9, 0x4c, 0x49, 0xd1, - 0x00, 0xbe, 0x0d, 0x2d, 0xb3, 0x31, 0xf2, 0x50, 0xa4, 0x53, 0xa7, 0x3d, 0xd3, 0x8f, 0x07, 0x4c, - 0xce, 0x30, 0x28, 0xed, 0x9f, 0x59, 0xd0, 0x75, 0x28, 0x63, 0x63, 0xaa, 0x35, 0x2a, 0xb8, 0xe7, - 0x51, 0xae, 0xda, 0xf9, 0x03, 0x56, 0x69, 0xda, 0x72, 0xac, 0x9b, 0x73, 0x17, 0x65, 0x7f, 0xa1, - 0x60, 0x54, 0x8f, 0x6b, 0xb0, 0x28, 0xc6, 0xb7, 0x01, 0xd7, 0x44, 0x97, 0x64, 0x77, 0xd2, 0xd8, - 0xb2, 0xd1, 0xa8, 0x11, 0x5b, 0xee, 0x41, 0x97, 0xbf, 0x24, 0xa0, 0x8f, 0x83, 0x7f, 0x78, 0xef, - 0x4b, 0x68, 0x68, 0xef, 0x29, 0x90, 0x0d, 0x58, 0x7d, 0xf9, 0xec, 0xe4, 0x70, 0xef, 0xf8, 0xb8, - 0x7f, 0xf4, 0xe2, 0xf1, 0x77, 0xf6, 0xbe, 0xdf, 0xdf, 0xdf, 0x3e, 0xde, 0xef, 0x2c, 0x90, 0x75, - 0x20, 0x87, 0x7b, 0xc7, 0x27, 0x7b, 0xbb, 0x06, 0xdc, 0x22, 0x37, 0xa1, 0xf7, 0xe2, 0xf0, 0xc5, - 0xf1, 0xde, 0x6e, 0xbf, 0xe8, 0xbb, 0x12, 0x79, 0x1f, 0xae, 0x0b, 0x7c, 0xc1, 0xe7, 0xe5, 0x07, - 0x3f, 0x2b, 0x43, 0x8b, 0xe7, 0x38, 0xf1, 0x27, 0xd8, 0x68, 0x44, 0x3e, 0x87, 0x25, 0xf1, 0x96, - 0x1f, 0x91, 0xf3, 0x69, 0xbe, 0x1e, 0xd8, 0x5b, 0xcf, 0x82, 0xc5, 0x24, 0xac, 0xfe, 0xd5, 0x3f, - 0xf9, 0x9f, 0x7f, 0xa7, 0xb4, 0x4c, 0x1a, 0x5b, 0x17, 0x9f, 0x6c, 0x8d, 0x68, 0x10, 0xb3, 0x3a, - 0xfe, 0x10, 0x20, 0x7d, 0xa1, 0x8e, 0x74, 0x95, 0x13, 0x9a, 0x79, 0xbe, 0xaf, 0x77, 0xbd, 0x00, - 0x23, 0xea, 0xbd, 0x8e, 0xf5, 0xae, 0xda, 0x2d, 0x56, 0xaf, 0x17, 0x78, 0x09, 0x7f, 0xad, 0xee, - 0x33, 0xeb, 0x1e, 0x19, 0x42, 0x53, 0x7f, 0x3b, 0x8e, 0xc8, 0xf8, 0x78, 0xc1, 0xeb, 0x77, 0xbd, - 0xf7, 0x0a, 0x71, 0x72, 0x01, 0xb1, 0x8d, 0x6b, 0x76, 0x87, 0xb5, 0x31, 0x45, 0x8a, 0xb4, 0x15, - 0x9f, 0xb3, 0x75, 0xfa, 0x44, 0x1c, 0xb9, 0xa1, 0x71, 0x5a, 0xee, 0x81, 0xba, 0xde, 0xfb, 0x73, - 0xb0, 0xa2, 0xad, 0xf7, 0xb1, 0xad, 0x0d, 0x9b, 0xb0, 0xb6, 0x06, 0x48, 0x23, 0x1f, 0xa8, 0xfb, - 0xcc, 0xba, 0xf7, 0xe0, 0xef, 0x7d, 0x04, 0x75, 0x75, 0x6e, 0x46, 0x7e, 0x0c, 0xcb, 0x46, 0x12, - 0x1a, 0x91, 0xc3, 0x28, 0xca, 0x59, 0xeb, 0xdd, 0x28, 0x46, 0x8a, 0x86, 0x6f, 0x62, 0xc3, 0x5d, - 0xb2, 0xce, 0x1a, 0x16, 0x59, 0x5c, 0x5b, 0x98, 0x4e, 0xc9, 0x6f, 0x63, 0xbd, 0xd2, 0xc4, 0x97, - 0x37, 0x76, 0x23, 0x2b, 0x51, 0x46, 0x6b, 0xef, 0xcf, 0xc1, 0x8a, 0xe6, 0x6e, 0x60, 0x73, 0xeb, - 0x64, 0x4d, 0x6f, 0x4e, 0x9d, 0x67, 0x51, 0xbc, 0x82, 0xa8, 0xbf, 0x9e, 0x46, 0xde, 0x57, 0x8c, - 0x55, 0xf4, 0xaa, 0x9a, 0x62, 0x91, 0xfc, 0xd3, 0x6a, 0x76, 0x17, 0x9b, 0x22, 0x04, 0x97, 0x4f, - 0x7f, 0x3c, 0x8d, 0x9c, 0x42, 0x43, 0x7b, 0x9d, 0x87, 0x5c, 0x9f, 0xfb, 0x92, 0x50, 0xaf, 0x57, - 0x84, 0x2a, 0x1a, 0x8a, 0x5e, 0xff, 0x16, 0xdb, 0x97, 0x7f, 0x08, 0x75, 0xf5, 0xde, 0x0b, 0xd9, - 0xd0, 0xde, 0xdf, 0xd1, 0xdf, 0xa7, 0xe9, 0x75, 0xf3, 0x88, 0x22, 0xe6, 0xd3, 0x6b, 0x67, 0xcc, - 0xf7, 0x12, 0x1a, 0xda, 0x9b, 0x2e, 0x6a, 0x00, 0xf9, 0x77, 0x63, 0xd4, 0x00, 0x0a, 0x9e, 0x80, - 0xb1, 0x57, 0xb0, 0x89, 0x06, 0xa9, 0x23, 0x7f, 0x27, 0xaf, 0xc3, 0x98, 0x1c, 0xc0, 0x35, 0xa1, - 0xa6, 0x4e, 0xe9, 0xbb, 0x2c, 0x43, 0xc1, 0x83, 0x75, 0xf7, 0x2d, 0xf2, 0x08, 0x6a, 0xf2, 0xe9, - 0x1e, 0xb2, 0x5e, 0xfc, 0x04, 0x51, 0x6f, 0x23, 0x07, 0x17, 0xe6, 0xc9, 0xf7, 0x01, 0xd2, 0x07, - 0x64, 0x94, 0x92, 0xc8, 0x3d, 0x48, 0xa3, 0x38, 0x20, 0xff, 0xda, 0x8c, 0xbd, 0x8e, 0x03, 0xec, - 0x10, 0x54, 0x12, 0x01, 0xbd, 0x94, 0xb7, 0x8d, 0x7f, 0x04, 0x0d, 0xed, 0x0d, 0x19, 0x35, 0x7d, - 0xf9, 0xf7, 0x67, 0xd4, 0xf4, 0x15, 0x3c, 0x39, 0x63, 0xf7, 0xb0, 0xf6, 0x35, 0xbb, 0xcd, 0x6a, - 0x8f, 0xbd, 0x51, 0x30, 0xe6, 0x04, 0x6c, 0x81, 0xce, 0x61, 0xd9, 0x78, 0x28, 0x46, 0x49, 0x68, - 0xd1, 0x33, 0x34, 0x4a, 0x42, 0x0b, 0xdf, 0x96, 0x91, 0x7c, 0x66, 0xaf, 0xb0, 0x76, 0x2e, 0x90, - 0x44, 0x6b, 0xe9, 0x07, 0xd0, 0xd0, 0x1e, 0x7d, 0x51, 0x63, 0xc9, 0xbf, 0x2f, 0xa3, 0xc6, 0x52, - 0xf4, 0x46, 0xcc, 0x1a, 0xb6, 0xd1, 0xb2, 0x91, 0x15, 0xf0, 0xde, 0x2c, 0xab, 0xfb, 0xc7, 0xd0, - 0x32, 0x9f, 0x81, 0x51, 0xb2, 0x5f, 0xf8, 0xa0, 0x8c, 0x92, 0xfd, 0x39, 0x6f, 0xc7, 0x08, 0x96, - 0xbe, 0xb7, 0xaa, 0x1a, 0xd9, 0xfa, 0x42, 0x64, 0xdd, 0x7c, 0x49, 0xbe, 0xcb, 0x14, 0x9c, 0xb8, - 0xc8, 0x4c, 0x36, 0x34, 0xae, 0xd5, 0xaf, 0x3b, 0x2b, 0x79, 0xc9, 0xdd, 0x79, 0x36, 0x99, 0x99, - 0xdf, 0xfc, 0xc5, 0x5d, 0x0b, 0x2f, 0x34, 0x6b, 0xbb, 0x96, 0x7e, 0xe7, 0x59, 0xdb, 0xb5, 0x8c, - 0x7b, 0xcf, 0xd9, 0x5d, 0x2b, 0xf1, 0x58, 0x1d, 0x01, 0xb4, 0x33, 0x89, 0xf2, 0x4a, 0x2a, 0x8a, - 0xef, 0x32, 0xf5, 0x6e, 0xbe, 0x39, 0xbf, 0xde, 0xd4, 0x20, 0x52, 0x09, 0x6e, 0xc9, 0x9b, 0x63, - 0x7f, 0x09, 0x9a, 0xfa, 0x03, 0x18, 0x44, 0x17, 0xe5, 0x6c, 0x4b, 0xef, 0x15, 0xe2, 0xcc, 0xc5, - 0x25, 0x4d, 0xbd, 0x19, 0xf2, 0x3d, 0x58, 0x57, 0xa2, 0xae, 0xe7, 0x5e, 0xc7, 0xe4, 0x83, 0x82, - 0x8c, 0x6c, 0xdd, 0x78, 0xe9, 0x5d, 0x9f, 0x9b, 0xb2, 0x7d, 0xdf, 0x62, 0x4c, 0x63, 0xbe, 0x2c, - 0x90, 0x6e, 0x18, 0x45, 0x0f, 0x2a, 0xa4, 0x1b, 0x46, 0xe1, 0x73, 0x04, 0x92, 0x69, 0xc8, 0xaa, - 0x31, 0x47, 0xfc, 0xc0, 0x92, 0xfc, 0x00, 0xda, 0xda, 0xed, 0x96, 0xe3, 0x59, 0x30, 0x50, 0x02, - 0x90, 0xbf, 0x78, 0xd9, 0x2b, 0x32, 0xcd, 0xed, 0x0d, 0xac, 0x7f, 0xc5, 0x36, 0x26, 0x87, 0x31, - 0xff, 0x0e, 0x34, 0xf4, 0x9b, 0x33, 0x6f, 0xa8, 0x77, 0x43, 0x43, 0xe9, 0xf7, 0x06, 0xef, 0x5b, - 0xe4, 0x1f, 0x5a, 0xd0, 0x34, 0xee, 0xa1, 0x18, 0x87, 0xf5, 0x99, 0x7a, 0xba, 0x3a, 0x4e, 0xaf, - 0xc8, 0x76, 0xb0, 0x93, 0x07, 0xf7, 0xbe, 0x6d, 0x4c, 0xc2, 0x17, 0x46, 0xfc, 0x65, 0x33, 0xfb, - 0x5c, 0xe0, 0x97, 0x59, 0x02, 0xfd, 0x72, 0xea, 0x97, 0xf7, 0x2d, 0xf2, 0xc7, 0x16, 0xb4, 0xcc, - 0xa8, 0xa1, 0x5a, 0xaa, 0xc2, 0xf8, 0xa4, 0x5a, 0xaa, 0x39, 0xa1, 0xc6, 0x1f, 0x60, 0x2f, 0x4f, - 0xee, 0x39, 0x46, 0x2f, 0xc5, 0x9b, 0x13, 0xbf, 0x59, 0x6f, 0xc9, 0x67, 0xfc, 0x95, 0x52, 0x19, - 0xdb, 0x27, 0xf9, 0xf7, 0x32, 0xd5, 0xf2, 0xea, 0x2f, 0x5c, 0xde, 0xb5, 0xee, 0x5b, 0xe4, 0x47, - 0xfc, 0x99, 0x3c, 0x19, 0x7e, 0x66, 0x5c, 0xf2, 0xb6, 0xdf, 0xdb, 0xb7, 0x71, 0x4c, 0x37, 0xed, - 0xeb, 0xc6, 0x98, 0xb2, 0xfb, 0xf1, 0x36, 0xef, 0x9d, 0x78, 0x9c, 0x32, 0xdd, 0x50, 0x72, 0x0f, - 0x56, 0xce, 0xef, 0xe4, 0x98, 0x77, 0x52, 0x90, 0x1b, 0xac, 0xfc, 0x96, 0xd5, 0xd8, 0xf7, 0xb0, - 0xaf, 0xb7, 0xed, 0x0f, 0xe6, 0xf6, 0x75, 0x0b, 0x63, 0x7f, 0xac, 0xc7, 0x47, 0x00, 0xe9, 0x39, - 0x1c, 0xc9, 0x9c, 0x03, 0x29, 0x01, 0xcf, 0x1f, 0xd5, 0x99, 0xf2, 0x22, 0x8f, 0x8b, 0x58, 0x8d, - 0x3f, 0xe4, 0xea, 0xea, 0x99, 0x3c, 0x41, 0xd2, 0x8d, 0x12, 0xf3, 0xc0, 0xcc, 0x30, 0x4a, 0xb2, - 0xf5, 0x1b, 0xca, 0x4a, 0x1d, 0x47, 0xbd, 0x80, 0xe5, 0x83, 0x30, 0x7c, 0x35, 0x9d, 0xa8, 0x33, - 0x75, 0x33, 0x2c, 0xbf, 0xef, 0xc6, 0xe7, 0xbd, 0xcc, 0x28, 0xec, 0x5b, 0x58, 0x55, 0x8f, 0x74, - 0xb5, 0xaa, 0xb6, 0xbe, 0x48, 0xcf, 0xf9, 0xbe, 0x24, 0x2e, 0xac, 0x28, 0x1d, 0xa8, 0x3a, 0xde, - 0x33, 0xab, 0x31, 0x34, 0x5f, 0xb6, 0x09, 0xc3, 0x7a, 0x96, 0xbd, 0xdd, 0x8a, 0x65, 0x9d, 0xf7, - 0x2d, 0x72, 0x04, 0xcd, 0x5d, 0x3a, 0xc0, 0x2c, 0x78, 0x8c, 0x6d, 0xaf, 0xa6, 0x1d, 0x57, 0x41, - 0xf1, 0xde, 0xb2, 0x01, 0x34, 0xf7, 0x85, 0x89, 0x3b, 0x8b, 0xe8, 0x4f, 0xb6, 0xbe, 0x10, 0x51, - 0xf3, 0x2f, 0xe5, 0xbe, 0x20, 0x8f, 0x15, 0x8c, 0x7d, 0x21, 0x73, 0x0e, 0x61, 0xec, 0x0b, 0xb9, - 0x73, 0x08, 0x63, 0xaa, 0xe5, 0xb1, 0x06, 0xf1, 0x61, 0x25, 0x77, 0x74, 0xa1, 0xb6, 0x84, 0x79, - 0x07, 0x1e, 0xbd, 0x5b, 0xf3, 0x09, 0xcc, 0xd6, 0xee, 0x99, 0xad, 0x1d, 0xc3, 0xf2, 0x2e, 0xe5, - 0x93, 0xc5, 0x93, 0x06, 0x33, 0x97, 0x99, 0xf4, 0x94, 0xc4, 0xac, 0x02, 0x47, 0x9c, 0xb9, 0xf1, - 0x63, 0xc6, 0x1e, 0xf9, 0x21, 0x34, 0x9e, 0xd2, 0x44, 0x66, 0x09, 0x2a, 0xd3, 0x33, 0x93, 0x36, - 0xd8, 0x2b, 0x48, 0x32, 0x34, 0x79, 0x06, 0x6b, 0xdb, 0xa2, 0xc3, 0x11, 0xe5, 0xca, 0xa9, 0xef, - 0x0d, 0xbf, 0x24, 0x7f, 0x11, 0x2b, 0x57, 0x29, 0xd2, 0xeb, 0x5a, 0xda, 0x97, 0x5e, 0x79, 0x3b, - 0x03, 0x2f, 0xaa, 0x39, 0x08, 0x87, 0x54, 0x33, 0x81, 0x02, 0x68, 0x68, 0x77, 0x14, 0x94, 0x00, - 0xe5, 0x2f, 0x6c, 0x28, 0x01, 0x2a, 0xb8, 0xd2, 0x60, 0xdf, 0xc5, 0x76, 0x6c, 0x72, 0x2b, 0x6d, - 0x87, 0x5f, 0x63, 0x48, 0x5b, 0xda, 0xfa, 0xc2, 0x1d, 0x27, 0x5f, 0x92, 0x97, 0xf8, 0x06, 0x8c, - 0x9e, 0x09, 0x99, 0xda, 0xd2, 0xd9, 0xa4, 0x49, 0x35, 0x59, 0x1a, 0xca, 0xb4, 0xaf, 0x79, 0x53, - 0x68, 0x29, 0x7d, 0x13, 0xe0, 0x38, 0x09, 0x27, 0xbb, 0x2e, 0x1d, 0x87, 0x41, 0xaa, 0x6b, 0xd3, - 0x3c, 0xbc, 0x54, 0x7f, 0x69, 0xc9, 0x78, 0xe4, 0xa5, 0xe6, 0x7c, 0x18, 0x89, 0xa4, 0x92, 0xb9, - 0xe6, 0xa6, 0xea, 0xa9, 0x09, 0x29, 0x48, 0xd7, 0xbb, 0x6f, 0x91, 0x6d, 0x80, 0xf4, 0xec, 0x4a, - 0xb9, 0x12, 0xb9, 0x63, 0x31, 0xa5, 0xf6, 0x0a, 0x0e, 0xba, 0x8e, 0xa0, 0x9e, 0x1e, 0x86, 0x6c, - 0xa4, 0xb7, 0x8c, 0x8c, 0xa3, 0x13, 0xb5, 0x83, 0xe7, 0x8e, 0x28, 0xec, 0x0e, 0x4e, 0x15, 0x90, - 0x1a, 0x9b, 0x2a, 0x3c, 0x77, 0xf0, 0x60, 0x95, 0x77, 0x50, 0x99, 0x23, 0x98, 0x43, 0x26, 0x47, - 0x52, 0x70, 0x4c, 0xa0, 0xa4, 0xb9, 0x30, 0xca, 0x6e, 0x44, 0x44, 0x18, 0xb7, 0xf2, 0xfc, 0x35, - 0xa6, 0x9a, 0xc7, 0xb0, 0x92, 0x0b, 0x03, 0x2b, 0x91, 0x9e, 0x17, 0x99, 0x57, 0x22, 0x3d, 0x37, - 0x82, 0x6c, 0x5f, 0xc3, 0x26, 0xdb, 0x36, 0xa0, 0x07, 0x74, 0xe9, 0x25, 0x83, 0x73, 0xd6, 0xdc, - 0x2f, 0x2c, 0x58, 0x2d, 0x88, 0xf2, 0x92, 0x0f, 0xa5, 0x33, 0x3d, 0x37, 0x02, 0xdc, 0x2b, 0x0c, - 0x02, 0xda, 0xc7, 0xd8, 0xce, 0xe7, 0xe4, 0x3b, 0xc6, 0xc6, 0xc6, 0xe3, 0x6f, 0x42, 0x32, 0xdf, - 0x68, 0x54, 0x14, 0x5a, 0x14, 0x3f, 0x81, 0x0d, 0xde, 0x91, 0x6d, 0xdf, 0xcf, 0x04, 0x28, 0x6f, - 0xe6, 0xfe, 0x51, 0x81, 0x11, 0x78, 0xed, 0xcd, 0xff, 0x47, 0x06, 0x73, 0xcc, 0x55, 0xde, 0x55, - 0x32, 0x85, 0x4e, 0x36, 0xe8, 0x47, 0xe6, 0xd7, 0xd5, 0xfb, 0xc0, 0x70, 0x0b, 0xf3, 0x81, 0x42, - 0xfb, 0x77, 0xb0, 0xb1, 0x0f, 0xec, 0x5e, 0xd1, 0xbc, 0x70, 0x4f, 0x91, 0xad, 0xc7, 0x5f, 0x51, - 0x11, 0xca, 0xcc, 0x38, 0x65, 0x03, 0xf3, 0x42, 0xaa, 0xca, 0x31, 0x2d, 0x0e, 0x70, 0x7e, 0x84, - 0xcd, 0xdf, 0xb2, 0xdf, 0x2b, 0x6a, 0x3e, 0xe2, 0x9f, 0x70, 0x17, 0x75, 0x23, 0x2b, 0xd7, 0xb2, - 0x07, 0xb7, 0x8a, 0xd6, 0x7b, 0xae, 0xaf, 0x91, 0x99, 0xeb, 0x85, 0xfb, 0xd6, 0xe3, 0x3b, 0x3f, - 0xf8, 0x9d, 0x91, 0x97, 0x9c, 0x4f, 0x4f, 0x37, 0x07, 0xe1, 0x78, 0xcb, 0x97, 0x21, 0x32, 0x91, - 0xf1, 0xbc, 0xe5, 0x07, 0xc3, 0x2d, 0xfc, 0xfe, 0x74, 0x11, 0xff, 0xef, 0xc9, 0xd7, 0xff, 0x5f, - 0x00, 0x00, 0x00, 0xff, 0xff, 0x77, 0xa0, 0xa0, 0x17, 0x29, 0x65, 0x00, 0x00, + 0x9d, 0xad, 0xcc, 0xdb, 0x99, 0x38, 0x1b, 0x7b, 0x76, 0x92, 0x2c, 0xf3, 0x76, 0x12, 0x1e, 0x1e, + 0xdb, 0x33, 0x9e, 0xc4, 0xeb, 0x71, 0xca, 0x9e, 0x0c, 0x49, 0x1e, 0xea, 0x94, 0xbb, 0xaf, 0xdb, + 0x95, 0xa9, 0xae, 0xea, 0x54, 0x55, 0xdb, 0xd3, 0x59, 0x96, 0x1f, 0x08, 0x21, 0xf4, 0x24, 0x84, + 0x02, 0x12, 0x02, 0x04, 0x42, 0x4a, 0x9e, 0x10, 0x4f, 0xfc, 0x00, 0x7e, 0x80, 0x40, 0x8a, 0xf4, + 0x7e, 0xf2, 0x0b, 0x21, 0xf4, 0x7e, 0x22, 0xf1, 0x84, 0x40, 0x42, 0x11, 0xff, 0x90, 0xf8, 0x8f, + 0xee, 0xb9, 0x1f, 0x75, 0x6f, 0x55, 0xf5, 0xcc, 0x6c, 0xb2, 0xf0, 0xcb, 0x7d, 0xcf, 0x39, 0x75, + 0x3f, 0xcf, 0x39, 0xf7, 0x9c, 0x73, 0xcf, 0xbd, 0x86, 0x7a, 0x34, 0x19, 0x6c, 0x4d, 0xa2, 0x30, + 0x09, 0x49, 0xd5, 0x0f, 0xa2, 0xc9, 0xa0, 0x77, 0x63, 0x14, 0x86, 0x23, 0x9f, 0x6e, 0xbb, 0x13, + 0x6f, 0xdb, 0x0d, 0x82, 0x30, 0x71, 0x13, 0x2f, 0x0c, 0x62, 0x4e, 0x64, 0xff, 0x04, 0x5a, 0x4f, + 0x68, 0x70, 0x42, 0xe9, 0xd0, 0xa1, 0x3f, 0x9b, 0xd2, 0x38, 0x21, 0x5f, 0x83, 0x15, 0x97, 0xfe, + 0x9c, 0xd2, 0x61, 0x7f, 0xe2, 0xc6, 0xf1, 0xe4, 0x22, 0x72, 0x63, 0xda, 0xb5, 0x6e, 0x59, 0x77, + 0x9b, 0x4e, 0x87, 0x23, 0x8e, 0x15, 0x9c, 0xbc, 0x0f, 0xcd, 0x98, 0x91, 0xd2, 0x20, 0x89, 0xc2, + 0xc9, 0xac, 0x5b, 0x42, 0xba, 0x06, 0x83, 0xed, 0x73, 0x90, 0xed, 0x43, 0x5b, 0xb5, 0x10, 0x4f, + 0xc2, 0x20, 0xa6, 0xe4, 0x1e, 0xac, 0x0d, 0xbc, 0xc9, 0x05, 0x8d, 0xfa, 0xf8, 0xf1, 0x38, 0xa0, + 0xe3, 0x30, 0xf0, 0x06, 0x5d, 0xeb, 0x56, 0xf9, 0x6e, 0xdd, 0x21, 0x1c, 0xc7, 0xbe, 0xf8, 0x54, + 0x60, 0xc8, 0x1d, 0x68, 0xd3, 0x80, 0xc3, 0xe9, 0x10, 0xbf, 0x12, 0x4d, 0xb5, 0x52, 0x30, 0xfb, + 0xc0, 0xfe, 0x5b, 0x25, 0x58, 0x79, 0x1a, 0x78, 0xc9, 0x0b, 0xd7, 0xf7, 0x69, 0x22, 0xc7, 0x74, + 0x07, 0xda, 0x57, 0x08, 0xc0, 0x31, 0x5d, 0x85, 0xd1, 0x50, 0x8c, 0xa8, 0xc5, 0xc1, 0xc7, 0x02, + 0x3a, 0xb7, 0x67, 0xa5, 0xb9, 0x3d, 0x2b, 0x9c, 0xae, 0xf2, 0x9c, 0xe9, 0xba, 0x03, 0xed, 0x88, + 0x0e, 0xc2, 0x4b, 0x1a, 0xcd, 0xfa, 0x57, 0x5e, 0x30, 0x0c, 0xaf, 0xba, 0x95, 0x5b, 0xd6, 0xdd, + 0xaa, 0xd3, 0x92, 0xe0, 0x17, 0x08, 0x25, 0x8f, 0xa0, 0x3d, 0xb8, 0x70, 0x83, 0x80, 0xfa, 0xfd, + 0x33, 0x77, 0xf0, 0x72, 0x3a, 0x89, 0xbb, 0xd5, 0x5b, 0xd6, 0xdd, 0xc6, 0xfd, 0xeb, 0x5b, 0xb8, + 0xaa, 0x5b, 0xbb, 0x17, 0x6e, 0xf0, 0x08, 0x31, 0x27, 0x81, 0x3b, 0x89, 0x2f, 0xc2, 0xc4, 0x69, + 0x89, 0x2f, 0x38, 0x38, 0xb6, 0xd7, 0x80, 0xe8, 0x33, 0xc1, 0xe7, 0xde, 0xfe, 0x17, 0x16, 0xac, + 0x3e, 0x0f, 0xfc, 0x70, 0xf0, 0xf2, 0xb7, 0x9c, 0xa2, 0x82, 0x31, 0x94, 0xde, 0x76, 0x0c, 0xe5, + 0x2f, 0x3a, 0x86, 0x75, 0x58, 0x33, 0x3b, 0x2b, 0x46, 0x41, 0xe1, 0x1a, 0xfb, 0x7a, 0x44, 0x65, + 0xb7, 0xe4, 0x30, 0xbe, 0x0a, 0x9d, 0xc1, 0x34, 0x8a, 0x68, 0x90, 0x1b, 0x47, 0x5b, 0xc0, 0xd5, + 0x40, 0xde, 0x87, 0x66, 0x40, 0xaf, 0x52, 0x32, 0xc1, 0xbb, 0x01, 0xbd, 0x92, 0x24, 0x76, 0x17, + 0xd6, 0xb3, 0xcd, 0x88, 0x0e, 0xfc, 0x37, 0x0b, 0x2a, 0xcf, 0x93, 0x57, 0x21, 0xd9, 0x82, 0x4a, + 0x32, 0x9b, 0x70, 0x09, 0x69, 0xdd, 0x27, 0x62, 0x68, 0x3b, 0xc3, 0x61, 0x44, 0xe3, 0xf8, 0x74, + 0x36, 0xa1, 0x4e, 0xd3, 0xe5, 0x85, 0x3e, 0xa3, 0x23, 0x5d, 0x58, 0x12, 0x65, 0x6c, 0xb0, 0xee, + 0xc8, 0x22, 0xb9, 0x09, 0xe0, 0x8e, 0xc3, 0x69, 0x90, 0xf4, 0x63, 0x37, 0xc1, 0xa9, 0x2a, 0x3b, + 0x1a, 0x84, 0xdc, 0x80, 0xfa, 0xe4, 0x65, 0x3f, 0x1e, 0x44, 0xde, 0x24, 0x41, 0xb6, 0xa9, 0x3b, + 0x29, 0x80, 0x7c, 0x0d, 0x6a, 0xe1, 0x34, 0x99, 0x84, 0x5e, 0x90, 0x08, 0x56, 0x69, 0x8b, 0xbe, + 0x3c, 0x9b, 0x26, 0xc7, 0x0c, 0xec, 0x28, 0x02, 0x72, 0x1b, 0x96, 0x07, 0x61, 0x70, 0xee, 0x45, + 0x63, 0xae, 0x0c, 0xba, 0x8b, 0xd8, 0x9a, 0x09, 0xb4, 0xff, 0x7d, 0x09, 0x1a, 0xa7, 0x91, 0x1b, + 0xc4, 0xee, 0x80, 0x01, 0x58, 0xd7, 0x93, 0x57, 0xfd, 0x0b, 0x37, 0xbe, 0xc0, 0xd1, 0xd6, 0x1d, + 0x59, 0x24, 0xeb, 0xb0, 0xc8, 0x3b, 0x8a, 0x63, 0x2a, 0x3b, 0xa2, 0x44, 0x3e, 0x84, 0x95, 0x60, + 0x3a, 0xee, 0x9b, 0x6d, 0x95, 0x91, 0x5b, 0xf2, 0x08, 0x36, 0x01, 0x67, 0x6c, 0xad, 0x79, 0x13, + 0x7c, 0x84, 0x1a, 0x84, 0xd8, 0xd0, 0x14, 0x25, 0xea, 0x8d, 0x2e, 0xf8, 0x30, 0xab, 0x8e, 0x01, + 0x63, 0x75, 0x24, 0xde, 0x98, 0xf6, 0xe3, 0xc4, 0x1d, 0x4f, 0xc4, 0xb0, 0x34, 0x08, 0xe2, 0xc3, + 0xc4, 0xf5, 0xfb, 0xe7, 0x94, 0xc6, 0xdd, 0x25, 0x81, 0x57, 0x10, 0xf2, 0x01, 0xb4, 0x86, 0x34, + 0x4e, 0xfa, 0x62, 0x51, 0x68, 0xdc, 0xad, 0xa1, 0xe8, 0x67, 0xa0, 0xac, 0x9e, 0xc8, 0xbd, 0xea, + 0xb3, 0x09, 0xa0, 0xaf, 0xba, 0x75, 0xde, 0xd7, 0x14, 0xc2, 0x38, 0xe7, 0x09, 0x4d, 0xb4, 0xd9, + 0x8b, 0x05, 0x87, 0xda, 0x87, 0x40, 0x34, 0xf0, 0x1e, 0x4d, 0x5c, 0xcf, 0x8f, 0xc9, 0xc7, 0xd0, + 0x4c, 0x34, 0x62, 0x54, 0x85, 0x0d, 0xc5, 0x4e, 0xda, 0x07, 0x8e, 0x41, 0x67, 0x3f, 0x81, 0xda, + 0x63, 0x4a, 0x0f, 0xbd, 0xb1, 0x97, 0x90, 0x75, 0xa8, 0x9e, 0x7b, 0xaf, 0x28, 0x67, 0xf8, 0xf2, + 0xc1, 0x82, 0xc3, 0x8b, 0xa4, 0x07, 0x4b, 0x13, 0x1a, 0x0d, 0xa8, 0x5c, 0x9e, 0x83, 0x05, 0x47, + 0x02, 0x1e, 0x2d, 0x41, 0xd5, 0x67, 0x1f, 0xdb, 0xbf, 0x29, 0x43, 0xe3, 0x84, 0x06, 0x4a, 0x90, + 0x08, 0x54, 0xd8, 0x90, 0x85, 0xf0, 0xe0, 0x6f, 0xf2, 0x1e, 0x34, 0x70, 0x1a, 0xe2, 0x24, 0xf2, + 0x82, 0x91, 0xe0, 0x5f, 0x60, 0xa0, 0x13, 0x84, 0x90, 0x0e, 0x94, 0xdd, 0xb1, 0xe4, 0x5d, 0xf6, + 0x93, 0x09, 0xd9, 0xc4, 0x9d, 0x8d, 0x99, 0x3c, 0xaa, 0x55, 0x6d, 0x3a, 0x0d, 0x01, 0x3b, 0x60, + 0xcb, 0xba, 0x05, 0xab, 0x3a, 0x89, 0xac, 0xbd, 0x8a, 0xb5, 0xaf, 0x68, 0x94, 0xa2, 0x91, 0x3b, + 0xd0, 0x96, 0xf4, 0x11, 0xef, 0x2c, 0xae, 0x73, 0xdd, 0x69, 0x09, 0xb0, 0x1c, 0xc2, 0x5d, 0xe8, + 0x9c, 0x7b, 0x81, 0xeb, 0xf7, 0x07, 0x7e, 0x72, 0xd9, 0x1f, 0x52, 0x3f, 0x71, 0x71, 0xc5, 0xab, + 0x4e, 0x0b, 0xe1, 0xbb, 0x7e, 0x72, 0xb9, 0xc7, 0xa0, 0xe4, 0x43, 0xa8, 0x9f, 0x53, 0xda, 0xc7, + 0x99, 0xe8, 0xd6, 0x0c, 0xe9, 0x91, 0xb3, 0xeb, 0xd4, 0xce, 0xe5, 0x3c, 0xdf, 0x85, 0x4e, 0x38, + 0x4d, 0x46, 0xa1, 0x17, 0x8c, 0xfa, 0x4c, 0x5f, 0xf5, 0xbd, 0x21, 0x72, 0x40, 0xc5, 0x69, 0x49, + 0x38, 0xd3, 0x1a, 0x4f, 0x87, 0xe4, 0x5d, 0x00, 0x6c, 0x9b, 0x57, 0x0c, 0xb7, 0xac, 0xbb, 0xcb, + 0x4e, 0x9d, 0x41, 0x78, 0x45, 0x9f, 0x40, 0x0d, 0xe7, 0x33, 0xf1, 0x2f, 0xbb, 0x0d, 0x5c, 0xf0, + 0xf7, 0x44, 0xab, 0xda, 0x4a, 0x6c, 0xed, 0xd1, 0x38, 0x39, 0xf5, 0x2f, 0xd9, 0x7e, 0x3a, 0x73, + 0x96, 0x86, 0xbc, 0xd4, 0xfb, 0x04, 0x9a, 0x3a, 0x82, 0x4d, 0xfd, 0x4b, 0x3a, 0xc3, 0xe5, 0xaa, + 0x38, 0xec, 0x27, 0x59, 0x83, 0xea, 0xa5, 0xeb, 0x4f, 0xa9, 0x50, 0x6c, 0xbc, 0xf0, 0x49, 0xe9, + 0x81, 0x65, 0xff, 0x3b, 0x0b, 0x9a, 0xbc, 0x05, 0xb1, 0x21, 0xdf, 0x86, 0x65, 0x39, 0xa5, 0x34, + 0x8a, 0xc2, 0x48, 0xc8, 0xb7, 0x09, 0x24, 0x9b, 0xd0, 0x91, 0x80, 0x49, 0x44, 0xbd, 0xb1, 0x3b, + 0x92, 0x75, 0xe7, 0xe0, 0xe4, 0x7e, 0x5a, 0x63, 0x14, 0x4e, 0x13, 0x2a, 0x54, 0x7f, 0x53, 0x8c, + 0xcf, 0x61, 0x30, 0xc7, 0x24, 0x61, 0xf2, 0x5d, 0xc0, 0x2b, 0x06, 0xcc, 0xfe, 0x85, 0x05, 0x84, + 0x75, 0xfd, 0x34, 0xe4, 0x55, 0x88, 0xa5, 0xce, 0xb2, 0x99, 0xf5, 0xd6, 0x6c, 0x56, 0x9a, 0xc7, + 0x66, 0x36, 0x54, 0x79, 0xcf, 0x2b, 0x05, 0x3d, 0xe7, 0xa8, 0xef, 0x56, 0x6a, 0xe5, 0x4e, 0xc5, + 0xfe, 0xa5, 0x05, 0xcd, 0x5d, 0xbe, 0x6f, 0xa1, 0xa2, 0x25, 0xf7, 0x80, 0x9c, 0x4f, 0x83, 0x21, + 0xe3, 0x8f, 0xe4, 0x95, 0x37, 0xec, 0x9f, 0xcd, 0x12, 0x1a, 0xf3, 0x3e, 0x1d, 0x2c, 0x38, 0x05, + 0x38, 0xf2, 0x21, 0x74, 0x0c, 0x68, 0x9c, 0x44, 0xbc, 0x67, 0x07, 0x0b, 0x4e, 0x0e, 0xc3, 0x26, + 0x8a, 0xa9, 0xf2, 0x69, 0xd2, 0xf7, 0x82, 0x21, 0x7d, 0x85, 0x73, 0xbb, 0xec, 0x18, 0xb0, 0x47, + 0x2d, 0x68, 0xea, 0xdf, 0xd9, 0x3f, 0x85, 0x9a, 0xdc, 0x08, 0x50, 0x09, 0x66, 0xfa, 0xe5, 0x68, + 0x10, 0xd2, 0x83, 0x9a, 0xd9, 0x0b, 0xa7, 0xf6, 0x45, 0xda, 0xb6, 0xff, 0x22, 0x74, 0x0e, 0x99, + 0x36, 0x0e, 0xbc, 0x60, 0x24, 0x76, 0x42, 0xb6, 0x45, 0x4c, 0xa6, 0x67, 0x92, 0x45, 0xeb, 0x8e, + 0x28, 0x31, 0x3d, 0x73, 0x11, 0xc6, 0x89, 0x68, 0x07, 0x7f, 0xdb, 0xff, 0xc1, 0x02, 0xb2, 0x1f, + 0x27, 0xde, 0xd8, 0x4d, 0xe8, 0x63, 0xaa, 0x16, 0xf9, 0x19, 0x34, 0x59, 0x6d, 0xa7, 0xe1, 0x0e, + 0xdf, 0x6b, 0xb8, 0x8e, 0xfc, 0x9a, 0x58, 0x98, 0xfc, 0x07, 0x5b, 0x3a, 0x35, 0x17, 0x1f, 0xa3, + 0x02, 0xa6, 0xcf, 0x12, 0x37, 0x1a, 0xd1, 0x04, 0x37, 0x22, 0x61, 0xc6, 0x00, 0x07, 0xed, 0x86, + 0xc1, 0x79, 0xef, 0x0f, 0x60, 0x25, 0x57, 0x87, 0x2e, 0x69, 0xf5, 0x02, 0x49, 0x2b, 0xeb, 0x92, + 0x36, 0x80, 0x55, 0xa3, 0x5f, 0x42, 0xde, 0xba, 0xb0, 0xc4, 0xf4, 0x0d, 0xdb, 0xe7, 0x51, 0x57, + 0x3b, 0xb2, 0x48, 0xee, 0xc3, 0xda, 0x39, 0xa5, 0x91, 0x9b, 0x60, 0xb1, 0x3f, 0xa1, 0x11, 0xae, + 0x89, 0xa8, 0xb9, 0x10, 0x67, 0xff, 0x77, 0x0b, 0xda, 0x4c, 0x26, 0x3e, 0x75, 0x83, 0x99, 0x9c, + 0xab, 0xc3, 0xc2, 0xb9, 0xba, 0xab, 0xa9, 0x17, 0x8d, 0xfa, 0x8b, 0x4e, 0x54, 0x39, 0x3b, 0x51, + 0xe4, 0x16, 0x34, 0x8d, 0xee, 0x56, 0xf9, 0xc6, 0x1a, 0xbb, 0xc9, 0x31, 0x8d, 0x1e, 0xcd, 0x12, + 0xfa, 0xbb, 0x4f, 0xe5, 0x07, 0xd0, 0x49, 0xbb, 0x2d, 0xe6, 0x91, 0x40, 0x85, 0x31, 0xa6, 0xa8, + 0x00, 0x7f, 0xdb, 0xff, 0xd8, 0xe2, 0x84, 0xbb, 0xa1, 0xa7, 0x36, 0x5d, 0x46, 0xc8, 0xf6, 0x6e, + 0x49, 0xc8, 0x7e, 0xcf, 0x35, 0x5a, 0x7e, 0xf7, 0xc1, 0x92, 0xeb, 0x50, 0x8b, 0x69, 0x30, 0xec, + 0xbb, 0xbe, 0x8f, 0x7b, 0x53, 0xcd, 0x59, 0x62, 0xe5, 0x1d, 0xdf, 0xb7, 0xef, 0xc0, 0x8a, 0xd6, + 0xbb, 0xd7, 0x8c, 0xe3, 0x08, 0xc8, 0xa1, 0x17, 0x27, 0xcf, 0x83, 0x78, 0xa2, 0xed, 0x69, 0xef, + 0x40, 0x7d, 0xec, 0x05, 0xd8, 0x33, 0x2e, 0xb9, 0x55, 0xa7, 0x36, 0xf6, 0x02, 0xd6, 0xaf, 0x18, + 0x91, 0xee, 0x2b, 0x81, 0x2c, 0x09, 0xa4, 0xfb, 0x0a, 0x91, 0xf6, 0x03, 0x58, 0x35, 0xea, 0x13, + 0x4d, 0xbf, 0x0f, 0xd5, 0x69, 0xf2, 0x2a, 0x94, 0x16, 0x47, 0x43, 0x70, 0x08, 0xb3, 0x6d, 0x1d, + 0x8e, 0xb1, 0x1f, 0xc2, 0xca, 0x11, 0xbd, 0x12, 0x82, 0x2c, 0x3b, 0xf2, 0xc1, 0x1b, 0xed, 0x5e, + 0xc4, 0xdb, 0x5b, 0x40, 0xf4, 0x8f, 0x53, 0x01, 0x90, 0x56, 0xb0, 0x65, 0x58, 0xc1, 0xf6, 0x07, + 0x40, 0x4e, 0xbc, 0x51, 0xf0, 0x29, 0x8d, 0x63, 0x77, 0xa4, 0x44, 0xbf, 0x03, 0xe5, 0x71, 0x3c, + 0x12, 0xaa, 0x8a, 0xfd, 0xb4, 0xbf, 0x01, 0xab, 0x06, 0x9d, 0xa8, 0xf8, 0x06, 0xd4, 0x63, 0x6f, + 0x14, 0xb8, 0xc9, 0x34, 0xa2, 0xa2, 0xea, 0x14, 0x60, 0x3f, 0x86, 0xb5, 0x1f, 0xd0, 0xc8, 0x3b, + 0x9f, 0xbd, 0xa9, 0x7a, 0xb3, 0x9e, 0x52, 0xb6, 0x9e, 0x7d, 0xb8, 0x96, 0xa9, 0x47, 0x34, 0xcf, + 0xd9, 0x57, 0xac, 0x64, 0xcd, 0xe1, 0x05, 0x4d, 0xf7, 0x95, 0x74, 0xdd, 0x67, 0x3f, 0x07, 0xb2, + 0x1b, 0x06, 0x01, 0x1d, 0x24, 0xc7, 0x94, 0x46, 0xa9, 0x03, 0x9e, 0xf2, 0x6a, 0xe3, 0xfe, 0x86, + 0x98, 0xd9, 0xac, 0x42, 0x15, 0x4c, 0x4c, 0xa0, 0x32, 0xa1, 0xd1, 0x18, 0x2b, 0xae, 0x39, 0xf8, + 0xdb, 0xbe, 0x06, 0xab, 0x46, 0xb5, 0xc2, 0x65, 0xf9, 0x08, 0xae, 0xed, 0x79, 0xf1, 0x20, 0xdf, + 0x60, 0x17, 0x96, 0x26, 0xd3, 0xb3, 0x7e, 0x2a, 0x89, 0xb2, 0xc8, 0xac, 0xd8, 0xec, 0x27, 0xa2, + 0xb2, 0xbf, 0x69, 0x41, 0xe5, 0xe0, 0xf4, 0x70, 0x97, 0xed, 0x15, 0x5e, 0x30, 0x08, 0xc7, 0x6c, + 0x2f, 0xe5, 0x83, 0x56, 0xe5, 0xb9, 0x12, 0x76, 0x03, 0xea, 0xb8, 0x05, 0x33, 0xc3, 0x5d, 0xf8, + 0xca, 0x29, 0x80, 0x39, 0x0d, 0xf4, 0xd5, 0xc4, 0x8b, 0xd0, 0x2b, 0x90, 0xb6, 0x7e, 0x05, 0xb7, + 0x99, 0x3c, 0xc2, 0xfe, 0xf5, 0x22, 0x2c, 0x89, 0xcd, 0x17, 0xdb, 0x1b, 0x24, 0xde, 0x25, 0x15, + 0x3d, 0x11, 0x25, 0x66, 0xde, 0x44, 0x74, 0x1c, 0x26, 0xb4, 0x6f, 0x2c, 0x83, 0x09, 0x44, 0xa7, + 0x48, 0xf8, 0xab, 0xdc, 0x8d, 0x2a, 0x73, 0x2a, 0x03, 0xc8, 0x26, 0x4b, 0xda, 0x7c, 0x15, 0xb4, + 0xb5, 0x64, 0x91, 0xcd, 0xc4, 0xc0, 0x9d, 0xb8, 0x03, 0x2f, 0x99, 0x09, 0x95, 0xa0, 0xca, 0xac, + 0x6e, 0x3f, 0x1c, 0xb8, 0xcc, 0x13, 0xf6, 0xdd, 0x60, 0x40, 0xa5, 0xc3, 0x65, 0x00, 0x99, 0xf3, + 0x21, 0xba, 0x24, 0xc9, 0xb8, 0x83, 0x92, 0x81, 0xb2, 0xfd, 0x7b, 0x10, 0x8e, 0xc7, 0x5e, 0xc2, + 0x7c, 0x16, 0xb4, 0x57, 0xcb, 0x8e, 0x06, 0xe1, 0xee, 0x1d, 0x96, 0xae, 0xf8, 0xec, 0xd5, 0xa5, + 0x7b, 0xa7, 0x01, 0x59, 0x2d, 0x6c, 0xd7, 0x61, 0x6a, 0xec, 0xe5, 0x15, 0x1a, 0xa7, 0x65, 0x47, + 0x83, 0xb0, 0x75, 0x98, 0x06, 0x31, 0x4d, 0x12, 0x9f, 0x0e, 0x55, 0x87, 0x1a, 0x48, 0x96, 0x47, + 0x90, 0x7b, 0xb0, 0xca, 0xdd, 0xa8, 0xd8, 0x4d, 0xc2, 0xf8, 0xc2, 0x8b, 0xfb, 0x31, 0x73, 0x38, + 0x9a, 0x48, 0x5f, 0x84, 0x22, 0x0f, 0x60, 0x23, 0x03, 0x8e, 0xe8, 0x80, 0x7a, 0x97, 0x74, 0xd8, + 0x5d, 0xc6, 0xaf, 0xe6, 0xa1, 0xc9, 0x2d, 0x68, 0x30, 0xef, 0x71, 0x3a, 0x19, 0xba, 0xcc, 0x80, + 0x69, 0xe1, 0x3a, 0xe8, 0x20, 0xf2, 0x11, 0x2c, 0x4f, 0x28, 0xb7, 0x7e, 0x2e, 0x12, 0x7f, 0x10, + 0x77, 0xdb, 0x86, 0x76, 0x63, 0x9c, 0xeb, 0x98, 0x14, 0x8c, 0x29, 0x07, 0x31, 0xba, 0x09, 0xee, + 0xac, 0xdb, 0x11, 0xa6, 0xba, 0x04, 0xa0, 0x8c, 0x44, 0xde, 0xa5, 0x9b, 0xd0, 0xee, 0x0a, 0x57, + 0xe8, 0xa2, 0xc8, 0xbe, 0xf3, 0x02, 0x2f, 0xf1, 0xdc, 0x24, 0x8c, 0xba, 0x04, 0x71, 0x29, 0x80, + 0x4d, 0x22, 0xf2, 0x47, 0x9c, 0xb8, 0xc9, 0x34, 0xee, 0x9f, 0xfb, 0xee, 0x28, 0xee, 0xae, 0x72, + 0x9b, 0x33, 0x87, 0x20, 0x1f, 0xc3, 0x3a, 0xe7, 0x08, 0x44, 0x45, 0x34, 0xa6, 0xd1, 0x25, 0x37, + 0x13, 0xd6, 0x70, 0x46, 0xe6, 0x60, 0xd9, 0x54, 0x0a, 0x16, 0xc9, 0x7d, 0x78, 0x8d, 0x4f, 0xe5, + 0x1c, 0xb4, 0xfd, 0x4f, 0x2d, 0xbe, 0x2d, 0x08, 0x11, 0x52, 0xea, 0xfd, 0x3d, 0x68, 0x70, 0xe1, + 0xe9, 0x87, 0x81, 0x3f, 0x13, 0xf2, 0x04, 0x1c, 0xf4, 0x2c, 0xf0, 0x67, 0xe4, 0x2b, 0xb0, 0xec, + 0x05, 0x3a, 0x09, 0xd7, 0x40, 0x4d, 0x09, 0x44, 0xa2, 0xf7, 0xa0, 0x31, 0x99, 0x9e, 0xf9, 0xde, + 0x80, 0x93, 0x94, 0x79, 0x2d, 0x1c, 0x84, 0x04, 0xcc, 0x6e, 0xe7, 0xf3, 0xc8, 0x29, 0x2a, 0x48, + 0xd1, 0x10, 0x30, 0x46, 0x62, 0x3f, 0x82, 0x35, 0xb3, 0x83, 0x42, 0xd5, 0x6e, 0x42, 0x4d, 0x48, + 0x66, 0x2c, 0x9c, 0xa7, 0x96, 0x16, 0x57, 0x0a, 0xa8, 0xef, 0x28, 0xbc, 0xfd, 0x6f, 0x2b, 0xb0, + 0x2a, 0xa0, 0xbb, 0x7e, 0x18, 0xd3, 0x93, 0xe9, 0x78, 0xec, 0x46, 0x05, 0x22, 0x6f, 0xbd, 0x41, + 0xe4, 0x4b, 0xa6, 0xc8, 0x33, 0x41, 0xbc, 0x70, 0xbd, 0x80, 0x3b, 0x1d, 0x5c, 0x5f, 0x68, 0x10, + 0x72, 0x17, 0xda, 0x03, 0x3f, 0x8c, 0xb9, 0x11, 0xae, 0x87, 0x35, 0xb2, 0xe0, 0xbc, 0x8a, 0xaa, + 0x16, 0xa9, 0x28, 0x5d, 0xc5, 0x2c, 0x66, 0x54, 0x8c, 0x0d, 0x4d, 0x56, 0x29, 0x95, 0x1a, 0x73, + 0x89, 0x1b, 0xe6, 0x3a, 0x8c, 0xf5, 0x27, 0x2b, 0xd0, 0x5c, 0x7b, 0xb4, 0x8b, 0xc4, 0xd9, 0x1b, + 0x53, 0xd4, 0xc8, 0x1a, 0x75, 0x5d, 0x88, 0x73, 0x1e, 0x45, 0x1e, 0x33, 0x5f, 0x97, 0xb5, 0x85, + 0x66, 0x01, 0xa0, 0x59, 0xf0, 0x81, 0xb9, 0x22, 0xfa, 0xdc, 0x6f, 0xb1, 0xc2, 0x34, 0xa2, 0x68, + 0x2a, 0x68, 0x5f, 0xda, 0x7f, 0x64, 0x41, 0x43, 0xc3, 0x91, 0x6b, 0xb0, 0xb2, 0xfb, 0xec, 0xd9, + 0xf1, 0xbe, 0xb3, 0x73, 0xfa, 0xf4, 0x07, 0xfb, 0xfd, 0xdd, 0xc3, 0x67, 0x27, 0xfb, 0x9d, 0x05, + 0x06, 0x3e, 0x7c, 0xb6, 0xbb, 0x73, 0xd8, 0x7f, 0xfc, 0xcc, 0xd9, 0x95, 0x60, 0x8b, 0xac, 0x03, + 0x71, 0xf6, 0x3f, 0x7d, 0x76, 0xba, 0x6f, 0xc0, 0x4b, 0xa4, 0x03, 0xcd, 0x47, 0xce, 0xfe, 0xce, + 0xee, 0x81, 0x80, 0x94, 0xc9, 0x1a, 0x74, 0x1e, 0x3f, 0x3f, 0xda, 0x7b, 0x7a, 0xf4, 0xa4, 0xbf, + 0xbb, 0x73, 0xb4, 0xbb, 0x7f, 0xb8, 0xbf, 0xd7, 0xa9, 0x90, 0x65, 0xa8, 0xef, 0x3c, 0xda, 0x39, + 0xda, 0x7b, 0x76, 0xb4, 0xbf, 0xd7, 0xa9, 0xda, 0xff, 0xd5, 0x82, 0x6b, 0xd8, 0xeb, 0x61, 0x56, + 0x40, 0x6e, 0x41, 0x63, 0x10, 0x86, 0x13, 0x66, 0x8e, 0xa7, 0x1b, 0x8e, 0x0e, 0x62, 0xcc, 0xcf, + 0xc5, 0xf5, 0x3c, 0x8c, 0x06, 0x54, 0xc8, 0x07, 0x20, 0xe8, 0x31, 0x83, 0x30, 0xe6, 0x17, 0xcb, + 0xcb, 0x29, 0xb8, 0x78, 0x34, 0x38, 0x8c, 0x93, 0xac, 0xc3, 0xe2, 0x59, 0x44, 0xdd, 0xc1, 0x85, + 0x90, 0x0c, 0x51, 0x22, 0x5f, 0x4d, 0xfd, 0xc5, 0x01, 0x9b, 0x7d, 0x9f, 0x0e, 0x91, 0x63, 0x6a, + 0x4e, 0x5b, 0xc0, 0x77, 0x05, 0x98, 0xe9, 0x27, 0xf7, 0xcc, 0x0d, 0x86, 0x61, 0x40, 0x87, 0xc2, + 0x18, 0x4d, 0x01, 0xf6, 0x31, 0xac, 0x67, 0xc7, 0x27, 0xe4, 0xeb, 0x63, 0x4d, 0xbe, 0xb8, 0x6d, + 0xd8, 0x9b, 0xbf, 0x9a, 0x9a, 0xac, 0xfd, 0x79, 0x09, 0x2a, 0xcc, 0x54, 0x98, 0x6f, 0x56, 0xe8, + 0xd6, 0x5f, 0x39, 0x17, 0x03, 0x45, 0x17, 0x94, 0x6f, 0x1e, 0x7c, 0x83, 0xd5, 0x20, 0x29, 0x3e, + 0xa2, 0x83, 0x4b, 0x1c, 0xb1, 0xc2, 0x33, 0x08, 0x13, 0x10, 0x66, 0x9a, 0xe3, 0xd7, 0x42, 0x40, + 0x64, 0x59, 0xe2, 0xf0, 0xcb, 0xa5, 0x14, 0x87, 0xdf, 0x75, 0x61, 0xc9, 0x0b, 0xce, 0xc2, 0x69, + 0x30, 0x44, 0x81, 0xa8, 0x39, 0xb2, 0x88, 0x51, 0x57, 0x14, 0x54, 0x6f, 0x2c, 0xd9, 0x3f, 0x05, + 0x90, 0xfb, 0x50, 0x8f, 0x67, 0xc1, 0x40, 0xe7, 0xf9, 0x35, 0x31, 0x4b, 0x6c, 0x0e, 0xb6, 0x4e, + 0x66, 0xc1, 0x00, 0x39, 0x3c, 0x25, 0xb3, 0xff, 0x00, 0x6a, 0x12, 0xcc, 0xd8, 0xf2, 0xf9, 0xd1, + 0xf7, 0x8e, 0x9e, 0xbd, 0x38, 0xea, 0x9f, 0xfc, 0xf0, 0x68, 0xb7, 0xb3, 0x40, 0xda, 0xd0, 0xd8, + 0xd9, 0x45, 0x4e, 0x47, 0x80, 0xc5, 0x48, 0x8e, 0x77, 0x4e, 0x4e, 0x14, 0xa4, 0x64, 0x13, 0xe6, + 0x5e, 0xc7, 0x68, 0x8f, 0xa9, 0xa8, 0xe2, 0xc7, 0xb0, 0xa2, 0xc1, 0x52, 0xdb, 0x7e, 0xc2, 0x00, + 0x19, 0xdb, 0x1e, 0x0d, 0x39, 0x8e, 0xb1, 0x3b, 0xd0, 0x7a, 0x42, 0x93, 0xa7, 0xc1, 0x79, 0x28, + 0x6b, 0xfa, 0x9f, 0x15, 0x68, 0x2b, 0x90, 0xa8, 0xe8, 0x2e, 0xb4, 0xbd, 0x21, 0x0d, 0x12, 0x2f, + 0x99, 0xf5, 0x0d, 0x2f, 0x3e, 0x0b, 0x66, 0x06, 0xb0, 0xeb, 0x7b, 0xae, 0x0c, 0x6e, 0xf3, 0x02, + 0xf3, 0x6a, 0xd9, 0xee, 0x2c, 0x37, 0x5c, 0xc5, 0x57, 0x3c, 0x78, 0x50, 0x88, 0x63, 0x1a, 0x88, + 0xc1, 0xc5, 0x16, 0xa3, 0x3e, 0xe1, 0x86, 0x60, 0x11, 0x8a, 0x2d, 0x15, 0xaf, 0x89, 0x0d, 0xb9, + 0xca, 0x77, 0x70, 0x05, 0xc8, 0x45, 0x8f, 0x17, 0xb9, 0x7e, 0xcc, 0x46, 0x8f, 0xb5, 0x08, 0x74, + 0x2d, 0x17, 0x81, 0x66, 0xfa, 0x73, 0x16, 0x0c, 0xe8, 0xb0, 0x9f, 0x84, 0x7d, 0xd4, 0xf3, 0xc8, + 0x12, 0x35, 0x27, 0x0b, 0x26, 0x37, 0x60, 0x29, 0xa1, 0x71, 0x12, 0x50, 0x1e, 0xf6, 0xab, 0x3d, + 0x2a, 0x75, 0x2d, 0x47, 0x82, 0x98, 0xd5, 0x3e, 0x8d, 0xbc, 0xb8, 0xdb, 0xc4, 0xd8, 0x32, 0xfe, + 0x26, 0xdf, 0x84, 0x6b, 0x67, 0x34, 0x4e, 0xfa, 0x17, 0xd4, 0x1d, 0xd2, 0x08, 0xd9, 0x8b, 0x07, + 0xb1, 0xb9, 0x31, 0x54, 0x8c, 0x64, 0x8c, 0x7b, 0x49, 0xa3, 0xd8, 0x0b, 0x03, 0x34, 0x83, 0xea, + 0x8e, 0x2c, 0xb2, 0xfa, 0xd8, 0xe0, 0xd5, 0x26, 0xad, 0x66, 0xb0, 0x8d, 0x03, 0x2f, 0x46, 0x92, + 0xdb, 0xb0, 0x88, 0x03, 0x88, 0xbb, 0x1d, 0xe4, 0x99, 0x66, 0x2a, 0xf3, 0x5e, 0xe0, 0x08, 0x1c, + 0x5b, 0xe5, 0x41, 0xe8, 0x87, 0x11, 0xda, 0x42, 0x75, 0x87, 0x17, 0xcc, 0xd9, 0x19, 0x45, 0xee, + 0xe4, 0x42, 0xd8, 0x43, 0x59, 0xf0, 0x77, 0x2b, 0xb5, 0x46, 0xa7, 0x69, 0xff, 0x05, 0xa8, 0x62, + 0xb5, 0x58, 0x1d, 0x4e, 0xa6, 0x25, 0xaa, 0x43, 0x68, 0x17, 0x96, 0x02, 0x9a, 0x5c, 0x85, 0xd1, + 0x4b, 0x79, 0x52, 0x22, 0x8a, 0xf6, 0xcf, 0xd1, 0x6f, 0x52, 0x27, 0x07, 0xcf, 0xd1, 0xe8, 0x63, + 0xde, 0x2f, 0x5f, 0xaa, 0xf8, 0xc2, 0x15, 0xae, 0x5c, 0x0d, 0x01, 0x27, 0x17, 0x2e, 0xd3, 0xb5, + 0xc6, 0xea, 0x73, 0xef, 0xb8, 0x81, 0xb0, 0x03, 0xbe, 0xf8, 0xb7, 0xa1, 0x25, 0xcf, 0x24, 0xe2, + 0xbe, 0x4f, 0xcf, 0x13, 0x19, 0xdb, 0x0a, 0xa6, 0x63, 0x74, 0xa1, 0x0f, 0xe9, 0x79, 0x62, 0x1f, + 0xc1, 0x8a, 0xd0, 0x7f, 0xcf, 0x26, 0x54, 0x36, 0xfd, 0xfb, 0x45, 0x76, 0x44, 0xe3, 0xfe, 0xaa, + 0xa9, 0x30, 0xf9, 0x29, 0x8c, 0x49, 0x69, 0x3b, 0x40, 0x74, 0x7d, 0x2a, 0x2a, 0x14, 0x9b, 0xb9, + 0x8c, 0xde, 0x89, 0xe1, 0x18, 0x30, 0x36, 0x3f, 0xf1, 0x74, 0x30, 0x90, 0x27, 0x49, 0x35, 0x47, + 0x16, 0xed, 0x7f, 0x6e, 0xc1, 0x2a, 0xd6, 0x26, 0x2d, 0x21, 0xb1, 0x67, 0x3d, 0xf8, 0x02, 0xdd, + 0x6c, 0x0e, 0xf4, 0x88, 0xe6, 0x1a, 0x54, 0xf5, 0x5d, 0x8c, 0x17, 0xbe, 0x78, 0xa4, 0xa4, 0x92, + 0x8d, 0x94, 0xd8, 0xff, 0xc0, 0x82, 0x15, 0xbe, 0x91, 0xa0, 0x1d, 0x2c, 0x86, 0xff, 0x6d, 0x58, + 0xe6, 0x16, 0x81, 0xd0, 0x0a, 0xa2, 0xa3, 0xa9, 0x6a, 0x45, 0x28, 0x27, 0x3e, 0x58, 0x70, 0x4c, + 0x62, 0xf2, 0x10, 0xad, 0xb2, 0xa0, 0x8f, 0xd0, 0x82, 0x33, 0x47, 0x73, 0xae, 0x0f, 0x16, 0x1c, + 0x8d, 0xfc, 0x51, 0x0d, 0x16, 0xb9, 0x13, 0x61, 0x3f, 0x81, 0x65, 0xa3, 0x21, 0x23, 0x4a, 0xd3, + 0xe4, 0x51, 0x9a, 0x5c, 0x38, 0xb4, 0x54, 0x10, 0x0e, 0xfd, 0xd7, 0x65, 0x20, 0x8c, 0x59, 0x32, + 0xab, 0xc1, 0xbc, 0x98, 0x70, 0x68, 0xf8, 0xa4, 0x4d, 0x47, 0x07, 0x91, 0x2d, 0x20, 0x5a, 0x51, + 0x46, 0xac, 0xf9, 0x96, 0x59, 0x80, 0x61, 0x6a, 0x56, 0x58, 0x1c, 0xc2, 0x36, 0x10, 0xde, 0x37, + 0x9f, 0xf6, 0x42, 0x1c, 0xdb, 0x15, 0x27, 0xd3, 0xf8, 0x02, 0x7d, 0x05, 0xe1, 0xb5, 0xca, 0x72, + 0x76, 0x7d, 0x17, 0xdf, 0xb8, 0xbe, 0x4b, 0xb9, 0x48, 0x98, 0xe6, 0x37, 0xd5, 0x4c, 0xbf, 0xe9, + 0x36, 0x2c, 0x8f, 0x99, 0x9d, 0x9c, 0xf8, 0x83, 0xfe, 0x98, 0xb5, 0x2e, 0x9c, 0x54, 0x03, 0x48, + 0x36, 0xa1, 0x23, 0x5d, 0x17, 0xe5, 0x9c, 0xf1, 0x73, 0x94, 0x1c, 0x9c, 0xe9, 0xff, 0x34, 0x36, + 0xd6, 0xc0, 0xce, 0xa6, 0x00, 0xe6, 0x89, 0xc5, 0x8c, 0x43, 0xfa, 0xd3, 0x40, 0x1c, 0x3b, 0xd2, + 0x21, 0xba, 0xa7, 0x35, 0x27, 0x8f, 0xb0, 0xff, 0xae, 0x05, 0x1d, 0xb6, 0x66, 0x06, 0x5b, 0x7e, + 0x02, 0x28, 0x15, 0x6f, 0xc9, 0x95, 0x06, 0x2d, 0x79, 0x00, 0x75, 0x2c, 0x87, 0x13, 0x1a, 0x08, + 0x9e, 0xec, 0x9a, 0x3c, 0x99, 0xea, 0x93, 0x83, 0x05, 0x27, 0x25, 0xd6, 0x38, 0xf2, 0x3f, 0x59, + 0xd0, 0x10, 0xad, 0xfc, 0xd6, 0xb1, 0x97, 0x9e, 0x76, 0x4e, 0xcc, 0x39, 0x29, 0x3d, 0x16, 0xbe, + 0x0b, 0xed, 0xb1, 0x9b, 0x4c, 0x23, 0xb6, 0x9f, 0x1b, 0x71, 0x97, 0x2c, 0x98, 0x6d, 0xce, 0xa8, + 0x3a, 0xe3, 0x7e, 0xe2, 0xf9, 0x7d, 0x89, 0x15, 0x27, 0xb2, 0x45, 0x28, 0xa6, 0x41, 0xe2, 0xc4, + 0x1d, 0x51, 0xb1, 0xef, 0xf2, 0x82, 0xdd, 0x85, 0x75, 0x31, 0xa0, 0x8c, 0x7d, 0x6d, 0xff, 0xcb, + 0x65, 0xd8, 0xc8, 0xa1, 0x54, 0xfe, 0x88, 0x08, 0x28, 0xf8, 0xde, 0xf8, 0x2c, 0x54, 0xce, 0x89, + 0xa5, 0xc7, 0x1a, 0x0c, 0x14, 0x19, 0xc1, 0x35, 0x69, 0x60, 0xb0, 0x39, 0x4d, 0x37, 0xc3, 0x12, + 0xee, 0x72, 0x1f, 0x99, 0x4b, 0x98, 0x6d, 0x50, 0xc2, 0x75, 0x21, 0x2e, 0xae, 0x8f, 0x5c, 0x40, + 0x57, 0x59, 0x32, 0x42, 0x59, 0x6b, 0xd6, 0x0e, 0x6b, 0xeb, 0xc3, 0x37, 0xb4, 0x65, 0x98, 0xe3, + 0xce, 0xdc, 0xda, 0xc8, 0x0c, 0x6e, 0x4a, 0x1c, 0x6a, 0xe3, 0x7c, 0x7b, 0x95, 0xb7, 0x1a, 0x1b, + 0x3a, 0x1a, 0x66, 0xa3, 0x6f, 0xa8, 0x98, 0xfc, 0x14, 0xd6, 0xaf, 0x5c, 0x2f, 0x91, 0xdd, 0xd2, + 0x6c, 0x8b, 0x2a, 0x36, 0x79, 0xff, 0x0d, 0x4d, 0xbe, 0xe0, 0x1f, 0x1b, 0x5b, 0xd4, 0x9c, 0x1a, + 0x7b, 0xbf, 0x2e, 0x41, 0xcb, 0xac, 0x87, 0xb1, 0xa9, 0x90, 0x7d, 0xa9, 0x03, 0xa5, 0x35, 0x9a, + 0x01, 0xe7, 0xfd, 0xfb, 0x52, 0x91, 0x7f, 0xaf, 0x7b, 0xd5, 0xe5, 0x37, 0x05, 0xee, 0x2a, 0x6f, + 0x17, 0xb8, 0xab, 0x16, 0x06, 0xee, 0xe6, 0xc7, 0x77, 0x16, 0x7f, 0xdb, 0xf8, 0xce, 0xd2, 0x6b, + 0xe3, 0x3b, 0xbd, 0xff, 0x63, 0x01, 0xc9, 0x73, 0x2f, 0x79, 0xc2, 0x43, 0x1a, 0x01, 0xf5, 0x85, + 0x12, 0xfb, 0xfa, 0xdb, 0x49, 0x80, 0x5c, 0x2d, 0xf9, 0x35, 0x13, 0x45, 0x3d, 0x89, 0x43, 0x37, + 0xaf, 0x96, 0x9d, 0x22, 0x54, 0x26, 0x78, 0x59, 0x79, 0x73, 0xf0, 0xb2, 0xfa, 0xe6, 0xe0, 0xe5, + 0x62, 0x36, 0x78, 0xd9, 0xfb, 0x1b, 0x16, 0xac, 0x16, 0xb0, 0xd9, 0x97, 0x37, 0x70, 0xc6, 0x18, + 0x86, 0xf6, 0x29, 0x09, 0xc6, 0xd0, 0x81, 0xbd, 0xbf, 0x0a, 0xcb, 0x86, 0x68, 0x7d, 0x79, 0xed, + 0x67, 0x2d, 0x44, 0xce, 0xd9, 0x06, 0xac, 0xf7, 0xbf, 0x4a, 0x40, 0xf2, 0xe2, 0xfd, 0xff, 0xb5, + 0x0f, 0xf9, 0x79, 0x2a, 0x17, 0xcc, 0xd3, 0xff, 0xd3, 0x9d, 0xe7, 0x43, 0x58, 0x11, 0x99, 0x69, + 0x5a, 0x20, 0x8b, 0x73, 0x4c, 0x1e, 0xc1, 0x6c, 0x64, 0x33, 0x72, 0x5c, 0x33, 0x32, 0x71, 0xb4, + 0xed, 0x37, 0x13, 0x40, 0xb6, 0x7b, 0xd0, 0x15, 0x33, 0xb4, 0x7f, 0x49, 0x83, 0xe4, 0x64, 0x7a, + 0xc6, 0x53, 0xb3, 0xbc, 0x30, 0xb0, 0xff, 0x4d, 0x59, 0x99, 0xf9, 0x88, 0x14, 0x06, 0xc5, 0x37, + 0xa1, 0xa9, 0x6f, 0x1f, 0x62, 0x39, 0x32, 0x71, 0x4c, 0x66, 0x4a, 0xe8, 0x54, 0x64, 0x0f, 0x5a, + 0xa8, 0x24, 0x87, 0xea, 0xbb, 0x12, 0x7e, 0xf7, 0x9a, 0xf8, 0xcc, 0xc1, 0x82, 0x93, 0xf9, 0x86, + 0x7c, 0x07, 0x5a, 0xa6, 0xf3, 0x27, 0xac, 0x92, 0x22, 0x6f, 0x80, 0x7d, 0x6e, 0x12, 0x93, 0x1d, + 0xe8, 0x64, 0xbd, 0x47, 0x91, 0x29, 0x31, 0xa7, 0x82, 0x1c, 0x39, 0x79, 0x20, 0x8e, 0x10, 0xab, + 0x18, 0x37, 0xb9, 0x6d, 0x7e, 0xa6, 0x4d, 0xd3, 0x16, 0xff, 0xa3, 0x1d, 0x2a, 0xfe, 0x21, 0x40, + 0x0a, 0x23, 0x1d, 0x68, 0x3e, 0x3b, 0xde, 0x3f, 0xea, 0xef, 0x1e, 0xec, 0x1c, 0x1d, 0xed, 0x1f, + 0x76, 0x16, 0x08, 0x81, 0x16, 0x86, 0xf9, 0xf6, 0x14, 0xcc, 0x62, 0x30, 0x11, 0x58, 0x91, 0xb0, + 0x12, 0x59, 0x83, 0xce, 0xd3, 0xa3, 0x0c, 0xb4, 0xfc, 0xa8, 0xae, 0xe4, 0xc3, 0x5e, 0x87, 0x35, + 0x9e, 0x79, 0xf8, 0x88, 0xb3, 0x87, 0xb4, 0x4e, 0xfe, 0x89, 0x05, 0xd7, 0x32, 0x88, 0x34, 0x95, + 0x86, 0x1b, 0x20, 0xa6, 0x55, 0x62, 0x02, 0xf1, 0x58, 0x40, 0xda, 0x9a, 0x19, 0x0d, 0x92, 0x47, + 0x30, 0x9e, 0xd7, 0x6c, 0xd3, 0x8c, 0x24, 0x15, 0xa1, 0xec, 0x0d, 0x9e, 0x1f, 0x89, 0x99, 0x94, + 0x46, 0xc7, 0xcf, 0x79, 0x46, 0xa3, 0x8e, 0x48, 0x8f, 0x64, 0xcd, 0x2e, 0xcb, 0x22, 0x73, 0x2b, + 0x0c, 0x63, 0xc7, 0xec, 0x6f, 0x21, 0xce, 0xfe, 0xa3, 0x0a, 0x90, 0xef, 0x4f, 0x69, 0x34, 0xc3, + 0x7c, 0x19, 0x15, 0x35, 0xdd, 0xc8, 0xc6, 0x04, 0x17, 0x27, 0xd3, 0xb3, 0xef, 0xd1, 0x99, 0xcc, + 0x1c, 0x2b, 0xa5, 0x99, 0x63, 0x45, 0xd9, 0x5b, 0x95, 0x37, 0x67, 0x6f, 0x55, 0xdf, 0x94, 0xbd, + 0xf5, 0x15, 0x58, 0xf6, 0x46, 0x41, 0xc8, 0x64, 0x9e, 0xd9, 0x09, 0x71, 0x77, 0xf1, 0x56, 0x99, + 0xf9, 0xd6, 0x02, 0x78, 0xc4, 0x60, 0xe4, 0x61, 0x4a, 0x44, 0x87, 0x23, 0xcc, 0x14, 0xd4, 0xb5, + 0xc0, 0xfe, 0x70, 0x44, 0x0f, 0xc3, 0x81, 0x9b, 0x84, 0x11, 0x06, 0x76, 0xe4, 0xc7, 0x0c, 0x1e, + 0x93, 0xdb, 0xd0, 0x8a, 0xc3, 0x29, 0xb3, 0x9c, 0xe4, 0x58, 0x79, 0x24, 0xa9, 0xc9, 0xa1, 0xc7, + 0x7c, 0xc4, 0x5b, 0xb0, 0x3a, 0x8d, 0x69, 0x7f, 0xec, 0xc5, 0x31, 0xdb, 0x1d, 0x07, 0x61, 0x90, + 0x44, 0xa1, 0x2f, 0xe2, 0x49, 0x2b, 0xd3, 0x98, 0x7e, 0xca, 0x31, 0xbb, 0x1c, 0x41, 0xbe, 0x99, + 0x76, 0x69, 0xe2, 0x7a, 0x51, 0xdc, 0x05, 0xec, 0x92, 0x1c, 0x29, 0xeb, 0xf7, 0xb1, 0xeb, 0x45, + 0xaa, 0x2f, 0xac, 0x10, 0x93, 0x9d, 0x5c, 0x8a, 0x99, 0x8c, 0xc9, 0xe7, 0x57, 0xe7, 0xcb, 0xcf, + 0x34, 0x13, 0x09, 0x52, 0x5b, 0x50, 0x93, 0xdd, 0x63, 0x4e, 0xf4, 0x79, 0x14, 0x8e, 0xa5, 0x13, + 0xcd, 0x7e, 0x93, 0x16, 0x94, 0x92, 0x50, 0x7c, 0x5c, 0x4a, 0x42, 0xfb, 0x87, 0xd0, 0xd0, 0x66, + 0x18, 0xb3, 0xe8, 0x84, 0xc1, 0x26, 0xbc, 0xef, 0x0a, 0xf7, 0x8f, 0x02, 0xea, 0x3f, 0x1d, 0x92, + 0xaf, 0xc1, 0xca, 0xd0, 0x8b, 0x28, 0x26, 0x44, 0xf6, 0x23, 0x7a, 0x49, 0xa3, 0x58, 0xc6, 0x29, + 0x3a, 0x0a, 0xe1, 0x70, 0xb8, 0xfd, 0x10, 0x56, 0x8d, 0x81, 0x2b, 0xa9, 0x5d, 0xc4, 0x8c, 0x2e, + 0x19, 0x2a, 0x35, 0xb3, 0xbd, 0x04, 0xce, 0xfe, 0x67, 0x65, 0x28, 0x1f, 0x84, 0x13, 0xfd, 0x40, + 0xc8, 0x32, 0x0f, 0x84, 0x84, 0xc1, 0xd9, 0x57, 0xf6, 0xa4, 0xb0, 0x0a, 0x0c, 0x20, 0xd9, 0x84, + 0x96, 0x3b, 0x4e, 0xfa, 0x49, 0xc8, 0x0c, 0xec, 0x2b, 0x37, 0x1a, 0x72, 0x51, 0x46, 0x56, 0xca, + 0x60, 0xc8, 0x1a, 0x94, 0x95, 0x9d, 0x84, 0x04, 0xac, 0xc8, 0xbc, 0x3b, 0x3c, 0x0a, 0x9f, 0x89, + 0x38, 0xa7, 0x28, 0x31, 0x4d, 0x61, 0x7e, 0xcf, 0x5d, 0x6b, 0xbe, 0xdb, 0x15, 0xa1, 0x98, 0xf1, + 0xcb, 0x84, 0x67, 0x9c, 0xda, 0x92, 0xaa, 0xac, 0x47, 0xf0, 0x6b, 0x66, 0x04, 0xff, 0x16, 0x34, + 0x12, 0xff, 0xb2, 0x3f, 0x71, 0x67, 0x7e, 0xe8, 0x0e, 0x05, 0xd3, 0xea, 0x20, 0xf2, 0x6d, 0x4e, + 0xc1, 0x36, 0xd8, 0x68, 0x28, 0x99, 0x55, 0xee, 0x50, 0x07, 0xe1, 0x64, 0xeb, 0xd4, 0xbf, 0x74, + 0x38, 0x92, 0xf3, 0x9b, 0x4e, 0xde, 0xfb, 0x0e, 0xb4, 0x33, 0xf8, 0x2f, 0x94, 0xe0, 0xf8, 0x1b, + 0x0b, 0xaa, 0xb8, 0x74, 0xcc, 0xb0, 0xe0, 0x9a, 0x57, 0x1d, 0x59, 0x61, 0x0d, 0xcb, 0x4e, 0x16, + 0x4c, 0x6c, 0x23, 0x33, 0xb8, 0xa4, 0xe6, 0x5b, 0xcf, 0x0e, 0xbe, 0x05, 0x75, 0x5e, 0x52, 0x59, + 0xae, 0x48, 0x92, 0x02, 0xc9, 0x4d, 0xa8, 0x5c, 0x84, 0x13, 0xe9, 0x7b, 0x41, 0x3a, 0x5e, 0x07, + 0xe1, 0x69, 0x7f, 0x58, 0x7d, 0x7c, 0xd6, 0xb9, 0x7d, 0x9b, 0x05, 0x33, 0x9f, 0x42, 0x55, 0xab, + 0xaf, 0x62, 0x06, 0x6a, 0x3f, 0x87, 0x36, 0x13, 0x2e, 0x2d, 0x84, 0x3f, 0x5f, 0xcb, 0x7e, 0x95, + 0x6d, 0xda, 0x03, 0x7f, 0x3a, 0xa4, 0xba, 0x07, 0x8c, 0x21, 0x5a, 0x01, 0x97, 0xb6, 0x9f, 0xfd, + 0xaf, 0x2c, 0x2e, 0xb4, 0xac, 0x5e, 0x72, 0x17, 0x2a, 0x4c, 0x57, 0x66, 0x02, 0x1e, 0x2a, 0x25, + 0x85, 0xd1, 0x39, 0x48, 0xc1, 0x4c, 0x42, 0x0c, 0xa2, 0xea, 0xb5, 0xf3, 0x10, 0x6a, 0xea, 0x3e, + 0xaa, 0x91, 0x65, 0xbc, 0xae, 0x0c, 0x94, 0x6c, 0x69, 0x27, 0x50, 0x15, 0x43, 0xff, 0x4a, 0x1b, + 0x61, 0x38, 0xa2, 0xda, 0xc9, 0xd3, 0x9f, 0x58, 0xb0, 0x6c, 0xf4, 0x89, 0xb1, 0xa9, 0xef, 0xc6, + 0x89, 0x48, 0x0b, 0x10, 0x2b, 0xaf, 0x83, 0x74, 0x16, 0x2f, 0x99, 0x2c, 0xae, 0x4e, 0x32, 0xca, + 0xfa, 0x49, 0xc6, 0x3d, 0xa8, 0xa7, 0xa9, 0xe1, 0x66, 0xa7, 0x58, 0x8b, 0x32, 0x39, 0x27, 0x25, + 0x4a, 0x63, 0xe5, 0x55, 0x2d, 0x56, 0x6e, 0x3f, 0x84, 0x86, 0x46, 0xaf, 0xc7, 0xba, 0x2d, 0x23, + 0xd6, 0xad, 0x32, 0xd7, 0x4a, 0x69, 0xe6, 0x9a, 0xfd, 0x8b, 0x12, 0x2c, 0x33, 0xf6, 0xf6, 0x82, + 0xd1, 0x71, 0xe8, 0x7b, 0x83, 0x19, 0xb2, 0x95, 0xe4, 0x64, 0xb1, 0x57, 0x4a, 0x36, 0x37, 0xc1, + 0x4c, 0xde, 0x65, 0x84, 0x4d, 0x28, 0x27, 0x55, 0x66, 0xda, 0x8b, 0xc9, 0xfe, 0x99, 0x1b, 0x0b, + 0x85, 0x20, 0x6c, 0x75, 0x03, 0xc8, 0x74, 0x0c, 0x03, 0x60, 0x1e, 0xe2, 0xd8, 0xf3, 0x7d, 0x8f, + 0xd3, 0x72, 0x4f, 0xae, 0x08, 0xc5, 0xda, 0x1c, 0x7a, 0xb1, 0x7b, 0x96, 0x9e, 0x52, 0xaa, 0x32, + 0x86, 0x01, 0xdd, 0x57, 0x5a, 0x18, 0x70, 0x11, 0x05, 0xdc, 0x04, 0x66, 0x17, 0x72, 0x29, 0xb7, + 0x90, 0xf6, 0x9f, 0x96, 0xa0, 0xa1, 0xb1, 0x85, 0x38, 0x9a, 0x37, 0x37, 0x0d, 0x0d, 0x22, 0xf1, + 0x46, 0x5c, 0x40, 0x83, 0x90, 0xdb, 0x66, 0x8b, 0x78, 0x14, 0x80, 0xc2, 0x6e, 0xb0, 0xcf, 0x0d, + 0xa8, 0x33, 0xb6, 0xff, 0x08, 0x83, 0x10, 0xe2, 0x4e, 0x86, 0x02, 0x48, 0xec, 0x7d, 0xc4, 0x56, + 0x53, 0x2c, 0x02, 0x5e, 0x7b, 0x98, 0xff, 0x00, 0x9a, 0xa2, 0x1a, 0x5c, 0x5f, 0x1c, 0x70, 0x2a, + 0x78, 0xc6, 0xda, 0x3b, 0x06, 0xa5, 0xfc, 0xf2, 0xbe, 0xfc, 0xb2, 0xf6, 0xa6, 0x2f, 0x25, 0xa5, + 0xfd, 0x44, 0xe5, 0x48, 0x3c, 0x89, 0xdc, 0xc9, 0x85, 0x54, 0x26, 0xf7, 0x60, 0x55, 0xea, 0x8c, + 0x69, 0xe0, 0x06, 0x41, 0x38, 0x0d, 0x06, 0x54, 0x26, 0xb8, 0x15, 0xa1, 0xec, 0xa1, 0x4a, 0x87, + 0xc6, 0x8a, 0xc8, 0x26, 0x54, 0xb9, 0xa5, 0xc5, 0xf7, 0xd6, 0x62, 0xf5, 0xc1, 0x49, 0xc8, 0x5d, + 0xa8, 0x72, 0x83, 0xab, 0x34, 0x57, 0xe0, 0x39, 0x81, 0xbd, 0x09, 0x6d, 0xcc, 0xb2, 0x37, 0xf5, + 0x9e, 0xb9, 0x2f, 0x2f, 0x0e, 0x30, 0x0f, 0xdf, 0x5e, 0x03, 0x72, 0xc4, 0xe5, 0x49, 0x3f, 0xe9, + 0xfc, 0x4d, 0x19, 0x1a, 0x1a, 0x98, 0xe9, 0x25, 0x3c, 0x9e, 0xea, 0x0f, 0x3d, 0x77, 0x4c, 0x13, + 0x1a, 0x09, 0x19, 0xca, 0x40, 0x19, 0x9d, 0x7b, 0x39, 0xea, 0x87, 0xd3, 0xa4, 0x3f, 0xa4, 0xa3, + 0x88, 0xf2, 0x0d, 0x88, 0x6d, 0xdd, 0x06, 0x94, 0xd1, 0x31, 0x2e, 0xd6, 0xe8, 0xf8, 0x81, 0x52, + 0x06, 0x2a, 0xcf, 0x2d, 0xf9, 0x1c, 0x55, 0xd2, 0x73, 0x4b, 0x3e, 0x23, 0x59, 0x8d, 0x5a, 0x2d, + 0xd0, 0xa8, 0x1f, 0xc3, 0x3a, 0xd7, 0x9d, 0x42, 0x6b, 0xf4, 0x33, 0x8c, 0x35, 0x07, 0x4b, 0x36, + 0xa1, 0xc3, 0xfa, 0x2c, 0xc5, 0x22, 0xf6, 0x7e, 0xce, 0x65, 0xcb, 0x72, 0x72, 0x70, 0x46, 0x8b, + 0xc1, 0x74, 0x9d, 0x96, 0x27, 0x8f, 0xe4, 0xe0, 0x48, 0xeb, 0xbe, 0x32, 0x69, 0xeb, 0x82, 0x36, + 0x03, 0x27, 0x0f, 0x60, 0x63, 0x4c, 0x87, 0x9e, 0x6b, 0x56, 0x81, 0xb1, 0x2d, 0x9e, 0x93, 0x36, + 0x0f, 0xcd, 0x5a, 0x61, 0xb3, 0xf0, 0xf3, 0x70, 0x7c, 0xe6, 0xf1, 0x0d, 0x8d, 0x87, 0xfd, 0x2b, + 0x4e, 0x0e, 0x6e, 0x2f, 0x43, 0xe3, 0x24, 0x09, 0x27, 0x72, 0xe9, 0x5b, 0xd0, 0xe4, 0x45, 0x91, + 0xce, 0xf8, 0x0e, 0x5c, 0x47, 0x5e, 0x3d, 0x0d, 0x27, 0xa1, 0x1f, 0x8e, 0x66, 0x86, 0xf3, 0xfe, + 0x1f, 0x2d, 0x58, 0x35, 0xb0, 0xa9, 0xf7, 0x8e, 0x91, 0x46, 0x99, 0x87, 0xc6, 0xd9, 0x7b, 0x45, + 0xdb, 0x0e, 0x38, 0x21, 0x3f, 0xd4, 0x79, 0x2e, 0x52, 0xd3, 0x76, 0xd2, 0x6b, 0x71, 0xf2, 0x43, + 0xce, 0xeb, 0xdd, 0x3c, 0xaf, 0x8b, 0xef, 0xe5, 0xad, 0x38, 0x59, 0xc5, 0x77, 0x44, 0xaa, 0xcf, + 0x50, 0x0c, 0xba, 0x6c, 0xa6, 0x67, 0xe8, 0xc1, 0x1e, 0xd9, 0x83, 0x81, 0x02, 0xc6, 0xf6, 0x2f, + 0x2d, 0x80, 0xb4, 0x77, 0x98, 0x20, 0xa2, 0xb6, 0x34, 0x7e, 0x05, 0x53, 0xdb, 0xbe, 0xde, 0x87, + 0xa6, 0x3a, 0xe3, 0x4f, 0x77, 0xc9, 0x86, 0x84, 0x31, 0xab, 0xe2, 0x0e, 0xb4, 0x47, 0x7e, 0x78, + 0x86, 0xd6, 0x0b, 0xe6, 0xc7, 0xc6, 0x22, 0xa9, 0xb3, 0xc5, 0xc1, 0x8f, 0x05, 0x34, 0xdd, 0x52, + 0x2b, 0xfa, 0x96, 0x5a, 0xbc, 0x41, 0xfe, 0xed, 0x92, 0x3a, 0x68, 0x4d, 0x67, 0x62, 0xae, 0x84, + 0x93, 0xfb, 0x39, 0x75, 0x3e, 0xe7, 0x5c, 0x13, 0x1d, 0x87, 0xe3, 0x37, 0xc6, 0x7d, 0x1f, 0x42, + 0x2b, 0xe2, 0xba, 0x52, 0x2a, 0xd2, 0xca, 0x6b, 0x14, 0xe9, 0x72, 0x64, 0xec, 0xc6, 0x5f, 0x85, + 0x8e, 0x3b, 0xbc, 0xa4, 0x51, 0xe2, 0x61, 0x1c, 0x0c, 0x4d, 0x27, 0x3e, 0xb8, 0xb6, 0x06, 0x47, + 0x0b, 0xe5, 0x0e, 0xb4, 0x45, 0x7a, 0xad, 0xa2, 0x14, 0x97, 0x99, 0x52, 0x30, 0x23, 0xb4, 0x7f, + 0x25, 0xcf, 0x74, 0xcd, 0x95, 0x9d, 0x3f, 0x23, 0xfa, 0xe8, 0x4a, 0x99, 0xd1, 0x7d, 0x45, 0x9c, + 0xaf, 0x0e, 0x65, 0xb0, 0xad, 0xac, 0x25, 0x8b, 0x0d, 0xc5, 0x79, 0xb8, 0x39, 0xa5, 0x95, 0xb7, + 0x99, 0x52, 0xfb, 0xcf, 0x2c, 0x58, 0x3a, 0x08, 0x27, 0x07, 0x22, 0x6d, 0x0e, 0xc5, 0x43, 0xe5, + 0xb5, 0xcb, 0xe2, 0x6b, 0x12, 0xea, 0x0a, 0x2d, 0x90, 0xe5, 0xac, 0x05, 0xf2, 0x97, 0xe0, 0x1d, + 0x0c, 0xf5, 0x46, 0xe1, 0x24, 0x8c, 0x98, 0x88, 0xba, 0x3e, 0x37, 0x37, 0xc2, 0x20, 0xb9, 0x90, + 0x2a, 0xf4, 0x75, 0x24, 0x18, 0x7f, 0xf1, 0x93, 0xcb, 0x3e, 0x77, 0x9b, 0x84, 0xc5, 0xc4, 0x35, + 0x6b, 0x1e, 0x61, 0xff, 0x3e, 0xd4, 0xd1, 0x9b, 0xc0, 0x61, 0x7d, 0x08, 0xf5, 0x8b, 0x70, 0xd2, + 0xbf, 0xf0, 0x82, 0x44, 0x8a, 0x7c, 0x2b, 0x35, 0xf3, 0x0f, 0x70, 0x42, 0x14, 0x81, 0xfd, 0xa7, + 0x8b, 0xb0, 0xf4, 0x34, 0xb8, 0x0c, 0xbd, 0x01, 0x9e, 0x1f, 0x8f, 0xe9, 0x38, 0x94, 0x59, 0xfe, + 0xec, 0x37, 0xb9, 0x01, 0x4b, 0x98, 0xd6, 0x3a, 0xe1, 0x4c, 0xdb, 0xe4, 0x79, 0x22, 0x02, 0x84, + 0xb7, 0x0c, 0xd3, 0xbb, 0x56, 0x5c, 0xa8, 0x34, 0x08, 0x73, 0x03, 0x23, 0xfd, 0xae, 0x94, 0x28, + 0xa5, 0x9e, 0x51, 0x55, 0xbb, 0x45, 0xc1, 0xda, 0x12, 0x69, 0x7e, 0x3c, 0x0f, 0x8c, 0xb7, 0x25, + 0x40, 0xe8, 0xba, 0x46, 0x94, 0x87, 0xea, 0x95, 0x91, 0xc5, 0x5c, 0x57, 0x1d, 0xc8, 0x0c, 0x31, + 0xfe, 0x01, 0xa7, 0xe1, 0x1b, 0x80, 0x0e, 0x62, 0xa6, 0x68, 0xf6, 0x7a, 0x1e, 0xbf, 0x1e, 0x99, + 0x05, 0x33, 0xfd, 0x3d, 0xa4, 0x4a, 0xcd, 0xf2, 0x71, 0x00, 0xbf, 0x4f, 0x96, 0x85, 0x6b, 0x0e, + 0x2f, 0xcf, 0x40, 0x96, 0x0e, 0x2f, 0x63, 0x18, 0xd7, 0xf7, 0xcf, 0xdc, 0xc1, 0x4b, 0xbc, 0x9d, + 0x89, 0x27, 0xba, 0x75, 0xc7, 0x04, 0x62, 0xb2, 0x5e, 0xba, 0xaa, 0x98, 0x51, 0x53, 0x71, 0x74, + 0x10, 0xb9, 0x0f, 0x0d, 0x74, 0xf2, 0xc5, 0xba, 0xb6, 0x70, 0x5d, 0x3b, 0x7a, 0x14, 0x00, 0x57, + 0x56, 0x27, 0xd2, 0xcf, 0xb6, 0xdb, 0xb9, 0x9c, 0x60, 0x77, 0x38, 0x14, 0x29, 0x01, 0x1d, 0x1e, + 0xb0, 0x50, 0x00, 0xb6, 0xa3, 0x8b, 0x09, 0xe3, 0x04, 0x2b, 0x48, 0x60, 0xc0, 0xc8, 0x4d, 0xa8, + 0x31, 0x0f, 0x6f, 0xe2, 0x7a, 0x43, 0x4c, 0xa2, 0xe1, 0x8e, 0xa6, 0x82, 0xb1, 0x3a, 0xe4, 0x6f, + 0xdc, 0x2a, 0x57, 0x71, 0x56, 0x0c, 0x18, 0x9b, 0x1b, 0x55, 0x1e, 0xa7, 0x49, 0xc4, 0x26, 0x90, + 0x7c, 0x84, 0x07, 0xb3, 0x09, 0xc5, 0x4c, 0xe1, 0xd6, 0xfd, 0x77, 0xc4, 0x98, 0x05, 0xd3, 0xca, + 0xbf, 0x27, 0x8c, 0xc4, 0xe1, 0x94, 0xcc, 0x48, 0xe3, 0xb1, 0xf1, 0x75, 0xc3, 0x48, 0x13, 0xa4, + 0x18, 0x1b, 0xe7, 0x04, 0xf6, 0x0e, 0x34, 0xf5, 0x0a, 0x48, 0x0d, 0x2a, 0xcf, 0x8e, 0xf7, 0x8f, + 0x3a, 0x0b, 0xa4, 0x01, 0x4b, 0x27, 0xfb, 0xa7, 0xa7, 0x87, 0xfb, 0x7b, 0x1d, 0x8b, 0x34, 0xa1, + 0xa6, 0x72, 0x30, 0x4b, 0xac, 0xb4, 0xb3, 0xbb, 0xbb, 0x7f, 0x7c, 0xba, 0xbf, 0xd7, 0x29, 0xdb, + 0x7f, 0x5c, 0x82, 0x86, 0x56, 0xf3, 0x6b, 0x82, 0x2f, 0x37, 0x01, 0xd0, 0x63, 0x48, 0x33, 0x31, + 0x2a, 0x8e, 0x06, 0x61, 0x1a, 0x51, 0xf9, 0xd2, 0x65, 0xc4, 0xaa, 0x32, 0xce, 0xd5, 0x60, 0x40, + 0x27, 0x89, 0x7e, 0xfc, 0x50, 0x75, 0x4c, 0x20, 0xe3, 0x23, 0x01, 0xc0, 0x74, 0x40, 0x2e, 0x5d, + 0x3a, 0x88, 0xad, 0x4b, 0x44, 0xe3, 0xd0, 0xbf, 0xa4, 0x9c, 0x84, 0xdb, 0x5f, 0x06, 0x8c, 0xb5, + 0x25, 0xd4, 0x8b, 0x96, 0xaa, 0x5b, 0x75, 0x4c, 0x20, 0xf9, 0xba, 0x5c, 0x97, 0x1a, 0xae, 0xcb, + 0x46, 0x7e, 0x92, 0xf5, 0x35, 0xb1, 0x13, 0x20, 0x3b, 0xc3, 0xa1, 0xc0, 0xaa, 0xb8, 0x56, 0xaa, + 0x20, 0x2c, 0x43, 0x41, 0x14, 0x08, 0x69, 0xa9, 0x58, 0x48, 0x5f, 0xcb, 0xca, 0xf6, 0x3e, 0x34, + 0x8e, 0xb5, 0x3b, 0x96, 0xa8, 0xaf, 0xe4, 0xed, 0x4a, 0xa1, 0xe7, 0x34, 0x88, 0xd6, 0x9d, 0x92, + 0xde, 0x1d, 0xfb, 0x8f, 0x2d, 0x7e, 0xd9, 0x49, 0x75, 0x9f, 0xb7, 0x6d, 0x43, 0x53, 0x05, 0x97, + 0xd3, 0x2c, 0x74, 0x03, 0xc6, 0x68, 0xb0, 0x2b, 0xfd, 0xf0, 0xfc, 0x3c, 0xa6, 0x32, 0x67, 0xd4, + 0x80, 0x49, 0x43, 0x91, 0x99, 0x9e, 0x1e, 0x6f, 0x21, 0x16, 0xb9, 0xa3, 0x39, 0x38, 0x63, 0x12, + 0x11, 0x43, 0x94, 0xd9, 0xb2, 0xaa, 0xac, 0x92, 0xe5, 0xb3, 0xb3, 0xbc, 0x09, 0x35, 0x55, 0xaf, + 0xb9, 0x23, 0x48, 0x4a, 0x85, 0x67, 0x3b, 0x0f, 0x3a, 0x90, 0x46, 0xa7, 0x39, 0xaf, 0xe6, 0x11, + 0x64, 0x0b, 0xc8, 0xb9, 0x17, 0x65, 0xc9, 0x39, 0xf3, 0x16, 0x60, 0xec, 0x17, 0xb0, 0x2a, 0xe5, + 0x4d, 0xb3, 0x60, 0xcd, 0x45, 0xb4, 0xde, 0xa4, 0x8f, 0x4a, 0x79, 0x7d, 0x64, 0xff, 0x79, 0x19, + 0x96, 0xc4, 0x4a, 0xe7, 0xee, 0xe9, 0xf2, 0x75, 0x36, 0x60, 0xa4, 0x6b, 0xdc, 0xe3, 0x43, 0xe5, + 0x25, 0x76, 0xa1, 0xdc, 0x3e, 0x53, 0x2e, 0xda, 0x67, 0x08, 0x54, 0x26, 0x6e, 0x72, 0x81, 0x21, + 0x96, 0xba, 0x83, 0xbf, 0x65, 0x28, 0xb4, 0x6a, 0x86, 0x42, 0x8b, 0x6e, 0x25, 0x73, 0x13, 0x2a, + 0x7f, 0x2b, 0xf9, 0x06, 0xd4, 0xb1, 0x13, 0xda, 0xc9, 0x79, 0x0a, 0x60, 0xdc, 0xcb, 0x0b, 0xa8, + 0x21, 0xc4, 0xb5, 0x9a, 0x14, 0xf2, 0x05, 0x76, 0xb6, 0x6f, 0xc2, 0x22, 0xbf, 0xd7, 0x21, 0x72, + 0x82, 0x6f, 0xc8, 0xd3, 0x43, 0x4e, 0x27, 0xff, 0xf2, 0xe4, 0x22, 0x47, 0xd0, 0xea, 0xb7, 0x42, + 0x1b, 0xe6, 0xad, 0x50, 0x3d, 0x48, 0xdb, 0x34, 0x83, 0xb4, 0xf6, 0x63, 0x58, 0x36, 0xaa, 0x63, + 0x9a, 0x55, 0xe4, 0x14, 0x77, 0x16, 0xc8, 0x32, 0xd4, 0x9f, 0x1e, 0xf5, 0x1f, 0x1f, 0x3e, 0x7d, + 0x72, 0x70, 0xda, 0xb1, 0x58, 0xf1, 0xe4, 0xf9, 0xee, 0xee, 0xfe, 0xfe, 0x1e, 0x6a, 0x5a, 0x80, + 0xc5, 0xc7, 0x3b, 0x4f, 0x0f, 0x51, 0xcf, 0xee, 0x71, 0xde, 0x16, 0x75, 0xa9, 0x13, 0x9b, 0xaf, + 0x03, 0x91, 0x3e, 0x3e, 0xe6, 0x16, 0x4d, 0x7c, 0x9a, 0xc8, 0x74, 0xf7, 0x15, 0x81, 0x79, 0xaa, + 0x10, 0xf2, 0xb6, 0x46, 0x5a, 0x4b, 0x2a, 0x22, 0x62, 0x92, 0xb2, 0x22, 0x22, 0x48, 0x1d, 0x85, + 0xb7, 0x7b, 0xd0, 0xdd, 0xa3, 0xac, 0xb6, 0x1d, 0xdf, 0xcf, 0x74, 0x87, 0x39, 0x6a, 0x05, 0x38, + 0xe1, 0xc5, 0x7d, 0x1f, 0xae, 0xed, 0xf0, 0xcc, 0xf6, 0x2f, 0x2b, 0xf1, 0xd1, 0xee, 0xc2, 0x7a, + 0xb6, 0x4a, 0xd1, 0xd8, 0x63, 0x58, 0xd9, 0xa3, 0x67, 0xd3, 0xd1, 0x21, 0xbd, 0x4c, 0x1b, 0x22, + 0x50, 0x89, 0x2f, 0xc2, 0x2b, 0x31, 0x3f, 0xf8, 0x9b, 0xbc, 0x0b, 0xe0, 0x33, 0x9a, 0x7e, 0x3c, + 0xa1, 0x03, 0x79, 0x97, 0x10, 0x21, 0x27, 0x13, 0x3a, 0xb0, 0x3f, 0x06, 0xa2, 0xd7, 0x23, 0xe6, + 0x8b, 0xd9, 0x59, 0xd3, 0xb3, 0x7e, 0x3c, 0x8b, 0x13, 0x3a, 0x96, 0x97, 0x24, 0x75, 0x90, 0x7d, + 0x07, 0x9a, 0xc7, 0xee, 0xcc, 0xa1, 0x3f, 0x13, 0xf7, 0xd5, 0x37, 0x60, 0x69, 0xe2, 0xce, 0x18, + 0x0b, 0xaa, 0xa0, 0x2f, 0xa2, 0xed, 0xff, 0x5d, 0x82, 0x45, 0x4e, 0xc9, 0x6a, 0x1d, 0xd2, 0x38, + 0xf1, 0x02, 0x94, 0x34, 0x59, 0xab, 0x06, 0xca, 0xc9, 0x76, 0xa9, 0x40, 0xb6, 0x45, 0x44, 0x42, + 0xde, 0xcb, 0x12, 0x02, 0x6c, 0xc0, 0x98, 0xa4, 0xa5, 0x19, 0xcc, 0x3c, 0x34, 0x98, 0x02, 0x32, + 0xc7, 0x17, 0xa9, 0x35, 0xc7, 0xfb, 0x27, 0xd5, 0x96, 0x10, 0x63, 0x1d, 0x54, 0x68, 0x33, 0x2e, + 0x71, 0x69, 0xcf, 0xd9, 0x8c, 0x39, 0xdb, 0xb0, 0xf6, 0x16, 0xb6, 0x21, 0x0f, 0x53, 0xbc, 0xce, + 0x36, 0x84, 0xb7, 0xb0, 0x0d, 0x6d, 0x02, 0x1d, 0xbc, 0xf0, 0xcd, 0xbc, 0x0f, 0xc9, 0xbb, 0xff, + 0xd0, 0x82, 0x8e, 0xe0, 0x22, 0x85, 0x23, 0xef, 0x1b, 0x5e, 0x56, 0xe1, 0xfd, 0xa3, 0xdb, 0xb0, + 0x8c, 0xbe, 0x8f, 0x52, 0x01, 0xe2, 0x50, 0xc9, 0x00, 0xb2, 0x71, 0xc8, 0xfc, 0x97, 0xb1, 0xe7, + 0x8b, 0x45, 0xd1, 0x41, 0x52, 0x8b, 0x44, 0xae, 0xc8, 0xc4, 0xb5, 0x1c, 0x55, 0xb6, 0x7f, 0x6d, + 0xc1, 0x8a, 0xd6, 0x61, 0xc1, 0x85, 0x0f, 0x41, 0x4a, 0x03, 0x3f, 0x15, 0xe1, 0x92, 0xbb, 0x61, + 0x8a, 0x4d, 0xfa, 0x99, 0x41, 0x8c, 0x8b, 0xe9, 0xce, 0xb0, 0x83, 0xf1, 0x74, 0x2c, 0x76, 0x15, + 0x1d, 0xc4, 0x18, 0xe9, 0x8a, 0xd2, 0x97, 0x8a, 0x84, 0xef, 0x6b, 0x06, 0x0c, 0xe3, 0xc3, 0xcc, + 0x67, 0x53, 0x44, 0x15, 0x11, 0x1f, 0xd6, 0x81, 0xf6, 0x7f, 0xb1, 0x60, 0x95, 0x3b, 0xdf, 0x22, + 0xe0, 0xa1, 0xae, 0xb6, 0x2e, 0xf2, 0x18, 0x04, 0x97, 0xc8, 0x83, 0x05, 0x47, 0x94, 0xc9, 0xb7, + 0xde, 0x32, 0x60, 0xa0, 0xd2, 0x83, 0xe7, 0xac, 0x45, 0xb9, 0x68, 0x2d, 0x5e, 0x33, 0xd3, 0x45, + 0xa1, 0xfa, 0x6a, 0x61, 0xa8, 0xfe, 0xd1, 0x12, 0x54, 0xe3, 0x41, 0x38, 0xa1, 0xf6, 0x3a, 0xac, + 0x99, 0x83, 0x13, 0x2a, 0xe8, 0x97, 0x16, 0x74, 0x1f, 0xf3, 0xc3, 0x3c, 0x2f, 0x18, 0x1d, 0x78, + 0x71, 0x12, 0x46, 0xea, 0x05, 0x80, 0x9b, 0x00, 0x71, 0xe2, 0x46, 0xc2, 0x54, 0x15, 0x01, 0xf0, + 0x14, 0xc2, 0xfa, 0x48, 0x83, 0x21, 0xc7, 0xf2, 0xb5, 0x51, 0xe5, 0x9c, 0x51, 0x25, 0xc2, 0x03, + 0x86, 0x69, 0xf2, 0x01, 0x4f, 0x97, 0x67, 0xc6, 0x13, 0xbd, 0x44, 0xbd, 0xce, 0xfd, 0xee, 0x0c, + 0xd4, 0xfe, 0xcf, 0x16, 0xb4, 0xd3, 0x4e, 0x62, 0x5a, 0x87, 0xa9, 0x1d, 0x84, 0x3d, 0x92, 0x6a, + 0x07, 0x19, 0x9a, 0xf7, 0x98, 0x81, 0x22, 0xed, 0xf8, 0x14, 0x82, 0x12, 0x2b, 0x4a, 0xe1, 0x54, + 0x5a, 0x7c, 0x3a, 0x88, 0x27, 0xbf, 0x32, 0xd3, 0x48, 0x98, 0x79, 0xa2, 0x84, 0x17, 0x8f, 0xc6, + 0x09, 0x7e, 0xc5, 0x8f, 0x19, 0x64, 0x91, 0x74, 0xb8, 0x6d, 0xb1, 0xc4, 0x4f, 0x17, 0x99, 0x5d, + 0xa1, 0xef, 0xb9, 0x35, 0x3e, 0x3f, 0x6a, 0xcf, 0xfd, 0x3b, 0x16, 0x5c, 0x2f, 0x98, 0x78, 0x21, + 0x35, 0x7b, 0xb0, 0x72, 0xae, 0x90, 0x72, 0x72, 0xb8, 0xe8, 0xac, 0xcb, 0xbc, 0x04, 0x73, 0x42, + 0x9c, 0xfc, 0x07, 0xca, 0x50, 0xe4, 0xd3, 0x6d, 0xa4, 0x97, 0xe7, 0x11, 0xf6, 0x31, 0xf4, 0xf6, + 0x5f, 0x31, 0x21, 0xdc, 0xd5, 0x1f, 0xd0, 0x92, 0xbc, 0x70, 0x3f, 0xa7, 0x64, 0xde, 0x1c, 0xca, + 0x39, 0x87, 0x65, 0xa3, 0x2e, 0xf2, 0x8d, 0xb7, 0xad, 0x44, 0x97, 0x17, 0xb9, 0x56, 0xfc, 0x05, + 0x30, 0x99, 0xe4, 0xae, 0x81, 0xec, 0x4b, 0x68, 0x7f, 0x3a, 0xf5, 0x13, 0x2f, 0x7d, 0x0d, 0x8c, + 0x7c, 0x4b, 0x7c, 0x84, 0x55, 0xc8, 0xa9, 0x2b, 0x6c, 0x4a, 0xa7, 0x63, 0x33, 0x36, 0x66, 0x35, + 0xf5, 0xf3, 0x2d, 0xe6, 0x11, 0xf6, 0x75, 0xd8, 0x48, 0x9b, 0xe4, 0x73, 0x27, 0x15, 0xf5, 0xaf, + 0x2c, 0x9e, 0xad, 0x65, 0x3e, 0x4e, 0x46, 0x9e, 0xc0, 0x6a, 0xec, 0x05, 0x23, 0x9f, 0xea, 0xf5, + 0xc4, 0x62, 0x26, 0xae, 0x99, 0xdd, 0x13, 0x0f, 0x98, 0x39, 0x45, 0x5f, 0x30, 0x06, 0x29, 0xee, + 0x68, 0xca, 0x20, 0x99, 0x29, 0x29, 0x1a, 0xc0, 0x77, 0xa1, 0x65, 0x36, 0x46, 0x1e, 0x88, 0xfc, + 0xf4, 0xb4, 0x67, 0xfa, 0x79, 0x8b, 0xc9, 0x19, 0x06, 0xa5, 0xfd, 0x0b, 0x0b, 0xba, 0x0e, 0x65, + 0x6c, 0x4c, 0xb5, 0x46, 0x05, 0xf7, 0x3c, 0xcc, 0x55, 0x3b, 0x7f, 0xc0, 0x2a, 0xef, 0x5d, 0x8e, + 0x75, 0x6b, 0xee, 0xa2, 0x1c, 0x2c, 0x14, 0x8c, 0xea, 0x51, 0x0d, 0x16, 0xc5, 0xf8, 0x36, 0xe0, + 0x9a, 0xe8, 0x92, 0xec, 0x4e, 0x1a, 0xac, 0x37, 0x1a, 0x35, 0x82, 0xf5, 0x3d, 0xe8, 0xf2, 0xa7, + 0x19, 0xf4, 0x71, 0xf0, 0x0f, 0x37, 0x3f, 0x87, 0x86, 0xf6, 0x40, 0x05, 0xd9, 0x80, 0xd5, 0x17, + 0x4f, 0x4f, 0x8f, 0xf6, 0x4f, 0x4e, 0xfa, 0xc7, 0xcf, 0x1f, 0x7d, 0x6f, 0xff, 0x87, 0xfd, 0x83, + 0x9d, 0x93, 0x83, 0xce, 0x02, 0x59, 0x07, 0x72, 0xb4, 0x7f, 0x72, 0xba, 0xbf, 0x67, 0xc0, 0x2d, + 0x72, 0x13, 0x7a, 0xcf, 0x8f, 0x9e, 0x9f, 0xec, 0xef, 0xf5, 0x8b, 0xbe, 0x2b, 0x91, 0x77, 0xe1, + 0xba, 0xc0, 0x17, 0x7c, 0x5e, 0xde, 0xfc, 0x36, 0x74, 0xb2, 0xde, 0xbb, 0x11, 0xeb, 0xc8, 0x04, + 0x45, 0x96, 0xa1, 0xce, 0x83, 0x22, 0x18, 0x15, 0xb9, 0xff, 0x8b, 0x32, 0xb4, 0x78, 0xca, 0x19, + 0x7f, 0x11, 0x8f, 0x46, 0xe4, 0x53, 0x58, 0x12, 0x4f, 0x2b, 0x12, 0xb9, 0x1a, 0xe6, 0x63, 0x8e, + 0xbd, 0xf5, 0x2c, 0x58, 0x4c, 0xe1, 0xea, 0x5f, 0xff, 0xb3, 0xff, 0xf1, 0xf7, 0x4a, 0xcb, 0xa4, + 0xb1, 0x7d, 0xf9, 0xd1, 0xf6, 0x88, 0x06, 0x31, 0xab, 0xe3, 0x0f, 0x01, 0xd2, 0x07, 0x03, 0x49, + 0x57, 0xb9, 0xb0, 0x99, 0xd7, 0x14, 0x7b, 0xd7, 0x0b, 0x30, 0xa2, 0xde, 0xeb, 0x58, 0xef, 0xaa, + 0xdd, 0x62, 0xf5, 0x7a, 0x81, 0x97, 0xf0, 0xc7, 0x03, 0x3f, 0xb1, 0x36, 0xc9, 0x10, 0x9a, 0xfa, + 0x53, 0x7e, 0x44, 0x1e, 0x57, 0x14, 0x3c, 0x46, 0xd8, 0x7b, 0xa7, 0x10, 0x27, 0x97, 0x1f, 0xdb, + 0xb8, 0x66, 0x77, 0x58, 0x1b, 0x53, 0xa4, 0x48, 0x5b, 0xf1, 0xb9, 0x50, 0xa4, 0x2f, 0xf6, 0x91, + 0x1b, 0x1a, 0x9f, 0xe6, 0xde, 0x0b, 0xec, 0xbd, 0x3b, 0x07, 0x2b, 0xda, 0x7a, 0x17, 0xdb, 0xda, + 0xb0, 0x09, 0x6b, 0x6b, 0x80, 0x34, 0xf2, 0xbd, 0xc0, 0x4f, 0xac, 0xcd, 0xfb, 0x7f, 0xff, 0x03, + 0xa8, 0xab, 0x63, 0x4c, 0xf2, 0x53, 0x58, 0x36, 0x72, 0x02, 0x89, 0x1c, 0x46, 0x51, 0x0a, 0x61, + 0xef, 0x46, 0x31, 0x52, 0x34, 0x7c, 0x13, 0x1b, 0xee, 0x92, 0x75, 0xd6, 0xb0, 0x48, 0xaa, 0xdb, + 0xc6, 0xec, 0x56, 0x7e, 0x39, 0xee, 0xa5, 0x26, 0xfc, 0xbc, 0xb1, 0x1b, 0x59, 0x79, 0x34, 0x5a, + 0x7b, 0x77, 0x0e, 0x56, 0x34, 0x77, 0x03, 0x9b, 0x5b, 0x27, 0x6b, 0x7a, 0x73, 0xea, 0x78, 0x91, + 0xe2, 0x8d, 0x50, 0xfd, 0x31, 0x3b, 0xf2, 0xae, 0x62, 0xac, 0xa2, 0x47, 0xee, 0x14, 0x8b, 0xe4, + 0x5f, 0xba, 0xb3, 0xbb, 0xd8, 0x14, 0x21, 0xb8, 0x7c, 0xfa, 0x5b, 0x76, 0xe4, 0x0c, 0x1a, 0xda, + 0x63, 0x49, 0xe4, 0xfa, 0xdc, 0x87, 0x9d, 0x7a, 0xbd, 0x22, 0x54, 0xd1, 0x50, 0xf4, 0xfa, 0xb7, + 0xd9, 0xae, 0xfe, 0x63, 0xa8, 0xab, 0xe7, 0x77, 0xc8, 0x86, 0xf6, 0x1c, 0x92, 0xfe, 0x5c, 0x50, + 0xaf, 0x9b, 0x47, 0x14, 0x31, 0x9f, 0x5e, 0x3b, 0x63, 0xbe, 0x17, 0xd0, 0xd0, 0x9e, 0xd8, 0x51, + 0x03, 0xc8, 0x3f, 0xe3, 0xa3, 0x06, 0x50, 0xf0, 0x22, 0x8f, 0xbd, 0x82, 0x4d, 0x34, 0x48, 0x1d, + 0xf9, 0x3b, 0x79, 0x15, 0xc6, 0xe4, 0x10, 0xae, 0x09, 0x25, 0x77, 0x46, 0xbf, 0xc8, 0x32, 0x14, + 0xbc, 0x1f, 0x78, 0xcf, 0x22, 0x0f, 0xa1, 0x26, 0x5f, 0x52, 0x22, 0xeb, 0xc5, 0x2f, 0x42, 0xf5, + 0x36, 0x72, 0x70, 0x61, 0xdc, 0xfc, 0x10, 0x20, 0x7d, 0xcf, 0x47, 0x29, 0x89, 0xdc, 0xfb, 0x40, + 0x8a, 0x03, 0xf2, 0x8f, 0xff, 0xd8, 0xeb, 0x38, 0xc0, 0x0e, 0x41, 0x25, 0x11, 0xd0, 0x2b, 0x79, + 0xf9, 0xfb, 0x27, 0xd0, 0xd0, 0x9e, 0xf4, 0x51, 0xd3, 0x97, 0x7f, 0x0e, 0x48, 0x4d, 0x5f, 0xc1, + 0x0b, 0x40, 0x76, 0x0f, 0x6b, 0x5f, 0xb3, 0xdb, 0xac, 0xf6, 0xd8, 0x1b, 0x05, 0x63, 0x4e, 0xc0, + 0x16, 0xe8, 0x02, 0x96, 0x8d, 0x77, 0x7b, 0x94, 0x84, 0x16, 0xbd, 0x0a, 0xa4, 0x24, 0xb4, 0xf0, + 0xa9, 0x1f, 0xc9, 0x67, 0xf6, 0x0a, 0x6b, 0xe7, 0x12, 0x49, 0xb4, 0x96, 0x7e, 0x04, 0x0d, 0xed, + 0x0d, 0x1e, 0x35, 0x96, 0xfc, 0x73, 0x3f, 0x6a, 0x2c, 0x45, 0x4f, 0xf6, 0xac, 0x61, 0x1b, 0x2d, + 0x1b, 0x59, 0x01, 0xaf, 0x31, 0xb3, 0xba, 0x7f, 0x0a, 0x2d, 0xf3, 0x55, 0x1e, 0x25, 0xfb, 0x85, + 0xef, 0xfb, 0x28, 0xd9, 0x9f, 0xf3, 0x94, 0x8f, 0x60, 0xe9, 0xcd, 0x55, 0xd5, 0xc8, 0xf6, 0x67, + 0x22, 0x09, 0xea, 0x73, 0xf2, 0x7d, 0xa6, 0xe0, 0xc4, 0xbd, 0x72, 0xb2, 0xa1, 0x71, 0xad, 0x7e, + 0xfb, 0x5c, 0xc9, 0x4b, 0xee, 0x0a, 0xba, 0xc9, 0xcc, 0xfc, 0x22, 0x36, 0xee, 0x5a, 0x78, 0xbf, + 0x5c, 0xdb, 0xb5, 0xf4, 0x2b, 0xe8, 0xda, 0xae, 0x65, 0x5c, 0x43, 0xcf, 0xee, 0x5a, 0x89, 0xc7, + 0xea, 0x08, 0xa0, 0x9d, 0xb9, 0xb7, 0xa0, 0xa4, 0xa2, 0xf8, 0x6a, 0x59, 0xef, 0xe6, 0xeb, 0xaf, + 0x3b, 0x98, 0x1a, 0x44, 0x2a, 0xc1, 0x6d, 0x79, 0x91, 0xef, 0xaf, 0x40, 0x53, 0x7f, 0x8f, 0x84, + 0xe8, 0xa2, 0x9c, 0x6d, 0xe9, 0x9d, 0x42, 0x9c, 0xb9, 0xb8, 0xa4, 0xa9, 0x37, 0x43, 0x7e, 0x00, + 0xeb, 0x4a, 0xd4, 0xf5, 0x54, 0xf8, 0x98, 0xbc, 0x57, 0x90, 0x20, 0xaf, 0x9b, 0x3e, 0xbd, 0xeb, + 0x73, 0x33, 0xe8, 0xef, 0x59, 0x8c, 0x69, 0xcc, 0x87, 0x1e, 0xd2, 0x0d, 0xa3, 0xe8, 0x7d, 0x8b, + 0x74, 0xc3, 0x28, 0x7c, 0x1d, 0x42, 0x32, 0x0d, 0x59, 0x35, 0xe6, 0x88, 0x9f, 0x1f, 0x93, 0x1f, + 0x41, 0x5b, 0xbb, 0x6c, 0x74, 0x32, 0x0b, 0x06, 0x4a, 0x00, 0xf2, 0xf7, 0x60, 0x7b, 0x45, 0x86, + 0xbd, 0xbd, 0x81, 0xf5, 0xaf, 0xd8, 0xc6, 0xe4, 0x30, 0xe6, 0xdf, 0x85, 0x86, 0x7e, 0x91, 0xe9, + 0x35, 0xf5, 0x6e, 0x68, 0x28, 0xfd, 0x1a, 0xe7, 0x3d, 0x8b, 0xfc, 0x23, 0x0b, 0x9a, 0xc6, 0xb5, + 0x20, 0x23, 0x77, 0x22, 0x53, 0x4f, 0x57, 0xc7, 0xe9, 0x15, 0xd9, 0x0e, 0x76, 0xf2, 0x70, 0xf3, + 0xbb, 0xc6, 0x24, 0x7c, 0x66, 0x44, 0x6f, 0xb6, 0xb2, 0xaf, 0x37, 0x7e, 0x9e, 0x25, 0xd0, 0xef, + 0x0a, 0x7f, 0x7e, 0xcf, 0x22, 0x7f, 0x62, 0x41, 0xcb, 0x8c, 0x39, 0xaa, 0xa5, 0x2a, 0x8c, 0x6e, + 0xaa, 0xa5, 0x9a, 0x13, 0xa8, 0xfc, 0x11, 0xf6, 0xf2, 0x74, 0xd3, 0x31, 0x7a, 0x29, 0x9e, 0x00, + 0xf9, 0xdd, 0x7a, 0x4b, 0x3e, 0xe1, 0x8f, 0xc6, 0xca, 0x93, 0x01, 0x92, 0x7f, 0xbe, 0x54, 0x2d, + 0xaf, 0xfe, 0xe0, 0xe8, 0x5d, 0xeb, 0x9e, 0x45, 0x7e, 0xc2, 0x5f, 0x2d, 0x94, 0xc1, 0x6b, 0xc6, + 0x25, 0x6f, 0xfb, 0xbd, 0x7d, 0x1b, 0xc7, 0x74, 0xd3, 0xbe, 0x6e, 0x8c, 0x29, 0xbb, 0x1f, 0xef, + 0xf0, 0xde, 0x89, 0xb7, 0x42, 0xd3, 0x0d, 0x25, 0xf7, 0x7e, 0xe8, 0xfc, 0x4e, 0x8e, 0x79, 0x27, + 0x05, 0xb9, 0xc1, 0xca, 0x6f, 0x59, 0x8d, 0xbd, 0x89, 0x7d, 0xbd, 0x6d, 0xbf, 0x37, 0xb7, 0xaf, + 0xdb, 0x18, 0x39, 0x64, 0x3d, 0x3e, 0x06, 0x48, 0x4f, 0xf1, 0x48, 0xe6, 0x14, 0x49, 0x09, 0x78, + 0xfe, 0xa0, 0xcf, 0x94, 0x17, 0x79, 0xd8, 0xc4, 0x6a, 0xfc, 0x31, 0x57, 0x57, 0x4f, 0xe5, 0xf9, + 0x93, 0x6e, 0x94, 0x98, 0xc7, 0x6d, 0x86, 0x51, 0x92, 0xad, 0xdf, 0x50, 0x56, 0xea, 0x30, 0xeb, + 0x39, 0x2c, 0x1f, 0x86, 0xe1, 0xcb, 0xe9, 0x44, 0xa5, 0x38, 0x98, 0x41, 0xfd, 0x03, 0x37, 0xbe, + 0xe8, 0x65, 0x46, 0x61, 0xdf, 0xc2, 0xaa, 0x7a, 0xa4, 0xab, 0x55, 0xb5, 0xfd, 0x59, 0x7a, 0x4a, + 0xf8, 0x39, 0x71, 0x61, 0x45, 0xe9, 0x40, 0xd5, 0xf1, 0x9e, 0x59, 0x8d, 0xa1, 0xf9, 0xb2, 0x4d, + 0x18, 0xd6, 0xb3, 0xec, 0xed, 0x76, 0x2c, 0xeb, 0xbc, 0x67, 0x91, 0x63, 0x68, 0xee, 0xd1, 0x01, + 0x5e, 0x4a, 0xc0, 0xc8, 0xf8, 0x6a, 0xda, 0x71, 0x15, 0x52, 0xef, 0x2d, 0x1b, 0x40, 0x73, 0x5f, + 0x98, 0xb8, 0xb3, 0x88, 0xfe, 0x6c, 0xfb, 0x33, 0x11, 0x73, 0xff, 0x5c, 0xee, 0x0b, 0xf2, 0x50, + 0xc2, 0xd8, 0x17, 0x32, 0xa7, 0x18, 0xc6, 0xbe, 0x90, 0x3b, 0xc5, 0x30, 0xa6, 0x5a, 0x1e, 0x8a, + 0x10, 0x1f, 0x56, 0x72, 0x07, 0x1f, 0x6a, 0x4b, 0x98, 0x77, 0x5c, 0xd2, 0xbb, 0x35, 0x9f, 0xc0, + 0x6c, 0x6d, 0xd3, 0x6c, 0xed, 0x04, 0x96, 0xf7, 0x28, 0x9f, 0x2c, 0x9e, 0xc3, 0x99, 0xb9, 0x5b, + 0xa6, 0x67, 0x88, 0x66, 0x15, 0x38, 0xe2, 0xcc, 0x8d, 0x1f, 0x13, 0x28, 0xc9, 0x8f, 0xa1, 0xf1, + 0x84, 0x26, 0x32, 0x69, 0x53, 0x99, 0x9e, 0x99, 0x2c, 0xce, 0x5e, 0x41, 0xce, 0xa7, 0xc9, 0x33, + 0x58, 0xdb, 0x36, 0x1d, 0x8e, 0x28, 0x57, 0x4e, 0x7d, 0x6f, 0xf8, 0x39, 0xf9, 0xcb, 0x58, 0xb9, + 0xca, 0x58, 0x5f, 0xd7, 0xb2, 0xf0, 0xf4, 0xca, 0xdb, 0x19, 0x78, 0x51, 0xcd, 0x41, 0x38, 0xa4, + 0x9a, 0x09, 0x14, 0x40, 0x43, 0xbb, 0x32, 0xa2, 0x04, 0x28, 0x7f, 0x7f, 0x46, 0x09, 0x50, 0xc1, + 0x0d, 0x13, 0xfb, 0x2e, 0xb6, 0x63, 0x93, 0x5b, 0x69, 0x3b, 0xfc, 0x56, 0x49, 0xda, 0xd2, 0xf6, + 0x67, 0xee, 0x38, 0xf9, 0x9c, 0xbc, 0xc0, 0x27, 0x79, 0xf4, 0xc4, 0xd4, 0xd4, 0x96, 0xce, 0xe6, + 0xb0, 0xaa, 0xc9, 0xd2, 0x50, 0xa6, 0x7d, 0xcd, 0x9b, 0x42, 0x4b, 0xe9, 0x5b, 0x00, 0x27, 0x49, + 0x38, 0xd9, 0x73, 0xe9, 0x38, 0x0c, 0x52, 0x5d, 0x9b, 0xa6, 0x45, 0xa6, 0xfa, 0x4b, 0xcb, 0x8d, + 0x24, 0x2f, 0x34, 0xe7, 0xc3, 0xc8, 0xeb, 0x95, 0xcc, 0x35, 0x37, 0x73, 0x52, 0x4d, 0x48, 0x41, + 0xf6, 0xe4, 0x3d, 0x8b, 0xec, 0x00, 0xa4, 0x27, 0x5f, 0xca, 0x95, 0xc8, 0x1d, 0xaa, 0x29, 0xb5, + 0x57, 0x70, 0x4c, 0x76, 0x0c, 0xf5, 0xf4, 0x28, 0x65, 0x23, 0xbd, 0xf4, 0x65, 0x1c, 0xbc, 0xa8, + 0x1d, 0x3c, 0x77, 0xc0, 0x61, 0x77, 0x70, 0xaa, 0x80, 0xd4, 0xd8, 0x54, 0xe1, 0xa9, 0x85, 0x07, + 0xab, 0xbc, 0x83, 0xca, 0x1c, 0xc1, 0x94, 0x3e, 0x39, 0x92, 0x82, 0x43, 0x06, 0x25, 0xcd, 0x85, + 0x31, 0x7a, 0x23, 0x22, 0xc2, 0xb8, 0x95, 0xa7, 0x13, 0x32, 0xd5, 0x3c, 0x86, 0x95, 0x5c, 0x10, + 0x59, 0x89, 0xf4, 0xbc, 0xb8, 0xbe, 0x12, 0xe9, 0xb9, 0xf1, 0x67, 0xfb, 0x1a, 0x36, 0xd9, 0xb6, + 0x01, 0x3d, 0xa0, 0x2b, 0x2f, 0x19, 0x5c, 0xb0, 0xe6, 0x7e, 0x65, 0xc1, 0x6a, 0x41, 0x8c, 0x98, + 0xbc, 0x2f, 0x9d, 0xe9, 0xb9, 0xf1, 0xe3, 0x5e, 0x61, 0x08, 0xd1, 0x3e, 0xc1, 0x76, 0x3e, 0x25, + 0xdf, 0x33, 0x36, 0x36, 0x1e, 0xbd, 0x13, 0x92, 0xf9, 0x5a, 0xa3, 0xa2, 0xd0, 0xa2, 0xf8, 0x19, + 0x6c, 0xf0, 0x8e, 0xec, 0xf8, 0x7e, 0x26, 0xbc, 0x79, 0x33, 0xf7, 0x7f, 0x23, 0x8c, 0xb0, 0x6d, + 0x6f, 0xfe, 0xff, 0x95, 0x98, 0x63, 0xae, 0xf2, 0xae, 0x92, 0x29, 0x74, 0xb2, 0x21, 0x43, 0x32, + 0xbf, 0xae, 0xde, 0x7b, 0x86, 0x5b, 0x98, 0x0f, 0x33, 0xda, 0xbf, 0x87, 0x8d, 0xbd, 0x67, 0xf7, + 0x8a, 0xe6, 0x85, 0x7b, 0x8a, 0x6c, 0x3d, 0xfe, 0x9a, 0x8a, 0x6f, 0x66, 0xc6, 0x29, 0x1b, 0x98, + 0x17, 0x90, 0x55, 0x8e, 0x69, 0x71, 0x78, 0xf4, 0x03, 0x6c, 0xfe, 0x96, 0xfd, 0x4e, 0x51, 0xf3, + 0x11, 0xff, 0x84, 0xbb, 0xa8, 0x1b, 0x59, 0xb9, 0x96, 0x3d, 0xb8, 0x55, 0xb4, 0xde, 0x73, 0x7d, + 0x8d, 0xcc, 0x5c, 0x2f, 0xdc, 0xb3, 0x1e, 0xdd, 0xf9, 0xd1, 0xef, 0x8d, 0xbc, 0xe4, 0x62, 0x7a, + 0xb6, 0x35, 0x08, 0xc7, 0xdb, 0xbe, 0x0c, 0x91, 0x89, 0x04, 0xf4, 0x6d, 0x3f, 0x18, 0x6e, 0xe3, + 0xf7, 0x67, 0x8b, 0xf8, 0x6f, 0x68, 0xbe, 0xf1, 0x7f, 0x03, 0x00, 0x00, 0xff, 0xff, 0x98, 0x77, + 0x63, 0x94, 0xb8, 0x66, 0x00, 0x00, } // Reference imports to suppress errors if they are not otherwise used. diff --git a/lnrpc/rpc.proto b/lnrpc/rpc.proto index 3c361627f..3264eed7a 100644 --- a/lnrpc/rpc.proto +++ b/lnrpc/rpc.proto @@ -2117,6 +2117,42 @@ message Invoice { The state the invoice is in. */ InvoiceState state = 21 [json_name = "state"]; + + /// List of HTLCs paying to this invoice [EXPERIMENTAL]. + repeated InvoiceHTLC htlcs = 22 [json_name = "htlcs"]; +} + +enum InvoiceHTLCState { + ACCEPTED = 0; + SETTLED = 1; + CANCELLED = 2; +} + +/// Details of an HTLC that paid to an invoice +message InvoiceHTLC { + /// Short channel id over which the htlc was received. + uint64 chan_id = 1 [json_name = "chan_id"]; + + /// Index identifying the htlc on the channel. + uint64 htlc_index = 2 [json_name = "htlc_index"]; + + /// The amount of the htlc in msat. + uint64 amt_msat = 3 [json_name = "amt_msat"]; + + /// Block height at which this htlc was accepted. + int32 accept_height = 4 [json_name = "accept_height"]; + + /// Time at which this htlc was accepted. + int64 accept_time = 5 [json_name = "accept_time"]; + + /// Time at which this htlc was settled or cancelled. + int64 resolve_time = 6 [json_name = "resolve_time"]; + + /// Block height at which this htlc expires. + int32 expiry_height = 7 [json_name = "expiry_height"]; + + /// Current state the htlc is in. + InvoiceHTLCState state = 8 [json_name = "state"]; } message AddInvoiceResponse { diff --git a/lnrpc/rpc.swagger.json b/lnrpc/rpc.swagger.json index bb7262350..f78ae7960 100644 --- a/lnrpc/rpc.swagger.json +++ b/lnrpc/rpc.swagger.json @@ -2489,9 +2489,70 @@ "state": { "$ref": "#/definitions/InvoiceInvoiceState", "description": "*\nThe state the invoice is in." + }, + "htlcs": { + "type": "array", + "items": { + "$ref": "#/definitions/lnrpcInvoiceHTLC" + }, + "description": "/ List of HTLCs paying to this invoice [EXPERIMENTAL]." } } }, + "lnrpcInvoiceHTLC": { + "type": "object", + "properties": { + "chan_id": { + "type": "string", + "format": "uint64", + "description": "/ Short channel id over which the htlc was received." + }, + "htlc_index": { + "type": "string", + "format": "uint64", + "description": "/ Index identifying the htlc on the channel." + }, + "amt_msat": { + "type": "string", + "format": "uint64", + "description": "/ The amount of the htlc in msat." + }, + "accept_height": { + "type": "integer", + "format": "int32", + "description": "/ Block height at which this htlc was accepted." + }, + "accept_time": { + "type": "string", + "format": "int64", + "description": "/ Time at which this htlc was accepted." + }, + "resolve_time": { + "type": "string", + "format": "int64", + "description": "/ Time at which this htlc was settled or cancelled." + }, + "expiry_height": { + "type": "integer", + "format": "int32", + "description": "/ Block height at which this htlc expires." + }, + "state": { + "$ref": "#/definitions/lnrpcInvoiceHTLCState", + "description": "/ Current state the htlc is in." + } + }, + "title": "/ Details of an HTLC that paid to an invoice" + }, + "lnrpcInvoiceHTLCState": { + "type": "string", + "enum": [ + "ACCEPTED", + "SETTLED", + "CANCELLED" + ], + "default": "ACCEPTED" + }, "lnrpcLightningAddress": { "type": "object", "properties": {