From dbf7e4525a61156b13770351f24d9c9427103ae7 Mon Sep 17 00:00:00 2001 From: Olaoluwa Osuntokun Date: Tue, 24 Apr 2018 20:34:30 -0700 Subject: [PATCH 01/26] lntest: ensure we always cancel active streaming notifications --- channeldb/db.go | 6 ++++++ channeldb/payments.go | 2 +- lntest/node.go | 6 ++++-- 3 files changed, 11 insertions(+), 3 deletions(-) diff --git a/channeldb/db.go b/channeldb/db.go index 076b1c2c..911e40db 100644 --- a/channeldb/db.go +++ b/channeldb/db.go @@ -47,6 +47,12 @@ var ( number: 1, migration: migrateNodeAndEdgeUpdateIndex, }, + { + // The DB version that added the invoice event time + // series. + number: 2, + migration: migrateInvoiceTimeSeries, + }, } // Big endian is the preferred byte order, due to cursor scans over diff --git a/channeldb/payments.go b/channeldb/payments.go index 2a0cd914..0e5f47b9 100644 --- a/channeldb/payments.go +++ b/channeldb/payments.go @@ -177,7 +177,7 @@ func deserializeOutgoingPayment(r io.Reader) (*OutgoingPayment, error) { if err != nil { return nil, err } - p.Invoice = *inv + p.Invoice = inv if _, err := r.Read(scratch[:]); err != nil { return nil, err diff --git a/lntest/node.go b/lntest/node.go index d8ccfa4a..809092eb 100644 --- a/lntest/node.go +++ b/lntest/node.go @@ -639,9 +639,9 @@ func (hn *HarnessNode) lightningNetworkWatcher() { go func() { defer hn.wg.Done() - ctxb := context.Background() req := &lnrpc.GraphTopologySubscription{} - topologyClient, err := hn.SubscribeChannelGraph(ctxb, req) + ctx, cancelFunc := context.WithCancel(context.Background()) + topologyClient, err := hn.SubscribeChannelGraph(ctx, req) if err != nil { // We panic here in case of an error as failure to // create the topology client will cause all subsequent @@ -650,6 +650,8 @@ func (hn *HarnessNode) lightningNetworkWatcher() { "client: %v", err)) } + defer cancelFunc() + for { update, err := topologyClient.Recv() if err == io.EOF { From 29a27bbc3ab634177b6e58ae6fcedc087a78e9c4 Mon Sep 17 00:00:00 2001 From: Olaoluwa Osuntokun Date: Tue, 24 Apr 2018 20:34:56 -0700 Subject: [PATCH 02/26] test: ensure we always cancel active streaming notifications --- lnd_test.go | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/lnd_test.go b/lnd_test.go index 240d99ce..47c07aa8 100644 --- a/lnd_test.go +++ b/lnd_test.go @@ -4302,10 +4302,12 @@ func testInvoiceSubscriptions(net *lntest.NetworkHarness, t *harnessTest) { // Create a new invoice subscription client for Bob, the notification // should be dispatched shortly below. req := &lnrpc.InvoiceSubscription{} - bobInvoiceSubscription, err := net.Bob.SubscribeInvoices(ctxb, req) + ctx, cancelFunc := context.WithCancel(context.Background()) + bobInvoiceSubscription, err := net.Bob.SubscribeInvoices(ctx, req) if err != nil { t.Fatalf("unable to subscribe to bob's invoice updates: %v", err) } + defer cancelFunc() quit := make(chan struct{}) updateSent := make(chan struct{}) @@ -5986,7 +5988,8 @@ func subscribeGraphNotifications(t *harnessTest, ctxb context.Context, // We'll first start by establishing a notification client which will // send us notifications upon detected changes in the channel graph. req := &lnrpc.GraphTopologySubscription{} - topologyClient, err := node.SubscribeChannelGraph(ctxb, req) + ctx, cancelFunc := context.WithCancel(context.Background()) + topologyClient, err := node.SubscribeChannelGraph(ctx, req) if err != nil { t.Fatalf("unable to create topology client: %v", err) } @@ -5997,6 +6000,8 @@ func subscribeGraphNotifications(t *harnessTest, ctxb context.Context, graphUpdates := make(chan *lnrpc.GraphTopologyUpdate, 20) go func() { for { + defer cancelFunc() + select { case <-quit: return From edbdcddea1110d0b149c983ab481f1ae7f1e71a5 Mon Sep 17 00:00:00 2001 From: Olaoluwa Osuntokun Date: Tue, 24 Apr 2018 20:41:22 -0700 Subject: [PATCH 03/26] lnrpc: extend invoice subscriptions to allow callers to receive backlog notifications In this commit, we extend the current SubscribeInvoice streaming RPC call. We add two new values to the InvoiceSubscription message: add_index and settle_index. These fields have also been added to the current Invoice message. Each time a new invoice is added, the add index will be incremented. Each time a new invoice is settled the settle index will be incremented. The new field on the InvoiceSubscription message allow callers to specify the last add index and the last settle index they know of. With this new addition, callers will now be able to reliably receive notifications for new received payments. Care has been taken to ensure that these changes are backwards compatible. If callers don't specify either of the new fields, then they won't receive any notification backlog at all. Fixes #862. --- lnrpc/rpc.pb.go | 882 +++++++++++++++++++++++------------------ lnrpc/rpc.pb.gw.go | 8 + lnrpc/rpc.proto | 50 ++- lnrpc/rpc.swagger.json | 35 +- 4 files changed, 579 insertions(+), 396 deletions(-) diff --git a/lnrpc/rpc.pb.go b/lnrpc/rpc.pb.go index 04b097b5..6f1394ce 100644 --- a/lnrpc/rpc.pb.go +++ b/lnrpc/rpc.pb.go @@ -518,7 +518,9 @@ func (m *FeeLimit) String() string { return proto.CompactTextString(m func (*FeeLimit) ProtoMessage() {} func (*FeeLimit) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{11} } -type isFeeLimit_Limit interface{ isFeeLimit_Limit() } +type isFeeLimit_Limit interface { + isFeeLimit_Limit() +} type FeeLimit_Fixed struct { Fixed int64 `protobuf:"varint,1,opt,name=fixed,oneof"` @@ -785,7 +787,9 @@ func (m *ChannelPoint) String() string { return proto.CompactTextStri func (*ChannelPoint) ProtoMessage() {} func (*ChannelPoint) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{15} } -type isChannelPoint_FundingTxid interface{ isChannelPoint_FundingTxid() } +type isChannelPoint_FundingTxid interface { + isChannelPoint_FundingTxid() +} type ChannelPoint_FundingTxidBytes struct { FundingTxidBytes []byte `protobuf:"bytes,1,opt,name=funding_txid_bytes,proto3,oneof"` @@ -2034,7 +2038,9 @@ func (m *CloseStatusUpdate) String() string { return proto.CompactTex func (*CloseStatusUpdate) ProtoMessage() {} func (*CloseStatusUpdate) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{48} } -type isCloseStatusUpdate_Update interface{ isCloseStatusUpdate_Update() } +type isCloseStatusUpdate_Update interface { + isCloseStatusUpdate_Update() +} type CloseStatusUpdate_ClosePending struct { ClosePending *PendingUpdate `protobuf:"bytes,1,opt,name=close_pending,oneof"` @@ -2297,7 +2303,9 @@ func (m *OpenStatusUpdate) String() string { return proto.CompactText func (*OpenStatusUpdate) ProtoMessage() {} func (*OpenStatusUpdate) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{51} } -type isOpenStatusUpdate_Update interface{ isOpenStatusUpdate_Update() } +type isOpenStatusUpdate_Update interface { + isOpenStatusUpdate_Update() +} type OpenStatusUpdate_ChanPending struct { ChanPending *PendingUpdate `protobuf:"bytes,1,opt,name=chan_pending,oneof"` @@ -3834,6 +3842,26 @@ type Invoice struct { RouteHints []*RouteHint `protobuf:"bytes,14,rep,name=route_hints" json:"route_hints,omitempty"` // / Whether this invoice should include routing hints for private channels. Private bool `protobuf:"varint,15,opt,name=private" json:"private,omitempty"` + // * + // The "add" index of this invoice. Each newly created invoice will increment + // this index making it monotonically increasing. Callers to the + // SubscribeInvoices call can use this to instantly get notified of all added + // invoices with an add_index greater than this one. + AddIndex uint64 `protobuf:"varint,16,opt,name=add_index" json:"add_index,omitempty"` + // * + // The "settle" index of this invoice. Each newly settled invoice will + // increment this index making it monotonically increasing. Callers to the + // SubscribeInvoices call can use this to instantly get notified of all + // settled invoices with an settle_index greater than this one. + SettleIndex uint64 `protobuf:"varint,17,opt,name=settle_index" json:"settle_index,omitempty"` + // * + // The amount that was accepted for this invoice. This will ONLY be set if + // this invoice has been settled. We provide this field as if the invoice was + // created with a zero value, then we need to record what amount was + // ultimately accepted. Additionally, it's possible that the sender paid MORE + // that was specified in the original invoice. So we'll record that here as + // well. + AmtPaid int64 `protobuf:"varint,18,opt,name=amt_paid" json:"amt_paid,omitempty"` } func (m *Invoice) Reset() { *m = Invoice{} } @@ -3946,6 +3974,27 @@ func (m *Invoice) GetPrivate() bool { return false } +func (m *Invoice) GetAddIndex() uint64 { + if m != nil { + return m.AddIndex + } + return 0 +} + +func (m *Invoice) GetSettleIndex() uint64 { + if m != nil { + return m.SettleIndex + } + return 0 +} + +func (m *Invoice) GetAmtPaid() int64 { + if m != nil { + return m.AmtPaid + } + return 0 +} + type AddInvoiceResponse struct { RHash []byte `protobuf:"bytes,1,opt,name=r_hash,proto3" json:"r_hash,omitempty"` // * @@ -4036,6 +4085,18 @@ func (m *ListInvoiceResponse) GetInvoices() []*Invoice { } type InvoiceSubscription struct { + // * + // If specified (non-zero), then we'll first start by sending out + // notifications for all added indexes with an add_index greater than this + // value. This allows callers to catch up on any events they missed while they + // weren't connected to the streaming RPC. + AddIndex uint64 `protobuf:"varint,1,opt,name=add_index" json:"add_index,omitempty"` + // * + // If specified (non-zero), then we'll first start by sending out + // notifications for all settled indexes with an settle_index greater than + // this value. This allows callers to catch up on any events they missed while + // they weren't connected to the streaming RPC. + SettleIndex uint64 `protobuf:"varint,2,opt,name=settle_index" json:"settle_index,omitempty"` } func (m *InvoiceSubscription) Reset() { *m = InvoiceSubscription{} } @@ -4043,6 +4104,20 @@ func (m *InvoiceSubscription) String() string { return proto.CompactT func (*InvoiceSubscription) ProtoMessage() {} func (*InvoiceSubscription) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{88} } +func (m *InvoiceSubscription) GetAddIndex() uint64 { + if m != nil { + return m.AddIndex + } + return 0 +} + +func (m *InvoiceSubscription) GetSettleIndex() uint64 { + if m != nil { + return m.SettleIndex + } + return 0 +} + type Payment struct { // / The payment hash PaymentHash string `protobuf:"bytes,1,opt,name=payment_hash" json:"payment_hash,omitempty"` @@ -4405,7 +4480,9 @@ func (m *PolicyUpdateRequest) String() string { return proto.CompactT func (*PolicyUpdateRequest) ProtoMessage() {} func (*PolicyUpdateRequest) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{101} } -type isPolicyUpdateRequest_Scope interface{ isPolicyUpdateRequest_Scope() } +type isPolicyUpdateRequest_Scope interface { + isPolicyUpdateRequest_Scope() +} type PolicyUpdateRequest_Global struct { Global bool `protobuf:"varint,1,opt,name=global,oneof"` @@ -5158,7 +5235,14 @@ type LightningClient interface { LookupInvoice(ctx context.Context, in *PaymentHash, opts ...grpc.CallOption) (*Invoice, error) // * // SubscribeInvoices returns a uni-directional stream (sever -> client) for - // notifying the client of newly added/settled invoices. + // notifying the client of newly added/settled invoices. The caller can + // optionally specify the add_index and/or the settle_index. If the add_index + // is specified, then we'll first start by sending add invoice events for all + // invoices with an add_index greater than the specified value. If the + // settle_index is specified, the next, we'll send out all settle events for + // invoices with a settle_index greater than the specified value. One or both + // of these fields can be set. If no fields are set, then we'll only send out + // the latest add/settle events. SubscribeInvoices(ctx context.Context, in *InvoiceSubscription, opts ...grpc.CallOption) (Lightning_SubscribeInvoicesClient, error) // * lncli: `decodepayreq` // DecodePayReq takes an encoded payment request string and attempts to decode @@ -5928,7 +6012,14 @@ type LightningServer interface { LookupInvoice(context.Context, *PaymentHash) (*Invoice, error) // * // SubscribeInvoices returns a uni-directional stream (sever -> client) for - // notifying the client of newly added/settled invoices. + // notifying the client of newly added/settled invoices. The caller can + // optionally specify the add_index and/or the settle_index. If the add_index + // is specified, then we'll first start by sending add invoice events for all + // invoices with an add_index greater than the specified value. If the + // settle_index is specified, the next, we'll send out all settle events for + // invoices with a settle_index greater than the specified value. One or both + // of these fields can be set. If no fields are set, then we'll only send out + // the latest add/settle events. SubscribeInvoices(*InvoiceSubscription, Lightning_SubscribeInvoicesServer) error // * lncli: `decodepayreq` // DecodePayReq takes an encoded payment request string and attempts to decode @@ -6991,391 +7082,394 @@ var _Lightning_serviceDesc = grpc.ServiceDesc{ func init() { proto.RegisterFile("rpc.proto", fileDescriptor0) } var fileDescriptor0 = []byte{ - // 6171 bytes of a gzipped FileDescriptorProto - 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xbc, 0x5c, 0x4f, 0x8c, 0x1c, 0xd9, - 0x59, 0x77, 0x75, 0xf7, 0xfc, 0xe9, 0xaf, 0x7b, 0x7a, 0x7a, 0xde, 0x8c, 0xc7, 0xed, 0xde, 0x5d, - 0xaf, 0xb7, 0x62, 0xad, 0x9d, 0x61, 0xb1, 0xbd, 0x93, 0x64, 0xd9, 0xec, 0x42, 0x82, 0x3d, 0x1e, - 0x7b, 0x9c, 0xcc, 0xda, 0x93, 0x1a, 0x3b, 0x86, 0x04, 0xd4, 0xa9, 0xe9, 0x7e, 0xd3, 0x53, 0x71, - 0x77, 0x55, 0xa7, 0xaa, 0x7a, 0xc6, 0x9d, 0xc5, 0x52, 0xf8, 0x23, 0x4e, 0x44, 0x08, 0xc1, 0x25, - 0x48, 0x08, 0x29, 0x48, 0x28, 0x1c, 0x38, 0xc2, 0x25, 0x20, 0x71, 0xe0, 0x84, 0x84, 0x38, 0xe4, - 0x14, 0x71, 0x04, 0x0e, 0x10, 0x71, 0x41, 0xe2, 0x8a, 0xd0, 0xf7, 0xbd, 0x3f, 0xf5, 0x5e, 0x55, - 0xb5, 0xed, 0xfc, 0x81, 0x5b, 0xbf, 0xdf, 0xfb, 0xea, 0xfd, 0xfd, 0xfe, 0xbd, 0xef, 0x7d, 0xaf, - 0xa1, 0x1e, 0x4f, 0xfa, 0xd7, 0x27, 0x71, 0x94, 0x46, 0x6c, 0x61, 0x14, 0xc6, 0x93, 0x7e, 0xf7, - 0xf5, 0x61, 0x14, 0x0d, 0x47, 0xfc, 0x86, 0x3f, 0x09, 0x6e, 0xf8, 0x61, 0x18, 0xa5, 0x7e, 0x1a, - 0x44, 0x61, 0x22, 0x88, 0xdc, 0xaf, 0x41, 0xeb, 0x1e, 0x0f, 0x0f, 0x39, 0x1f, 0x78, 0xfc, 0x1b, - 0x53, 0x9e, 0xa4, 0xec, 0xe7, 0x60, 0xcd, 0xe7, 0xdf, 0xe4, 0x7c, 0xd0, 0x9b, 0xf8, 0x49, 0x32, - 0x39, 0x89, 0xfd, 0x84, 0x77, 0x9c, 0xcb, 0xce, 0xb5, 0xa6, 0xd7, 0x16, 0x15, 0x07, 0x1a, 0x67, - 0x6f, 0x41, 0x33, 0x41, 0x52, 0x1e, 0xa6, 0x71, 0x34, 0x99, 0x75, 0x2a, 0x44, 0xd7, 0x40, 0x6c, - 0x57, 0x40, 0xee, 0x08, 0x56, 0x75, 0x0f, 0xc9, 0x24, 0x0a, 0x13, 0xce, 0x6e, 0xc2, 0x46, 0x3f, - 0x98, 0x9c, 0xf0, 0xb8, 0x47, 0x1f, 0x8f, 0x43, 0x3e, 0x8e, 0xc2, 0xa0, 0xdf, 0x71, 0x2e, 0x57, - 0xaf, 0xd5, 0x3d, 0x26, 0xea, 0xf0, 0x8b, 0x8f, 0x64, 0x0d, 0xbb, 0x0a, 0xab, 0x3c, 0x14, 0x38, - 0x1f, 0xd0, 0x57, 0xb2, 0xab, 0x56, 0x06, 0xe3, 0x07, 0xee, 0xdf, 0x3b, 0xb0, 0x76, 0x3f, 0x0c, - 0xd2, 0x27, 0xfe, 0x68, 0xc4, 0x53, 0x35, 0xa7, 0xab, 0xb0, 0x7a, 0x46, 0x00, 0xcd, 0xe9, 0x2c, - 0x8a, 0x07, 0x72, 0x46, 0x2d, 0x01, 0x1f, 0x48, 0x74, 0xee, 0xc8, 0x2a, 0x73, 0x47, 0x56, 0xba, - 0x5c, 0xd5, 0x39, 0xcb, 0x75, 0x15, 0x56, 0x63, 0xde, 0x8f, 0x4e, 0x79, 0x3c, 0xeb, 0x9d, 0x05, - 0xe1, 0x20, 0x3a, 0xeb, 0xd4, 0x2e, 0x3b, 0xd7, 0x16, 0xbc, 0x96, 0x82, 0x9f, 0x10, 0xea, 0x6e, - 0x00, 0x33, 0x67, 0x21, 0xd6, 0xcd, 0x1d, 0xc2, 0xfa, 0xe3, 0x70, 0x14, 0xf5, 0x9f, 0xfe, 0x84, - 0xb3, 0x2b, 0xe9, 0xbe, 0x52, 0xda, 0xfd, 0x26, 0x6c, 0xd8, 0x1d, 0xc9, 0x01, 0x70, 0x38, 0xbf, - 0x73, 0xe2, 0x87, 0x43, 0xae, 0x9a, 0x54, 0x43, 0xf8, 0x24, 0xb4, 0xfb, 0xd3, 0x38, 0xe6, 0x61, - 0x61, 0x0c, 0xab, 0x12, 0xd7, 0x83, 0x78, 0x0b, 0x9a, 0x21, 0x3f, 0xcb, 0xc8, 0x24, 0xcb, 0x84, - 0xfc, 0x4c, 0x91, 0xb8, 0x1d, 0xd8, 0xcc, 0x77, 0x23, 0x07, 0xf0, 0x9d, 0x0a, 0x34, 0x1e, 0xc5, - 0x7e, 0x98, 0xf8, 0x7d, 0xe4, 0x62, 0xd6, 0x81, 0xa5, 0xf4, 0x59, 0xef, 0xc4, 0x4f, 0x4e, 0xa8, - 0xbb, 0xba, 0xa7, 0x8a, 0x6c, 0x13, 0x16, 0xfd, 0x71, 0x34, 0x0d, 0x53, 0xea, 0xa0, 0xea, 0xc9, - 0x12, 0x7b, 0x07, 0xd6, 0xc2, 0xe9, 0xb8, 0xd7, 0x8f, 0xc2, 0xe3, 0x20, 0x1e, 0x0b, 0x59, 0xa0, - 0xfd, 0x5a, 0xf0, 0x8a, 0x15, 0xec, 0x12, 0xc0, 0x11, 0xae, 0x83, 0xe8, 0xa2, 0x46, 0x5d, 0x18, - 0x08, 0x73, 0xa1, 0x29, 0x4b, 0x3c, 0x18, 0x9e, 0xa4, 0x9d, 0x05, 0x6a, 0xc8, 0xc2, 0xb0, 0x8d, - 0x34, 0x18, 0xf3, 0x5e, 0x92, 0xfa, 0xe3, 0x49, 0x67, 0x91, 0x46, 0x63, 0x20, 0x54, 0x1f, 0xa5, - 0xfe, 0xa8, 0x77, 0xcc, 0x79, 0xd2, 0x59, 0x92, 0xf5, 0x1a, 0x61, 0x6f, 0x43, 0x6b, 0xc0, 0x93, - 0xb4, 0xe7, 0x0f, 0x06, 0x31, 0x4f, 0x12, 0x9e, 0x74, 0x96, 0x89, 0x1b, 0x73, 0x28, 0xae, 0xda, - 0x3d, 0x9e, 0x1a, 0xab, 0x93, 0xc8, 0xdd, 0x71, 0xf7, 0x81, 0x19, 0xf0, 0x1d, 0x9e, 0xfa, 0xc1, - 0x28, 0x61, 0xef, 0x41, 0x33, 0x35, 0x88, 0x49, 0xfa, 0x1a, 0xdb, 0xec, 0x3a, 0xa9, 0x8d, 0xeb, - 0xc6, 0x07, 0x9e, 0x45, 0xe7, 0xde, 0x83, 0xe5, 0xbb, 0x9c, 0xef, 0x07, 0xe3, 0x20, 0x65, 0x9b, - 0xb0, 0x70, 0x1c, 0x3c, 0xe3, 0x62, 0xb3, 0xab, 0x7b, 0xe7, 0x3c, 0x51, 0x64, 0x5d, 0x58, 0x9a, - 0xf0, 0xb8, 0xcf, 0xd5, 0xf2, 0xef, 0x9d, 0xf3, 0x14, 0x70, 0x7b, 0x09, 0x16, 0x46, 0xf8, 0xb1, - 0xfb, 0xbd, 0x0a, 0x34, 0x0e, 0x79, 0xa8, 0x99, 0x88, 0x41, 0x0d, 0xa7, 0x24, 0x19, 0x87, 0x7e, - 0xb3, 0x37, 0xa1, 0x41, 0xd3, 0x4c, 0xd2, 0x38, 0x08, 0x87, 0xd4, 0x58, 0xdd, 0x03, 0x84, 0x0e, - 0x09, 0x61, 0x6d, 0xa8, 0xfa, 0xe3, 0x94, 0x76, 0xb0, 0xea, 0xe1, 0x4f, 0x64, 0xb0, 0x89, 0x3f, - 0x1b, 0x23, 0x2f, 0xea, 0x5d, 0x6b, 0x7a, 0x0d, 0x89, 0xed, 0xe1, 0xb6, 0x5d, 0x87, 0x75, 0x93, - 0x44, 0xb5, 0xbe, 0x40, 0xad, 0xaf, 0x19, 0x94, 0xb2, 0x93, 0xab, 0xb0, 0xaa, 0xe8, 0x63, 0x31, - 0x58, 0xda, 0xc7, 0xba, 0xd7, 0x92, 0xb0, 0x9a, 0xc2, 0x35, 0x68, 0x1f, 0x07, 0xa1, 0x3f, 0xea, - 0xf5, 0x47, 0xe9, 0x69, 0x6f, 0xc0, 0x47, 0xa9, 0x4f, 0x3b, 0xba, 0xe0, 0xb5, 0x08, 0xdf, 0x19, - 0xa5, 0xa7, 0x77, 0x10, 0x65, 0xef, 0x40, 0xfd, 0x98, 0xf3, 0x1e, 0xad, 0x44, 0x67, 0xf9, 0xb2, - 0x73, 0xad, 0xb1, 0xbd, 0x2a, 0x97, 0x5e, 0xad, 0xae, 0xb7, 0x7c, 0x2c, 0x7f, 0xb9, 0x7f, 0xe4, - 0x40, 0x53, 0x2c, 0x95, 0x54, 0xa1, 0x57, 0x60, 0x45, 0x8d, 0x88, 0xc7, 0x71, 0x14, 0x4b, 0xf6, - 0xb7, 0x41, 0xb6, 0x05, 0x6d, 0x05, 0x4c, 0x62, 0x1e, 0x8c, 0xfd, 0x21, 0x97, 0xf2, 0x56, 0xc0, - 0xd9, 0x76, 0xd6, 0x62, 0x1c, 0x4d, 0x53, 0xa1, 0xc4, 0x1a, 0xdb, 0x4d, 0x39, 0x28, 0x0f, 0x31, - 0xcf, 0x26, 0x71, 0xbf, 0xed, 0x00, 0xc3, 0x61, 0x3d, 0x8a, 0x44, 0xb5, 0x5c, 0x85, 0xfc, 0x0e, - 0x38, 0xaf, 0xbc, 0x03, 0x95, 0x79, 0x3b, 0x70, 0x05, 0x16, 0xa9, 0x4b, 0x94, 0xd5, 0x6a, 0x61, - 0x58, 0xb2, 0xce, 0xfd, 0xae, 0x03, 0x4d, 0xd4, 0x1c, 0x21, 0x1f, 0x1d, 0x44, 0x41, 0x98, 0xb2, - 0x9b, 0xc0, 0x8e, 0xa7, 0xe1, 0x20, 0x08, 0x87, 0xbd, 0xf4, 0x59, 0x30, 0xe8, 0x1d, 0xcd, 0xb0, - 0x09, 0x1a, 0xcf, 0xde, 0x39, 0xaf, 0xa4, 0x8e, 0xbd, 0x03, 0x6d, 0x0b, 0x4d, 0xd2, 0x58, 0x8c, - 0x6a, 0xef, 0x9c, 0x57, 0xa8, 0x41, 0xf9, 0x8f, 0xa6, 0xe9, 0x64, 0x9a, 0xf6, 0x82, 0x70, 0xc0, - 0x9f, 0xd1, 0x9a, 0xad, 0x78, 0x16, 0x76, 0xbb, 0x05, 0x4d, 0xf3, 0x3b, 0xf7, 0x73, 0xd0, 0xde, - 0x47, 0xc5, 0x10, 0x06, 0xe1, 0xf0, 0x96, 0x90, 0x5e, 0xd4, 0x56, 0x93, 0xe9, 0xd1, 0x53, 0x3e, - 0x93, 0xfb, 0x28, 0x4b, 0x28, 0x12, 0x27, 0x51, 0x92, 0xca, 0x75, 0xa1, 0xdf, 0xee, 0xbf, 0x38, - 0xb0, 0x8a, 0x8b, 0xfe, 0x91, 0x1f, 0xce, 0xd4, 0x8a, 0xef, 0x43, 0x13, 0x9b, 0x7a, 0x14, 0xdd, - 0x12, 0x3a, 0x4f, 0xc8, 0xf2, 0x35, 0xb9, 0x48, 0x39, 0xea, 0xeb, 0x26, 0x29, 0x9a, 0xe9, 0x99, - 0x67, 0x7d, 0x8d, 0x42, 0x97, 0xfa, 0xf1, 0x90, 0xa7, 0xa4, 0x0d, 0xa5, 0x76, 0x04, 0x01, 0xed, - 0x44, 0xe1, 0x31, 0xbb, 0x0c, 0xcd, 0xc4, 0x4f, 0x7b, 0x13, 0x1e, 0xd3, 0xaa, 0x91, 0xe0, 0x54, - 0x3d, 0x48, 0xfc, 0xf4, 0x80, 0xc7, 0xb7, 0x67, 0x29, 0xef, 0x7e, 0x1e, 0xd6, 0x0a, 0xbd, 0xa0, - 0xac, 0x66, 0x53, 0xc4, 0x9f, 0x6c, 0x03, 0x16, 0x4e, 0xfd, 0xd1, 0x94, 0x4b, 0x25, 0x2d, 0x0a, - 0x1f, 0x54, 0xde, 0x77, 0xdc, 0xb7, 0xa1, 0x9d, 0x0d, 0x5b, 0x32, 0x3d, 0x83, 0x1a, 0xae, 0xa0, - 0x6c, 0x80, 0x7e, 0xbb, 0xbf, 0xe9, 0x08, 0xc2, 0x9d, 0x28, 0xd0, 0x0a, 0x0f, 0x09, 0x51, 0x2f, - 0x2a, 0x42, 0xfc, 0x3d, 0xd7, 0x20, 0xfc, 0xf4, 0x93, 0x75, 0xaf, 0xc2, 0x9a, 0x31, 0x84, 0x17, - 0x0c, 0xf6, 0xdb, 0x0e, 0xac, 0x3d, 0xe0, 0x67, 0x72, 0xd7, 0xd5, 0x68, 0xdf, 0x87, 0x5a, 0x3a, - 0x9b, 0x08, 0x27, 0xab, 0xb5, 0x7d, 0x45, 0x6e, 0x5a, 0x81, 0xee, 0xba, 0x2c, 0x3e, 0x9a, 0x4d, - 0xb8, 0x47, 0x5f, 0xb8, 0x9f, 0x83, 0x86, 0x01, 0xb2, 0x0b, 0xb0, 0xfe, 0xe4, 0xfe, 0xa3, 0x07, - 0xbb, 0x87, 0x87, 0xbd, 0x83, 0xc7, 0xb7, 0xbf, 0xb8, 0xfb, 0xab, 0xbd, 0xbd, 0x5b, 0x87, 0x7b, - 0xed, 0x73, 0x6c, 0x13, 0xd8, 0x83, 0xdd, 0xc3, 0x47, 0xbb, 0x77, 0x2c, 0xdc, 0x71, 0xbb, 0xd0, - 0x79, 0xc0, 0xcf, 0x9e, 0x04, 0x69, 0xc8, 0x93, 0xc4, 0xee, 0xcd, 0xbd, 0x0e, 0xcc, 0x1c, 0x82, - 0x9c, 0x55, 0x07, 0x96, 0xa4, 0xc5, 0x51, 0x06, 0x57, 0x16, 0xdd, 0xb7, 0x81, 0x1d, 0x06, 0xc3, - 0xf0, 0x23, 0x9e, 0x24, 0xfe, 0x50, 0xab, 0x82, 0x36, 0x54, 0xc7, 0xc9, 0x50, 0x6a, 0x00, 0xfc, - 0xe9, 0x7e, 0x0a, 0xd6, 0x2d, 0x3a, 0xd9, 0xf0, 0xeb, 0x50, 0x4f, 0x82, 0x61, 0xe8, 0xa7, 0xd3, - 0x98, 0xcb, 0xa6, 0x33, 0xc0, 0xbd, 0x0b, 0x1b, 0x5f, 0xe6, 0x71, 0x70, 0x3c, 0x7b, 0x59, 0xf3, - 0x76, 0x3b, 0x95, 0x7c, 0x3b, 0xbb, 0x70, 0x3e, 0xd7, 0x8e, 0xec, 0x5e, 0x30, 0xa2, 0xdc, 0xae, - 0x65, 0x4f, 0x14, 0x0c, 0xb1, 0xac, 0x98, 0x62, 0xe9, 0x3e, 0x06, 0xb6, 0x13, 0x85, 0x21, 0xef, - 0xa7, 0x07, 0x9c, 0xc7, 0x99, 0xe7, 0x9c, 0x71, 0x5d, 0x63, 0xfb, 0x82, 0xdc, 0xc7, 0xbc, 0xac, - 0x4b, 0x76, 0x64, 0x50, 0x9b, 0xf0, 0x78, 0x4c, 0x0d, 0x2f, 0x7b, 0xf4, 0xdb, 0x3d, 0x0f, 0xeb, - 0x56, 0xb3, 0xd2, 0xe9, 0x79, 0x17, 0xce, 0xdf, 0x09, 0x92, 0x7e, 0xb1, 0xc3, 0x0e, 0x2c, 0x4d, - 0xa6, 0x47, 0xbd, 0x4c, 0xa6, 0x54, 0x11, 0x7d, 0x81, 0xfc, 0x27, 0xb2, 0xb1, 0xdf, 0x75, 0xa0, - 0xb6, 0xf7, 0x68, 0x7f, 0x87, 0x75, 0x61, 0x39, 0x08, 0xfb, 0xd1, 0x18, 0xd5, 0xae, 0x98, 0xb4, - 0x2e, 0xcf, 0x95, 0x95, 0xd7, 0xa1, 0x4e, 0xda, 0x1a, 0xdd, 0x1b, 0xe9, 0xe4, 0x66, 0x00, 0xba, - 0x56, 0xfc, 0xd9, 0x24, 0x88, 0xc9, 0x77, 0x52, 0x1e, 0x51, 0x8d, 0x34, 0x62, 0xb1, 0xc2, 0xfd, - 0x9f, 0x1a, 0x2c, 0x49, 0x5d, 0x4d, 0xfd, 0xf5, 0xd3, 0xe0, 0x94, 0xcb, 0x91, 0xc8, 0x12, 0x5a, - 0xb9, 0x98, 0x8f, 0xa3, 0x94, 0xf7, 0xac, 0x6d, 0xb0, 0x41, 0xa4, 0xea, 0x8b, 0x86, 0x7a, 0x13, - 0xd4, 0xfa, 0x34, 0xb2, 0xba, 0x67, 0x83, 0xb8, 0x58, 0x08, 0xf4, 0x82, 0x01, 0x8d, 0xa9, 0xe6, - 0xa9, 0x22, 0xae, 0x44, 0xdf, 0x9f, 0xf8, 0xfd, 0x20, 0x9d, 0x49, 0xe1, 0xd6, 0x65, 0x6c, 0x7b, - 0x14, 0xf5, 0xfd, 0x51, 0xef, 0xc8, 0x1f, 0xf9, 0x61, 0x9f, 0x4b, 0xff, 0xcd, 0x06, 0xd1, 0x45, - 0x93, 0x43, 0x52, 0x64, 0xc2, 0x8d, 0xcb, 0xa1, 0xe8, 0xea, 0xf5, 0xa3, 0xf1, 0x38, 0x48, 0xd1, - 0xb3, 0x23, 0xab, 0x5f, 0xf5, 0x0c, 0x84, 0x66, 0x22, 0x4a, 0x67, 0x62, 0xf5, 0xea, 0xa2, 0x37, - 0x0b, 0xc4, 0x56, 0xd0, 0x75, 0x40, 0x85, 0xf4, 0xf4, 0xac, 0x03, 0xa2, 0x95, 0x0c, 0xc1, 0x7d, - 0x98, 0x86, 0x09, 0x4f, 0xd3, 0x11, 0x1f, 0xe8, 0x01, 0x35, 0x88, 0xac, 0x58, 0xc1, 0x6e, 0xc2, - 0xba, 0x70, 0x36, 0x13, 0x3f, 0x8d, 0x92, 0x93, 0x20, 0xe9, 0x25, 0xe8, 0xb6, 0x35, 0x89, 0xbe, - 0xac, 0x8a, 0xbd, 0x0f, 0x17, 0x72, 0x70, 0xcc, 0xfb, 0x3c, 0x38, 0xe5, 0x83, 0xce, 0x0a, 0x7d, - 0x35, 0xaf, 0x9a, 0x5d, 0x86, 0x06, 0xfa, 0xd8, 0xd3, 0xc9, 0xc0, 0x47, 0x3b, 0xdc, 0xa2, 0x7d, - 0x30, 0x21, 0xf6, 0x2e, 0xac, 0x4c, 0xb8, 0x30, 0x96, 0x27, 0xe9, 0xa8, 0x9f, 0x74, 0x56, 0xc9, - 0x92, 0x35, 0xa4, 0x30, 0x21, 0xe7, 0x7a, 0x36, 0x05, 0x32, 0x65, 0x3f, 0x21, 0x67, 0xcb, 0x9f, - 0x75, 0xda, 0xc4, 0x6e, 0x19, 0x40, 0x32, 0x12, 0x07, 0xa7, 0x7e, 0xca, 0x3b, 0x6b, 0xc4, 0x5b, - 0xaa, 0xe8, 0xfe, 0xa9, 0x03, 0xeb, 0xfb, 0x41, 0x92, 0x4a, 0x26, 0xd4, 0xea, 0xf8, 0x4d, 0x68, - 0x08, 0xf6, 0xeb, 0x45, 0xe1, 0x68, 0x26, 0x39, 0x12, 0x04, 0xf4, 0x30, 0x1c, 0xcd, 0xd8, 0x27, - 0x60, 0x25, 0x08, 0x4d, 0x12, 0x21, 0xc3, 0x4d, 0x05, 0x12, 0xd1, 0x9b, 0xd0, 0x98, 0x4c, 0x8f, - 0x46, 0x41, 0x5f, 0x90, 0x54, 0x45, 0x2b, 0x02, 0x22, 0x02, 0x74, 0x92, 0xc4, 0x48, 0x04, 0x45, - 0x8d, 0x28, 0x1a, 0x12, 0x43, 0x12, 0xf7, 0x36, 0x6c, 0xd8, 0x03, 0x94, 0xca, 0x6a, 0x0b, 0x96, - 0x25, 0x6f, 0x27, 0x9d, 0x06, 0xad, 0x4f, 0x4b, 0xae, 0x8f, 0x24, 0xf5, 0x74, 0xbd, 0xfb, 0xe7, - 0x35, 0x58, 0x97, 0xe8, 0xce, 0x28, 0x4a, 0xf8, 0xe1, 0x74, 0x3c, 0xf6, 0xe3, 0x12, 0xa1, 0x71, - 0x5e, 0x22, 0x34, 0x15, 0x5b, 0x68, 0x90, 0x95, 0x4f, 0xfc, 0x20, 0x14, 0x1e, 0x9e, 0x90, 0x38, - 0x03, 0x61, 0xd7, 0x60, 0xb5, 0x3f, 0x8a, 0x12, 0xe1, 0xf5, 0x98, 0xc7, 0xa7, 0x3c, 0x5c, 0x14, - 0xf2, 0x85, 0x32, 0x21, 0x37, 0x85, 0x74, 0x31, 0x27, 0xa4, 0x2e, 0x34, 0xb1, 0x51, 0xae, 0x74, - 0xce, 0x92, 0xf0, 0xc2, 0x4c, 0x0c, 0xc7, 0x93, 0x17, 0x09, 0x21, 0x7f, 0xab, 0x65, 0x02, 0x81, - 0xa7, 0x33, 0xd4, 0x69, 0x06, 0x75, 0x5d, 0x0a, 0x44, 0xb1, 0x8a, 0xdd, 0x05, 0x10, 0x7d, 0x91, - 0x19, 0x07, 0x32, 0xe3, 0x6f, 0xdb, 0x3b, 0x62, 0xae, 0xfd, 0x75, 0x2c, 0x4c, 0x63, 0x4e, 0x86, - 0xdc, 0xf8, 0xd2, 0xfd, 0x18, 0x1a, 0x46, 0x15, 0x3b, 0x0f, 0x6b, 0x3b, 0x0f, 0x1f, 0x1e, 0xec, - 0x7a, 0xb7, 0x1e, 0xdd, 0xff, 0xf2, 0x6e, 0x6f, 0x67, 0xff, 0xe1, 0xe1, 0x6e, 0xfb, 0x1c, 0xc2, - 0xfb, 0x0f, 0x77, 0x6e, 0xed, 0xf7, 0xee, 0x3e, 0xf4, 0x76, 0x14, 0xec, 0xa0, 0x8d, 0xf7, 0x76, - 0x3f, 0x7a, 0xf8, 0x68, 0xd7, 0xc2, 0x2b, 0xac, 0x0d, 0xcd, 0xdb, 0xde, 0xee, 0xad, 0x9d, 0x3d, - 0x89, 0x54, 0xd9, 0x06, 0xb4, 0xef, 0x3e, 0x7e, 0x70, 0xe7, 0xfe, 0x83, 0x7b, 0xbd, 0x9d, 0x5b, - 0x0f, 0x76, 0x76, 0xf7, 0x77, 0xef, 0xb4, 0x6b, 0xee, 0xdf, 0x39, 0x70, 0x9e, 0x46, 0x39, 0xc8, - 0x0b, 0xc4, 0x65, 0x68, 0xf4, 0xa3, 0x68, 0xc2, 0x51, 0x7f, 0x6b, 0x15, 0x6d, 0x42, 0xc8, 0xec, - 0x42, 0x21, 0x1e, 0x47, 0x71, 0x9f, 0x4b, 0x79, 0x00, 0x82, 0xee, 0x22, 0x82, 0xcc, 0x2e, 0xb7, - 0x53, 0x50, 0x08, 0x71, 0x68, 0x08, 0x4c, 0x90, 0x6c, 0xc2, 0xe2, 0x51, 0xcc, 0xfd, 0xfe, 0x89, - 0x94, 0x04, 0x59, 0x62, 0x9f, 0xcc, 0x1c, 0xf2, 0x3e, 0xae, 0xf6, 0x88, 0x0f, 0x88, 0x43, 0x96, - 0xbd, 0x55, 0x89, 0xef, 0x48, 0xd8, 0x3d, 0x80, 0xcd, 0xfc, 0x0c, 0xa4, 0xc4, 0xbc, 0x67, 0x48, - 0x8c, 0xf0, 0x8d, 0xbb, 0xf3, 0xf7, 0xc7, 0x90, 0x9e, 0xff, 0x70, 0xa0, 0x86, 0xe6, 0x73, 0xbe, - 0xa9, 0x35, 0x3d, 0xa2, 0xaa, 0xe5, 0x11, 0x51, 0xf0, 0x00, 0xcf, 0x14, 0x42, 0xa1, 0x0a, 0xa3, - 0x63, 0x20, 0x59, 0x7d, 0xcc, 0xfb, 0xa7, 0x34, 0x27, 0x5d, 0x8f, 0x08, 0xb2, 0x3c, 0x3a, 0x9e, - 0xf4, 0xb5, 0x64, 0x79, 0x55, 0x56, 0x75, 0xf4, 0xe5, 0x52, 0x56, 0x47, 0xdf, 0x75, 0x60, 0x29, - 0x08, 0x8f, 0xa2, 0x69, 0x38, 0x20, 0x16, 0x5f, 0xf6, 0x54, 0x11, 0x55, 0xe5, 0x84, 0x44, 0x2f, - 0x18, 0x2b, 0x86, 0xce, 0x00, 0x97, 0xe1, 0xc1, 0x24, 0x21, 0x77, 0x41, 0x7b, 0x81, 0xef, 0xc1, - 0x9a, 0x81, 0xc9, 0xd5, 0x7c, 0x0b, 0x16, 0x26, 0x08, 0xc8, 0xa5, 0x54, 0xca, 0x99, 0xfc, 0x0c, - 0x51, 0xe3, 0xb6, 0xa1, 0x75, 0x8f, 0xa7, 0xf7, 0xc3, 0xe3, 0x48, 0xb5, 0xf4, 0xc3, 0x2a, 0xac, - 0x6a, 0x48, 0x36, 0x74, 0x0d, 0x56, 0x83, 0x01, 0x0f, 0xd3, 0x20, 0x9d, 0xf5, 0xac, 0xf3, 0x4f, - 0x1e, 0x46, 0xff, 0xcc, 0x1f, 0x05, 0x7e, 0x22, 0x3d, 0x00, 0x51, 0x60, 0xdb, 0xb0, 0x81, 0xc6, - 0x43, 0xd9, 0x03, 0xbd, 0xc5, 0xe2, 0x18, 0x56, 0x5a, 0x87, 0xe2, 0x8d, 0xb8, 0xd4, 0xdf, 0xfa, - 0x13, 0xe1, 0xa7, 0x94, 0x55, 0xe1, 0xaa, 0x89, 0x96, 0x70, 0xca, 0x0b, 0xc2, 0xc0, 0x68, 0xa0, - 0x10, 0x02, 0x5a, 0x14, 0xca, 0x27, 0x1f, 0x02, 0x32, 0xc2, 0x48, 0xcb, 0x85, 0x30, 0x12, 0x2a, - 0xa7, 0x59, 0xd8, 0xe7, 0x83, 0x5e, 0x1a, 0xf5, 0x48, 0x89, 0xd2, 0xee, 0x2c, 0x7b, 0x79, 0x98, - 0x02, 0x5e, 0x3c, 0x49, 0x43, 0x9e, 0x92, 0x9e, 0x59, 0xf6, 0x54, 0x11, 0xe5, 0x87, 0x48, 0x84, - 0x49, 0xa8, 0x7b, 0xb2, 0x84, 0x8e, 0xe6, 0x34, 0x0e, 0x92, 0x4e, 0x93, 0x50, 0xfa, 0xcd, 0x3e, - 0x0d, 0xe7, 0x8f, 0x78, 0x92, 0xf6, 0x4e, 0xb8, 0x3f, 0xe0, 0x31, 0xed, 0xbe, 0x88, 0x4e, 0x09, - 0xfb, 0x5d, 0x5e, 0x89, 0x7d, 0x9f, 0xf2, 0x38, 0x09, 0xa2, 0x90, 0x2c, 0x77, 0xdd, 0x53, 0x45, - 0xf7, 0x9b, 0xe4, 0x0f, 0xeb, 0xb8, 0xd9, 0x63, 0x32, 0xe6, 0xec, 0x35, 0xa8, 0x8b, 0x39, 0x26, - 0x27, 0xbe, 0x74, 0xd1, 0x97, 0x09, 0x38, 0x3c, 0xf1, 0x51, 0x23, 0x58, 0xcb, 0x26, 0x02, 0x91, - 0x0d, 0xc2, 0xf6, 0xc4, 0xaa, 0x5d, 0x81, 0x96, 0x8a, 0xc8, 0x25, 0xbd, 0x11, 0x3f, 0x4e, 0xd5, - 0xf1, 0x3a, 0x9c, 0x8e, 0xb1, 0xbb, 0x64, 0x9f, 0x1f, 0xa7, 0xee, 0x03, 0x58, 0x93, 0x32, 0xfc, - 0x70, 0xc2, 0x55, 0xd7, 0x9f, 0x2d, 0xb3, 0x6e, 0x8d, 0xed, 0x75, 0x5b, 0xe8, 0x29, 0x46, 0x90, - 0x33, 0x79, 0xae, 0x07, 0xcc, 0xd4, 0x09, 0xb2, 0x41, 0x69, 0x62, 0xd4, 0x21, 0x5e, 0x4e, 0xc7, - 0xc2, 0x70, 0x7d, 0x92, 0x69, 0xbf, 0x8f, 0x9a, 0x40, 0x68, 0x40, 0x55, 0x74, 0xbf, 0xe7, 0xc0, - 0x3a, 0xb5, 0xa6, 0xec, 0xb3, 0x3e, 0xf9, 0xbd, 0xfa, 0x30, 0x9b, 0x7d, 0x33, 0xb0, 0xb1, 0x01, - 0x0b, 0xa6, 0xae, 0x15, 0x85, 0x1f, 0xff, 0x2c, 0x5b, 0x2b, 0x9c, 0x65, 0x7f, 0xe8, 0xc0, 0x9a, - 0x50, 0x86, 0xa9, 0x9f, 0x4e, 0x13, 0x39, 0xfd, 0x5f, 0x84, 0x15, 0x61, 0xa7, 0xa4, 0x38, 0xc9, - 0x81, 0x6e, 0x68, 0xc9, 0x27, 0x54, 0x10, 0xef, 0x9d, 0xf3, 0x6c, 0x62, 0xf6, 0x79, 0x68, 0x9a, - 0x61, 0x55, 0x1a, 0x73, 0x63, 0xfb, 0xa2, 0x9a, 0x65, 0x81, 0x73, 0xf6, 0xce, 0x79, 0xd6, 0x07, - 0xec, 0x43, 0x72, 0x36, 0xc2, 0x1e, 0x35, 0x2b, 0x03, 0x53, 0x17, 0x4b, 0x14, 0xb8, 0xfe, 0xdc, - 0x20, 0xbf, 0xbd, 0x0c, 0x8b, 0xc2, 0xbb, 0x74, 0xef, 0xc1, 0x8a, 0x35, 0x52, 0xeb, 0x8c, 0xde, - 0x14, 0x67, 0xf4, 0x42, 0x48, 0xa7, 0x52, 0x0c, 0xe9, 0xb8, 0xff, 0x56, 0x01, 0x86, 0xdc, 0x96, - 0xdb, 0x4e, 0x74, 0x6f, 0xa3, 0x81, 0x75, 0x58, 0x69, 0x7a, 0x26, 0xc4, 0xae, 0x03, 0x33, 0x8a, - 0x2a, 0xea, 0x25, 0xec, 0x46, 0x49, 0x0d, 0x2a, 0x38, 0x69, 0x58, 0xa5, 0x09, 0x94, 0xc7, 0x32, - 0xb1, 0x6f, 0xa5, 0x75, 0x68, 0x1a, 0x26, 0xd3, 0xe4, 0x04, 0xdd, 0x6f, 0x75, 0x9c, 0x51, 0xe5, - 0x3c, 0x83, 0x2c, 0xbe, 0x94, 0x41, 0x96, 0xf2, 0x0c, 0x62, 0x3a, 0xd4, 0xcb, 0x96, 0x43, 0x8d, - 0x8e, 0xdc, 0x18, 0xdd, 0xbf, 0x74, 0xd4, 0xef, 0x8d, 0xb1, 0x77, 0x79, 0x7a, 0xb1, 0x40, 0xb6, - 0x05, 0x6d, 0xe9, 0x0a, 0x64, 0x5e, 0x3b, 0xd0, 0x1a, 0x17, 0x70, 0xf7, 0x07, 0x0e, 0xb4, 0x71, - 0x9d, 0x2d, 0x5e, 0xfc, 0x00, 0x48, 0x14, 0x5e, 0x91, 0x15, 0x2d, 0xda, 0x9f, 0x9e, 0x13, 0xdf, - 0x87, 0x3a, 0x35, 0x18, 0x4d, 0x78, 0x28, 0x19, 0xb1, 0x63, 0x33, 0x62, 0xa6, 0x85, 0xf6, 0xce, - 0x79, 0x19, 0xb1, 0xc1, 0x86, 0xff, 0xe4, 0x40, 0x43, 0x0e, 0xf3, 0x27, 0x3e, 0x89, 0x77, 0x61, - 0x19, 0x39, 0xd2, 0x38, 0xee, 0xea, 0x32, 0x5a, 0x93, 0xb1, 0x9f, 0x4e, 0x63, 0x34, 0x9f, 0xd6, - 0x29, 0x3c, 0x0f, 0xa3, 0x2d, 0x24, 0x85, 0x9b, 0xf4, 0xd2, 0x60, 0xd4, 0x53, 0xb5, 0xf2, 0x16, - 0xa3, 0xac, 0x0a, 0xf5, 0x4e, 0x92, 0xfa, 0x43, 0x2e, 0xcd, 0x9c, 0x28, 0xb8, 0x1d, 0xd8, 0x94, - 0x13, 0xca, 0xf9, 0x8e, 0xee, 0xdf, 0x36, 0xe1, 0x42, 0xa1, 0x4a, 0x5f, 0x03, 0xca, 0xe3, 0xe5, - 0x28, 0x18, 0x1f, 0x45, 0xda, 0xd1, 0x76, 0xcc, 0x93, 0xa7, 0x55, 0xc5, 0x86, 0x70, 0x5e, 0xd9, - 0x73, 0x5c, 0xd3, 0xcc, 0x7a, 0x57, 0xc8, 0x11, 0x79, 0xd7, 0xe6, 0x81, 0x7c, 0x87, 0x0a, 0x37, - 0x25, 0xb7, 0xbc, 0x3d, 0x76, 0x02, 0x1d, 0xed, 0x38, 0x48, 0x15, 0x6f, 0x38, 0x17, 0xd8, 0xd7, - 0x3b, 0x2f, 0xe9, 0xcb, 0x72, 0x44, 0xbd, 0xb9, 0xad, 0xb1, 0x19, 0x5c, 0x52, 0x75, 0xa4, 0xc3, - 0x8b, 0xfd, 0xd5, 0x5e, 0x69, 0x6e, 0xe4, 0x44, 0xdb, 0x9d, 0xbe, 0xa4, 0x61, 0xf6, 0x75, 0xd8, - 0x3c, 0xf3, 0x83, 0x54, 0x0d, 0xcb, 0x70, 0x86, 0x16, 0xa8, 0xcb, 0xed, 0x97, 0x74, 0xf9, 0x44, - 0x7c, 0x6c, 0x19, 0xb6, 0x39, 0x2d, 0x76, 0xff, 0xc1, 0x81, 0x96, 0xdd, 0x0e, 0xb2, 0xa9, 0x14, - 0x78, 0xa5, 0xf8, 0x94, 0xf3, 0x97, 0x83, 0x8b, 0x67, 0xd5, 0x4a, 0xd9, 0x59, 0xd5, 0x3c, 0x21, - 0x56, 0x5f, 0x16, 0xc6, 0xa9, 0xbd, 0x5a, 0x18, 0x67, 0xa1, 0x2c, 0x8c, 0xd3, 0xfd, 0x6f, 0x07, - 0x58, 0x91, 0x97, 0xd8, 0x3d, 0x71, 0x58, 0x0e, 0xf9, 0x48, 0xea, 0xa4, 0x9f, 0x7f, 0x35, 0x7e, - 0x54, 0x6b, 0xa7, 0xbe, 0x46, 0xc1, 0x30, 0x95, 0x8e, 0xe9, 0x22, 0xad, 0x78, 0x65, 0x55, 0xb9, - 0xc0, 0x52, 0xed, 0xe5, 0x81, 0xa5, 0x85, 0x97, 0x07, 0x96, 0x16, 0xf3, 0x81, 0xa5, 0xee, 0xef, - 0x38, 0xb0, 0x5e, 0xb2, 0xe9, 0x3f, 0xbb, 0x89, 0xe3, 0x36, 0x59, 0xba, 0xa0, 0x22, 0xb7, 0xc9, - 0x04, 0xbb, 0xbf, 0x01, 0x2b, 0x16, 0xa3, 0xff, 0xec, 0xfa, 0xcf, 0x7b, 0x79, 0x82, 0xcf, 0x2c, - 0xac, 0xfb, 0xa3, 0x0a, 0xb0, 0xa2, 0xb0, 0xfd, 0xbf, 0x8e, 0xa1, 0xb8, 0x4e, 0xd5, 0x92, 0x75, - 0xfa, 0x3f, 0xb5, 0x03, 0xef, 0xc0, 0x9a, 0xcc, 0x19, 0x30, 0x42, 0x24, 0x82, 0x63, 0x8a, 0x15, - 0xe8, 0xe7, 0xda, 0x51, 0xbd, 0x65, 0xeb, 0xae, 0xd9, 0x30, 0x86, 0xb9, 0xe0, 0x9e, 0xbb, 0x09, - 0x1b, 0x22, 0x07, 0xe1, 0xb6, 0x68, 0x4a, 0xd9, 0x95, 0x3f, 0x71, 0xe0, 0x7c, 0xae, 0x22, 0xbb, - 0x19, 0x15, 0xa6, 0xc3, 0xb6, 0x27, 0x36, 0x88, 0xe3, 0x97, 0x72, 0x64, 0x8c, 0x5f, 0x70, 0x5b, - 0xb1, 0x02, 0xd7, 0x67, 0x1a, 0x16, 0xe9, 0xc5, 0xaa, 0x97, 0x55, 0xb9, 0x17, 0x44, 0xa6, 0x44, - 0xc8, 0x47, 0xb9, 0x81, 0x1f, 0x8b, 0xdc, 0x06, 0xb3, 0x22, 0xbb, 0x5a, 0xb1, 0x87, 0xac, 0x8a, - 0xe8, 0x05, 0x5a, 0x66, 0xca, 0x1e, 0x6f, 0x69, 0x9d, 0xfb, 0xd7, 0x0e, 0xb0, 0x2f, 0x4d, 0x79, - 0x3c, 0xa3, 0x1b, 0x52, 0x1d, 0xcb, 0xb9, 0x90, 0x8f, 0x63, 0x2c, 0x4e, 0xa6, 0x47, 0x5f, 0xe4, - 0x33, 0x75, 0x8f, 0x5e, 0xc9, 0xee, 0xd1, 0xdf, 0x00, 0xc0, 0xe3, 0x97, 0xbe, 0x76, 0x45, 0x5e, - 0xc0, 0x73, 0xaf, 0x68, 0xb0, 0xf4, 0xaa, 0xbb, 0xf6, 0xf2, 0xab, 0xee, 0x85, 0x97, 0x5d, 0x75, - 0x7f, 0x08, 0xeb, 0xd6, 0xb8, 0xf5, 0xb6, 0xaa, 0x0b, 0x60, 0xe7, 0x05, 0x17, 0xc0, 0xff, 0xe9, - 0x40, 0x75, 0x2f, 0x9a, 0x98, 0x71, 0x4b, 0xc7, 0x8e, 0x5b, 0x4a, 0x5b, 0xd2, 0xd3, 0xa6, 0x42, - 0xaa, 0x18, 0x0b, 0x64, 0x5b, 0xd0, 0xf2, 0xc7, 0x29, 0x1e, 0xbb, 0x8f, 0xa3, 0xf8, 0xcc, 0x8f, - 0x07, 0x62, 0xaf, 0x6f, 0x57, 0x3a, 0x8e, 0x97, 0xab, 0x61, 0x1b, 0x50, 0xd5, 0x4a, 0x97, 0x08, - 0xb0, 0x88, 0x8e, 0x1b, 0xdd, 0x79, 0xcc, 0x64, 0xc4, 0x40, 0x96, 0x90, 0x95, 0xec, 0xef, 0x85, - 0xab, 0x2c, 0x44, 0xa7, 0xac, 0x0a, 0xed, 0x1a, 0x2e, 0x1f, 0x91, 0xc9, 0x50, 0x8f, 0x2a, 0xbb, - 0xff, 0xee, 0xc0, 0x02, 0xad, 0x00, 0x0a, 0xbb, 0xe0, 0x70, 0x1d, 0xa0, 0xa4, 0x99, 0xaf, 0x78, - 0x79, 0x98, 0xb9, 0x56, 0xbe, 0x49, 0x45, 0x0f, 0xdb, 0xcc, 0x39, 0xb9, 0x0c, 0x75, 0x51, 0xd2, - 0xb9, 0x15, 0x44, 0x92, 0x81, 0xec, 0x12, 0xd4, 0x4e, 0xa2, 0x89, 0xf2, 0x4e, 0x40, 0xc5, 0xe7, - 0xa3, 0x89, 0x47, 0x78, 0x36, 0x1e, 0x6c, 0x4f, 0x0c, 0x5e, 0xd8, 0x9c, 0x3c, 0x8c, 0x56, 0x57, - 0x37, 0x6b, 0x2e, 0x46, 0x0e, 0x75, 0xb7, 0x60, 0xf5, 0x41, 0x34, 0xe0, 0x46, 0x4c, 0x69, 0x2e, - 0x37, 0xbb, 0xdf, 0x72, 0x60, 0x59, 0x11, 0xb3, 0x6b, 0x50, 0x43, 0x57, 0x22, 0x77, 0x50, 0xd0, - 0xf7, 0x72, 0x48, 0xe7, 0x11, 0x05, 0xea, 0x5e, 0x8a, 0x38, 0x64, 0x6e, 0xa5, 0x8a, 0x37, 0x64, - 0x5e, 0x93, 0x1e, 0x6e, 0xce, 0xd9, 0xc8, 0xa1, 0xee, 0x5f, 0x38, 0xb0, 0x62, 0xf5, 0x81, 0xc7, - 0xc3, 0x91, 0x9f, 0xa4, 0xf2, 0xae, 0x43, 0x6e, 0x8f, 0x09, 0x99, 0x51, 0xc6, 0x8a, 0x1d, 0x65, - 0xd4, 0xf1, 0xaf, 0xaa, 0x19, 0xff, 0xba, 0x09, 0xf5, 0x2c, 0x2b, 0xa8, 0x66, 0xe9, 0x54, 0xec, - 0x51, 0xdd, 0x38, 0x66, 0x44, 0xd8, 0x4e, 0x3f, 0x1a, 0x45, 0xb1, 0x0c, 0xb2, 0x8b, 0x82, 0xfb, - 0x21, 0x34, 0x0c, 0x7a, 0x1c, 0x46, 0xc8, 0xd3, 0xb3, 0x28, 0x7e, 0xaa, 0x82, 0x9d, 0xb2, 0xa8, - 0x2f, 0xd6, 0x2b, 0xd9, 0xc5, 0xba, 0xfb, 0x97, 0x0e, 0xac, 0x20, 0x0f, 0x06, 0xe1, 0xf0, 0x20, - 0x1a, 0x05, 0xfd, 0x19, 0xed, 0xbd, 0x62, 0x37, 0xa9, 0x19, 0x14, 0x2f, 0xda, 0x30, 0xf2, 0xb6, - 0x3a, 0x1d, 0x4a, 0x41, 0xd4, 0x65, 0x94, 0x54, 0xe4, 0xf3, 0x23, 0x3f, 0x91, 0xcc, 0x2f, 0x8d, - 0x9c, 0x05, 0xa2, 0x3c, 0x21, 0x10, 0xfb, 0x29, 0xef, 0x8d, 0x83, 0xd1, 0x28, 0x10, 0xb4, 0xc2, - 0x05, 0x2a, 0xab, 0x72, 0xbf, 0x5f, 0x81, 0x86, 0x54, 0xc1, 0xbb, 0x83, 0x21, 0x97, 0x37, 0x19, - 0xe4, 0x48, 0x6a, 0x75, 0x61, 0x20, 0xaa, 0xde, 0x72, 0x3d, 0x0d, 0x24, 0xbf, 0xad, 0xd5, 0xe2, - 0xb6, 0xbe, 0x0e, 0x75, 0x64, 0xaf, 0x77, 0xc9, 0xc7, 0x15, 0xb7, 0x20, 0x19, 0xa0, 0x6a, 0xb7, - 0xa9, 0x76, 0x21, 0xab, 0x25, 0xe0, 0x85, 0xf7, 0x1e, 0xef, 0x43, 0x53, 0x36, 0x43, 0xeb, 0x4e, - 0xda, 0x21, 0x63, 0x70, 0x6b, 0x4f, 0x3c, 0x8b, 0x52, 0x7d, 0xb9, 0xad, 0xbe, 0x5c, 0x7e, 0xd9, - 0x97, 0x8a, 0x92, 0xee, 0xa8, 0xc5, 0xda, 0xdc, 0x8b, 0xfd, 0xc9, 0x89, 0x32, 0x6b, 0x03, 0x9d, - 0x78, 0x43, 0x30, 0xdb, 0x82, 0x05, 0xfc, 0x4c, 0x69, 0xeb, 0x72, 0xa1, 0x13, 0x24, 0xec, 0x1a, - 0x2c, 0xf0, 0xc1, 0x90, 0xab, 0x53, 0x1c, 0xb3, 0xcf, 0xd3, 0xb8, 0x47, 0x9e, 0x20, 0x40, 0x15, - 0x80, 0x68, 0x4e, 0x05, 0xd8, 0x9a, 0x7e, 0x11, 0x8b, 0xf7, 0x07, 0xee, 0x06, 0xb0, 0x07, 0x82, - 0x6b, 0xcd, 0x28, 0xf4, 0x6f, 0x57, 0xa1, 0x61, 0xc0, 0x28, 0xcd, 0x43, 0x1c, 0x70, 0x6f, 0x10, - 0xf8, 0x63, 0x9e, 0xf2, 0x58, 0x72, 0x6a, 0x0e, 0x45, 0x3a, 0xff, 0x74, 0xd8, 0x8b, 0xa6, 0x69, - 0x6f, 0xc0, 0x87, 0x31, 0x17, 0xc6, 0x17, 0x8d, 0x81, 0x85, 0x22, 0xdd, 0xd8, 0x7f, 0x66, 0xd2, - 0x09, 0x7e, 0xc8, 0xa1, 0x2a, 0xa6, 0x2c, 0xd6, 0xa8, 0x96, 0xc5, 0x94, 0xc5, 0x8a, 0xe4, 0xf5, - 0xd0, 0x42, 0x89, 0x1e, 0x7a, 0x0f, 0x36, 0x85, 0xc6, 0x91, 0xb2, 0xd9, 0xcb, 0xb1, 0xc9, 0x9c, - 0x5a, 0xb6, 0x05, 0x6d, 0x1c, 0xb3, 0x62, 0xf0, 0x24, 0xf8, 0xa6, 0x88, 0xf2, 0x38, 0x5e, 0x01, - 0x47, 0x5a, 0x14, 0x47, 0x8b, 0x56, 0xdc, 0x9a, 0x15, 0x70, 0xa2, 0xf5, 0x9f, 0xd9, 0xb4, 0x75, - 0x49, 0x9b, 0xc3, 0xdd, 0x15, 0x68, 0x1c, 0xa6, 0xd1, 0x44, 0x6d, 0x4a, 0x0b, 0x9a, 0xa2, 0x28, - 0x73, 0x14, 0x5e, 0x83, 0x8b, 0xc4, 0x45, 0x8f, 0xa2, 0x49, 0x34, 0x8a, 0x86, 0xb3, 0xc3, 0xe9, - 0x51, 0xd2, 0x8f, 0x83, 0x09, 0x9e, 0x78, 0xdc, 0x7f, 0x74, 0x60, 0xdd, 0xaa, 0x95, 0x61, 0xa1, - 0x4f, 0x0b, 0x96, 0xd6, 0x97, 0xcb, 0x82, 0xf1, 0xd6, 0x0c, 0x75, 0x28, 0x08, 0x45, 0x40, 0xee, - 0xb1, 0xbc, 0x6f, 0xbe, 0x05, 0xab, 0x6a, 0x64, 0xea, 0x43, 0xc1, 0x85, 0x9d, 0x22, 0x17, 0xca, - 0xef, 0x5b, 0xf2, 0x03, 0xd5, 0xc4, 0x2f, 0xc9, 0xdb, 0xc7, 0x01, 0xcd, 0x51, 0xc5, 0x07, 0xf4, - 0xfd, 0x92, 0x79, 0x4a, 0x50, 0x23, 0xe8, 0x6b, 0x30, 0x71, 0x7f, 0xcf, 0x01, 0xc8, 0x46, 0x87, - 0x8c, 0x91, 0xa9, 0x74, 0x91, 0x10, 0x6d, 0xa8, 0xef, 0xb7, 0xa0, 0xa9, 0x6f, 0x46, 0x32, 0x2b, - 0xd1, 0x50, 0x18, 0x3a, 0x72, 0x57, 0x61, 0x75, 0x38, 0x8a, 0x8e, 0xc8, 0xc4, 0x52, 0xd2, 0x4b, - 0x22, 0x33, 0x35, 0x5a, 0x02, 0xbe, 0x2b, 0xd1, 0xcc, 0xa4, 0xd4, 0x0c, 0x93, 0xe2, 0x7e, 0xbb, - 0xa2, 0xe3, 0xe9, 0xd9, 0x9c, 0xe7, 0x4a, 0x19, 0xdb, 0x2e, 0x28, 0xc7, 0x39, 0xe1, 0x6b, 0x8a, - 0x84, 0x1d, 0xbc, 0xf4, 0xa0, 0xfe, 0x21, 0xb4, 0x62, 0xa1, 0x7d, 0x94, 0x6a, 0xaa, 0xbd, 0x40, - 0x35, 0xad, 0xc4, 0x96, 0xdd, 0xf9, 0x24, 0xb4, 0xfd, 0xc1, 0x29, 0x8f, 0xd3, 0x80, 0x8e, 0x4a, - 0x64, 0xf4, 0x85, 0x42, 0x5d, 0x35, 0x70, 0xb2, 0xc5, 0x57, 0x61, 0x55, 0x66, 0xc7, 0x68, 0x4a, - 0x99, 0xd1, 0x99, 0xc1, 0x48, 0xe8, 0xfe, 0x99, 0x0a, 0xdd, 0xdb, 0x7b, 0x38, 0x7f, 0x45, 0xcc, - 0xd9, 0x55, 0x72, 0xb3, 0xfb, 0x84, 0x0c, 0xa3, 0x0f, 0xd4, 0x79, 0xac, 0x6a, 0xdc, 0x54, 0x0f, - 0xe4, 0xb5, 0x87, 0xbd, 0xa4, 0xb5, 0x57, 0x59, 0x52, 0xf7, 0x07, 0x0e, 0x2c, 0xed, 0x45, 0x93, - 0x3d, 0x79, 0x67, 0x4f, 0x82, 0xa0, 0x73, 0xcf, 0x54, 0xf1, 0x05, 0xb7, 0xf9, 0xa5, 0xb6, 0x76, - 0x25, 0x6f, 0x6b, 0x7f, 0x19, 0x5e, 0xa3, 0x68, 0x40, 0x1c, 0x4d, 0xa2, 0x18, 0x85, 0xd1, 0x1f, - 0x09, 0xc3, 0x1a, 0x85, 0xe9, 0x89, 0x52, 0x63, 0x2f, 0x22, 0xa1, 0x63, 0x17, 0x1e, 0x17, 0x84, - 0x33, 0x2c, 0x7d, 0x03, 0xa1, 0xdd, 0x8a, 0x15, 0xee, 0x67, 0xa1, 0x4e, 0xce, 0x2d, 0x4d, 0xeb, - 0x1d, 0xa8, 0x9f, 0x44, 0x93, 0xde, 0x49, 0x10, 0xa6, 0x4a, 0xb8, 0x5b, 0x99, 0xd7, 0xb9, 0x47, - 0x0b, 0xa2, 0x09, 0xdc, 0x1f, 0x55, 0x61, 0xe9, 0x7e, 0x78, 0x1a, 0x05, 0x7d, 0x8a, 0xf2, 0x8f, - 0xf9, 0x38, 0x52, 0x99, 0x78, 0xf8, 0x1b, 0x97, 0x82, 0xb2, 0x52, 0x26, 0xa9, 0x0c, 0xd3, 0xab, - 0x22, 0x9a, 0xfb, 0x38, 0xcb, 0x96, 0x15, 0xa2, 0x63, 0x20, 0xe8, 0xd8, 0xc7, 0x66, 0x62, 0xb1, - 0x2c, 0x65, 0xa9, 0x8c, 0x0b, 0x46, 0x2a, 0x23, 0xdd, 0x09, 0x89, 0xfc, 0x02, 0xe2, 0xaf, 0x65, - 0x4f, 0x15, 0xe9, 0x20, 0x12, 0x73, 0x11, 0xc5, 0x21, 0xc7, 0x61, 0x49, 0x1e, 0x44, 0x4c, 0x10, - 0x9d, 0x0b, 0xf1, 0x81, 0xa0, 0x11, 0xca, 0xd7, 0x84, 0xd0, 0xd9, 0xca, 0xe7, 0x26, 0xd7, 0x05, - 0xcf, 0xe7, 0x60, 0xd4, 0xd0, 0x03, 0xae, 0x15, 0xa9, 0x98, 0x03, 0x88, 0x6c, 0xe0, 0x3c, 0x6e, - 0x1c, 0x5f, 0x44, 0xe2, 0x90, 0x3a, 0xbe, 0x20, 0xa3, 0xf8, 0xa3, 0xd1, 0x91, 0xdf, 0x7f, 0x4a, - 0xa9, 0xe7, 0x94, 0x27, 0x54, 0xf7, 0x6c, 0x90, 0x32, 0x06, 0xb2, 0xdd, 0xa4, 0x5b, 0xc5, 0x9a, - 0x67, 0x42, 0x6c, 0x1b, 0x1a, 0x74, 0x64, 0x93, 0xfb, 0xd9, 0xa2, 0xfd, 0x6c, 0x9b, 0x67, 0x3a, - 0xda, 0x51, 0x93, 0xc8, 0xbc, 0x79, 0x58, 0xb5, 0x53, 0x79, 0xbe, 0x0c, 0xec, 0xd6, 0x60, 0x20, - 0xf7, 0x5b, 0x1f, 0x19, 0xb3, 0x9d, 0x72, 0xac, 0x9d, 0x2a, 0x59, 0xb1, 0x4a, 0xe9, 0x8a, 0xb9, - 0xbb, 0xd0, 0x38, 0x30, 0x92, 0x96, 0x89, 0x35, 0x54, 0xba, 0xb2, 0x64, 0x27, 0x03, 0x31, 0x3a, - 0xac, 0x98, 0x1d, 0xba, 0xbf, 0x00, 0x6c, 0x3f, 0x48, 0x52, 0x3d, 0xbe, 0x2c, 0x4b, 0x5a, 0x9d, - 0xdc, 0xb3, 0x44, 0xa3, 0x86, 0xc4, 0x28, 0x01, 0xe8, 0x96, 0xc8, 0x50, 0xca, 0x4f, 0x6c, 0x0b, - 0x96, 0x03, 0x01, 0xe5, 0x25, 0x41, 0x51, 0xea, 0x7a, 0xf4, 0xd7, 0x24, 0x68, 0x59, 0xd1, 0xef, - 0x3b, 0xb0, 0x24, 0xa7, 0x86, 0xde, 0x46, 0x21, 0x5d, 0xbb, 0xee, 0x59, 0x58, 0x79, 0xa2, 0x6e, - 0x91, 0x87, 0xab, 0x65, 0x3c, 0xcc, 0xa0, 0x36, 0xf1, 0xd3, 0x13, 0x3a, 0xa0, 0xd4, 0x3d, 0xfa, - 0xcd, 0xda, 0xe2, 0xd0, 0x2c, 0x64, 0x85, 0x0e, 0xcc, 0x65, 0xb9, 0xea, 0x42, 0x25, 0x17, 0x70, - 0x9c, 0x14, 0x65, 0x25, 0x08, 0x5c, 0x5f, 0x36, 0xc8, 0x7c, 0xa9, 0x0c, 0xce, 0xd6, 0x4b, 0x36, - 0x91, 0x5f, 0x2f, 0x49, 0xea, 0xe9, 0x7a, 0xb7, 0x0b, 0x9d, 0x3b, 0x7c, 0xc4, 0x53, 0x7e, 0x6b, - 0x34, 0xca, 0xb7, 0xff, 0x1a, 0x5c, 0x2c, 0xa9, 0x93, 0x4e, 0xcb, 0x5d, 0x58, 0xbb, 0xc3, 0x8f, - 0xa6, 0xc3, 0x7d, 0x7e, 0x9a, 0xdd, 0x08, 0x32, 0xa8, 0x25, 0x27, 0xd1, 0x99, 0xdc, 0x5b, 0xfa, - 0xcd, 0xde, 0x00, 0x18, 0x21, 0x4d, 0x2f, 0x99, 0xf0, 0xbe, 0x4a, 0x51, 0x25, 0xe4, 0x70, 0xc2, - 0xfb, 0xee, 0x7b, 0xc0, 0xcc, 0x76, 0xe4, 0x14, 0x50, 0x0f, 0x4c, 0x8f, 0x7a, 0xc9, 0x2c, 0x49, - 0xf9, 0x58, 0xe5, 0xde, 0x9a, 0x90, 0x7b, 0x15, 0x9a, 0x07, 0xfe, 0xcc, 0xe3, 0xdf, 0x90, 0x19, - 0xf3, 0x78, 0x36, 0xf6, 0x67, 0xc8, 0xca, 0xfa, 0x6c, 0x4c, 0xd5, 0xee, 0x7f, 0x55, 0x60, 0x51, - 0x50, 0x62, 0xab, 0x03, 0x9e, 0xa4, 0x41, 0x28, 0x6e, 0xc3, 0x64, 0xab, 0x06, 0x54, 0xe0, 0x8d, - 0x4a, 0x09, 0x6f, 0x48, 0x6f, 0x55, 0xa5, 0xfb, 0x49, 0x26, 0xb0, 0x30, 0x74, 0x6b, 0xb2, 0x2c, - 0x03, 0x71, 0x38, 0xcb, 0x80, 0x5c, 0xb0, 0x24, 0xd3, 0x36, 0x62, 0x7c, 0x8a, 0x69, 0x25, 0x3b, - 0x98, 0x50, 0xa9, 0x4e, 0x5b, 0x12, 0x5c, 0x53, 0xd0, 0x69, 0x05, 0xdd, 0xb5, 0xfc, 0x0a, 0xba, - 0x4b, 0xb8, 0xb0, 0x2f, 0xd2, 0x5d, 0xf0, 0x0a, 0xba, 0xcb, 0x65, 0xd0, 0xbe, 0xcb, 0xb9, 0xc7, - 0xd1, 0x2a, 0x2a, 0x76, 0xfa, 0x8e, 0x03, 0x6d, 0x69, 0xd0, 0x75, 0x1d, 0x7b, 0xcb, 0xb2, 0xfe, - 0xa5, 0x49, 0x79, 0x57, 0x60, 0x85, 0x6c, 0xb2, 0x8e, 0x0a, 0xc9, 0x10, 0x96, 0x05, 0xe2, 0x3c, - 0x54, 0xe8, 0x7e, 0x1c, 0x8c, 0xe4, 0xa6, 0x98, 0x90, 0x0a, 0x2c, 0xe1, 0xf9, 0x98, 0xb6, 0xc4, - 0xf1, 0x74, 0xd9, 0xfd, 0x1b, 0x07, 0xd6, 0x8c, 0x01, 0x4b, 0x2e, 0xfc, 0x10, 0x54, 0x16, 0x82, - 0x08, 0x1e, 0x09, 0x61, 0xba, 0x60, 0x3b, 0x27, 0xd9, 0x67, 0x16, 0x31, 0x6d, 0xa6, 0x3f, 0xa3, - 0x01, 0x26, 0xd3, 0xb1, 0xf4, 0x40, 0x4c, 0x08, 0x19, 0xe9, 0x8c, 0xf3, 0xa7, 0x9a, 0xa4, 0x4a, - 0x24, 0x16, 0x46, 0x97, 0xcc, 0xe8, 0x4b, 0x68, 0x22, 0x91, 0x57, 0x65, 0x83, 0xee, 0x3f, 0x3b, - 0xb0, 0x2e, 0x9c, 0x42, 0xe9, 0x72, 0xeb, 0x8c, 0xe9, 0x45, 0xe1, 0x05, 0x0b, 0x89, 0xdc, 0x3b, - 0xe7, 0xc9, 0x32, 0xfb, 0xcc, 0x2b, 0x3a, 0xb2, 0x3a, 0xb9, 0x60, 0xce, 0x5e, 0x54, 0xcb, 0xf6, - 0xe2, 0x05, 0x2b, 0x5d, 0x16, 0x2c, 0x59, 0x28, 0x0d, 0x96, 0xdc, 0x5e, 0x82, 0x85, 0xa4, 0x1f, - 0x4d, 0xb8, 0xbb, 0x09, 0x1b, 0xf6, 0xe4, 0xa4, 0x0a, 0xfa, 0xae, 0x03, 0x9d, 0xbb, 0x22, 0x74, - 0x18, 0x84, 0xc3, 0xbd, 0x20, 0x49, 0xa3, 0x58, 0x3f, 0x11, 0xb9, 0x04, 0x90, 0xa4, 0x7e, 0x9c, - 0x8a, 0xe4, 0x2f, 0x19, 0xe6, 0xc8, 0x10, 0x1c, 0x23, 0x0f, 0x07, 0xa2, 0x56, 0xec, 0x8d, 0x2e, - 0xe3, 0xc6, 0x50, 0xe2, 0x43, 0x2f, 0x3a, 0x3e, 0x4e, 0xb8, 0x76, 0x5b, 0x4d, 0x0c, 0x4f, 0xbe, - 0x28, 0xf1, 0x78, 0xd6, 0xe3, 0xa7, 0xa4, 0x6a, 0x85, 0x3f, 0x98, 0x43, 0xdd, 0xbf, 0x72, 0x60, - 0x35, 0x1b, 0xe4, 0x2e, 0x82, 0xb6, 0x76, 0x10, 0x43, 0x33, 0xb4, 0x83, 0x0a, 0xc0, 0x04, 0x83, - 0x5e, 0x10, 0xca, 0xb1, 0x19, 0x08, 0x49, 0xac, 0x2c, 0x45, 0x53, 0x95, 0x68, 0x67, 0x42, 0xe2, - 0x16, 0x3d, 0xc5, 0xaf, 0x45, 0x96, 0x9d, 0x2c, 0x51, 0xee, 0xde, 0x38, 0xa5, 0xaf, 0x16, 0x85, - 0x43, 0x2c, 0x8b, 0xca, 0x3e, 0x2d, 0x11, 0x8a, 0x3f, 0xdd, 0xdf, 0x77, 0xe0, 0x62, 0xc9, 0xe2, - 0x4a, 0xc9, 0xb8, 0x03, 0x6b, 0xc7, 0xba, 0x52, 0x2d, 0x80, 0x10, 0x8f, 0x4d, 0x15, 0xeb, 0xb6, - 0x27, 0xed, 0x15, 0x3f, 0x40, 0xf7, 0x98, 0xe2, 0x46, 0x62, 0x49, 0xad, 0x04, 0x94, 0x62, 0xc5, - 0xf6, 0x1f, 0x54, 0xa1, 0x25, 0xee, 0x40, 0xc4, 0x63, 0x4d, 0x1e, 0xb3, 0x8f, 0x60, 0x49, 0x3e, - 0xb6, 0x65, 0xe7, 0x65, 0xb7, 0xf6, 0xf3, 0xde, 0xee, 0x66, 0x1e, 0x96, 0xbc, 0xb3, 0xfe, 0x5b, - 0x3f, 0xf8, 0xd7, 0x3f, 0xac, 0xac, 0xb0, 0xc6, 0x8d, 0xd3, 0x77, 0x6f, 0x0c, 0x79, 0x98, 0x60, - 0x1b, 0xbf, 0x06, 0x90, 0x3d, 0x43, 0x65, 0x1d, 0xed, 0x64, 0xe4, 0xde, 0xd7, 0x76, 0x2f, 0x96, - 0xd4, 0xc8, 0x76, 0x2f, 0x52, 0xbb, 0xeb, 0x6e, 0x0b, 0xdb, 0x0d, 0xc2, 0x20, 0x15, 0x6f, 0x52, - 0x3f, 0x70, 0xb6, 0xd8, 0x00, 0x9a, 0xe6, 0x2b, 0x53, 0xa6, 0x8e, 0xcc, 0x25, 0x6f, 0x5c, 0xbb, - 0xaf, 0x95, 0xd6, 0xa9, 0x78, 0x01, 0xf5, 0x71, 0xde, 0x6d, 0x63, 0x1f, 0x53, 0xa2, 0xc8, 0x7a, - 0x19, 0x41, 0xcb, 0x7e, 0x4c, 0xca, 0x5e, 0x37, 0xc4, 0xba, 0xf0, 0x94, 0xb5, 0xfb, 0xc6, 0x9c, - 0x5a, 0xd9, 0xd7, 0x1b, 0xd4, 0xd7, 0x05, 0x97, 0x61, 0x5f, 0x7d, 0xa2, 0x51, 0x4f, 0x59, 0x3f, - 0x70, 0xb6, 0xb6, 0xbf, 0x75, 0x09, 0xea, 0x3a, 0xc8, 0xc5, 0xbe, 0x0e, 0x2b, 0xd6, 0x25, 0x15, - 0x53, 0xd3, 0x28, 0xbb, 0xd3, 0xea, 0xbe, 0x5e, 0x5e, 0x29, 0x3b, 0xbe, 0x44, 0x1d, 0x77, 0xd8, - 0x26, 0x76, 0x2c, 0x6f, 0x79, 0x6e, 0xd0, 0xd5, 0x9c, 0xc8, 0x0c, 0x7c, 0x2a, 0xe6, 0x99, 0x5d, - 0x2c, 0x59, 0xf3, 0x2c, 0x5c, 0x44, 0x59, 0xf3, 0x2c, 0xde, 0x46, 0xb9, 0xaf, 0x53, 0x77, 0x9b, - 0x6c, 0xc3, 0xec, 0x4e, 0x07, 0x9f, 0x38, 0xe5, 0x72, 0x9a, 0x6f, 0x4d, 0xd9, 0x1b, 0x9a, 0xb1, - 0xca, 0xde, 0xa0, 0x6a, 0x16, 0x29, 0x3e, 0x44, 0x75, 0x3b, 0xd4, 0x15, 0x63, 0xb4, 0x7d, 0xe6, - 0x53, 0x53, 0xf6, 0x55, 0xa8, 0xeb, 0x87, 0x55, 0xec, 0x82, 0xf1, 0x9a, 0xcd, 0x7c, 0xed, 0xd5, - 0xed, 0x14, 0x2b, 0xca, 0x18, 0xc3, 0x6c, 0x19, 0x19, 0x63, 0x1f, 0xce, 0x4b, 0x97, 0xf8, 0x88, - 0xff, 0x38, 0x33, 0x29, 0x79, 0x21, 0x7b, 0xd3, 0x61, 0x1f, 0xc2, 0xb2, 0x7a, 0xaf, 0xc6, 0x36, - 0xcb, 0xdf, 0xdd, 0x75, 0x2f, 0x14, 0x70, 0xa9, 0x3d, 0x6e, 0x01, 0x64, 0x6f, 0xad, 0xb4, 0x9c, - 0x15, 0x5e, 0x80, 0xe9, 0x45, 0x2c, 0x79, 0x98, 0x35, 0xa4, 0x97, 0x65, 0xf6, 0x53, 0x2e, 0xf6, - 0x66, 0x46, 0x5f, 0xfa, 0xc8, 0xeb, 0x05, 0x0d, 0xba, 0x9b, 0xb4, 0x76, 0x6d, 0x46, 0x82, 0x1b, - 0xf2, 0x33, 0x95, 0xd5, 0x7c, 0x07, 0x1a, 0xc6, 0xfb, 0x2d, 0xa6, 0x5a, 0x28, 0xbe, 0xfd, 0xea, - 0x76, 0xcb, 0xaa, 0xe4, 0x70, 0xbf, 0x00, 0x2b, 0xd6, 0x43, 0x2c, 0x2d, 0x19, 0x65, 0xcf, 0xbc, - 0xb4, 0x64, 0x94, 0xbf, 0xdd, 0xfa, 0x0a, 0x34, 0x8c, 0x67, 0x53, 0xcc, 0xc8, 0xe6, 0xca, 0x3d, - 0x98, 0xd2, 0x23, 0x2a, 0x7b, 0x65, 0xb5, 0x41, 0xf3, 0x6d, 0xb9, 0x75, 0x9c, 0x2f, 0xa5, 0xf6, - 0x22, 0x93, 0x7c, 0x1d, 0x5a, 0xf6, 0x43, 0x2a, 0x2d, 0x55, 0xa5, 0x4f, 0xb2, 0xb4, 0x54, 0xcd, - 0x79, 0x7d, 0x25, 0x19, 0x72, 0x6b, 0x5d, 0x77, 0x72, 0xe3, 0x63, 0x79, 0xc5, 0xf3, 0x9c, 0x7d, - 0x09, 0x55, 0x87, 0xcc, 0xb5, 0x66, 0xd9, 0xf3, 0x31, 0x3b, 0x23, 0x5b, 0x73, 0x7b, 0x21, 0x2d, - 0xdb, 0x5d, 0xa3, 0xc6, 0x1b, 0x2c, 0x9b, 0x81, 0xb0, 0x07, 0x94, 0x73, 0x6d, 0xd8, 0x03, 0x33, - 0x2d, 0xdb, 0xb0, 0x07, 0x56, 0x6a, 0x76, 0xde, 0x1e, 0xa4, 0x01, 0xb6, 0x11, 0xc2, 0x6a, 0x2e, - 0x9d, 0x41, 0x0b, 0x4b, 0x79, 0xfe, 0x57, 0xf7, 0xd2, 0x8b, 0xb3, 0x20, 0x6c, 0x35, 0xa3, 0xd4, - 0xcb, 0x0d, 0x95, 0xae, 0xf7, 0xeb, 0xd0, 0x34, 0x1f, 0xc0, 0x68, 0x0b, 0x51, 0xf2, 0x6c, 0x47, - 0x5b, 0x88, 0xb2, 0x17, 0x33, 0x6a, 0x73, 0x59, 0xd3, 0xec, 0x06, 0x37, 0xd7, 0x7e, 0x2f, 0x90, - 0xa9, 0xcc, 0xb2, 0x87, 0x10, 0x99, 0xca, 0x2c, 0x7d, 0x64, 0xa0, 0x36, 0x97, 0xad, 0x5b, 0x73, - 0x11, 0xb1, 0x3d, 0xf6, 0x15, 0x58, 0x35, 0x72, 0x85, 0x0e, 0x67, 0x61, 0x5f, 0x33, 0x6a, 0x31, - 0x93, 0xb4, 0x5b, 0xe6, 0x79, 0xba, 0x17, 0xa8, 0xfd, 0x35, 0xd7, 0x9a, 0x04, 0x32, 0xe9, 0x0e, - 0x34, 0xcc, 0x3c, 0xa4, 0x17, 0xb4, 0x7b, 0xc1, 0xa8, 0x32, 0x93, 0x2a, 0x6f, 0x3a, 0xec, 0x8f, - 0x1d, 0x68, 0x5a, 0x59, 0x3d, 0x56, 0x04, 0x3b, 0xd7, 0x4e, 0xc7, 0xac, 0x33, 0x1b, 0x72, 0x3d, - 0x1a, 0xe4, 0xfe, 0xd6, 0x17, 0xac, 0x45, 0xf8, 0xd8, 0x3a, 0xc1, 0x5c, 0xcf, 0xbf, 0xa3, 0x7e, - 0x9e, 0x27, 0x30, 0xb3, 0x6d, 0x9f, 0xdf, 0x74, 0xd8, 0x07, 0xe2, 0x9f, 0x02, 0x54, 0xc4, 0x82, - 0x19, 0x8a, 0x34, 0xbf, 0x64, 0xe6, 0x33, 0xf9, 0x6b, 0xce, 0x4d, 0x87, 0x7d, 0x4d, 0x3c, 0x97, - 0x96, 0xdf, 0xd2, 0xca, 0xbf, 0xea, 0xf7, 0xee, 0x15, 0x9a, 0xcd, 0x25, 0xf7, 0xa2, 0x35, 0x9b, - 0xbc, 0x25, 0xb9, 0x25, 0x46, 0x27, 0x5f, 0xc1, 0x67, 0x2a, 0xb1, 0xf0, 0x32, 0x7e, 0xfe, 0x20, - 0xc7, 0x62, 0x90, 0x92, 0xdc, 0x62, 0x8f, 0x57, 0x6c, 0xc6, 0xdd, 0xa2, 0xb1, 0x5e, 0x71, 0xdf, - 0x9c, 0x3b, 0xd6, 0x1b, 0x74, 0x22, 0xc5, 0x11, 0x1f, 0x00, 0x64, 0x01, 0x33, 0x96, 0x8b, 0x1e, - 0x69, 0xab, 0x50, 0x8c, 0xa9, 0xd9, 0x3c, 0xa8, 0x82, 0x4c, 0xd8, 0xe2, 0x57, 0x85, 0xa8, 0x4a, - 0xfa, 0x44, 0x8f, 0xbe, 0x18, 0xf8, 0xea, 0x76, 0xcb, 0xaa, 0xca, 0x04, 0x55, 0xb5, 0xcf, 0x1e, - 0xc3, 0xca, 0x7e, 0x14, 0x3d, 0x9d, 0x4e, 0x74, 0x48, 0xd7, 0x8e, 0xdf, 0xec, 0xf9, 0xc9, 0x49, - 0x37, 0x37, 0x0b, 0xf7, 0x32, 0x35, 0xd5, 0x65, 0x1d, 0xa3, 0xa9, 0x1b, 0x1f, 0x67, 0xe1, 0xba, - 0xe7, 0xcc, 0x87, 0x35, 0xed, 0x01, 0xe8, 0x81, 0x77, 0xed, 0x66, 0xcc, 0xa8, 0x59, 0xa1, 0x0b, - 0xcb, 0x27, 0x53, 0xa3, 0xbd, 0x91, 0xa8, 0x36, 0x6f, 0x3a, 0xec, 0x00, 0x9a, 0x77, 0x78, 0x3f, - 0x1a, 0x70, 0x19, 0x71, 0x59, 0xcf, 0x06, 0xae, 0x43, 0x35, 0xdd, 0x15, 0x0b, 0xb4, 0x75, 0xe2, - 0xc4, 0x9f, 0xc5, 0xfc, 0x1b, 0x37, 0x3e, 0x96, 0xb1, 0x9c, 0xe7, 0x4a, 0x27, 0xaa, 0xf8, 0x93, - 0xa5, 0x13, 0x73, 0x01, 0x2b, 0x4b, 0x27, 0x16, 0x02, 0x56, 0xd6, 0x52, 0xab, 0xf8, 0x17, 0x1b, - 0xc1, 0x5a, 0x21, 0xc6, 0xa5, 0xfd, 0x88, 0x79, 0x91, 0xb1, 0xee, 0xe5, 0xf9, 0x04, 0x76, 0x6f, - 0x5b, 0x76, 0x6f, 0x87, 0xb0, 0x72, 0x87, 0x8b, 0xc5, 0x12, 0xf7, 0xc6, 0xb9, 0x67, 0x59, 0xe6, - 0x1d, 0x73, 0x5e, 0x29, 0x52, 0x9d, 0x6d, 0xf4, 0xe8, 0xd2, 0x96, 0x7d, 0x15, 0x1a, 0xf7, 0x78, - 0xaa, 0x2e, 0x8a, 0xb5, 0x37, 0x96, 0xbb, 0x39, 0xee, 0x96, 0xdc, 0x33, 0xdb, 0x3c, 0x43, 0xad, - 0xdd, 0xe0, 0x83, 0x21, 0x17, 0xea, 0xa9, 0x17, 0x0c, 0x9e, 0xb3, 0x5f, 0xa1, 0xc6, 0x75, 0x6e, - 0xc9, 0xa6, 0x71, 0xbf, 0x68, 0x36, 0xbe, 0x9a, 0xc3, 0xcb, 0x5a, 0x0e, 0xa3, 0x01, 0x37, 0xcc, - 0x7f, 0x08, 0x0d, 0x23, 0xf1, 0x49, 0x0b, 0x50, 0x31, 0x89, 0x4b, 0x0b, 0x50, 0x49, 0x9e, 0x94, - 0x7b, 0x8d, 0xfa, 0x71, 0xd9, 0xe5, 0xac, 0x1f, 0x91, 0x1b, 0x95, 0xf5, 0x74, 0xe3, 0x63, 0x7f, - 0x9c, 0x3e, 0x67, 0x4f, 0xe8, 0x89, 0x96, 0x79, 0x19, 0x9e, 0x79, 0x83, 0xf9, 0x7b, 0x73, 0xbd, - 0x58, 0x46, 0x95, 0xed, 0x21, 0x8a, 0xae, 0xc8, 0x4b, 0xf8, 0x0c, 0xc0, 0x61, 0x1a, 0x4d, 0xee, - 0xf8, 0x7c, 0x1c, 0x85, 0x99, 0xae, 0xcd, 0x2e, 0x7c, 0x33, 0xfd, 0x65, 0xdc, 0xfa, 0xb2, 0x27, - 0x86, 0x3f, 0x6e, 0xe5, 0x12, 0x28, 0xe6, 0x9a, 0x7b, 0x27, 0xac, 0x17, 0xa4, 0xe4, 0x5e, 0xf8, - 0xa6, 0x83, 0xde, 0x75, 0x16, 0x51, 0xd5, 0xde, 0x75, 0x21, 0x58, 0xab, 0xd5, 0x5e, 0x49, 0xf8, - 0xf5, 0x00, 0xea, 0x59, 0x88, 0xee, 0x42, 0x96, 0xbc, 0x66, 0x05, 0xf4, 0xb4, 0x55, 0x2c, 0x04, - 0xce, 0xdc, 0x36, 0x2d, 0x15, 0xb0, 0x65, 0x5c, 0x2a, 0x8a, 0x86, 0x05, 0xb0, 0x2e, 0x06, 0xa8, - 0x4d, 0x3c, 0x5d, 0x61, 0xaa, 0x99, 0x94, 0x04, 0xaf, 0xb4, 0x34, 0x97, 0xc6, 0x7e, 0xac, 0x73, - 0x36, 0x72, 0xab, 0xb8, 0x3e, 0x45, 0xd5, 0x3c, 0x86, 0xb5, 0x42, 0xe0, 0x42, 0x8b, 0xf4, 0xbc, - 0x78, 0x91, 0x16, 0xe9, 0xb9, 0x31, 0x0f, 0xf7, 0x3c, 0x75, 0xb9, 0xea, 0x02, 0x76, 0x99, 0x9c, - 0x05, 0x69, 0xff, 0xe4, 0x03, 0x67, 0xeb, 0x68, 0x91, 0xfe, 0x59, 0xec, 0x53, 0xff, 0x1b, 0x00, - 0x00, 0xff, 0xff, 0x49, 0x74, 0xd2, 0x22, 0x8b, 0x4c, 0x00, 0x00, + // 6215 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xbc, 0x5c, 0x4d, 0x6c, 0x1c, 0xc9, + 0x75, 0x56, 0x0f, 0x87, 0x3f, 0xf3, 0x66, 0x48, 0x0e, 0x8b, 0x14, 0x35, 0x9a, 0xdd, 0xd5, 0x6a, + 0xdb, 0xc2, 0x4a, 0x66, 0x36, 0x92, 0x96, 0xb6, 0x37, 0xeb, 0xdd, 0xc4, 0x8e, 0x44, 0x52, 0xa2, + 0x6c, 0xae, 0x44, 0x37, 0x25, 0x2b, 0xb1, 0x13, 0x8c, 0x9b, 0x33, 0xc5, 0x61, 0x5b, 0x33, 0xdd, + 0xed, 0xee, 0x1e, 0x52, 0xe3, 0x8d, 0x00, 0xe7, 0x07, 0x39, 0xc5, 0x08, 0x82, 0xe4, 0xe2, 0x00, + 0x41, 0x00, 0x07, 0x08, 0x9c, 0x43, 0x8e, 0xc9, 0xc5, 0x09, 0x90, 0x43, 0x2e, 0x09, 0x10, 0xe4, + 0xe0, 0x93, 0x91, 0x63, 0x92, 0x43, 0x12, 0xe4, 0x12, 0x20, 0xd7, 0x20, 0x78, 0xaf, 0x7e, 0xba, + 0xaa, 0xbb, 0x29, 0xca, 0x3f, 0xc9, 0x6d, 0xea, 0xab, 0xd7, 0xf5, 0xfb, 0xfe, 0xea, 0xd5, 0xab, + 0x81, 0x46, 0x12, 0xf7, 0x6f, 0xc6, 0x49, 0x94, 0x45, 0x6c, 0x76, 0x14, 0x26, 0x71, 0xbf, 0xfb, + 0xfa, 0x30, 0x8a, 0x86, 0x23, 0x7e, 0xcb, 0x8f, 0x83, 0x5b, 0x7e, 0x18, 0x46, 0x99, 0x9f, 0x05, + 0x51, 0x98, 0x0a, 0x22, 0xf7, 0x6b, 0xb0, 0x74, 0x9f, 0x87, 0x07, 0x9c, 0x0f, 0x3c, 0xfe, 0x8d, + 0x09, 0x4f, 0x33, 0xf6, 0x33, 0xb0, 0xe2, 0xf3, 0x6f, 0x72, 0x3e, 0xe8, 0xc5, 0x7e, 0x9a, 0xc6, + 0xc7, 0x89, 0x9f, 0xf2, 0x8e, 0x73, 0xd5, 0xb9, 0xd1, 0xf2, 0xda, 0xa2, 0x62, 0x5f, 0xe3, 0xec, + 0x2d, 0x68, 0xa5, 0x48, 0xca, 0xc3, 0x2c, 0x89, 0xe2, 0x69, 0xa7, 0x46, 0x74, 0x4d, 0xc4, 0x76, + 0x04, 0xe4, 0x8e, 0x60, 0x59, 0xf7, 0x90, 0xc6, 0x51, 0x98, 0x72, 0x76, 0x1b, 0xd6, 0xfa, 0x41, + 0x7c, 0xcc, 0x93, 0x1e, 0x7d, 0x3c, 0x0e, 0xf9, 0x38, 0x0a, 0x83, 0x7e, 0xc7, 0xb9, 0x3a, 0x73, + 0xa3, 0xe1, 0x31, 0x51, 0x87, 0x5f, 0x7c, 0x24, 0x6b, 0xd8, 0x75, 0x58, 0xe6, 0xa1, 0xc0, 0xf9, + 0x80, 0xbe, 0x92, 0x5d, 0x2d, 0xe5, 0x30, 0x7e, 0xe0, 0xfe, 0xad, 0x03, 0x2b, 0x0f, 0xc2, 0x20, + 0x7b, 0xea, 0x8f, 0x46, 0x3c, 0x53, 0x73, 0xba, 0x0e, 0xcb, 0xa7, 0x04, 0xd0, 0x9c, 0x4e, 0xa3, + 0x64, 0x20, 0x67, 0xb4, 0x24, 0xe0, 0x7d, 0x89, 0x9e, 0x39, 0xb2, 0xda, 0x99, 0x23, 0xab, 0x5c, + 0xae, 0x99, 0x33, 0x96, 0xeb, 0x3a, 0x2c, 0x27, 0xbc, 0x1f, 0x9d, 0xf0, 0x64, 0xda, 0x3b, 0x0d, + 0xc2, 0x41, 0x74, 0xda, 0xa9, 0x5f, 0x75, 0x6e, 0xcc, 0x7a, 0x4b, 0x0a, 0x7e, 0x4a, 0xa8, 0xbb, + 0x06, 0xcc, 0x9c, 0x85, 0x58, 0x37, 0x77, 0x08, 0xab, 0x4f, 0xc2, 0x51, 0xd4, 0x7f, 0xf6, 0x63, + 0xce, 0xae, 0xa2, 0xfb, 0x5a, 0x65, 0xf7, 0xeb, 0xb0, 0x66, 0x77, 0x24, 0x07, 0xc0, 0xe1, 0xe2, + 0xd6, 0xb1, 0x1f, 0x0e, 0xb9, 0x6a, 0x52, 0x0d, 0xe1, 0x93, 0xd0, 0xee, 0x4f, 0x92, 0x84, 0x87, + 0xa5, 0x31, 0x2c, 0x4b, 0x5c, 0x0f, 0xe2, 0x2d, 0x68, 0x85, 0xfc, 0x34, 0x27, 0x93, 0x2c, 0x13, + 0xf2, 0x53, 0x45, 0xe2, 0x76, 0x60, 0xbd, 0xd8, 0x8d, 0x1c, 0xc0, 0x77, 0x6a, 0xd0, 0x7c, 0x9c, + 0xf8, 0x61, 0xea, 0xf7, 0x91, 0x8b, 0x59, 0x07, 0xe6, 0xb3, 0xe7, 0xbd, 0x63, 0x3f, 0x3d, 0xa6, + 0xee, 0x1a, 0x9e, 0x2a, 0xb2, 0x75, 0x98, 0xf3, 0xc7, 0xd1, 0x24, 0xcc, 0xa8, 0x83, 0x19, 0x4f, + 0x96, 0xd8, 0x3b, 0xb0, 0x12, 0x4e, 0xc6, 0xbd, 0x7e, 0x14, 0x1e, 0x05, 0xc9, 0x58, 0xc8, 0x02, + 0xed, 0xd7, 0xac, 0x57, 0xae, 0x60, 0x57, 0x00, 0x0e, 0x71, 0x1d, 0x44, 0x17, 0x75, 0xea, 0xc2, + 0x40, 0x98, 0x0b, 0x2d, 0x59, 0xe2, 0xc1, 0xf0, 0x38, 0xeb, 0xcc, 0x52, 0x43, 0x16, 0x86, 0x6d, + 0x64, 0xc1, 0x98, 0xf7, 0xd2, 0xcc, 0x1f, 0xc7, 0x9d, 0x39, 0x1a, 0x8d, 0x81, 0x50, 0x7d, 0x94, + 0xf9, 0xa3, 0xde, 0x11, 0xe7, 0x69, 0x67, 0x5e, 0xd6, 0x6b, 0x84, 0xbd, 0x0d, 0x4b, 0x03, 0x9e, + 0x66, 0x3d, 0x7f, 0x30, 0x48, 0x78, 0x9a, 0xf2, 0xb4, 0xb3, 0x40, 0xdc, 0x58, 0x40, 0x71, 0xd5, + 0xee, 0xf3, 0xcc, 0x58, 0x9d, 0x54, 0xee, 0x8e, 0xbb, 0x07, 0xcc, 0x80, 0xb7, 0x79, 0xe6, 0x07, + 0xa3, 0x94, 0xbd, 0x07, 0xad, 0xcc, 0x20, 0x26, 0xe9, 0x6b, 0x6e, 0xb2, 0x9b, 0xa4, 0x36, 0x6e, + 0x1a, 0x1f, 0x78, 0x16, 0x9d, 0x7b, 0x1f, 0x16, 0xee, 0x71, 0xbe, 0x17, 0x8c, 0x83, 0x8c, 0xad, + 0xc3, 0xec, 0x51, 0xf0, 0x9c, 0x8b, 0xcd, 0x9e, 0xd9, 0xbd, 0xe0, 0x89, 0x22, 0xeb, 0xc2, 0x7c, + 0xcc, 0x93, 0x3e, 0x57, 0xcb, 0xbf, 0x7b, 0xc1, 0x53, 0xc0, 0xdd, 0x79, 0x98, 0x1d, 0xe1, 0xc7, + 0xee, 0xf7, 0x6a, 0xd0, 0x3c, 0xe0, 0xa1, 0x66, 0x22, 0x06, 0x75, 0x9c, 0x92, 0x64, 0x1c, 0xfa, + 0xcd, 0xde, 0x84, 0x26, 0x4d, 0x33, 0xcd, 0x92, 0x20, 0x1c, 0x52, 0x63, 0x0d, 0x0f, 0x10, 0x3a, + 0x20, 0x84, 0xb5, 0x61, 0xc6, 0x1f, 0x67, 0xb4, 0x83, 0x33, 0x1e, 0xfe, 0x44, 0x06, 0x8b, 0xfd, + 0xe9, 0x18, 0x79, 0x51, 0xef, 0x5a, 0xcb, 0x6b, 0x4a, 0x6c, 0x17, 0xb7, 0xed, 0x26, 0xac, 0x9a, + 0x24, 0xaa, 0xf5, 0x59, 0x6a, 0x7d, 0xc5, 0xa0, 0x94, 0x9d, 0x5c, 0x87, 0x65, 0x45, 0x9f, 0x88, + 0xc1, 0xd2, 0x3e, 0x36, 0xbc, 0x25, 0x09, 0xab, 0x29, 0xdc, 0x80, 0xf6, 0x51, 0x10, 0xfa, 0xa3, + 0x5e, 0x7f, 0x94, 0x9d, 0xf4, 0x06, 0x7c, 0x94, 0xf9, 0xb4, 0xa3, 0xb3, 0xde, 0x12, 0xe1, 0x5b, + 0xa3, 0xec, 0x64, 0x1b, 0x51, 0xf6, 0x0e, 0x34, 0x8e, 0x38, 0xef, 0xd1, 0x4a, 0x74, 0x16, 0xae, + 0x3a, 0x37, 0x9a, 0x9b, 0xcb, 0x72, 0xe9, 0xd5, 0xea, 0x7a, 0x0b, 0x47, 0xf2, 0x97, 0xfb, 0x07, + 0x0e, 0xb4, 0xc4, 0x52, 0x49, 0x15, 0x7a, 0x0d, 0x16, 0xd5, 0x88, 0x78, 0x92, 0x44, 0x89, 0x64, + 0x7f, 0x1b, 0x64, 0x1b, 0xd0, 0x56, 0x40, 0x9c, 0xf0, 0x60, 0xec, 0x0f, 0xb9, 0x94, 0xb7, 0x12, + 0xce, 0x36, 0xf3, 0x16, 0x93, 0x68, 0x92, 0x09, 0x25, 0xd6, 0xdc, 0x6c, 0xc9, 0x41, 0x79, 0x88, + 0x79, 0x36, 0x89, 0xfb, 0x6d, 0x07, 0x18, 0x0e, 0xeb, 0x71, 0x24, 0xaa, 0xe5, 0x2a, 0x14, 0x77, + 0xc0, 0x79, 0xe5, 0x1d, 0xa8, 0x9d, 0xb5, 0x03, 0xd7, 0x60, 0x8e, 0xba, 0x44, 0x59, 0x9d, 0x29, + 0x0d, 0x4b, 0xd6, 0xb9, 0xdf, 0x75, 0xa0, 0x85, 0x9a, 0x23, 0xe4, 0xa3, 0xfd, 0x28, 0x08, 0x33, + 0x76, 0x1b, 0xd8, 0xd1, 0x24, 0x1c, 0x04, 0xe1, 0xb0, 0x97, 0x3d, 0x0f, 0x06, 0xbd, 0xc3, 0x29, + 0x36, 0x41, 0xe3, 0xd9, 0xbd, 0xe0, 0x55, 0xd4, 0xb1, 0x77, 0xa0, 0x6d, 0xa1, 0x69, 0x96, 0x88, + 0x51, 0xed, 0x5e, 0xf0, 0x4a, 0x35, 0x28, 0xff, 0xd1, 0x24, 0x8b, 0x27, 0x59, 0x2f, 0x08, 0x07, + 0xfc, 0x39, 0xad, 0xd9, 0xa2, 0x67, 0x61, 0x77, 0x97, 0xa0, 0x65, 0x7e, 0xe7, 0x7e, 0x0e, 0xda, + 0x7b, 0xa8, 0x18, 0xc2, 0x20, 0x1c, 0xde, 0x11, 0xd2, 0x8b, 0xda, 0x2a, 0x9e, 0x1c, 0x3e, 0xe3, + 0x53, 0xb9, 0x8f, 0xb2, 0x84, 0x22, 0x71, 0x1c, 0xa5, 0x99, 0x5c, 0x17, 0xfa, 0xed, 0xfe, 0xb3, + 0x03, 0xcb, 0xb8, 0xe8, 0x1f, 0xf9, 0xe1, 0x54, 0xad, 0xf8, 0x1e, 0xb4, 0xb0, 0xa9, 0xc7, 0xd1, + 0x1d, 0xa1, 0xf3, 0x84, 0x2c, 0xdf, 0x90, 0x8b, 0x54, 0xa0, 0xbe, 0x69, 0x92, 0xa2, 0x99, 0x9e, + 0x7a, 0xd6, 0xd7, 0x28, 0x74, 0x99, 0x9f, 0x0c, 0x79, 0x46, 0xda, 0x50, 0x6a, 0x47, 0x10, 0xd0, + 0x56, 0x14, 0x1e, 0xb1, 0xab, 0xd0, 0x4a, 0xfd, 0xac, 0x17, 0xf3, 0x84, 0x56, 0x8d, 0x04, 0x67, + 0xc6, 0x83, 0xd4, 0xcf, 0xf6, 0x79, 0x72, 0x77, 0x9a, 0xf1, 0xee, 0xe7, 0x61, 0xa5, 0xd4, 0x0b, + 0xca, 0x6a, 0x3e, 0x45, 0xfc, 0xc9, 0xd6, 0x60, 0xf6, 0xc4, 0x1f, 0x4d, 0xb8, 0x54, 0xd2, 0xa2, + 0xf0, 0x41, 0xed, 0x7d, 0xc7, 0x7d, 0x1b, 0xda, 0xf9, 0xb0, 0x25, 0xd3, 0x33, 0xa8, 0xe3, 0x0a, + 0xca, 0x06, 0xe8, 0xb7, 0xfb, 0xeb, 0x8e, 0x20, 0xdc, 0x8a, 0x02, 0xad, 0xf0, 0x90, 0x10, 0xf5, + 0xa2, 0x22, 0xc4, 0xdf, 0x67, 0x1a, 0x84, 0x9f, 0x7c, 0xb2, 0xee, 0x75, 0x58, 0x31, 0x86, 0xf0, + 0x92, 0xc1, 0x7e, 0xdb, 0x81, 0x95, 0x87, 0xfc, 0x54, 0xee, 0xba, 0x1a, 0xed, 0xfb, 0x50, 0xcf, + 0xa6, 0xb1, 0x70, 0xb2, 0x96, 0x36, 0xaf, 0xc9, 0x4d, 0x2b, 0xd1, 0xdd, 0x94, 0xc5, 0xc7, 0xd3, + 0x98, 0x7b, 0xf4, 0x85, 0xfb, 0x39, 0x68, 0x1a, 0x20, 0xbb, 0x04, 0xab, 0x4f, 0x1f, 0x3c, 0x7e, + 0xb8, 0x73, 0x70, 0xd0, 0xdb, 0x7f, 0x72, 0xf7, 0x8b, 0x3b, 0xbf, 0xdc, 0xdb, 0xbd, 0x73, 0xb0, + 0xdb, 0xbe, 0xc0, 0xd6, 0x81, 0x3d, 0xdc, 0x39, 0x78, 0xbc, 0xb3, 0x6d, 0xe1, 0x8e, 0xdb, 0x85, + 0xce, 0x43, 0x7e, 0xfa, 0x34, 0xc8, 0x42, 0x9e, 0xa6, 0x76, 0x6f, 0xee, 0x4d, 0x60, 0xe6, 0x10, + 0xe4, 0xac, 0x3a, 0x30, 0x2f, 0x2d, 0x8e, 0x32, 0xb8, 0xb2, 0xe8, 0xbe, 0x0d, 0xec, 0x20, 0x18, + 0x86, 0x1f, 0xf1, 0x34, 0xf5, 0x87, 0x5a, 0x15, 0xb4, 0x61, 0x66, 0x9c, 0x0e, 0xa5, 0x06, 0xc0, + 0x9f, 0xee, 0xa7, 0x60, 0xd5, 0xa2, 0x93, 0x0d, 0xbf, 0x0e, 0x8d, 0x34, 0x18, 0x86, 0x7e, 0x36, + 0x49, 0xb8, 0x6c, 0x3a, 0x07, 0xdc, 0x7b, 0xb0, 0xf6, 0x65, 0x9e, 0x04, 0x47, 0xd3, 0xf3, 0x9a, + 0xb7, 0xdb, 0xa9, 0x15, 0xdb, 0xd9, 0x81, 0x8b, 0x85, 0x76, 0x64, 0xf7, 0x82, 0x11, 0xe5, 0x76, + 0x2d, 0x78, 0xa2, 0x60, 0x88, 0x65, 0xcd, 0x14, 0x4b, 0xf7, 0x09, 0xb0, 0xad, 0x28, 0x0c, 0x79, + 0x3f, 0xdb, 0xe7, 0x3c, 0xc9, 0x3d, 0xe7, 0x9c, 0xeb, 0x9a, 0x9b, 0x97, 0xe4, 0x3e, 0x16, 0x65, + 0x5d, 0xb2, 0x23, 0x83, 0x7a, 0xcc, 0x93, 0x31, 0x35, 0xbc, 0xe0, 0xd1, 0x6f, 0xf7, 0x22, 0xac, + 0x5a, 0xcd, 0x4a, 0xa7, 0xe7, 0x5d, 0xb8, 0xb8, 0x1d, 0xa4, 0xfd, 0x72, 0x87, 0x1d, 0x98, 0x8f, + 0x27, 0x87, 0xbd, 0x5c, 0xa6, 0x54, 0x11, 0x7d, 0x81, 0xe2, 0x27, 0xb2, 0xb1, 0xdf, 0x76, 0xa0, + 0xbe, 0xfb, 0x78, 0x6f, 0x8b, 0x75, 0x61, 0x21, 0x08, 0xfb, 0xd1, 0x18, 0xd5, 0xae, 0x98, 0xb4, + 0x2e, 0x9f, 0x29, 0x2b, 0xaf, 0x43, 0x83, 0xb4, 0x35, 0xba, 0x37, 0xd2, 0xc9, 0xcd, 0x01, 0x74, + 0xad, 0xf8, 0xf3, 0x38, 0x48, 0xc8, 0x77, 0x52, 0x1e, 0x51, 0x9d, 0x34, 0x62, 0xb9, 0xc2, 0xfd, + 0x9f, 0x3a, 0xcc, 0x4b, 0x5d, 0x4d, 0xfd, 0xf5, 0xb3, 0xe0, 0x84, 0xcb, 0x91, 0xc8, 0x12, 0x5a, + 0xb9, 0x84, 0x8f, 0xa3, 0x8c, 0xf7, 0xac, 0x6d, 0xb0, 0x41, 0xa4, 0xea, 0x8b, 0x86, 0x7a, 0x31, + 0x6a, 0x7d, 0x1a, 0x59, 0xc3, 0xb3, 0x41, 0x5c, 0x2c, 0x04, 0x7a, 0xc1, 0x80, 0xc6, 0x54, 0xf7, + 0x54, 0x11, 0x57, 0xa2, 0xef, 0xc7, 0x7e, 0x3f, 0xc8, 0xa6, 0x52, 0xb8, 0x75, 0x19, 0xdb, 0x1e, + 0x45, 0x7d, 0x7f, 0xd4, 0x3b, 0xf4, 0x47, 0x7e, 0xd8, 0xe7, 0xd2, 0x7f, 0xb3, 0x41, 0x74, 0xd1, + 0xe4, 0x90, 0x14, 0x99, 0x70, 0xe3, 0x0a, 0x28, 0xba, 0x7a, 0xfd, 0x68, 0x3c, 0x0e, 0x32, 0xf4, + 0xec, 0xc8, 0xea, 0xcf, 0x78, 0x06, 0x42, 0x33, 0x11, 0xa5, 0x53, 0xb1, 0x7a, 0x0d, 0xd1, 0x9b, + 0x05, 0x62, 0x2b, 0xe8, 0x3a, 0xa0, 0x42, 0x7a, 0x76, 0xda, 0x01, 0xd1, 0x4a, 0x8e, 0xe0, 0x3e, + 0x4c, 0xc2, 0x94, 0x67, 0xd9, 0x88, 0x0f, 0xf4, 0x80, 0x9a, 0x44, 0x56, 0xae, 0x60, 0xb7, 0x61, + 0x55, 0x38, 0x9b, 0xa9, 0x9f, 0x45, 0xe9, 0x71, 0x90, 0xf6, 0x52, 0x74, 0xdb, 0x5a, 0x44, 0x5f, + 0x55, 0xc5, 0xde, 0x87, 0x4b, 0x05, 0x38, 0xe1, 0x7d, 0x1e, 0x9c, 0xf0, 0x41, 0x67, 0x91, 0xbe, + 0x3a, 0xab, 0x9a, 0x5d, 0x85, 0x26, 0xfa, 0xd8, 0x93, 0x78, 0xe0, 0xa3, 0x1d, 0x5e, 0xa2, 0x7d, + 0x30, 0x21, 0xf6, 0x2e, 0x2c, 0xc6, 0x5c, 0x18, 0xcb, 0xe3, 0x6c, 0xd4, 0x4f, 0x3b, 0xcb, 0x64, + 0xc9, 0x9a, 0x52, 0x98, 0x90, 0x73, 0x3d, 0x9b, 0x02, 0x99, 0xb2, 0x9f, 0x92, 0xb3, 0xe5, 0x4f, + 0x3b, 0x6d, 0x62, 0xb7, 0x1c, 0x20, 0x19, 0x49, 0x82, 0x13, 0x3f, 0xe3, 0x9d, 0x15, 0xe2, 0x2d, + 0x55, 0x74, 0xff, 0xd8, 0x81, 0xd5, 0xbd, 0x20, 0xcd, 0x24, 0x13, 0x6a, 0x75, 0xfc, 0x26, 0x34, + 0x05, 0xfb, 0xf5, 0xa2, 0x70, 0x34, 0x95, 0x1c, 0x09, 0x02, 0x7a, 0x14, 0x8e, 0xa6, 0xec, 0x13, + 0xb0, 0x18, 0x84, 0x26, 0x89, 0x90, 0xe1, 0x96, 0x02, 0x89, 0xe8, 0x4d, 0x68, 0xc6, 0x93, 0xc3, + 0x51, 0xd0, 0x17, 0x24, 0x33, 0xa2, 0x15, 0x01, 0x11, 0x01, 0x3a, 0x49, 0x62, 0x24, 0x82, 0xa2, + 0x4e, 0x14, 0x4d, 0x89, 0x21, 0x89, 0x7b, 0x17, 0xd6, 0xec, 0x01, 0x4a, 0x65, 0xb5, 0x01, 0x0b, + 0x92, 0xb7, 0xd3, 0x4e, 0x93, 0xd6, 0x67, 0x49, 0xae, 0x8f, 0x24, 0xf5, 0x74, 0xbd, 0xfb, 0xa7, + 0x75, 0x58, 0x95, 0xe8, 0xd6, 0x28, 0x4a, 0xf9, 0xc1, 0x64, 0x3c, 0xf6, 0x93, 0x0a, 0xa1, 0x71, + 0xce, 0x11, 0x9a, 0x9a, 0x2d, 0x34, 0xc8, 0xca, 0xc7, 0x7e, 0x10, 0x0a, 0x0f, 0x4f, 0x48, 0x9c, + 0x81, 0xb0, 0x1b, 0xb0, 0xdc, 0x1f, 0x45, 0xa9, 0xf0, 0x7a, 0xcc, 0xe3, 0x53, 0x11, 0x2e, 0x0b, + 0xf9, 0x6c, 0x95, 0x90, 0x9b, 0x42, 0x3a, 0x57, 0x10, 0x52, 0x17, 0x5a, 0xd8, 0x28, 0x57, 0x3a, + 0x67, 0x5e, 0x78, 0x61, 0x26, 0x86, 0xe3, 0x29, 0x8a, 0x84, 0x90, 0xbf, 0xe5, 0x2a, 0x81, 0xc0, + 0xd3, 0x19, 0xea, 0x34, 0x83, 0xba, 0x21, 0x05, 0xa2, 0x5c, 0xc5, 0xee, 0x01, 0x88, 0xbe, 0xc8, + 0x8c, 0x03, 0x99, 0xf1, 0xb7, 0xed, 0x1d, 0x31, 0xd7, 0xfe, 0x26, 0x16, 0x26, 0x09, 0x27, 0x43, + 0x6e, 0x7c, 0xe9, 0x7e, 0x0c, 0x4d, 0xa3, 0x8a, 0x5d, 0x84, 0x95, 0xad, 0x47, 0x8f, 0xf6, 0x77, + 0xbc, 0x3b, 0x8f, 0x1f, 0x7c, 0x79, 0xa7, 0xb7, 0xb5, 0xf7, 0xe8, 0x60, 0xa7, 0x7d, 0x01, 0xe1, + 0xbd, 0x47, 0x5b, 0x77, 0xf6, 0x7a, 0xf7, 0x1e, 0x79, 0x5b, 0x0a, 0x76, 0xd0, 0xc6, 0x7b, 0x3b, + 0x1f, 0x3d, 0x7a, 0xbc, 0x63, 0xe1, 0x35, 0xd6, 0x86, 0xd6, 0x5d, 0x6f, 0xe7, 0xce, 0xd6, 0xae, + 0x44, 0x66, 0xd8, 0x1a, 0xb4, 0xef, 0x3d, 0x79, 0xb8, 0xfd, 0xe0, 0xe1, 0xfd, 0xde, 0xd6, 0x9d, + 0x87, 0x5b, 0x3b, 0x7b, 0x3b, 0xdb, 0xed, 0xba, 0xfb, 0x37, 0x0e, 0x5c, 0xa4, 0x51, 0x0e, 0x8a, + 0x02, 0x71, 0x15, 0x9a, 0xfd, 0x28, 0x8a, 0x39, 0xea, 0x6f, 0xad, 0xa2, 0x4d, 0x08, 0x99, 0x5d, + 0x28, 0xc4, 0xa3, 0x28, 0xe9, 0x73, 0x29, 0x0f, 0x40, 0xd0, 0x3d, 0x44, 0x90, 0xd9, 0xe5, 0x76, + 0x0a, 0x0a, 0x21, 0x0e, 0x4d, 0x81, 0x09, 0x92, 0x75, 0x98, 0x3b, 0x4c, 0xb8, 0xdf, 0x3f, 0x96, + 0x92, 0x20, 0x4b, 0xec, 0x93, 0xb9, 0x43, 0xde, 0xc7, 0xd5, 0x1e, 0xf1, 0x01, 0x71, 0xc8, 0x82, + 0xb7, 0x2c, 0xf1, 0x2d, 0x09, 0xbb, 0xfb, 0xb0, 0x5e, 0x9c, 0x81, 0x94, 0x98, 0xf7, 0x0c, 0x89, + 0x11, 0xbe, 0x71, 0xf7, 0xec, 0xfd, 0x31, 0xa4, 0xe7, 0xdf, 0x1d, 0xa8, 0xa3, 0xf9, 0x3c, 0xdb, + 0xd4, 0x9a, 0x1e, 0xd1, 0x8c, 0xe5, 0x11, 0x51, 0xf0, 0x00, 0xcf, 0x14, 0x42, 0xa1, 0x0a, 0xa3, + 0x63, 0x20, 0x79, 0x7d, 0xc2, 0xfb, 0x27, 0x34, 0x27, 0x5d, 0x8f, 0x08, 0xb2, 0x3c, 0x3a, 0x9e, + 0xf4, 0xb5, 0x64, 0x79, 0x55, 0x56, 0x75, 0xf4, 0xe5, 0x7c, 0x5e, 0x47, 0xdf, 0x75, 0x60, 0x3e, + 0x08, 0x0f, 0xa3, 0x49, 0x38, 0x20, 0x16, 0x5f, 0xf0, 0x54, 0x11, 0x55, 0x65, 0x4c, 0xa2, 0x17, + 0x8c, 0x15, 0x43, 0xe7, 0x80, 0xcb, 0xf0, 0x60, 0x92, 0x92, 0xbb, 0xa0, 0xbd, 0xc0, 0xf7, 0x60, + 0xc5, 0xc0, 0xe4, 0x6a, 0xbe, 0x05, 0xb3, 0x31, 0x02, 0x72, 0x29, 0x95, 0x72, 0x26, 0x3f, 0x43, + 0xd4, 0xb8, 0x6d, 0x58, 0xba, 0xcf, 0xb3, 0x07, 0xe1, 0x51, 0xa4, 0x5a, 0xfa, 0xe1, 0x0c, 0x2c, + 0x6b, 0x48, 0x36, 0x74, 0x03, 0x96, 0x83, 0x01, 0x0f, 0xb3, 0x20, 0x9b, 0xf6, 0xac, 0xf3, 0x4f, + 0x11, 0x46, 0xff, 0xcc, 0x1f, 0x05, 0x7e, 0x2a, 0x3d, 0x00, 0x51, 0x60, 0x9b, 0xb0, 0x86, 0xc6, + 0x43, 0xd9, 0x03, 0xbd, 0xc5, 0xe2, 0x18, 0x56, 0x59, 0x87, 0xe2, 0x8d, 0xb8, 0xd4, 0xdf, 0xfa, + 0x13, 0xe1, 0xa7, 0x54, 0x55, 0xe1, 0xaa, 0x89, 0x96, 0x70, 0xca, 0xb3, 0xc2, 0xc0, 0x68, 0xa0, + 0x14, 0x02, 0x9a, 0x13, 0xca, 0xa7, 0x18, 0x02, 0x32, 0xc2, 0x48, 0x0b, 0xa5, 0x30, 0x12, 0x2a, + 0xa7, 0x69, 0xd8, 0xe7, 0x83, 0x5e, 0x16, 0xf5, 0x48, 0x89, 0xd2, 0xee, 0x2c, 0x78, 0x45, 0x98, + 0x02, 0x5e, 0x3c, 0xcd, 0x42, 0x9e, 0x91, 0x9e, 0x59, 0xf0, 0x54, 0x11, 0xe5, 0x87, 0x48, 0x84, + 0x49, 0x68, 0x78, 0xb2, 0x84, 0x8e, 0xe6, 0x24, 0x09, 0xd2, 0x4e, 0x8b, 0x50, 0xfa, 0xcd, 0x3e, + 0x0d, 0x17, 0x0f, 0x79, 0x9a, 0xf5, 0x8e, 0xb9, 0x3f, 0xe0, 0x09, 0xed, 0xbe, 0x88, 0x4e, 0x09, + 0xfb, 0x5d, 0x5d, 0x89, 0x7d, 0x9f, 0xf0, 0x24, 0x0d, 0xa2, 0x90, 0x2c, 0x77, 0xc3, 0x53, 0x45, + 0xf7, 0x9b, 0xe4, 0x0f, 0xeb, 0xb8, 0xd9, 0x13, 0x32, 0xe6, 0xec, 0x35, 0x68, 0x88, 0x39, 0xa6, + 0xc7, 0xbe, 0x74, 0xd1, 0x17, 0x08, 0x38, 0x38, 0xf6, 0x51, 0x23, 0x58, 0xcb, 0x26, 0x02, 0x91, + 0x4d, 0xc2, 0x76, 0xc5, 0xaa, 0x5d, 0x83, 0x25, 0x15, 0x91, 0x4b, 0x7b, 0x23, 0x7e, 0x94, 0xa9, + 0xe3, 0x75, 0x38, 0x19, 0x63, 0x77, 0xe9, 0x1e, 0x3f, 0xca, 0xdc, 0x87, 0xb0, 0x22, 0x65, 0xf8, + 0x51, 0xcc, 0x55, 0xd7, 0x9f, 0xad, 0xb2, 0x6e, 0xcd, 0xcd, 0x55, 0x5b, 0xe8, 0x29, 0x46, 0x50, + 0x30, 0x79, 0xae, 0x07, 0xcc, 0xd4, 0x09, 0xb2, 0x41, 0x69, 0x62, 0xd4, 0x21, 0x5e, 0x4e, 0xc7, + 0xc2, 0x70, 0x7d, 0xd2, 0x49, 0xbf, 0x8f, 0x9a, 0x40, 0x68, 0x40, 0x55, 0x74, 0xbf, 0xe7, 0xc0, + 0x2a, 0xb5, 0xa6, 0xec, 0xb3, 0x3e, 0xf9, 0xbd, 0xfa, 0x30, 0x5b, 0x7d, 0x33, 0xb0, 0xb1, 0x06, + 0xb3, 0xa6, 0xae, 0x15, 0x85, 0x1f, 0xfd, 0x2c, 0x5b, 0x2f, 0x9d, 0x65, 0x7f, 0xe8, 0xc0, 0x8a, + 0x50, 0x86, 0x99, 0x9f, 0x4d, 0x52, 0x39, 0xfd, 0x9f, 0x87, 0x45, 0x61, 0xa7, 0xa4, 0x38, 0xc9, + 0x81, 0xae, 0x69, 0xc9, 0x27, 0x54, 0x10, 0xef, 0x5e, 0xf0, 0x6c, 0x62, 0xf6, 0x79, 0x68, 0x99, + 0x61, 0x55, 0x1a, 0x73, 0x73, 0xf3, 0xb2, 0x9a, 0x65, 0x89, 0x73, 0x76, 0x2f, 0x78, 0xd6, 0x07, + 0xec, 0x43, 0x72, 0x36, 0xc2, 0x1e, 0x35, 0x2b, 0x03, 0x53, 0x97, 0x2b, 0x14, 0xb8, 0xfe, 0xdc, + 0x20, 0xbf, 0xbb, 0x00, 0x73, 0xc2, 0xbb, 0x74, 0xef, 0xc3, 0xa2, 0x35, 0x52, 0xeb, 0x8c, 0xde, + 0x12, 0x67, 0xf4, 0x52, 0x48, 0xa7, 0x56, 0x0e, 0xe9, 0xb8, 0xff, 0x5a, 0x03, 0x86, 0xdc, 0x56, + 0xd8, 0x4e, 0x74, 0x6f, 0xa3, 0x81, 0x75, 0x58, 0x69, 0x79, 0x26, 0xc4, 0x6e, 0x02, 0x33, 0x8a, + 0x2a, 0xea, 0x25, 0xec, 0x46, 0x45, 0x0d, 0x2a, 0x38, 0x69, 0x58, 0xa5, 0x09, 0x94, 0xc7, 0x32, + 0xb1, 0x6f, 0x95, 0x75, 0x68, 0x1a, 0xe2, 0x49, 0x7a, 0x8c, 0xee, 0xb7, 0x3a, 0xce, 0xa8, 0x72, + 0x91, 0x41, 0xe6, 0xce, 0x65, 0x90, 0xf9, 0x22, 0x83, 0x98, 0x0e, 0xf5, 0x82, 0xe5, 0x50, 0xa3, + 0x23, 0x37, 0x46, 0xf7, 0x2f, 0x1b, 0xf5, 0x7b, 0x63, 0xec, 0x5d, 0x9e, 0x5e, 0x2c, 0x90, 0x6d, + 0x40, 0x5b, 0xba, 0x02, 0xb9, 0xd7, 0x0e, 0xb4, 0xc6, 0x25, 0xdc, 0xfd, 0x81, 0x03, 0x6d, 0x5c, + 0x67, 0x8b, 0x17, 0x3f, 0x00, 0x12, 0x85, 0x57, 0x64, 0x45, 0x8b, 0xf6, 0x27, 0xe7, 0xc4, 0xf7, + 0xa1, 0x41, 0x0d, 0x46, 0x31, 0x0f, 0x25, 0x23, 0x76, 0x6c, 0x46, 0xcc, 0xb5, 0xd0, 0xee, 0x05, + 0x2f, 0x27, 0x36, 0xd8, 0xf0, 0x1f, 0x1d, 0x68, 0xca, 0x61, 0xfe, 0xd8, 0x27, 0xf1, 0x2e, 0x2c, + 0x20, 0x47, 0x1a, 0xc7, 0x5d, 0x5d, 0x46, 0x6b, 0x32, 0xf6, 0xb3, 0x49, 0x82, 0xe6, 0xd3, 0x3a, + 0x85, 0x17, 0x61, 0xb4, 0x85, 0xa4, 0x70, 0xd3, 0x5e, 0x16, 0x8c, 0x7a, 0xaa, 0x56, 0xde, 0x62, + 0x54, 0x55, 0xa1, 0xde, 0x49, 0x33, 0x7f, 0xc8, 0xa5, 0x99, 0x13, 0x05, 0xb7, 0x03, 0xeb, 0x72, + 0x42, 0x05, 0xdf, 0xd1, 0xfd, 0xeb, 0x16, 0x5c, 0x2a, 0x55, 0xe9, 0x6b, 0x40, 0x79, 0xbc, 0x1c, + 0x05, 0xe3, 0xc3, 0x48, 0x3b, 0xda, 0x8e, 0x79, 0xf2, 0xb4, 0xaa, 0xd8, 0x10, 0x2e, 0x2a, 0x7b, + 0x8e, 0x6b, 0x9a, 0x5b, 0xef, 0x1a, 0x39, 0x22, 0xef, 0xda, 0x3c, 0x50, 0xec, 0x50, 0xe1, 0xa6, + 0xe4, 0x56, 0xb7, 0xc7, 0x8e, 0xa1, 0xa3, 0x1d, 0x07, 0xa9, 0xe2, 0x0d, 0xe7, 0x02, 0xfb, 0x7a, + 0xe7, 0x9c, 0xbe, 0x2c, 0x47, 0xd4, 0x3b, 0xb3, 0x35, 0x36, 0x85, 0x2b, 0xaa, 0x8e, 0x74, 0x78, + 0xb9, 0xbf, 0xfa, 0x2b, 0xcd, 0x8d, 0x9c, 0x68, 0xbb, 0xd3, 0x73, 0x1a, 0x66, 0x5f, 0x87, 0xf5, + 0x53, 0x3f, 0xc8, 0xd4, 0xb0, 0x0c, 0x67, 0x68, 0x96, 0xba, 0xdc, 0x3c, 0xa7, 0xcb, 0xa7, 0xe2, + 0x63, 0xcb, 0xb0, 0x9d, 0xd1, 0x62, 0xf7, 0xef, 0x1d, 0x58, 0xb2, 0xdb, 0x41, 0x36, 0x95, 0x02, + 0xaf, 0x14, 0x9f, 0x72, 0xfe, 0x0a, 0x70, 0xf9, 0xac, 0x5a, 0xab, 0x3a, 0xab, 0x9a, 0x27, 0xc4, + 0x99, 0xf3, 0xc2, 0x38, 0xf5, 0x57, 0x0b, 0xe3, 0xcc, 0x56, 0x85, 0x71, 0xba, 0xff, 0xed, 0x00, + 0x2b, 0xf3, 0x12, 0xbb, 0x2f, 0x0e, 0xcb, 0x21, 0x1f, 0x49, 0x9d, 0xf4, 0xb3, 0xaf, 0xc6, 0x8f, + 0x6a, 0xed, 0xd4, 0xd7, 0x28, 0x18, 0xa6, 0xd2, 0x31, 0x5d, 0xa4, 0x45, 0xaf, 0xaa, 0xaa, 0x10, + 0x58, 0xaa, 0x9f, 0x1f, 0x58, 0x9a, 0x3d, 0x3f, 0xb0, 0x34, 0x57, 0x0c, 0x2c, 0x75, 0x7f, 0xcb, + 0x81, 0xd5, 0x8a, 0x4d, 0xff, 0xe9, 0x4d, 0x1c, 0xb7, 0xc9, 0xd2, 0x05, 0x35, 0xb9, 0x4d, 0x26, + 0xd8, 0xfd, 0x35, 0x58, 0xb4, 0x18, 0xfd, 0xa7, 0xd7, 0x7f, 0xd1, 0xcb, 0x13, 0x7c, 0x66, 0x61, + 0xdd, 0xff, 0xa8, 0x01, 0x2b, 0x0b, 0xdb, 0xff, 0xeb, 0x18, 0xca, 0xeb, 0x34, 0x53, 0xb1, 0x4e, + 0xff, 0xa7, 0x76, 0xe0, 0x1d, 0x58, 0x91, 0x39, 0x03, 0x46, 0x88, 0x44, 0x70, 0x4c, 0xb9, 0x02, + 0xfd, 0x5c, 0x3b, 0xaa, 0xb7, 0x60, 0xdd, 0x35, 0x1b, 0xc6, 0xb0, 0x10, 0xdc, 0x73, 0xd7, 0x61, + 0x4d, 0xe4, 0x20, 0xdc, 0x15, 0x4d, 0x29, 0xbb, 0xf2, 0x47, 0x0e, 0x5c, 0x2c, 0x54, 0xe4, 0x37, + 0xa3, 0xc2, 0x74, 0xd8, 0xf6, 0xc4, 0x06, 0x71, 0xfc, 0x52, 0x8e, 0x8c, 0xf1, 0x0b, 0x6e, 0x2b, + 0x57, 0xe0, 0xfa, 0x4c, 0xc2, 0x32, 0xbd, 0x58, 0xf5, 0xaa, 0x2a, 0xf7, 0x92, 0xc8, 0x94, 0x08, + 0xf9, 0xa8, 0x30, 0xf0, 0x23, 0x91, 0xdb, 0x60, 0x56, 0xe4, 0x57, 0x2b, 0xf6, 0x90, 0x55, 0x11, + 0xbd, 0x40, 0xcb, 0x4c, 0xd9, 0xe3, 0xad, 0xac, 0x73, 0xff, 0xd2, 0x01, 0xf6, 0xa5, 0x09, 0x4f, + 0xa6, 0x74, 0x43, 0xaa, 0x63, 0x39, 0x97, 0x8a, 0x71, 0x8c, 0xb9, 0x78, 0x72, 0xf8, 0x45, 0x3e, + 0x55, 0xf7, 0xe8, 0xb5, 0xfc, 0x1e, 0xfd, 0x0d, 0x00, 0x3c, 0x7e, 0xe9, 0x6b, 0x57, 0xe4, 0x05, + 0x3c, 0xf7, 0x8a, 0x06, 0x2b, 0xaf, 0xba, 0xeb, 0xe7, 0x5f, 0x75, 0xcf, 0x9e, 0x77, 0xd5, 0xfd, + 0x21, 0xac, 0x5a, 0xe3, 0xd6, 0xdb, 0xaa, 0x2e, 0x80, 0x9d, 0x97, 0x5c, 0x00, 0xff, 0xa7, 0x03, + 0x33, 0xbb, 0x51, 0x6c, 0xc6, 0x2d, 0x1d, 0x3b, 0x6e, 0x29, 0x6d, 0x49, 0x4f, 0x9b, 0x0a, 0xa9, + 0x62, 0x2c, 0x90, 0x6d, 0xc0, 0x92, 0x3f, 0xce, 0xf0, 0xd8, 0x7d, 0x14, 0x25, 0xa7, 0x7e, 0x32, + 0x10, 0x7b, 0x7d, 0xb7, 0xd6, 0x71, 0xbc, 0x42, 0x0d, 0x5b, 0x83, 0x19, 0xad, 0x74, 0x89, 0x00, + 0x8b, 0xe8, 0xb8, 0xd1, 0x9d, 0xc7, 0x54, 0x46, 0x0c, 0x64, 0x09, 0x59, 0xc9, 0xfe, 0x5e, 0xb8, + 0xca, 0x42, 0x74, 0xaa, 0xaa, 0xd0, 0xae, 0xe1, 0xf2, 0x11, 0x99, 0x0c, 0xf5, 0xa8, 0xb2, 0xfb, + 0x6f, 0x0e, 0xcc, 0xd2, 0x0a, 0xa0, 0xb0, 0x0b, 0x0e, 0xd7, 0x01, 0x4a, 0x9a, 0xf9, 0xa2, 0x57, + 0x84, 0x99, 0x6b, 0xe5, 0x9b, 0xd4, 0xf4, 0xb0, 0xcd, 0x9c, 0x93, 0xab, 0xd0, 0x10, 0x25, 0x9d, + 0x5b, 0x41, 0x24, 0x39, 0xc8, 0xae, 0x40, 0xfd, 0x38, 0x8a, 0x95, 0x77, 0x02, 0x2a, 0x3e, 0x1f, + 0xc5, 0x1e, 0xe1, 0xf9, 0x78, 0xb0, 0x3d, 0x31, 0x78, 0x61, 0x73, 0x8a, 0x30, 0x5a, 0x5d, 0xdd, + 0xac, 0xb9, 0x18, 0x05, 0xd4, 0xdd, 0x80, 0xe5, 0x87, 0xd1, 0x80, 0x1b, 0x31, 0xa5, 0x33, 0xb9, + 0xd9, 0xfd, 0x96, 0x03, 0x0b, 0x8a, 0x98, 0xdd, 0x80, 0x3a, 0xba, 0x12, 0x85, 0x83, 0x82, 0xbe, + 0x97, 0x43, 0x3a, 0x8f, 0x28, 0x50, 0xf7, 0x52, 0xc4, 0x21, 0x77, 0x2b, 0x55, 0xbc, 0x21, 0xf7, + 0x9a, 0xf4, 0x70, 0x0b, 0xce, 0x46, 0x01, 0x75, 0xff, 0xcc, 0x81, 0x45, 0xab, 0x0f, 0x3c, 0x1e, + 0x8e, 0xfc, 0x34, 0x93, 0x77, 0x1d, 0x72, 0x7b, 0x4c, 0xc8, 0x8c, 0x32, 0xd6, 0xec, 0x28, 0xa3, + 0x8e, 0x7f, 0xcd, 0x98, 0xf1, 0xaf, 0xdb, 0xd0, 0xc8, 0xb3, 0x82, 0xea, 0x96, 0x4e, 0xc5, 0x1e, + 0xd5, 0x8d, 0x63, 0x4e, 0x84, 0xed, 0xf4, 0xa3, 0x51, 0x94, 0xc8, 0x20, 0xbb, 0x28, 0xb8, 0x1f, + 0x42, 0xd3, 0xa0, 0xc7, 0x61, 0x84, 0x3c, 0x3b, 0x8d, 0x92, 0x67, 0x2a, 0xd8, 0x29, 0x8b, 0xfa, + 0x62, 0xbd, 0x96, 0x5f, 0xac, 0xbb, 0x7f, 0xee, 0xc0, 0x22, 0xf2, 0x60, 0x10, 0x0e, 0xf7, 0xa3, + 0x51, 0xd0, 0x9f, 0xd2, 0xde, 0x2b, 0x76, 0x93, 0x9a, 0x41, 0xf1, 0xa2, 0x0d, 0x23, 0x6f, 0xab, + 0xd3, 0xa1, 0x14, 0x44, 0x5d, 0x46, 0x49, 0x45, 0x3e, 0x3f, 0xf4, 0x53, 0xc9, 0xfc, 0xd2, 0xc8, + 0x59, 0x20, 0xca, 0x13, 0x02, 0x89, 0x9f, 0xf1, 0xde, 0x38, 0x18, 0x8d, 0x02, 0x41, 0x2b, 0x5c, + 0xa0, 0xaa, 0x2a, 0xf7, 0xfb, 0x35, 0x68, 0x4a, 0x15, 0xbc, 0x33, 0x18, 0x72, 0x79, 0x93, 0x41, + 0x8e, 0xa4, 0x56, 0x17, 0x06, 0xa2, 0xea, 0x2d, 0xd7, 0xd3, 0x40, 0x8a, 0xdb, 0x3a, 0x53, 0xde, + 0xd6, 0xd7, 0xa1, 0x81, 0xec, 0xf5, 0x2e, 0xf9, 0xb8, 0xe2, 0x16, 0x24, 0x07, 0x54, 0xed, 0x26, + 0xd5, 0xce, 0xe6, 0xb5, 0x04, 0xbc, 0xf4, 0xde, 0xe3, 0x7d, 0x68, 0xc9, 0x66, 0x68, 0xdd, 0x49, + 0x3b, 0xe4, 0x0c, 0x6e, 0xed, 0x89, 0x67, 0x51, 0xaa, 0x2f, 0x37, 0xd5, 0x97, 0x0b, 0xe7, 0x7d, + 0xa9, 0x28, 0xe9, 0x8e, 0x5a, 0xac, 0xcd, 0xfd, 0xc4, 0x8f, 0x8f, 0x95, 0x59, 0x1b, 0xe8, 0xc4, + 0x1b, 0x82, 0xd9, 0x06, 0xcc, 0xe2, 0x67, 0x4a, 0x5b, 0x57, 0x0b, 0x9d, 0x20, 0x61, 0x37, 0x60, + 0x96, 0x0f, 0x86, 0x5c, 0x9d, 0xe2, 0x98, 0x7d, 0x9e, 0xc6, 0x3d, 0xf2, 0x04, 0x01, 0xaa, 0x00, + 0x44, 0x0b, 0x2a, 0xc0, 0xd6, 0xf4, 0x73, 0x58, 0x7c, 0x30, 0x70, 0xd7, 0x80, 0x3d, 0x14, 0x5c, + 0x6b, 0x46, 0xa1, 0x7f, 0x73, 0x06, 0x9a, 0x06, 0x8c, 0xd2, 0x3c, 0xc4, 0x01, 0xf7, 0x06, 0x81, + 0x3f, 0xe6, 0x19, 0x4f, 0x24, 0xa7, 0x16, 0x50, 0xa4, 0xf3, 0x4f, 0x86, 0xbd, 0x68, 0x92, 0xf5, + 0x06, 0x7c, 0x98, 0x70, 0x61, 0x7c, 0xd1, 0x18, 0x58, 0x28, 0xd2, 0x8d, 0xfd, 0xe7, 0x26, 0x9d, + 0xe0, 0x87, 0x02, 0xaa, 0x62, 0xca, 0x62, 0x8d, 0xea, 0x79, 0x4c, 0x59, 0xac, 0x48, 0x51, 0x0f, + 0xcd, 0x56, 0xe8, 0xa1, 0xf7, 0x60, 0x5d, 0x68, 0x1c, 0x29, 0x9b, 0xbd, 0x02, 0x9b, 0x9c, 0x51, + 0xcb, 0x36, 0xa0, 0x8d, 0x63, 0x56, 0x0c, 0x9e, 0x06, 0xdf, 0x14, 0x51, 0x1e, 0xc7, 0x2b, 0xe1, + 0x48, 0x8b, 0xe2, 0x68, 0xd1, 0x8a, 0x5b, 0xb3, 0x12, 0x4e, 0xb4, 0xfe, 0x73, 0x9b, 0xb6, 0x21, + 0x69, 0x0b, 0xb8, 0xbb, 0x08, 0xcd, 0x83, 0x2c, 0x8a, 0xd5, 0xa6, 0x2c, 0x41, 0x4b, 0x14, 0x65, + 0x8e, 0xc2, 0x6b, 0x70, 0x99, 0xb8, 0xe8, 0x71, 0x14, 0x47, 0xa3, 0x68, 0x38, 0x3d, 0x98, 0x1c, + 0xa6, 0xfd, 0x24, 0x88, 0xf1, 0xc4, 0xe3, 0xfe, 0x83, 0x03, 0xab, 0x56, 0xad, 0x0c, 0x0b, 0x7d, + 0x5a, 0xb0, 0xb4, 0xbe, 0x5c, 0x16, 0x8c, 0xb7, 0x62, 0xa8, 0x43, 0x41, 0x28, 0x02, 0x72, 0x4f, + 0xe4, 0x7d, 0xf3, 0x1d, 0x58, 0x56, 0x23, 0x53, 0x1f, 0x0a, 0x2e, 0xec, 0x94, 0xb9, 0x50, 0x7e, + 0xbf, 0x24, 0x3f, 0x50, 0x4d, 0xfc, 0x82, 0xbc, 0x7d, 0x1c, 0xd0, 0x1c, 0x55, 0x7c, 0x40, 0xdf, + 0x2f, 0x99, 0xa7, 0x04, 0x35, 0x82, 0xbe, 0x06, 0x53, 0xf7, 0x77, 0x1c, 0x80, 0x7c, 0x74, 0xc8, + 0x18, 0xb9, 0x4a, 0x17, 0x09, 0xd1, 0x86, 0xfa, 0x7e, 0x0b, 0x5a, 0xfa, 0x66, 0x24, 0xb7, 0x12, + 0x4d, 0x85, 0xa1, 0x23, 0x77, 0x1d, 0x96, 0x87, 0xa3, 0xe8, 0x90, 0x4c, 0x2c, 0x25, 0xbd, 0xa4, + 0x32, 0x53, 0x63, 0x49, 0xc0, 0xf7, 0x24, 0x9a, 0x9b, 0x94, 0xba, 0x61, 0x52, 0xdc, 0x6f, 0xd7, + 0x74, 0x3c, 0x3d, 0x9f, 0xf3, 0x99, 0x52, 0xc6, 0x36, 0x4b, 0xca, 0xf1, 0x8c, 0xf0, 0x35, 0x45, + 0xc2, 0xf6, 0xcf, 0x3d, 0xa8, 0x7f, 0x08, 0x4b, 0x89, 0xd0, 0x3e, 0x4a, 0x35, 0xd5, 0x5f, 0xa2, + 0x9a, 0x16, 0x13, 0xcb, 0xee, 0x7c, 0x12, 0xda, 0xfe, 0xe0, 0x84, 0x27, 0x59, 0x40, 0x47, 0x25, + 0x32, 0xfa, 0x42, 0xa1, 0x2e, 0x1b, 0x38, 0xd9, 0xe2, 0xeb, 0xb0, 0x2c, 0xb3, 0x63, 0x34, 0xa5, + 0xcc, 0xe8, 0xcc, 0x61, 0x24, 0x74, 0xff, 0x44, 0x85, 0xee, 0xed, 0x3d, 0x3c, 0x7b, 0x45, 0xcc, + 0xd9, 0xd5, 0x0a, 0xb3, 0xfb, 0x84, 0x0c, 0xa3, 0x0f, 0xd4, 0x79, 0x6c, 0xc6, 0xb8, 0xa9, 0x1e, + 0xc8, 0x6b, 0x0f, 0x7b, 0x49, 0xeb, 0xaf, 0xb2, 0xa4, 0xee, 0x0f, 0x1c, 0x98, 0xdf, 0x8d, 0xe2, + 0x5d, 0x79, 0x67, 0x4f, 0x82, 0xa0, 0x73, 0xcf, 0x54, 0xf1, 0x25, 0xb7, 0xf9, 0x95, 0xb6, 0x76, + 0xb1, 0x68, 0x6b, 0x7f, 0x11, 0x5e, 0xa3, 0x68, 0x40, 0x12, 0xc5, 0x51, 0x82, 0xc2, 0xe8, 0x8f, + 0x84, 0x61, 0x8d, 0xc2, 0xec, 0x58, 0xa9, 0xb1, 0x97, 0x91, 0xd0, 0xb1, 0x0b, 0x8f, 0x0b, 0xc2, + 0x19, 0x96, 0xbe, 0x81, 0xd0, 0x6e, 0xe5, 0x0a, 0xf7, 0xb3, 0xd0, 0x20, 0xe7, 0x96, 0xa6, 0xf5, + 0x0e, 0x34, 0x8e, 0xa3, 0xb8, 0x77, 0x1c, 0x84, 0x99, 0x12, 0xee, 0xa5, 0xdc, 0xeb, 0xdc, 0xa5, + 0x05, 0xd1, 0x04, 0xee, 0xdf, 0xd5, 0x61, 0xfe, 0x41, 0x78, 0x12, 0x05, 0x7d, 0x8a, 0xf2, 0x8f, + 0xf9, 0x38, 0x52, 0x99, 0x78, 0xf8, 0x1b, 0x97, 0x82, 0xb2, 0x52, 0xe2, 0x4c, 0x86, 0xe9, 0x55, + 0x11, 0xcd, 0x7d, 0x92, 0x67, 0xcb, 0x0a, 0xd1, 0x31, 0x10, 0x74, 0xec, 0x13, 0x33, 0xb1, 0x58, + 0x96, 0xf2, 0x54, 0xc6, 0x59, 0x23, 0x95, 0x91, 0xee, 0x84, 0x44, 0x7e, 0x01, 0xf1, 0xd7, 0x82, + 0xa7, 0x8a, 0x74, 0x10, 0x49, 0xb8, 0x88, 0xe2, 0x90, 0xe3, 0x30, 0x2f, 0x0f, 0x22, 0x26, 0x88, + 0xce, 0x85, 0xf8, 0x40, 0xd0, 0x08, 0xe5, 0x6b, 0x42, 0xe8, 0x6c, 0x15, 0x73, 0x93, 0x1b, 0x82, + 0xe7, 0x0b, 0x30, 0x6a, 0xe8, 0x01, 0xd7, 0x8a, 0x54, 0xcc, 0x01, 0x44, 0x36, 0x70, 0x11, 0x37, + 0x8e, 0x2f, 0x22, 0x71, 0x48, 0x1d, 0x5f, 0x90, 0x51, 0xfc, 0xd1, 0xe8, 0xd0, 0xef, 0x3f, 0xa3, + 0xd4, 0x73, 0xca, 0x13, 0x6a, 0x78, 0x36, 0x48, 0x19, 0x03, 0xf9, 0x6e, 0xd2, 0xad, 0x62, 0xdd, + 0x33, 0x21, 0xb6, 0x09, 0x4d, 0x3a, 0xb2, 0xc9, 0xfd, 0x5c, 0xa2, 0xfd, 0x6c, 0x9b, 0x67, 0x3a, + 0xda, 0x51, 0x93, 0xc8, 0xbc, 0x79, 0x58, 0xb6, 0x6f, 0x1e, 0x84, 0xd2, 0x94, 0x17, 0x36, 0x6d, + 0xea, 0x2d, 0x07, 0xd0, 0x9a, 0xca, 0x05, 0x13, 0x04, 0x2b, 0x44, 0x60, 0x61, 0x28, 0xb5, 0x78, + 0xd0, 0x88, 0xfd, 0x60, 0xd0, 0x61, 0x42, 0x6a, 0x55, 0xd9, 0xfd, 0x32, 0xb0, 0x3b, 0x83, 0x81, + 0xe4, 0x26, 0x7d, 0x20, 0xcd, 0xf9, 0xc0, 0xb1, 0xf8, 0xa0, 0x62, 0x3f, 0x6a, 0x95, 0xfb, 0xe1, + 0xee, 0x40, 0x73, 0xdf, 0x48, 0x89, 0x26, 0xc6, 0x53, 0xc9, 0xd0, 0x92, 0x59, 0x0d, 0xc4, 0xe8, + 0xb0, 0x66, 0x76, 0xe8, 0xfe, 0x1c, 0xb0, 0xbd, 0x20, 0xcd, 0xf4, 0xf8, 0xf2, 0x1c, 0x6c, 0x15, + 0x17, 0xc8, 0xd3, 0x98, 0x9a, 0x12, 0xa3, 0xf4, 0xa2, 0x3b, 0x22, 0xff, 0xa9, 0x38, 0xb1, 0x0d, + 0x58, 0x08, 0x04, 0x54, 0x94, 0x33, 0x45, 0xa9, 0xeb, 0xdd, 0xa7, 0xb0, 0x2a, 0x41, 0xd3, 0x46, + 0xdb, 0xfb, 0xe1, 0x9c, 0xb7, 0x1f, 0xb5, 0xf2, 0x7e, 0xb8, 0xdf, 0x77, 0x60, 0x5e, 0x2e, 0x0e, + 0xd2, 0x97, 0xd2, 0xc9, 0x1b, 0x9e, 0x85, 0x55, 0x27, 0x12, 0x97, 0x65, 0x6c, 0xa6, 0x4a, 0xc6, + 0x18, 0xd4, 0x63, 0x3f, 0x3b, 0xa6, 0x03, 0x54, 0xc3, 0xa3, 0xdf, 0xac, 0x2d, 0x0e, 0xf5, 0x42, + 0x96, 0xe9, 0x40, 0x5f, 0x95, 0x4b, 0x2f, 0x4c, 0x46, 0x09, 0x47, 0x27, 0x99, 0xb2, 0x26, 0x04, + 0xae, 0x2f, 0x43, 0x64, 0x3e, 0x57, 0x0e, 0xe7, 0x2b, 0x2e, 0x9b, 0x28, 0xae, 0xb8, 0x24, 0xf5, + 0x74, 0xbd, 0xdb, 0x85, 0xce, 0x36, 0x1f, 0xf1, 0x8c, 0xdf, 0x19, 0x8d, 0x8a, 0xed, 0xbf, 0x06, + 0x97, 0x2b, 0xea, 0xa4, 0x53, 0x75, 0x0f, 0x56, 0xb6, 0xf9, 0xe1, 0x64, 0xb8, 0xc7, 0x4f, 0xf2, + 0x1b, 0x4b, 0x06, 0xf5, 0xf4, 0x38, 0x3a, 0x95, 0xdc, 0x41, 0xbf, 0xd9, 0x1b, 0x00, 0x23, 0xa4, + 0xe9, 0xa5, 0x31, 0xef, 0xab, 0x14, 0x5a, 0x42, 0x0e, 0x62, 0xde, 0x77, 0xdf, 0x03, 0x66, 0xb6, + 0x23, 0xa7, 0x80, 0x7a, 0x6a, 0x72, 0xd8, 0x4b, 0xa7, 0x69, 0xc6, 0xc7, 0x2a, 0x37, 0xd8, 0x84, + 0xdc, 0xeb, 0xd0, 0xda, 0xf7, 0xa7, 0x1e, 0xff, 0x86, 0xcc, 0xe8, 0xc7, 0xb3, 0xbb, 0x3f, 0x45, + 0x61, 0xd0, 0x67, 0x77, 0xaa, 0x76, 0xff, 0xab, 0x06, 0x73, 0x82, 0x12, 0x5b, 0x1d, 0xf0, 0x34, + 0x0b, 0x42, 0x71, 0x5b, 0x27, 0x5b, 0x35, 0xa0, 0x12, 0x6f, 0xd4, 0x2a, 0x78, 0x43, 0x7a, 0xd3, + 0x2a, 0x1d, 0x51, 0x32, 0x81, 0x85, 0x21, 0xc7, 0xe6, 0x59, 0x10, 0xe2, 0xf0, 0x98, 0x03, 0x85, + 0x60, 0x4e, 0xae, 0x0d, 0xc5, 0xf8, 0x14, 0xdb, 0x4b, 0x76, 0x30, 0xa1, 0x4a, 0x9d, 0x3b, 0x2f, + 0xb8, 0xa6, 0xa4, 0x73, 0x4b, 0xba, 0x75, 0xe1, 0x15, 0x74, 0xab, 0x70, 0xb1, 0x5f, 0xa6, 0x5b, + 0xe1, 0x15, 0x74, 0xab, 0xcb, 0xa0, 0x7d, 0x8f, 0x73, 0x8f, 0xa3, 0xd5, 0x56, 0xec, 0xf4, 0x1d, + 0x07, 0xda, 0xd2, 0xe1, 0xd0, 0x75, 0xec, 0x2d, 0xcb, 0x3b, 0xa9, 0x4c, 0x1a, 0xbc, 0x06, 0x8b, + 0xe4, 0x33, 0xe8, 0xa8, 0x95, 0x0c, 0xb1, 0x59, 0x20, 0xce, 0x43, 0x5d, 0x2d, 0x8c, 0x83, 0x91, + 0xdc, 0x14, 0x13, 0x52, 0x81, 0x2f, 0x3c, 0xbf, 0xd3, 0x96, 0x38, 0x9e, 0x2e, 0xbb, 0x7f, 0xe5, + 0xc0, 0x8a, 0x31, 0x60, 0xc9, 0x85, 0x1f, 0x82, 0xca, 0x92, 0x10, 0xc1, 0x2d, 0x21, 0x4c, 0x97, + 0x6c, 0xe7, 0x29, 0xff, 0xcc, 0x22, 0xa6, 0xcd, 0xf4, 0xa7, 0x34, 0xc0, 0x74, 0x32, 0x96, 0x5a, + 0xc9, 0x84, 0x90, 0x91, 0x4e, 0x39, 0x7f, 0xa6, 0x49, 0x66, 0x84, 0xe2, 0x32, 0x31, 0xba, 0x04, + 0x47, 0x5f, 0x47, 0x13, 0x89, 0xbc, 0x2f, 0x1b, 0x74, 0xff, 0xc9, 0x81, 0x55, 0xe1, 0xb4, 0xca, + 0x23, 0x81, 0xce, 0xe8, 0x9e, 0x13, 0x5e, 0xba, 0x90, 0xc8, 0xdd, 0x0b, 0x9e, 0x2c, 0xb3, 0xcf, + 0xbc, 0xa2, 0xa3, 0xad, 0x93, 0x1f, 0xce, 0xd8, 0x8b, 0x99, 0xaa, 0xbd, 0x78, 0xc9, 0x4a, 0x57, + 0x05, 0x73, 0x66, 0x2b, 0x83, 0x39, 0x77, 0xe7, 0x61, 0x36, 0xed, 0x47, 0x31, 0x77, 0xd7, 0x61, + 0xcd, 0x9e, 0x9c, 0x54, 0x41, 0xdf, 0x75, 0xa0, 0x73, 0x4f, 0x84, 0x36, 0x83, 0x70, 0xb8, 0x1b, + 0xa4, 0x59, 0x94, 0xe8, 0x27, 0x2c, 0x57, 0x00, 0xd2, 0xcc, 0x4f, 0x32, 0x91, 0x9c, 0x26, 0xc3, + 0x30, 0x39, 0x82, 0x63, 0xe4, 0xe1, 0x40, 0xd4, 0x8a, 0xbd, 0xd1, 0x65, 0xdc, 0x18, 0x32, 0x1b, + 0xbd, 0xe8, 0xe8, 0x28, 0xe5, 0xda, 0xad, 0x36, 0x31, 0x3c, 0x99, 0xa3, 0xc4, 0xe3, 0x59, 0x94, + 0x9f, 0x90, 0xaa, 0x15, 0xfe, 0x6a, 0x01, 0x75, 0xff, 0xc2, 0x81, 0xe5, 0x7c, 0x90, 0x3b, 0x08, + 0xda, 0xda, 0x41, 0xda, 0xb3, 0x5c, 0x3b, 0xa8, 0x00, 0x51, 0x80, 0x06, 0x4e, 0x8e, 0xcd, 0x40, + 0x48, 0x62, 0x65, 0x29, 0x9a, 0xa8, 0x44, 0x40, 0x13, 0x12, 0xb7, 0xfc, 0x19, 0x7e, 0x2d, 0xb2, + 0x00, 0x65, 0x89, 0x72, 0x0b, 0xc7, 0x19, 0x7d, 0x35, 0x27, 0x1c, 0x76, 0x59, 0x54, 0xf6, 0x69, + 0x9e, 0x50, 0xfc, 0xe9, 0xfe, 0xae, 0x03, 0x97, 0x2b, 0x16, 0x57, 0x4a, 0xc6, 0x36, 0xac, 0x1c, + 0xe9, 0x4a, 0xb5, 0x00, 0x42, 0x3c, 0xd6, 0x55, 0x2c, 0xde, 0x9e, 0xb4, 0x57, 0xfe, 0x00, 0xdd, + 0x77, 0x8a, 0x6b, 0x89, 0x25, 0xb5, 0x12, 0x64, 0xca, 0x15, 0x9b, 0xbf, 0x37, 0x03, 0x4b, 0xe2, + 0x8e, 0x46, 0x3c, 0x26, 0xe5, 0x09, 0xfb, 0x08, 0xe6, 0xe5, 0x63, 0x60, 0x76, 0x51, 0x76, 0x6b, + 0x3f, 0x3f, 0xee, 0xae, 0x17, 0x61, 0xc9, 0x3b, 0xab, 0xbf, 0xf1, 0x83, 0x7f, 0xf9, 0xfd, 0xda, + 0x22, 0x6b, 0xde, 0x3a, 0x79, 0xf7, 0xd6, 0x90, 0x87, 0x29, 0xb6, 0xf1, 0x2b, 0x00, 0xf9, 0x33, + 0x59, 0xd6, 0xd1, 0x6e, 0x4a, 0xe1, 0xfd, 0x6f, 0xf7, 0x72, 0x45, 0x8d, 0x6c, 0xf7, 0x32, 0xb5, + 0xbb, 0xea, 0x2e, 0x61, 0xbb, 0x41, 0x18, 0x64, 0xe2, 0xcd, 0xec, 0x07, 0xce, 0x06, 0x1b, 0x40, + 0xcb, 0x7c, 0x05, 0xcb, 0xd4, 0x91, 0xbe, 0xe2, 0x0d, 0x6e, 0xf7, 0xb5, 0xca, 0x3a, 0x15, 0xcf, + 0xa0, 0x3e, 0x2e, 0xba, 0x6d, 0xec, 0x63, 0x42, 0x14, 0x79, 0x2f, 0x23, 0x58, 0xb2, 0x1f, 0xbb, + 0xb2, 0xd7, 0x0d, 0xb1, 0x2e, 0x3d, 0xb5, 0xed, 0xbe, 0x71, 0x46, 0xad, 0xec, 0xeb, 0x0d, 0xea, + 0xeb, 0x92, 0xcb, 0xb0, 0xaf, 0x3e, 0xd1, 0xa8, 0xa7, 0xb6, 0x1f, 0x38, 0x1b, 0x9b, 0xdf, 0xba, + 0x02, 0x0d, 0x1d, 0x84, 0x63, 0x5f, 0x87, 0x45, 0xeb, 0x12, 0x8d, 0xa9, 0x69, 0x54, 0xdd, 0xb9, + 0x75, 0x5f, 0xaf, 0xae, 0x94, 0x1d, 0x5f, 0xa1, 0x8e, 0x3b, 0x6c, 0x1d, 0x3b, 0x96, 0xb7, 0x50, + 0xb7, 0xe8, 0xea, 0x50, 0x64, 0x2e, 0x3e, 0x13, 0xf3, 0xcc, 0x2f, 0xbe, 0xac, 0x79, 0x96, 0x2e, + 0xca, 0xac, 0x79, 0x96, 0x6f, 0xcb, 0xdc, 0xd7, 0xa9, 0xbb, 0x75, 0xb6, 0x66, 0x76, 0xa7, 0x83, + 0x63, 0x9c, 0x72, 0x4d, 0xcd, 0xb7, 0xb0, 0xec, 0x0d, 0xcd, 0x58, 0x55, 0x6f, 0x64, 0x35, 0x8b, + 0x94, 0x1f, 0xca, 0xba, 0x1d, 0xea, 0x8a, 0x31, 0xda, 0x3e, 0xf3, 0x29, 0x2c, 0xfb, 0x2a, 0x34, + 0xf4, 0xc3, 0x2f, 0x76, 0xc9, 0x78, 0x6d, 0x67, 0xbe, 0x46, 0xeb, 0x76, 0xca, 0x15, 0x55, 0x8c, + 0x61, 0xb6, 0x8c, 0x8c, 0xb1, 0x07, 0x17, 0xa5, 0x53, 0x7d, 0xc8, 0x7f, 0x94, 0x99, 0x54, 0xbc, + 0xe0, 0xbd, 0xed, 0xb0, 0x0f, 0x61, 0x41, 0xbd, 0xa7, 0x63, 0xeb, 0xd5, 0xef, 0x02, 0xbb, 0x97, + 0x4a, 0xb8, 0xd4, 0x1e, 0x77, 0x00, 0xf2, 0xb7, 0x60, 0x5a, 0xce, 0x4a, 0x2f, 0xd4, 0xf4, 0x22, + 0x56, 0x3c, 0x1c, 0x1b, 0xd2, 0xcb, 0x37, 0xfb, 0xa9, 0x19, 0x7b, 0x33, 0xa7, 0xaf, 0x7c, 0x84, + 0xf6, 0x92, 0x06, 0xdd, 0x75, 0x5a, 0xbb, 0x36, 0x23, 0xc1, 0x0d, 0xf9, 0xa9, 0xca, 0xba, 0xde, + 0x86, 0xa6, 0xf1, 0xbe, 0x8c, 0xa9, 0x16, 0xca, 0x6f, 0xd3, 0xba, 0xdd, 0xaa, 0x2a, 0x39, 0xdc, + 0x2f, 0xc0, 0xa2, 0xf5, 0x50, 0x4c, 0x4b, 0x46, 0xd5, 0x33, 0x34, 0x2d, 0x19, 0xd5, 0x6f, 0xcb, + 0xbe, 0x02, 0x4d, 0xe3, 0x59, 0x17, 0x33, 0xb2, 0xcd, 0x0a, 0x0f, 0xba, 0xf4, 0x88, 0xaa, 0x5e, + 0x81, 0xad, 0xd1, 0x7c, 0x97, 0xdc, 0x06, 0xce, 0x97, 0x52, 0x8f, 0x91, 0x49, 0xbe, 0x0e, 0x4b, + 0xf6, 0x43, 0x2f, 0x2d, 0x55, 0x95, 0x4f, 0xc6, 0xb4, 0x54, 0x9d, 0xf1, 0x3a, 0x4c, 0x32, 0xe4, + 0xc6, 0xaa, 0xee, 0xe4, 0xd6, 0xc7, 0xf2, 0x0a, 0xea, 0x05, 0xfb, 0x12, 0xaa, 0x0e, 0x99, 0x0b, + 0xce, 0xf2, 0xe7, 0x6d, 0x76, 0xc6, 0xb8, 0xe6, 0xf6, 0x52, 0xda, 0xb8, 0xbb, 0x42, 0x8d, 0x37, + 0x59, 0x3e, 0x03, 0x61, 0x0f, 0x28, 0x27, 0xdc, 0xb0, 0x07, 0x66, 0xda, 0xb8, 0x61, 0x0f, 0xac, + 0xd4, 0xf1, 0xa2, 0x3d, 0xc8, 0x02, 0x6c, 0x23, 0x84, 0xe5, 0x42, 0xba, 0x85, 0x16, 0x96, 0xea, + 0xfc, 0xb4, 0xee, 0x95, 0x97, 0x67, 0x69, 0xd8, 0x6a, 0x46, 0xa9, 0x97, 0x5b, 0x2a, 0x9d, 0xf0, + 0x57, 0xa1, 0x65, 0x3e, 0xd0, 0xd1, 0x16, 0xa2, 0xe2, 0x59, 0x91, 0xb6, 0x10, 0x55, 0x2f, 0x7a, + 0xd4, 0xe6, 0xb2, 0x96, 0xd9, 0x0d, 0x6e, 0xae, 0xfd, 0x9e, 0x21, 0x57, 0x99, 0x55, 0x0f, 0x35, + 0x72, 0x95, 0x59, 0xf9, 0x08, 0x42, 0x6d, 0x2e, 0x5b, 0xb5, 0xe6, 0x22, 0x62, 0x8f, 0xec, 0x2b, + 0xb0, 0x6c, 0xe4, 0x32, 0x1d, 0x4c, 0xc3, 0xbe, 0x66, 0xd4, 0x72, 0xa6, 0x6b, 0xb7, 0xca, 0xf3, + 0x74, 0x2f, 0x51, 0xfb, 0x2b, 0xae, 0x35, 0x09, 0x64, 0xd2, 0x2d, 0x68, 0x9a, 0x79, 0x52, 0x2f, + 0x69, 0xf7, 0x92, 0x51, 0x65, 0x26, 0x7d, 0xde, 0x76, 0xd8, 0x1f, 0x3a, 0xd0, 0xb2, 0xb2, 0x8e, + 0xac, 0x08, 0x7b, 0xa1, 0x9d, 0x8e, 0x59, 0x67, 0x36, 0xe4, 0x7a, 0x34, 0xc8, 0xbd, 0x8d, 0x2f, + 0x58, 0x8b, 0xf0, 0xb1, 0x75, 0x82, 0xb9, 0x59, 0x7c, 0xe7, 0xfd, 0xa2, 0x48, 0x60, 0x66, 0x03, + 0xbf, 0xb8, 0xed, 0xb0, 0x0f, 0xc4, 0x3f, 0x19, 0xa8, 0x88, 0x05, 0x33, 0x14, 0x69, 0x71, 0xc9, + 0xcc, 0x67, 0xfc, 0x37, 0x9c, 0xdb, 0x0e, 0xfb, 0x9a, 0x78, 0xce, 0x2d, 0xbf, 0xa5, 0x95, 0x7f, + 0xd5, 0xef, 0xdd, 0x6b, 0x34, 0x9b, 0x2b, 0xee, 0x65, 0x6b, 0x36, 0x45, 0x4b, 0x72, 0x47, 0x8c, + 0x4e, 0xbe, 0xd2, 0xcf, 0x55, 0x62, 0xe9, 0xe5, 0xfe, 0xd9, 0x83, 0x1c, 0x8b, 0x41, 0x4a, 0x72, + 0x8b, 0x3d, 0x5e, 0xb1, 0x19, 0x77, 0x83, 0xc6, 0x7a, 0xcd, 0x7d, 0xf3, 0xcc, 0xb1, 0xde, 0xa2, + 0x13, 0x29, 0x8e, 0x78, 0x1f, 0x20, 0x0f, 0xb9, 0xb1, 0x42, 0xfc, 0x49, 0x5b, 0x85, 0x72, 0x54, + 0xce, 0xe6, 0x41, 0x15, 0xa6, 0xc2, 0x16, 0xbf, 0x2a, 0x44, 0x55, 0xd2, 0xa7, 0x7a, 0xf4, 0xe5, + 0xd0, 0x59, 0xb7, 0x5b, 0x55, 0x55, 0x25, 0xa8, 0xaa, 0x7d, 0xf6, 0x04, 0x16, 0xf7, 0xa2, 0xe8, + 0xd9, 0x24, 0xd6, 0x21, 0x67, 0x3b, 0x7e, 0xb3, 0xeb, 0xa7, 0xc7, 0xdd, 0xc2, 0x2c, 0xdc, 0xab, + 0xd4, 0x54, 0x97, 0x75, 0x8c, 0xa6, 0x6e, 0x7d, 0x9c, 0x07, 0xfc, 0x5e, 0x30, 0x1f, 0x56, 0xb4, + 0x07, 0xa0, 0x07, 0xde, 0xb5, 0x9b, 0x31, 0xe3, 0x6e, 0xa5, 0x2e, 0x2c, 0x9f, 0x4c, 0x8d, 0xf6, + 0x56, 0xaa, 0xda, 0xbc, 0xed, 0xb0, 0x7d, 0x68, 0x6d, 0xf3, 0x7e, 0x34, 0xe0, 0x32, 0xe2, 0xb2, + 0x9a, 0x0f, 0x5c, 0x87, 0x6a, 0xba, 0x8b, 0x16, 0x68, 0xeb, 0xc4, 0xd8, 0x9f, 0x26, 0xfc, 0x1b, + 0xb7, 0x3e, 0x96, 0xb1, 0x9c, 0x17, 0x4a, 0x27, 0xaa, 0xf8, 0x93, 0xa5, 0x13, 0x0b, 0x01, 0x2b, + 0x4b, 0x27, 0x96, 0x02, 0x56, 0xd6, 0x52, 0xab, 0xf8, 0x17, 0x1b, 0xc1, 0x4a, 0x29, 0xc6, 0xa5, + 0xfd, 0x88, 0xb3, 0x22, 0x63, 0xdd, 0xab, 0x67, 0x13, 0xd8, 0xbd, 0x6d, 0xd8, 0xbd, 0x1d, 0xc0, + 0xe2, 0x36, 0x17, 0x8b, 0x25, 0xee, 0xb5, 0x0b, 0xcf, 0xc6, 0xcc, 0x3b, 0xf0, 0xa2, 0x52, 0xa4, + 0x3a, 0xdb, 0xe8, 0xd1, 0xa5, 0x32, 0xfb, 0x2a, 0x34, 0xef, 0xf3, 0x4c, 0x5d, 0x64, 0x6b, 0x6f, + 0xac, 0x70, 0xb3, 0xdd, 0xad, 0xb8, 0x07, 0xb7, 0x79, 0x86, 0x5a, 0xbb, 0xc5, 0x07, 0x43, 0x2e, + 0xd4, 0x53, 0x2f, 0x18, 0xbc, 0x60, 0xbf, 0x44, 0x8d, 0xeb, 0xdc, 0x97, 0x75, 0xe3, 0xfe, 0xd3, + 0x6c, 0x7c, 0xb9, 0x80, 0x57, 0xb5, 0x1c, 0x46, 0x03, 0x6e, 0x98, 0xff, 0x10, 0x9a, 0x46, 0x62, + 0x96, 0x16, 0xa0, 0x72, 0x92, 0x99, 0x16, 0xa0, 0x8a, 0x3c, 0x2e, 0xf7, 0x06, 0xf5, 0xe3, 0xb2, + 0xab, 0x79, 0x3f, 0x22, 0x77, 0x2b, 0xef, 0xe9, 0xd6, 0xc7, 0xfe, 0x38, 0x7b, 0xc1, 0x9e, 0xd2, + 0x13, 0x32, 0xf3, 0xb2, 0x3e, 0xf7, 0x06, 0x8b, 0xf7, 0xfa, 0x7a, 0xb1, 0x8c, 0x2a, 0xdb, 0x43, + 0x14, 0x5d, 0x91, 0x97, 0xf0, 0x19, 0x80, 0x83, 0x2c, 0x8a, 0xb7, 0x7d, 0x3e, 0x8e, 0xc2, 0x5c, + 0xd7, 0xe6, 0x17, 0xd2, 0xb9, 0xfe, 0x32, 0x6e, 0xa5, 0xd9, 0x53, 0xc3, 0x1f, 0xb7, 0x72, 0x1d, + 0x14, 0x73, 0x9d, 0x79, 0x67, 0xad, 0x17, 0xa4, 0xe2, 0xde, 0xfa, 0xb6, 0x83, 0xde, 0x75, 0x1e, + 0x51, 0xd5, 0xde, 0x75, 0x29, 0x58, 0xab, 0xd5, 0x5e, 0x45, 0xf8, 0x75, 0x1f, 0x1a, 0x79, 0x88, + 0xee, 0x52, 0x9e, 0x5c, 0x67, 0x05, 0xf4, 0xb4, 0x55, 0x2c, 0x05, 0xce, 0xdc, 0x36, 0x2d, 0x15, + 0xb0, 0x05, 0x5c, 0x2a, 0x8a, 0x86, 0x05, 0xb0, 0x2a, 0x06, 0xa8, 0x4d, 0x3c, 0x5d, 0xb1, 0xaa, + 0x99, 0x54, 0x04, 0xaf, 0xb4, 0x34, 0x57, 0xc6, 0x7e, 0xac, 0x73, 0x36, 0x72, 0xab, 0xb8, 0xde, + 0x45, 0xd5, 0x3c, 0x86, 0x95, 0x52, 0xe0, 0x42, 0x8b, 0xf4, 0x59, 0xf1, 0x22, 0x2d, 0xd2, 0x67, + 0xc6, 0x3c, 0xdc, 0x8b, 0xd4, 0xe5, 0xb2, 0x0b, 0xd8, 0x65, 0x7a, 0x1a, 0x64, 0xfd, 0xe3, 0x0f, + 0x9c, 0x8d, 0xc3, 0x39, 0xfa, 0xe7, 0xb3, 0x4f, 0xfd, 0x6f, 0x00, 0x00, 0x00, 0xff, 0xff, 0x29, + 0x75, 0x77, 0x36, 0x2b, 0x4d, 0x00, 0x00, } diff --git a/lnrpc/rpc.pb.gw.go b/lnrpc/rpc.pb.gw.go index 5a509b7a..2646dde9 100644 --- a/lnrpc/rpc.pb.gw.go +++ b/lnrpc/rpc.pb.gw.go @@ -392,10 +392,18 @@ func request_Lightning_LookupInvoice_0(ctx context.Context, marshaler runtime.Ma } +var ( + filter_Lightning_SubscribeInvoices_0 = &utilities.DoubleArray{Encoding: map[string]int{}, Base: []int(nil), Check: []int(nil)} +) + func request_Lightning_SubscribeInvoices_0(ctx context.Context, marshaler runtime.Marshaler, client LightningClient, req *http.Request, pathParams map[string]string) (Lightning_SubscribeInvoicesClient, runtime.ServerMetadata, error) { var protoReq InvoiceSubscription var metadata runtime.ServerMetadata + if err := runtime.PopulateQueryParameters(&protoReq, req.URL.Query(), filter_Lightning_SubscribeInvoices_0); err != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) + } + stream, err := client.SubscribeInvoices(ctx, &protoReq) if err != nil { return nil, metadata, err diff --git a/lnrpc/rpc.proto b/lnrpc/rpc.proto index 05e706fb..19a3cf7e 100644 --- a/lnrpc/rpc.proto +++ b/lnrpc/rpc.proto @@ -464,7 +464,14 @@ service Lightning { /** SubscribeInvoices returns a uni-directional stream (sever -> client) for - notifying the client of newly added/settled invoices. + notifying the client of newly added/settled invoices. The caller can + optionally specify the add_index and/or the settle_index. If the add_index + is specified, then we'll first start by sending add invoice events for all + invoices with an add_index greater than the specified value. If the + settle_index is specified, the next, we'll send out all settle events for + invoices with a settle_index greater than the specified value. One or both + of these fields can be set. If no fields are set, then we'll only send out + the latest add/settle events. */ rpc SubscribeInvoices (InvoiceSubscription) returns (stream Invoice) { option (google.api.http) = { @@ -1642,6 +1649,32 @@ message Invoice { /// Whether this invoice should include routing hints for private channels. bool private = 15 [json_name = "private"]; + + /** + The "add" index of this invoice. Each newly created invoice will increment + this index making it monotonically increasing. Callers to the + SubscribeInvoices call can use this to instantly get notified of all added + invoices with an add_index greater than this one. + */ + uint64 add_index = 16 [json_name = "add_index"]; + + /** + The "settle" index of this invoice. Each newly settled invoice will + increment this index making it monotonically increasing. Callers to the + SubscribeInvoices call can use this to instantly get notified of all + settled invoices with an settle_index greater than this one. + */ + uint64 settle_index = 17 [json_name = "settle_index"]; + + /** + The amount that was accepted for this invoice. This will ONLY be set if + this invoice has been settled. We provide this field as if the invoice was + created with a zero value, then we need to record what amount was + ultimately accepted. Additionally, it's possible that the sender paid MORE + that was specified in the original invoice. So we'll record that here as + well. + */ + int64 amt_paid = 18 [json_name = "amt_paid"]; } message AddInvoiceResponse { bytes r_hash = 1 [json_name = "r_hash"]; @@ -1672,6 +1705,21 @@ message ListInvoiceResponse { } message InvoiceSubscription { + /** + If specified (non-zero), then we'll first start by sending out + notifications for all added indexes with an add_index greater than this + value. This allows callers to catch up on any events they missed while they + weren't connected to the streaming RPC. + */ + uint64 add_index = 1 [json_name = "add_index"]; + + /** + If specified (non-zero), then we'll first start by sending out + notifications for all settled indexes with an settle_index greater than + this value. This allows callers to catch up on any events they missed while + they weren't connected to the streaming RPC. + */ + uint64 settle_index = 2 [json_name = "settle_index"]; } diff --git a/lnrpc/rpc.swagger.json b/lnrpc/rpc.swagger.json index 64bd3411..3187fa0c 100644 --- a/lnrpc/rpc.swagger.json +++ b/lnrpc/rpc.swagger.json @@ -666,7 +666,7 @@ }, "/v1/invoices/subscribe": { "get": { - "summary": "*\nSubscribeInvoices returns a uni-directional stream (sever -\u003e client) for\nnotifying the client of newly added/settled invoices.", + "summary": "*\nSubscribeInvoices returns a uni-directional stream (sever -\u003e client) for\nnotifying the client of newly added/settled invoices. The caller can\noptionally specify the add_index and/or the settle_index. If the add_index\nis specified, then we'll first start by sending add invoice events for all\ninvoices with an add_index greater than the specified value. If the\nsettle_index is specified, the next, we'll send out all settle events for\ninvoices with a settle_index greater than the specified value. One or both\nof these fields can be set. If no fields are set, then we'll only send out\nthe latest add/settle events.", "operationId": "SubscribeInvoices", "responses": { "200": { @@ -676,6 +676,24 @@ } } }, + "parameters": [ + { + "name": "add_index", + "description": "*\nIf specified (non-zero), then we'll first start by sending out\nnotifications for all added indexes with an add_index greater than this\nvalue. This allows callers to catch up on any events they missed while they\nweren't connected to the streaming RPC.", + "in": "query", + "required": false, + "type": "string", + "format": "uint64" + }, + { + "name": "settle_index", + "description": "*\nIf specified (non-zero), then we'll first start by sending out\nnotifications for all settled indexes with an settle_index greater than\nthis value. This allows callers to catch up on any events they missed while\nthey weren't connected to the streaming RPC.", + "in": "query", + "required": false, + "type": "string", + "format": "uint64" + } + ], "tags": [ "Lightning" ] @@ -1893,6 +1911,21 @@ "type": "boolean", "format": "boolean", "description": "/ Whether this invoice should include routing hints for private channels." + }, + "add_index": { + "type": "string", + "format": "uint64", + "description": "*\nThe \"add\" index of this invoice. Each newly created invoice will increment\nthis index making it monotonically increasing. Callers to the\nSubscribeInvoices call can use this to instantly get notified of all added\ninvoices with an add_index greater than this one." + }, + "settle_index": { + "type": "string", + "format": "uint64", + "description": "*\nThe \"settle\" index of this invoice. Each newly settled invoice will\nincrement this index making it monotonically increasing. Callers to the\nSubscribeInvoices call can use this to instantly get notified of all\nsettled invoices with an settle_index greater than this one." + }, + "amt_paid": { + "type": "string", + "format": "int64", + "description": "*\nThe amount that was accepted for this invoice. This will ONLY be set if\nthis invoice has been settled. We provide this field as if the invoice was\ncreated with a zero value, then we need to record what amount was\nultimately accepted. Additionally, it's possible that the sender paid MORE\nthat was specified in the original invoice. So we'll record that here as\nwell." } } }, From a32f2b79da319a166535f744b9fd6e813f2198b2 Mon Sep 17 00:00:00 2001 From: Olaoluwa Osuntokun Date: Tue, 24 Apr 2018 20:43:55 -0700 Subject: [PATCH 04/26] htlcswitch: modify the InvoiceDatabase interface to allow specifying final payment amt In this commit, we modify the InvoiceDatabase slightly to allow the link to record what the final payment about for an invoice was. It may be the case that the invoice actually had no specified value, or that the payer paid more than necessary. As a result, it's important that our on-disk records properly reflect this. To fix this issue, the SettleInvoice method now also accepts the final amount paid. Fixes #856. --- htlcswitch/interfaces.go | 2 +- htlcswitch/link.go | 12 ++++++++---- htlcswitch/link_test.go | 7 +++++++ htlcswitch/mock.go | 5 ++++- 4 files changed, 20 insertions(+), 6 deletions(-) diff --git a/htlcswitch/interfaces.go b/htlcswitch/interfaces.go index 196dad4c..60c400f0 100644 --- a/htlcswitch/interfaces.go +++ b/htlcswitch/interfaces.go @@ -18,7 +18,7 @@ type InvoiceDatabase interface { // SettleInvoice attempts to mark an invoice corresponding to the // passed payment hash as fully settled. - SettleInvoice(chainhash.Hash) error + SettleInvoice(payHash chainhash.Hash, paidAmount lnwire.MilliSatoshi) error } // ChannelLink is an interface which represents the subsystem for managing the diff --git a/htlcswitch/link.go b/htlcswitch/link.go index 68261ca2..467a8a73 100644 --- a/htlcswitch/link.go +++ b/htlcswitch/link.go @@ -2298,8 +2298,9 @@ func (l *channelLink) processRemoteAdds(fwdPkg *channeldb.FwdPkg, } preimage := invoice.Terms.PaymentPreimage - err = l.channel.SettleHTLC(preimage, - pd.HtlcIndex, pd.SourceRef, nil, nil) + err = l.channel.SettleHTLC( + preimage, pd.HtlcIndex, pd.SourceRef, nil, nil, + ) if err != nil { l.fail(LinkFailureError{code: ErrInternalError}, "unable to settle htlc: %v", err) @@ -2307,8 +2308,11 @@ func (l *channelLink) processRemoteAdds(fwdPkg *channeldb.FwdPkg, } // Notify the invoiceRegistry of the invoices we just - // settled with this latest commitment update. - err = l.cfg.Registry.SettleInvoice(invoiceHash) + // settled (with the amount accepted at settle time) + // with this latest commitment update. + err = l.cfg.Registry.SettleInvoice( + invoiceHash, pd.Amount, + ) if err != nil { l.fail(LinkFailureError{code: ErrInternalError}, "unable to settle invoice: %v", err) diff --git a/htlcswitch/link_test.go b/htlcswitch/link_test.go index c42c6e50..a97da231 100644 --- a/htlcswitch/link_test.go +++ b/htlcswitch/link_test.go @@ -3657,6 +3657,13 @@ func TestChannelLinkAcceptOverpay(t *testing.T) { t.Fatalf("channel bandwidth incorrect: expected %v, got %v", expectedCarolBandwidth, n.carolChannelLink.Bandwidth()) } + + // Finally, we'll ensure that the amount we paid is properly reflected + // in the stored invoice. + if invoice.AmtPaid != amount { + t.Fatalf("expected amt paid to be %v, is instead %v", amount, + invoice.AmtPaid) + } } // chanRestoreFunc is a method signature for functions that can reload both diff --git a/htlcswitch/mock.go b/htlcswitch/mock.go index e0016188..7f86c049 100644 --- a/htlcswitch/mock.go +++ b/htlcswitch/mock.go @@ -673,7 +673,9 @@ func (i *mockInvoiceRegistry) LookupInvoice(rHash chainhash.Hash) (channeldb.Inv return invoice, i.finalDelta, nil } -func (i *mockInvoiceRegistry) SettleInvoice(rhash chainhash.Hash) error { +func (i *mockInvoiceRegistry) SettleInvoice(rhash chainhash.Hash, + amt lnwire.MilliSatoshi) error { + i.Lock() defer i.Unlock() @@ -687,6 +689,7 @@ func (i *mockInvoiceRegistry) SettleInvoice(rhash chainhash.Hash) error { } invoice.Terms.Settled = true + invoice.AmtPaid = amt i.invoices[rhash] = invoice return nil From fc0f0d33b28cd4e43d3132a10e8dc9f6e55e157b Mon Sep 17 00:00:00 2001 From: Olaoluwa Osuntokun Date: Tue, 24 Apr 2018 20:50:56 -0700 Subject: [PATCH 05/26] channeldb: add new AmtPaid field to the Invoice struct In this commit, in order to allow the caller to specify the amount that was ultimately accepted for an invoice, the SettleInvoice method has gained a new parameter: amtPaid. SettleInvoice will now populate the final amount paid in the database upon db commit. --- channeldb/invoices.go | 37 +++++++++++++++++++++++++++---------- 1 file changed, 27 insertions(+), 10 deletions(-) diff --git a/channeldb/invoices.go b/channeldb/invoices.go index 8d2c585f..9d4a7172 100644 --- a/channeldb/invoices.go +++ b/channeldb/invoices.go @@ -26,6 +26,8 @@ var ( // index is used to detect duplicates, and also to provide a fast path // for looking up incoming HTLCs to determine if we're able to settle // them fully. + // + // maps: payHash => invoiceIndex invoiceIndexBucket = []byte("paymenthashes") // numInvoicesKey is the name of key which houses the auto-incrementing @@ -61,8 +63,8 @@ type ContractTerm struct { // extended. PaymentPreimage [32]byte - // Value is the expected amount of milli-satoshis to be paid to an - // HTLC which can be satisfied by the above preimage. + // Value is the expected amount of milli-satoshis to be paid to an HTLC + // which can be satisfied by the above preimage. Value lnwire.MilliSatoshi // Settled indicates if this particular contract term has been fully @@ -79,8 +81,7 @@ type ContractTerm struct { // invoices are never deleted from the database, instead a bit is toggled // denoting the invoice has been fully settled. Within the database, all // invoices must have a unique payment hash which is generated by taking the -// sha256 of the payment -// preimage. +// sha256 of the payment preimage. type Invoice struct { // Memo is an optional memo to be stored along side an invoice. The // memo may contain further details pertaining to the invoice itself, @@ -103,13 +104,18 @@ type Invoice struct { // SettleDate is the exact time the invoice was settled. SettleDate time.Time - // Terms are the contractual payment terms of the invoice. Once - // all the terms have been satisfied by the payer, then the invoice can - // be considered fully fulfilled. + // Terms are the contractual payment terms of the invoice. Once all the + // terms have been satisfied by the payer, then the invoice can be + // considered fully fulfilled. // // TODO(roasbeef): later allow for multiple terms to fulfill the final // invoice: payment fragmentation, etc. Terms ContractTerm + // AmtPaid is the final amount that we ultimately accepted for pay for + // this invoice. We specify this value independently as it's possible + // that the invoice originally didn't specify an amount, or the sender + // overpaid. + AmtPaid lnwire.MilliSatoshi } func validateInvoice(i *Invoice) error { @@ -262,7 +268,8 @@ func (d *DB) FetchAllInvoices(pendingOnly bool) ([]*Invoice, error) { // payment hash as fully 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. -func (d *DB) SettleInvoice(paymentHash [32]byte) error { +func (d *DB) SettleInvoice(paymentHash [32]byte, amtPaid lnwire.MilliSatoshi) error { + return d.Update(func(tx *bolt.Tx) error { invoices, err := tx.CreateBucketIfNotExists(invoiceBucket) if err != nil { @@ -280,7 +287,9 @@ func (d *DB) SettleInvoice(paymentHash [32]byte) error { return ErrInvoiceNotFound } - return settleInvoice(invoices, invoiceNum) + return settleInvoice( + invoices, settleIndex, invoiceNum, amtPaid, + ) }) } @@ -361,6 +370,9 @@ func serializeInvoice(w io.Writer, i *Invoice) error { return err } + if err := binary.Write(w, byteOrder, int64(i.AmtPaid)); err != nil { + return err + } return nil } @@ -421,12 +433,16 @@ func deserializeInvoice(r io.Reader) (*Invoice, error) { if err := binary.Read(r, byteOrder, &invoice.Terms.Settled); err != nil { return nil, err + if err := binary.Read(r, byteOrder, &invoice.AmtPaid); err != nil { + return invoice, err } return invoice, nil } -func settleInvoice(invoices *bolt.Bucket, invoiceNum []byte) error { +func settleInvoice(invoices, settleIndex *bolt.Bucket, invoiceNum []byte, + amtPaid lnwire.MilliSatoshi) error { + invoice, err := fetchInvoice(invoiceNum, invoices) if err != nil { return err @@ -438,6 +454,7 @@ func settleInvoice(invoices *bolt.Bucket, invoiceNum []byte) error { return nil } + invoice.AmtPaid = amtPaid invoice.Terms.Settled = true invoice.SettleDate = time.Now() From 5d20c02ea8c768b34089e587003805bc77afe0b4 Mon Sep 17 00:00:00 2001 From: Olaoluwa Osuntokun Date: Tue, 24 Apr 2018 20:57:30 -0700 Subject: [PATCH 06/26] channeldb: add new add+settle index to invoice database In this commit, we add two new indexes to the invoice database: the add index, and the settle index. These to indexes essentially form a time series index on top of the existing primary index bucket. Each time an invoice is added, we'll advance the addIndex seqno, and then create a mapping from seqNo -> invoiceNum. Each time an invoice is settled, we'll do the same, but within the settle index. This change is required in order to allow callers to effectively seek into the current invoice database in order to obtain notifications for any invoices they may have missed out on while they were disconnected. This will allow us to implement robust streaming invoice notifications within lnd to ensure that clients never miss an event. --- channeldb/invoices.go | 177 +++++++++++++++++++++++++++++++++++------- 1 file changed, 147 insertions(+), 30 deletions(-) diff --git a/channeldb/invoices.go b/channeldb/invoices.go index 9d4a7172..85338a9c 100644 --- a/channeldb/invoices.go +++ b/channeldb/invoices.go @@ -36,6 +36,26 @@ var ( // stored within the invoiceIndexBucket. Within the invoiceBucket // invoices are uniquely identified by the invoice ID. numInvoicesKey = []byte("nik") + + // addIndexBucket is an index bucket that we'll use to create a + // monotonically increasing set of add indexes. Each time we add a new + // invoice, this sequence number will be incremented and then populated + // within the new invoice. + // + // In addition to this sequence number, we map: + // + // addIndexNo => invoiceIndex + addIndexBucket = []byte("invoice-add-index") + + // settleIndexBucket is an index bucket that we'll use to create a + // monotonically increasing integer for tracking a "settle index". Each + // time an invoice is settled, this sequence number will be incremented + // as populate within the newly settled invoice. + // + // In addition to this sequence number, we map: + // + // settleIndexNo => invoiceIndex + settleIndexBucket = []byte("invoice-settle-index") ) const ( @@ -111,6 +131,25 @@ type Invoice struct { // TODO(roasbeef): later allow for multiple terms to fulfill the final // invoice: payment fragmentation, etc. Terms ContractTerm + + // AddIndex is an auto-incrementing integer that acts as a + // monotonically increasing sequence number for all invoices created. + // Clients can then use this field as a "checkpoint" of sorts when + // implementing a streaming RPC to notify consumers of instances where + // an invoice has been added before they re-connected. + // + // NOTE: This index starts at 1. + AddIndex uint64 + + // SettleIndex is an auto-incrementing integer that acts as a + // monotonically increasing sequence number for all settled invoices. + // Clients can then use this field as a "checkpoint" of sorts when + // implementing a streaming RPC to notify consumers of instances where + // an invoice has been settled before they re-connected. + // + // NOTE: This index starts at 1. + SettleIndex uint64 + // AmtPaid is the final amount that we ultimately accepted for pay for // this invoice. We specify this value independently as it's possible // that the invoice originally didn't specify an amount, or the sender @@ -140,24 +179,35 @@ func validateInvoice(i *Invoice) error { // has *any* payment hashes which already exists within the database, then the // insertion will be aborted and rejected due to the strict policy banning any // duplicate payment hashes. -func (d *DB) AddInvoice(i *Invoice) error { - if err := validateInvoice(i); err != nil { +func (d *DB) AddInvoice(newInvoice *Invoice) error { + if err := validateInvoice(newInvoice); err != nil { return err } - return d.Update(func(tx *bolt.Tx) error { + + err := d.Update(func(tx *bolt.Tx) error { invoices, err := tx.CreateBucketIfNotExists(invoiceBucket) if err != nil { return err } - invoiceIndex, err := invoices.CreateBucketIfNotExists(invoiceIndexBucket) + invoiceIndex, err := invoices.CreateBucketIfNotExists( + invoiceIndexBucket, + ) + if err != nil { + return err + } + addIndex, err := invoices.CreateBucketIfNotExists( + addIndexBucket, + ) if err != nil { return err } // Ensure that an invoice an identical payment hash doesn't // already exist within the index. - paymentHash := sha256.Sum256(i.Terms.PaymentPreimage[:]) + paymentHash := sha256.Sum256( + newInvoice.Terms.PaymentPreimage[:], + ) if invoiceIndex.Get(paymentHash[:]) != nil { return ErrDuplicateInvoice } @@ -169,14 +219,24 @@ func (d *DB) AddInvoice(i *Invoice) error { if invoiceCounter == nil { var scratch [4]byte byteOrder.PutUint32(scratch[:], invoiceNum) - if err := invoiceIndex.Put(numInvoicesKey, scratch[:]); err != nil { + err := invoiceIndex.Put(numInvoicesKey, scratch[:]) + if err != nil { return nil } } else { invoiceNum = byteOrder.Uint32(invoiceCounter) } - return putInvoice(invoices, invoiceIndex, i, invoiceNum) + return putInvoice( + invoices, invoiceIndex, addIndex, newInvoice, invoiceNum, + ) + }) + if err != nil { + return err + } + + return err +} }) } @@ -186,8 +246,8 @@ func (d *DB) AddInvoice(i *Invoice) error { // full invoice is returned. Before setting the incoming HTLC, the values // SHOULD be checked to ensure the payer meets the agreed upon contractual // terms of the payment. -func (d *DB) LookupInvoice(paymentHash [32]byte) (*Invoice, error) { - var invoice *Invoice +func (d *DB) LookupInvoice(paymentHash [32]byte) (Invoice, error) { + var invoice Invoice err := d.View(func(tx *bolt.Tx) error { invoices := tx.Bucket(invoiceBucket) if invoices == nil { @@ -216,7 +276,7 @@ func (d *DB) LookupInvoice(paymentHash [32]byte) (*Invoice, error) { return nil }) if err != nil { - return nil, err + return invoice, err } return invoice, nil @@ -225,8 +285,8 @@ func (d *DB) LookupInvoice(paymentHash [32]byte) (*Invoice, error) { // FetchAllInvoices returns all invoices currently stored within the database. // If the pendingOnly param is true, then only unsettled invoices will be // returned, skipping all invoices that are fully settled. -func (d *DB) FetchAllInvoices(pendingOnly bool) ([]*Invoice, error) { - var invoices []*Invoice +func (d *DB) FetchAllInvoices(pendingOnly bool) ([]Invoice, error) { + var invoices []Invoice err := d.View(func(tx *bolt.Tx) error { invoiceB := tx.Bucket(invoiceBucket) @@ -275,7 +335,15 @@ func (d *DB) SettleInvoice(paymentHash [32]byte, amtPaid lnwire.MilliSatoshi) er if err != nil { return err } - invoiceIndex, err := invoices.CreateBucketIfNotExists(invoiceIndexBucket) + invoiceIndex, err := invoices.CreateBucketIfNotExists( + invoiceIndexBucket, + ) + if err != nil { + return err + } + settleIndex, err := invoices.CreateBucketIfNotExists( + settleIndexBucket, + ) if err != nil { return err } @@ -293,7 +361,6 @@ func (d *DB) SettleInvoice(paymentHash [32]byte, amtPaid lnwire.MilliSatoshi) er }) } -func putInvoice(invoices *bolt.Bucket, invoiceIndex *bolt.Bucket, i *Invoice, invoiceNum uint32) error { // Create the invoice key which is just the big-endian representation @@ -314,10 +381,30 @@ func putInvoice(invoices *bolt.Bucket, invoiceIndex *bolt.Bucket, // identify if we can settle an incoming payment, and also to possibly // allow a single invoice to have multiple payment installations. paymentHash := sha256.Sum256(i.Terms.PaymentPreimage[:]) - if err := invoiceIndex.Put(paymentHash[:], invoiceKey[:]); err != nil { + err := invoiceIndex.Put(paymentHash[:], invoiceKey[:]) + if err != nil { return err } + // Next, we'll obtain the next add invoice index (sequence + // number), so we can properly place this invoice within this + // event stream. + nextAddSeqNo, err := addIndex.NextSequence() + if err != nil { + return err + } + + // With the next sequence obtained, we'll updating the event series in + // the add index bucket to map this current add counter to the index of + // this new invoice. + var seqNoBytes [8]byte + byteOrder.PutUint64(seqNoBytes[:], nextAddSeqNo) + if err := addIndex.Put(seqNoBytes[:], invoiceKey[:]); err != nil { + return err + } + + i.AddIndex = nextAddSeqNo + // Finally, serialize the invoice itself to be written to the disk. var buf bytes.Buffer if err := serializeInvoice(&buf, i); err != nil { @@ -370,16 +457,23 @@ func serializeInvoice(w io.Writer, i *Invoice) error { 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 } -func fetchInvoice(invoiceNum []byte, invoices *bolt.Bucket) (*Invoice, error) { +func fetchInvoice(invoiceNum []byte, invoices *bolt.Bucket) (Invoice, error) { invoiceBytes := invoices.Get(invoiceNum) if invoiceBytes == nil { - return nil, ErrInvoiceNotFound + return Invoice{}, ErrInvoiceNotFound } invoiceReader := bytes.NewReader(invoiceBytes) @@ -387,52 +481,60 @@ func fetchInvoice(invoiceNum []byte, invoices *bolt.Bucket) (*Invoice, error) { return deserializeInvoice(invoiceReader) } -func deserializeInvoice(r io.Reader) (*Invoice, error) { +func deserializeInvoice(r io.Reader) (Invoice, error) { var err error - invoice := &Invoice{} + invoice := Invoice{} // TODO(roasbeef): use read full everywhere invoice.Memo, err = wire.ReadVarBytes(r, 0, MaxMemoSize, "") if err != nil { - return nil, err + return invoice, err } invoice.Receipt, err = wire.ReadVarBytes(r, 0, MaxReceiptSize, "") if err != nil { - return nil, err + return invoice, err } invoice.PaymentRequest, err = wire.ReadVarBytes(r, 0, MaxPaymentRequestSize, "") if err != nil { - return nil, err + return invoice, err } birthBytes, err := wire.ReadVarBytes(r, 0, 300, "birth") if err != nil { - return nil, err + return invoice, err } if err := invoice.CreationDate.UnmarshalBinary(birthBytes); err != nil { - return nil, err + return invoice, err } settledBytes, err := wire.ReadVarBytes(r, 0, 300, "settled") if err != nil { - return nil, err + return invoice, err } if err := invoice.SettleDate.UnmarshalBinary(settledBytes); err != nil { - return nil, err + return invoice, err } if _, err := io.ReadFull(r, invoice.Terms.PaymentPreimage[:]); err != nil { - return nil, err + return invoice, err } var scratch [8]byte if _, err := io.ReadFull(r, scratch[:]); err != nil { - return nil, err + return invoice, err } invoice.Terms.Value = lnwire.MilliSatoshi(byteOrder.Uint64(scratch[:])) if err := binary.Read(r, byteOrder, &invoice.Terms.Settled); err != nil { - return nil, err + 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 } @@ -454,12 +556,27 @@ func settleInvoice(invoices, settleIndex *bolt.Bucket, invoiceNum []byte, return nil } + // 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 + // proper location within our time series. + nextSettleSeqNo, err := settleIndex.NextSequence() + if err != nil { + return err + } + + var seqNoBytes [8]byte + byteOrder.PutUint64(seqNoBytes[:], nextSettleSeqNo) + if err := settleIndex.Put(seqNoBytes[:], invoiceNum); err != nil { + return err + } + invoice.AmtPaid = amtPaid invoice.Terms.Settled = true invoice.SettleDate = time.Now() + invoice.SettleIndex = nextSettleSeqNo var buf bytes.Buffer - if err := serializeInvoice(&buf, invoice); err != nil { + if err := serializeInvoice(&buf, &invoice); err != nil { return nil } From 24ae13d3a5d8dcbeba1a3f3cda0a1554391fcc2d Mon Sep 17 00:00:00 2001 From: Olaoluwa Osuntokun Date: Tue, 24 Apr 2018 20:59:53 -0700 Subject: [PATCH 07/26] channeldb: add two methods to allow seeking into the invoice time series In this commit, we add two new methods: InvoicesAddedSince and InvoicesSettledSince. These methods will be used by higher level sub-systems that implement notifications to deliver any notifications backlog based on the last add index, and last settle index that the client knows of. It's important to note that care has been taken to ensure that this new API can be used in a backwards compatible manner. If a client specifies and index of 0 for either of the methods, then no backlog will be sent. This is due to the fact that current users of the API don't expect any backlog notifications to be sent. Additionally, the index actually starts at 1, instead of 0. --- channeldb/invoices.go | 127 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 127 insertions(+) diff --git a/channeldb/invoices.go b/channeldb/invoices.go index 85338a9c..5a2c044a 100644 --- a/channeldb/invoices.go +++ b/channeldb/invoices.go @@ -237,7 +237,72 @@ func (d *DB) AddInvoice(newInvoice *Invoice) error { return err } + +// InvoicesAddedSince can be used by callers to seek into the event time series +// of all the invoices added in the database. The specified sinceAddIndex +// should be the highest add index that the caller knows of. This method will +// return all invoices with an add index greater than the specified +// sinceAddIndex. +// +// NOTE: The index starts from 1, as a result. We enforce that specifying a +// value below the starting index value is a noop. +func (d *DB) InvoicesAddedSince(sinceAddIndex uint64) ([]Invoice, error) { + var newInvoices []Invoice + + // If an index of zero was specified, then in order to maintain + // backwards compat, we won't send out any new invoices. + if sinceAddIndex == 0 { + return newInvoices, nil + } + + var startIndex [8]byte + byteOrder.PutUint64(startIndex[:], sinceAddIndex) + + err := d.DB.View(func(tx *bolt.Tx) error { + invoices := tx.Bucket(invoiceBucket) + if invoices == nil { + return ErrNoInvoicesCreated + } + + addIndex := invoices.Bucket(addIndexBucket) + if addIndex == nil { + return ErrNoInvoicesCreated + } + + // We'll now run through each entry in the add index starting + // at our starting index. We'll continue until we reach the + // very end of the current key space. + invoiceCursor := addIndex.Cursor() + + // We'll seek to the starting index, then manually advance the + // cursor in order to skip the entry with the since add index. + invoiceCursor.Seek(startIndex[:]) + addSeqNo, invoiceKey := invoiceCursor.Next() + + for ; addSeqNo != nil && bytes.Compare(addSeqNo, startIndex[:]) > 0; addSeqNo, invoiceKey = invoiceCursor.Next() { + + // For each key found, we'll look up the actual + // invoice, then accumulate it into our return value. + invoice, err := fetchInvoice(invoiceKey, invoices) + if err != nil { + return err + } + + newInvoices = append(newInvoices, invoice) + } + + return nil }) + switch { + // If no invoices have been created, then we'll return the empty set of + // invoices. + case err == ErrNoInvoicesCreated: + + case err != nil: + return nil, err + } + + return newInvoices, nil } // LookupInvoice attempts to look up an invoice according to its 32 byte @@ -361,6 +426,68 @@ func (d *DB) SettleInvoice(paymentHash [32]byte, amtPaid lnwire.MilliSatoshi) er }) } +// 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 +// sinceSettleIndex. +// +// NOTE: The index starts from 1, as a result. We enforce that specifying a +// value below the starting index value is a noop. +func (d *DB) InvoicesSettledSince(sinceSettleIndex uint64) ([]Invoice, error) { + var settledInvoices []Invoice + + // If an index of zero was specified, then in order to maintain + // backwards compat, we won't send out any new invoices. + if sinceSettleIndex == 0 { + return settledInvoices, nil + } + + var startIndex [8]byte + byteOrder.PutUint64(startIndex[:], sinceSettleIndex) + + err := d.DB.View(func(tx *bolt.Tx) error { + invoices := tx.Bucket(invoiceBucket) + if invoices == nil { + return ErrNoInvoicesCreated + } + + settleIndex := invoices.Bucket(settleIndexBucket) + if settleIndex == nil { + return ErrNoInvoicesCreated + } + + // We'll now run through each entry in the add index starting + // at our starting index. We'll continue until we reach the + // very end of the current key space. + invoiceCursor := settleIndex.Cursor() + + // We'll seek to the starting index, then manually advance the + // cursor in order to skip the entry with the since add index. + invoiceCursor.Seek(startIndex[:]) + seqNo, invoiceKey := invoiceCursor.Next() + + for ; seqNo != nil && bytes.Compare(seqNo, startIndex[:]) > 0; seqNo, invoiceKey = invoiceCursor.Next() { + + // For each key found, we'll look up the actual + // invoice, then accumulate it into our return value. + invoice, err := fetchInvoice(invoiceKey, invoices) + if err != nil { + return err + } + + settledInvoices = append(settledInvoices, invoice) + } + + return nil + }) + if err != nil { + return nil, err + } + + return settledInvoices, nil +} + +func putInvoice(invoices, invoiceIndex, addIndex *bolt.Bucket, i *Invoice, invoiceNum uint32) error { // Create the invoice key which is just the big-endian representation From 532975588ded9832d11adbc71e91f167f79bac28 Mon Sep 17 00:00:00 2001 From: Olaoluwa Osuntokun Date: Tue, 24 Apr 2018 21:01:22 -0700 Subject: [PATCH 08/26] channeldb: update existing invoice tests to due to recent field additions --- channeldb/invoice_test.go | 31 +++++++++++++++++++++++++------ 1 file changed, 25 insertions(+), 6 deletions(-) diff --git a/channeldb/invoice_test.go b/channeldb/invoice_test.go index 0a04e65b..b348b554 100644 --- a/channeldb/invoice_test.go +++ b/channeldb/invoice_test.go @@ -82,15 +82,24 @@ func TestInvoiceWorkflow(t *testing.T) { if err != nil { t.Fatalf("unable to find invoice: %v", err) } - if !reflect.DeepEqual(fakeInvoice, dbInvoice) { + if !reflect.DeepEqual(*fakeInvoice, dbInvoice) { t.Fatalf("invoice fetched from db doesn't match original %v vs %v", spew.Sdump(fakeInvoice), spew.Sdump(dbInvoice)) } + // The add index of the invoice retrieved from the database should now + // be fully populated. As this is the first index written to the DB, + // the addIndex should be 1. + if dbInvoice.AddIndex != 1 { + t.Fatalf("wrong add index: expected %v, got %v", 1, + dbInvoice.AddIndex) + } + // Settle the invoice, the version retrieved from the database should // now have the settled bit toggle to true and a non-default // SettledDate - if err := db.SettleInvoice(paymentHash); err != nil { + payAmt := fakeInvoice.Terms.Value * 2 + if err := db.SettleInvoice(paymentHash, payAmt); err != nil { t.Fatalf("unable to settle invoice: %v", err) } dbInvoice2, err := db.LookupInvoice(paymentHash) @@ -100,11 +109,21 @@ func TestInvoiceWorkflow(t *testing.T) { if !dbInvoice2.Terms.Settled { t.Fatalf("invoice should now be settled but isn't") } - if dbInvoice2.SettleDate.IsZero() { t.Fatalf("invoice should have non-zero SettledDate but isn't") } + // Our 2x payment should be reflected, and also the settle index of 1 + // should also have been committed for this index. + if dbInvoice2.AmtPaid != payAmt { + t.Fatalf("wrong amt paid: expected %v, got %v", payAmt, + dbInvoice2.AmtPaid) + } + if dbInvoice2.SettleIndex != 1 { + t.Fatalf("wrong settle index: expected %v, got %v", 1, + dbInvoice2.SettleIndex) + } + // Attempt to insert generated above again, this should fail as // duplicates are rejected by the processing logic. if err := db.AddInvoice(fakeInvoice); err != ErrDuplicateInvoice { @@ -119,11 +138,11 @@ func TestInvoiceWorkflow(t *testing.T) { t.Fatalf("lookup should have failed, instead %v", err) } - // Add 100 random invoices. + // Add 10 random invoices. const numInvoices = 10 amt := lnwire.NewMSatFromSatoshis(1000) invoices := make([]*Invoice, numInvoices+1) - invoices[0] = dbInvoice2 + invoices[0] = &dbInvoice2 for i := 1; i < len(invoices)-1; i++ { invoice, err := randInvoice(amt) if err != nil { @@ -148,7 +167,7 @@ func TestInvoiceWorkflow(t *testing.T) { // order (and the primary key should be incremented with each // insertion). for i := 0; i < len(invoices)-1; i++ { - if !reflect.DeepEqual(invoices[i], dbInvoices[i]) { + if !reflect.DeepEqual(*invoices[i], dbInvoices[i]) { t.Fatalf("retrieved invoices don't match %v vs %v", spew.Sdump(invoices[i]), spew.Sdump(dbInvoices[i])) From 24d97b5ee523fec734584c729dc76677eecaf6e5 Mon Sep 17 00:00:00 2001 From: Olaoluwa Osuntokun Date: Tue, 24 Apr 2018 21:01:43 -0700 Subject: [PATCH 09/26] channeldb: add new test to exercise invoice time series index --- channeldb/invoice_test.go | 140 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 140 insertions(+) diff --git a/channeldb/invoice_test.go b/channeldb/invoice_test.go index b348b554..b261c477 100644 --- a/channeldb/invoice_test.go +++ b/channeldb/invoice_test.go @@ -174,3 +174,143 @@ func TestInvoiceWorkflow(t *testing.T) { } } } + +// TestInvoiceTimeSeries tests that newly added invoices invoices, as well as +// settled invoices are added to the database are properly placed in the add +// add or settle index which serves as an event time series. +func TestInvoiceAddTimeSeries(t *testing.T) { + t.Parallel() + + db, cleanUp, err := makeTestDB() + defer cleanUp() + if err != nil { + t.Fatalf("unable to make test db: %v", err) + } + + // We'll start off by creating 20 random invoices, and inserting them + // into the database. + const numInvoices = 20 + amt := lnwire.NewMSatFromSatoshis(1000) + invoices := make([]Invoice, numInvoices) + for i := 0; i < len(invoices); i++ { + invoice, err := randInvoice(amt) + if err != nil { + t.Fatalf("unable to create invoice: %v", err) + } + + if err := db.AddInvoice(invoice); err != nil { + t.Fatalf("unable to add invoice %v", err) + } + + invoices[i] = *invoice + } + + // With the invoices constructed, we'll now create a series of queries + // that we'll use to assert expected return values of + // InvoicesAddedSince. + addQueries := []struct { + sinceAddIndex uint64 + + resp []Invoice + }{ + // If we specify a value of zero, we shouldn't get any invoices + // back. + { + sinceAddIndex: 0, + }, + + // If we specify a value well beyond the number of inserted + // invoices, we shouldn't get any invoices back. + { + sinceAddIndex: 99999999, + }, + + // Using an index of 1 should result in all values, but the + // first one being returned. + { + sinceAddIndex: 1, + resp: invoices[1:], + }, + + // If we use an index of 10, then we should retrieve the + // reaming 10 invoices. + { + sinceAddIndex: 10, + resp: invoices[10:], + }, + } + + for i, query := range addQueries { + resp, err := db.InvoicesAddedSince(query.sinceAddIndex) + if err != nil { + t.Fatalf("unable to query: %v", err) + } + + if !reflect.DeepEqual(query.resp, resp) { + t.Fatalf("test #%v: expected %v, got %v", i, + spew.Sdump(query.resp), spew.Sdump(resp)) + } + } + + // We'll now only settle the latter half of each of those invoices. + for i := 10; i < len(invoices); i++ { + invoice := &invoices[i] + + paymentHash := sha256.Sum256( + invoice.Terms.PaymentPreimage[:], + ) + + err := db.SettleInvoice(paymentHash, 0) + if err != nil { + t.Fatalf("unable to settle invoice: %v", err) + } + } + + invoices, err = db.FetchAllInvoices(false) + if err != nil { + t.Fatalf("unable to fetch invoices: %v", err) + } + + // We'll slice off the first 10 invoices, as we only settled the last + // 10. + invoices = invoices[10:] + + // We'll now prepare an additional set of queries to ensure the settle + // time series has properly been maintained in the database. + settleQueries := []struct { + sinceSettleIndex uint64 + + resp []Invoice + }{ + // If we specify a value of zero, we shouldn't get any settled + // invoices back. + { + sinceSettleIndex: 0, + }, + + // If we specify a value well beyond the number of settled + // invoices, we shouldn't get any invoices back. + { + sinceSettleIndex: 99999999, + }, + + // Using an index of 1 should result in the final 10 invoices + // being returned, as we only settled those. + { + sinceSettleIndex: 1, + resp: invoices[1:], + }, + } + + for i, query := range settleQueries { + resp, err := db.InvoicesSettledSince(query.sinceSettleIndex) + if err != nil { + t.Fatalf("unable to query: %v", err) + } + + if !reflect.DeepEqual(query.resp, resp) { + t.Fatalf("test #%v: expected %v, got %v", i, + spew.Sdump(query.resp), spew.Sdump(resp)) + } + } +} From 2097564307d28f155ecab06fcd34794c62245d77 Mon Sep 17 00:00:00 2001 From: Olaoluwa Osuntokun Date: Tue, 24 Apr 2018 21:02:14 -0700 Subject: [PATCH 10/26] channeldb: add new migration to upgrade old databases to new invoice time series index --- channeldb/migrations.go | 96 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 96 insertions(+) diff --git a/channeldb/migrations.go b/channeldb/migrations.go index 50ceec79..a1f40228 100644 --- a/channeldb/migrations.go +++ b/channeldb/migrations.go @@ -112,3 +112,99 @@ func migrateNodeAndEdgeUpdateIndex(tx *bolt.Tx) error { return nil } + +// migrateInvoiceTimeSeries is a database migration that assigns all existing +// invoices an index in the add and/or the settle index. Additionally, all +// existing invoices will have their bytes padded out in order to encode the +// add+settle index as well as the amount paid. +func migrateInvoiceTimeSeries(tx *bolt.Tx) error { + invoices, err := tx.CreateBucketIfNotExists(invoiceBucket) + if err != nil { + return err + } + + addIndex, err := invoices.CreateBucketIfNotExists( + addIndexBucket, + ) + if err != nil { + return err + } + settleIndex, err := invoices.CreateBucketIfNotExists( + settleIndexBucket, + ) + if err != nil { + return err + } + + // Now that we have all the buckets we need, we'll run through each + // invoice in the database, and update it to reflect the new format + // expected post migration. + return invoices.ForEach(func(invoiceNum, invoiceBytes []byte) error { + // First, we'll make a copy of the encoded invoice bytes. + invoiceBytesCopy := make([]byte, len(invoiceBytes)) + copy(invoiceBytesCopy, invoiceBytes) + + // With the bytes copied over, we'll append 24 additional + // bytes. We do this so we can decode the invoice under the new + // serialization format. + padding := bytes.Repeat([]byte{0}, 24) + invoiceBytesCopy = append(invoiceBytesCopy, padding...) + + invoiceReader := bytes.NewReader(invoiceBytesCopy) + invoice, err := deserializeInvoice(invoiceReader) + if err != nil { + return err + } + + // Now that we have the fully decoded invoice, we can update + // the various indexes that we're added, and finally the + // invoice itself before re-inserting it. + + // First, we'll get the new sequence in the addIndex in order + // to create the proper mapping. + nextAddSeqNo, err := addIndex.NextSequence() + if err != nil { + return err + } + var seqNoBytes [8]byte + byteOrder.PutUint64(seqNoBytes[:], nextAddSeqNo) + err = addIndex.Put(seqNoBytes[:], invoiceNum[:]) + if err != nil { + return err + } + + // Next, we'll check if the invoice has been settled or not. If + // so, then we'll also add it to the settle index. + var nextSettleSeqNo uint64 + if invoice.Terms.Settled { + nextSettleSeqNo, err = settleIndex.NextSequence() + if err != nil { + return err + } + + var seqNoBytes [8]byte + byteOrder.PutUint64(seqNoBytes[:], nextSettleSeqNo) + err := settleIndex.Put(seqNoBytes[:], invoiceNum) + if err != nil { + return err + } + + invoice.AmtPaid = invoice.Terms.Value + } + + // Finally, we'll update the invoice itself with the new + // indexing information as well as the amount paid if it has + // been settled or not. + invoice.AddIndex = nextAddSeqNo + invoice.SettleIndex = nextSettleSeqNo + + // 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 { + return err + } + + return invoices.Put(invoiceNum, b.Bytes()) + }) +} From e4c5840472e63a1cd7e9844445a41459e78d2334 Mon Sep 17 00:00:00 2001 From: Olaoluwa Osuntokun Date: Tue, 24 Apr 2018 21:03:05 -0700 Subject: [PATCH 11/26] rpc: ensure new invoice fields are populated in RPC responses --- rpcserver.go | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/rpcserver.go b/rpcserver.go index 98241511..a1693054 100644 --- a/rpcserver.go +++ b/rpcserver.go @@ -2737,6 +2737,9 @@ func createRPCInvoice(invoice *channeldb.Invoice) (*lnrpc.Invoice, error) { CltvExpiry: cltvExpiry, FallbackAddr: fallbackAddr, RouteHints: routeHints, + AddIndex: invoice.AddIndex, + SettleIndex: invoice.SettleIndex, + AmtPaid: int64(invoice.AmtPaid), }, nil } @@ -2833,7 +2836,7 @@ func (r *rpcServer) ListInvoices(ctx context.Context, invoices := make([]*lnrpc.Invoice, len(dbInvoices)) for i, dbInvoice := range dbInvoices { - rpcInvoice, err := createRPCInvoice(dbInvoice) + rpcInvoice, err := createRPCInvoice(&dbInvoice) if err != nil { return nil, err } @@ -2867,6 +2870,7 @@ func (r *rpcServer) SubscribeInvoices(req *lnrpc.InvoiceSubscription, if err := updateStream.Send(rpcInvoice); err != nil { return err } + case <-r.quit: return nil } @@ -2899,6 +2903,7 @@ func (r *rpcServer) SubscribeTransactions(req *lnrpc.GetTransactionsRequest, if err := updateStream.Send(detail); err != nil { return err } + case tx := <-txClient.UnconfirmedTransactions(): detail := &lnrpc.Transaction{ TxHash: tx.Hash.String(), @@ -2909,6 +2914,7 @@ func (r *rpcServer) SubscribeTransactions(req *lnrpc.GetTransactionsRequest, if err := updateStream.Send(detail); err != nil { return err } + case <-r.quit: return nil } From 7917fd2ab0b739212c257e138ab6d68d670d1293 Mon Sep 17 00:00:00 2001 From: Olaoluwa Osuntokun Date: Tue, 24 Apr 2018 21:07:30 -0700 Subject: [PATCH 12/26] invoiceregistry: re-work logic to support delivering notification backlog In this commit, we re-work the existing invoiceRegistry struct to support delivering backlog notifications to subscription clients if needed. Rather than using 1 goroutine per-client per-event, each client now gains a concurrent notification queue. This queue will then be used to ensure in-order delivery of all notifications from the invoiceEventNotifier. The SubscribeNotifications method now takes two params: addIndex, and settleIndex. These should be the values of the last add index and settle index the caller knows of. If specified (not zero), then we'll look up all the notifications that the caller has missed, and then deliver those before sending out any new notifications. In order to do this without losing ordering of events, we've added a new central goroutine which will ensure that all events are properly serialized. --- invoiceregistry.go | 293 ++++++++++++++++++++++++++++++++++++++++----- 1 file changed, 261 insertions(+), 32 deletions(-) diff --git a/invoiceregistry.go b/invoiceregistry.go index 283c5359..a49a8581 100644 --- a/invoiceregistry.go +++ b/invoiceregistry.go @@ -3,10 +3,13 @@ package main import ( "bytes" "crypto/sha256" + "fmt" "sync" + "sync/atomic" "time" "github.com/davecgh/go-spew/spew" + "github.com/lightningnetwork/lnd/chainntnfs" "github.com/lightningnetwork/lnd/channeldb" "github.com/lightningnetwork/lnd/lnwire" "github.com/lightningnetwork/lnd/zpay32" @@ -37,10 +40,17 @@ type invoiceRegistry struct { nextClientID uint32 notificationClients map[uint32]*invoiceSubscription + newSubscriptions chan *invoiceSubscription + subscriptionCancels chan uint32 + invoiceEvents chan *invoiceEvent + // debugInvoices is a map which stores special "debug" invoices which // should be only created/used when manual tests require an invoice // that *all* nodes are able to fully settle. debugInvoices map[chainhash.Hash]*channeldb.Invoice + + wg sync.WaitGroup + quit chan struct{} } // newInvoiceRegistry creates a new invoice registry. The invoice registry @@ -52,9 +62,137 @@ func newInvoiceRegistry(cdb *channeldb.DB) *invoiceRegistry { cdb: cdb, debugInvoices: make(map[chainhash.Hash]*channeldb.Invoice), notificationClients: make(map[uint32]*invoiceSubscription), + newSubscriptions: make(chan *invoiceSubscription), + subscriptionCancels: make(chan uint32), + invoiceEvents: make(chan *invoiceEvent), + quit: make(chan struct{}), } } +// Start starts the registry and all goroutines it needs to carry out its task. +func (i *invoiceRegistry) Start() error { + i.wg.Add(1) + + go i.invoiceEventNotifier() + + return nil +} + +// Stop signals the registry for a graceful shutdown. +func (i *invoiceRegistry) Stop() { + close(i.quit) + + i.wg.Wait() +} + +// invoiceEvent represents a new event that has modified on invoice on disk. +// Only two event types are currently supported: newly created invoices, and +// instance where invoices are settled. +type invoiceEvent struct { + isSettle bool + + invoice *channeldb.Invoice +} + +// invoiceEventNotifier is the dedicated goroutine responsible for accepting +// new notification subscriptions, cancelling old subscriptions, and +// dispatching new invoice events. +func (i *invoiceRegistry) invoiceEventNotifier() { + defer i.wg.Done() + + for { + select { + // A new invoice subscription has just arrived! We'll query for + // any backlog notifications, then add it to the set of + // clients. + case newClient := <-i.newSubscriptions: + // Before we add the client to our set of active + // clients, we'll first attempt to deliver any backlog + // invoice events. + err := i.deliverBacklogEvents(newClient) + if err != nil { + ltndLog.Errorf("unable to deliver backlog invoice "+ + "notifications: %v", err) + } + + ltndLog.Infof("New invoice subscription "+ + "client: id=%v", newClient.id) + + // With the backlog notifications delivered (if any), + // we'll add this to our active subscriptions and + // continue. + i.notificationClients[newClient.id] = newClient + + // A client no longer wishes to receive invoice notifications. + // So we'll remove them from the set of active clients. + case clientID := <-i.subscriptionCancels: + ltndLog.Infof("Cancelling invoice subscription for "+ + "client=%v", clientID) + + delete(i.notificationClients, clientID) + + // A sub-systems has just modified the invoice state, so we'll + // dispatch notifications to all registered clients. + case event := <-i.invoiceEvents: + for _, client := range i.notificationClients { + select { + case client.ntfnQueue.ChanIn() <- &invoiceEvent{ + isSettle: event.isSettle, + invoice: event.invoice, + }: + case <-i.quit: + return + } + } + + case <-i.quit: + return + } + } +} + +// deliverBacklogEvents will attempts to query the invoice database for any +// notifications that the client has missed since it reconnected last. +func (i *invoiceRegistry) deliverBacklogEvents(client *invoiceSubscription) error { + // First, we'll query the database to see if based on the provided + // addIndex and settledIndex we need to deliver any backlog + // notifications. + addEvents, err := i.cdb.InvoicesAddedSince(client.addIndex) + if err != nil { + return err + } + settleEvents, err := i.cdb.InvoicesSettledSince(client.settleIndex) + if err != nil { + return err + } + + // If we have any to deliver, then we'll append them to the end of the + // notification queue in order to catch up the client before delivering + // any new notifications. + for _, addEvent := range addEvents { + select { + case client.ntfnQueue.ChanIn() <- &invoiceEvent{ + isSettle: false, + invoice: &addEvent, + }: + case <-i.quit: + return fmt.Errorf("registry shutting down") + } + } + for _, settleEvent := range settleEvents { + select { + case client.ntfnQueue.ChanIn() <- &invoiceEvent{ + isSettle: true, + invoice: &settleEvent, + }: + case <-i.quit: + return fmt.Errorf("registry shutting down") + } + } + + return nil +} + // AddDebugInvoice adds a debug invoice for the specified amount, identified // by the passed preimage. Once this invoice is added, subsystems within the // daemon add/forward HTLCs that are able to obtain the proper preimage @@ -82,18 +220,22 @@ func (i *invoiceRegistry) AddDebugInvoice(amt btcutil.Amount, preimage chainhash // AddInvoice adds a regular invoice for the specified amount, identified by // the passed preimage. Additionally, any memo or receipt data provided will // also be stored on-disk. Once this invoice is added, subsystems within the -// daemon add/forward HTLCs are able to obtain the proper preimage required -// for redemption in the case that we're the final destination. +// daemon add/forward HTLCs are able to obtain the proper preimage required for +// redemption in the case that we're the final destination. func (i *invoiceRegistry) AddInvoice(invoice *channeldb.Invoice) error { ltndLog.Debugf("Adding invoice %v", newLogClosure(func() string { return spew.Sdump(invoice) })) - // TODO(roasbeef): also check in memory for quick lookups/settles? - return i.cdb.AddInvoice(invoice) + if err := i.cdb.AddInvoice(invoice); err != nil { + return err + } - // TODO(roasbeef): re-enable? - //go i.notifyClients(invoice, false) + // We'll launch a new goroutine to notify all of our active listeners + // that a new invoice was just added. + i.notifyClients(invoice, false) + + return nil } // LookupInvoice looks up an invoice by its payment hash (R-Hash), if found @@ -107,12 +249,12 @@ func (i *invoiceRegistry) LookupInvoice(rHash chainhash.Hash) (channeldb.Invoice // First check the in-memory debug invoice index to see if this is an // existing invoice added for debugging. i.RLock() - invoice, ok := i.debugInvoices[rHash] + debugInv, ok := i.debugInvoices[rHash] i.RUnlock() // If found, then simply return the invoice directly. if ok { - return *invoice, 0, nil + return *debugInv, 0, nil } // Otherwise, we'll check the database to see if there's an existing @@ -135,7 +277,9 @@ func (i *invoiceRegistry) LookupInvoice(rHash chainhash.Hash) (channeldb.Invoice // SettleInvoice 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. -func (i *invoiceRegistry) SettleInvoice(rHash chainhash.Hash) error { +func (i *invoiceRegistry) SettleInvoice(rHash chainhash.Hash, + amtPaid lnwire.MilliSatoshi) error { + ltndLog.Debugf("Settling invoice %x", rHash[:]) // First check the in-memory debug invoice index to see if this is an @@ -152,7 +296,7 @@ func (i *invoiceRegistry) SettleInvoice(rHash chainhash.Hash) error { // If this isn't a debug invoice, then we'll attempt to settle an // invoice matching this rHash on disk (if one exists). - if err := i.cdb.SettleInvoice(rHash); err != nil { + if err := i.cdb.SettleInvoice(rHash, amtPaid); err != nil { return err } @@ -167,7 +311,7 @@ func (i *invoiceRegistry) SettleInvoice(rHash chainhash.Hash) error { ltndLog.Infof("Payment received: %v", spew.Sdump(invoice)) - i.notifyClients(invoice, true) + i.notifyClients(&invoice, true) }() return nil @@ -176,20 +320,14 @@ func (i *invoiceRegistry) SettleInvoice(rHash chainhash.Hash) error { // notifyClients notifies all currently registered invoice notification clients // of a newly added/settled invoice. func (i *invoiceRegistry) notifyClients(invoice *channeldb.Invoice, settle bool) { - i.clientMtx.Lock() - defer i.clientMtx.Unlock() + event := &invoiceEvent{ + isSettle: settle, + invoice: invoice, + } - for _, client := range i.notificationClients { - var eventChan chan *channeldb.Invoice - if settle { - eventChan = client.SettledInvoices - } else { - eventChan = client.NewInvoices - } - - go func() { - eventChan <- invoice - }() + select { + case i.invoiceEvents <- event: + case <-i.quit: } } @@ -199,36 +337,127 @@ func (i *invoiceRegistry) notifyClients(invoice *channeldb.Invoice, settle bool) // invoice, a copy of the invoice will be sent over the SettledInvoices // channel. type invoiceSubscription struct { - NewInvoices chan *channeldb.Invoice + cancelled uint32 // To be used atomically. + + // NewInvoices is a channel that we'll use to send all newly created + // invoices with an invoice index greater than the specified + // StartingInvoiceIndex field. + NewInvoices chan *channeldb.Invoice + + // SettledInvoices is a channel that we'll use to send all setted + // invoices with an invoices index greater than the specified + // StartingInvoiceIndex field. SettledInvoices chan *channeldb.Invoice + // addIndex is the highest add index the caller knows of. We'll use + // this information to send out an event backlog to the notifications + // subscriber. Any new add events with an index greater than this will + // be dispatched before any new notifications are sent out. + addIndex uint64 + + // settleIndex is the highest settle index the caller knows of. We'll + // use this information to send out an event backlog to the + // notifications subscriber. Any new settle events with an index + // greater than this will be dispatched before any new notifications + // are sent out. + settleIndex uint64 + + ntfnQueue *chainntnfs.ConcurrentQueue + + id uint32 + inv *invoiceRegistry - id uint32 + + cancelChan chan struct{} + + wg sync.WaitGroup } // Cancel unregisters the invoiceSubscription, freeing any previously allocated // resources. func (i *invoiceSubscription) Cancel() { - i.inv.clientMtx.Lock() - delete(i.inv.notificationClients, i.id) - i.inv.clientMtx.Unlock() + if !atomic.CompareAndSwapUint32(&i.cancelled, 0, 1) { + return + } + + select { + case i.inv.subscriptionCancels <- i.id: + case <-i.inv.quit: + } + + i.ntfnQueue.Stop() + close(i.cancelChan) + + i.wg.Wait() } // SubscribeNotifications returns an invoiceSubscription which allows the // caller to receive async notifications when any invoices are settled or -// added. -func (i *invoiceRegistry) SubscribeNotifications() *invoiceSubscription { +// added. The invoiceIndex parameter is a streaming "checkpoint". We'll start +// by first sending out all new events with an invoice index _greater_ than +// this value. Afterwards, we'll send out real-time notifications. +func (i *invoiceRegistry) SubscribeNotifications(addIndex, settleIndex uint64) *invoiceSubscription { client := &invoiceSubscription{ NewInvoices: make(chan *channeldb.Invoice), SettledInvoices: make(chan *channeldb.Invoice), + addIndex: addIndex, + settleIndex: settleIndex, inv: i, + ntfnQueue: chainntnfs.NewConcurrentQueue(20), + cancelChan: make(chan struct{}), } + client.ntfnQueue.Start() i.clientMtx.Lock() - i.notificationClients[i.nextClientID] = client client.id = i.nextClientID i.nextClientID++ i.clientMtx.Unlock() + // Before we register this new invoice subscription, we'll launch a new + // goroutine that will proxy all notifications appended to the end of + // the concurrent queue to the two client-side channels the caller will + // feed off of. + i.wg.Add(1) + go func() { + defer i.wg.Done() + + for { + select { + // A new invoice event has been sent by the + // invoiceRegistry! We'll figure out if this is an add + // event or a settle event, then dispatch the event to + // the client. + case ntfn := <-client.ntfnQueue.ChanOut(): + invoiceEvent := ntfn.(*invoiceEvent) + + targetChan := client.NewInvoices + if invoiceEvent.isSettle { + targetChan = client.SettledInvoices + } + + select { + case targetChan <- invoiceEvent.invoice: + + case <-client.cancelChan: + return + + case <-i.quit: + return + } + + case <-client.cancelChan: + return + + case <-i.quit: + return + } + } + }() + + select { + case i.newSubscriptions <- client: + case <-i.quit: + } + return client } From 892a413be36a2a82a8dd3bee32546d214bb61fa3 Mon Sep 17 00:00:00 2001 From: Olaoluwa Osuntokun Date: Tue, 24 Apr 2018 21:08:19 -0700 Subject: [PATCH 13/26] rpc: update SubscribeInvoices to send backlog ntfns, also send add ntfns --- rpcserver.go | 16 +++++++++++++--- 1 file changed, 13 insertions(+), 3 deletions(-) diff --git a/rpcserver.go b/rpcserver.go index a1693054..df512269 100644 --- a/rpcserver.go +++ b/rpcserver.go @@ -2854,14 +2854,24 @@ func (r *rpcServer) ListInvoices(ctx context.Context, func (r *rpcServer) SubscribeInvoices(req *lnrpc.InvoiceSubscription, updateStream lnrpc.Lightning_SubscribeInvoicesServer) error { - invoiceClient := r.server.invoices.SubscribeNotifications() + invoiceClient := r.server.invoices.SubscribeNotifications( + req.AddIndex, req.SettleIndex, + ) defer invoiceClient.Cancel() for { select { - // TODO(roasbeef): include newly added invoices? - case settledInvoice := <-invoiceClient.SettledInvoices: + case newInvoice := <-invoiceClient.NewInvoices: + rpcInvoice, err := createRPCInvoice(newInvoice) + if err != nil { + return err + } + if err := updateStream.Send(rpcInvoice); err != nil { + return err + } + + case settledInvoice := <-invoiceClient.SettledInvoices: rpcInvoice, err := createRPCInvoice(settledInvoice) if err != nil { return err From 2c08a22ed3cb0c0fbb93ce4cab1a855785e2743d Mon Sep 17 00:00:00 2001 From: Olaoluwa Osuntokun Date: Tue, 24 Apr 2018 21:08:46 -0700 Subject: [PATCH 14/26] server: launch new invoiceRegistry goroutines on start up --- server.go | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/server.go b/server.go index 12ccf2fc..2163fd1b 100644 --- a/server.go +++ b/server.go @@ -753,6 +753,10 @@ func (s *server) Start() error { } s.connMgr.Start() + if err := s.invoices.Start(); err != nil { + return err + } + // With all the relevant sub-systems started, we'll now attempt to // establish persistent connections to our direct channel collaborators // within the network. @@ -809,6 +813,7 @@ func (s *server) Stop() error { s.cc.chainView.Stop() s.connMgr.Stop() s.cc.feeEstimator.Stop() + s.invoices.Stop() // Disconnect from each active peers to ensure that // peerTerminationWatchers signal completion to each peer. From e5c579120e5651cba366423870afcc6db4bbc3e7 Mon Sep 17 00:00:00 2001 From: Olaoluwa Osuntokun Date: Thu, 28 Jun 2018 21:43:18 -0700 Subject: [PATCH 15/26] channeldb: modify SettleInvoice to also return the invoice being settled --- channeldb/invoice_test.go | 4 ++-- channeldb/invoices.go | 37 +++++++++++++++++++++++++++---------- 2 files changed, 29 insertions(+), 12 deletions(-) diff --git a/channeldb/invoice_test.go b/channeldb/invoice_test.go index b261c477..aca845d5 100644 --- a/channeldb/invoice_test.go +++ b/channeldb/invoice_test.go @@ -99,7 +99,7 @@ func TestInvoiceWorkflow(t *testing.T) { // now have the settled bit toggle to true and a non-default // SettledDate payAmt := fakeInvoice.Terms.Value * 2 - if err := db.SettleInvoice(paymentHash, payAmt); err != nil { + if _, err := db.SettleInvoice(paymentHash, payAmt); err != nil { t.Fatalf("unable to settle invoice: %v", err) } dbInvoice2, err := db.LookupInvoice(paymentHash) @@ -260,7 +260,7 @@ func TestInvoiceAddTimeSeries(t *testing.T) { invoice.Terms.PaymentPreimage[:], ) - err := db.SettleInvoice(paymentHash, 0) + _, err := db.SettleInvoice(paymentHash, 0) if err != nil { t.Fatalf("unable to settle invoice: %v", err) } diff --git a/channeldb/invoices.go b/channeldb/invoices.go index 5a2c044a..a6fe0df9 100644 --- a/channeldb/invoices.go +++ b/channeldb/invoices.go @@ -393,9 +393,11 @@ func (d *DB) FetchAllInvoices(pendingOnly bool) ([]Invoice, error) { // payment hash as fully 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. -func (d *DB) SettleInvoice(paymentHash [32]byte, amtPaid lnwire.MilliSatoshi) error { +func (d *DB) SettleInvoice(paymentHash [32]byte, + amtPaid lnwire.MilliSatoshi) (*Invoice, error) { - return d.Update(func(tx *bolt.Tx) error { + var settledInvoice *Invoice + err := d.Update(func(tx *bolt.Tx) error { invoices, err := tx.CreateBucketIfNotExists(invoiceBucket) if err != nil { return err @@ -420,10 +422,21 @@ func (d *DB) SettleInvoice(paymentHash [32]byte, amtPaid lnwire.MilliSatoshi) er return ErrInvoiceNotFound } - return settleInvoice( + invoice, err := settleInvoice( invoices, settleIndex, invoiceNum, amtPaid, ) + if err != nil { + return err + } + + settledInvoice = invoice + return nil }) + if err != nil { + return nil, err + } + + return settledInvoice, nil } // InvoicesSettledSince can be used by callers to catch up any settled invoices @@ -670,17 +683,17 @@ func deserializeInvoice(r io.Reader) (Invoice, error) { } func settleInvoice(invoices, settleIndex *bolt.Bucket, invoiceNum []byte, - amtPaid lnwire.MilliSatoshi) error { + amtPaid lnwire.MilliSatoshi) (*Invoice, error) { invoice, err := fetchInvoice(invoiceNum, invoices) if err != nil { - return err + return nil, err } // Add idempotency to duplicate settles, return here to avoid // overwriting the previous info. if invoice.Terms.Settled { - return nil + return nil, nil } // Now that we know the invoice hasn't already been settled, we'll @@ -688,13 +701,13 @@ func settleInvoice(invoices, settleIndex *bolt.Bucket, invoiceNum []byte, // proper location within our time series. nextSettleSeqNo, err := settleIndex.NextSequence() if err != nil { - return err + return nil, err } var seqNoBytes [8]byte byteOrder.PutUint64(seqNoBytes[:], nextSettleSeqNo) if err := settleIndex.Put(seqNoBytes[:], invoiceNum); err != nil { - return err + return nil, err } invoice.AmtPaid = amtPaid @@ -704,8 +717,12 @@ func settleInvoice(invoices, settleIndex *bolt.Bucket, invoiceNum []byte, var buf bytes.Buffer if err := serializeInvoice(&buf, &invoice); err != nil { - return nil + return nil, err } - return invoices.Put(invoiceNum[:], buf.Bytes()) + if err := invoices.Put(invoiceNum[:], buf.Bytes()); err != nil { + return nil, err + } + + return &invoice, nil } From 3bedffcc1a73eeaec15542f6d442a00060a6980e Mon Sep 17 00:00:00 2001 From: Olaoluwa Osuntokun Date: Thu, 28 Jun 2018 21:47:41 -0700 Subject: [PATCH 16/26] invoiceregistry: serialize all invoice modifications, eliminate extra db call for settle In this commit, we now ensure that all modifications to the invoice DB are properly serialized. This ensures that our time series within the database will be properly coherent. Additionally, within SettleInvoice, we remove an extra DB call by taking advantage of the new SettleInvoice method which will return the invoice being settled as well. --- invoiceregistry.go | 27 ++++++++++----------------- 1 file changed, 10 insertions(+), 17 deletions(-) diff --git a/invoiceregistry.go b/invoiceregistry.go index a49a8581..ab38ea77 100644 --- a/invoiceregistry.go +++ b/invoiceregistry.go @@ -223,6 +223,9 @@ func (i *invoiceRegistry) AddDebugInvoice(amt btcutil.Amount, preimage chainhash // daemon add/forward HTLCs are able to obtain the proper preimage required for // redemption in the case that we're the final destination. func (i *invoiceRegistry) AddInvoice(invoice *channeldb.Invoice) error { + i.Lock() + defer i.Unlock() + ltndLog.Debugf("Adding invoice %v", newLogClosure(func() string { return spew.Sdump(invoice) })) @@ -280,39 +283,29 @@ func (i *invoiceRegistry) LookupInvoice(rHash chainhash.Hash) (channeldb.Invoice func (i *invoiceRegistry) SettleInvoice(rHash chainhash.Hash, amtPaid lnwire.MilliSatoshi) error { + i.Lock() + defer i.Unlock() + ltndLog.Debugf("Settling invoice %x", rHash[:]) // First check the in-memory debug invoice index to see if this is an // existing invoice added for debugging. - i.RLock() if _, ok := i.debugInvoices[rHash]; ok { // Debug invoices are never fully settled, so we simply return // immediately in this case. - i.RUnlock() - return nil } - i.RUnlock() // If this isn't a debug invoice, then we'll attempt to settle an // invoice matching this rHash on disk (if one exists). - if err := i.cdb.SettleInvoice(rHash, amtPaid); err != nil { + invoice, err := i.cdb.SettleInvoice(rHash, amtPaid) + if err != nil { return err } - // Launch a new goroutine to notify any/all registered invoice - // notification clients. - go func() { - invoice, err := i.cdb.LookupInvoice(rHash) - if err != nil { - ltndLog.Errorf("unable to find invoice: %v", err) - return - } + ltndLog.Infof("Payment received: %v", spew.Sdump(invoice)) - ltndLog.Infof("Payment received: %v", spew.Sdump(invoice)) - - i.notifyClients(&invoice, true) - }() + i.notifyClients(invoice, true) return nil } From 2dcc2d63a666815218533b53318cbe52afe6e714 Mon Sep 17 00:00:00 2001 From: Olaoluwa Osuntokun Date: Fri, 29 Jun 2018 16:08:16 -0700 Subject: [PATCH 17/26] invoiceregistry: ensure we never send duplicate add/settle notifications MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit In this commit, we add additional logic to the primary notification dispatch loop to ensure that we'll never send the same add/settle event to the same client twice. Consider the case where as we're sending a client its notification backlog, a new invoice is settled (index=9). In this case, the database will be reflected immediately, and the event will also be queued for notifying later. We'll send out this latest event during the backlog clear, but then will send it again once we return back to the main loop. To ensure that this never happens, we'll now record the last index that we’ve sent to a client to ensure that we no longer are able to send duplicate notification events. --- invoiceregistry.go | 33 ++++++++++++++++++++++++++++++++- 1 file changed, 32 insertions(+), 1 deletion(-) diff --git a/invoiceregistry.go b/invoiceregistry.go index ab38ea77..1ed3e95b 100644 --- a/invoiceregistry.go +++ b/invoiceregistry.go @@ -135,14 +135,45 @@ func (i *invoiceRegistry) invoiceEventNotifier() { // dispatch notifications to all registered clients. case event := <-i.invoiceEvents: for _, client := range i.notificationClients { + // Before we dispatch this event, we'll check + // to ensure that this client hasn't already + // received this notification in order to + // ensure we don't duplicate any events. + invoice := event.invoice + switch { + // If we've already sent this settle event to + // the client, then we can skip this. + case event.isSettle && + client.settleIndex == invoice.SettleIndex: + continue + + // Similarly, if we've already sent this add to + // the client then we can skip this one. + case !event.isSettle && + client.addIndex == invoice.AddIndex: + continue + } + select { case client.ntfnQueue.ChanIn() <- &invoiceEvent{ isSettle: event.isSettle, - invoice: event.invoice, + invoice: invoice, }: case <-i.quit: return } + + // Each time we send a notification to a + // client, we'll record the latest add/settle + // index it has. We'll use this to ensure we + // don't send a notification twice, which can + // happen if a new event is added while we're + // catching up a new client. + if event.isSettle { + client.settleIndex = invoice.SettleIndex + } else { + client.addIndex = invoice.AddIndex + } } case <-i.quit: From 7aeed0b58f45138ba961c245347440edfe853e45 Mon Sep 17 00:00:00 2001 From: Olaoluwa Osuntokun Date: Fri, 29 Jun 2018 18:05:51 -0700 Subject: [PATCH 18/26] channeldb: AddInvoice now returns the addIndex of the new invoice --- channeldb/invoice_test.go | 8 ++++---- channeldb/invoices.go | 35 +++++++++++++++++++++++------------ 2 files changed, 27 insertions(+), 16 deletions(-) diff --git a/channeldb/invoice_test.go b/channeldb/invoice_test.go index aca845d5..4f3ee5dd 100644 --- a/channeldb/invoice_test.go +++ b/channeldb/invoice_test.go @@ -70,7 +70,7 @@ func TestInvoiceWorkflow(t *testing.T) { // Add the invoice to the database, this should succeed as there aren't // any existing invoices within the database with the same payment // hash. - if err := db.AddInvoice(fakeInvoice); err != nil { + if _, err := db.AddInvoice(fakeInvoice); err != nil { t.Fatalf("unable to find invoice: %v", err) } @@ -126,7 +126,7 @@ func TestInvoiceWorkflow(t *testing.T) { // Attempt to insert generated above again, this should fail as // duplicates are rejected by the processing logic. - if err := db.AddInvoice(fakeInvoice); err != ErrDuplicateInvoice { + if _, err := db.AddInvoice(fakeInvoice); err != ErrDuplicateInvoice { t.Fatalf("invoice insertion should fail due to duplication, "+ "instead %v", err) } @@ -149,7 +149,7 @@ func TestInvoiceWorkflow(t *testing.T) { t.Fatalf("unable to create invoice: %v", err) } - if err := db.AddInvoice(invoice); err != nil { + if _, err := db.AddInvoice(invoice); err != nil { t.Fatalf("unable to add invoice %v", err) } @@ -198,7 +198,7 @@ func TestInvoiceAddTimeSeries(t *testing.T) { t.Fatalf("unable to create invoice: %v", err) } - if err := db.AddInvoice(invoice); err != nil { + if _, err := db.AddInvoice(invoice); err != nil { t.Fatalf("unable to add invoice %v", err) } diff --git a/channeldb/invoices.go b/channeldb/invoices.go index a6fe0df9..a05f57df 100644 --- a/channeldb/invoices.go +++ b/channeldb/invoices.go @@ -179,11 +179,12 @@ func validateInvoice(i *Invoice) error { // has *any* payment hashes which already exists within the database, then the // insertion will be aborted and rejected due to the strict policy banning any // duplicate payment hashes. -func (d *DB) AddInvoice(newInvoice *Invoice) error { +func (d *DB) AddInvoice(newInvoice *Invoice) (uint64, error) { if err := validateInvoice(newInvoice); err != nil { - return err + return 0, err } + var invoiceAddIndex uint64 err := d.Update(func(tx *bolt.Tx) error { invoices, err := tx.CreateBucketIfNotExists(invoiceBucket) if err != nil { @@ -227,15 +228,21 @@ func (d *DB) AddInvoice(newInvoice *Invoice) error { invoiceNum = byteOrder.Uint32(invoiceCounter) } - return putInvoice( + newIndex, err := putInvoice( invoices, invoiceIndex, addIndex, newInvoice, invoiceNum, ) + if err != nil { + return err + } + + invoiceAddIndex = newIndex + return nil }) if err != nil { - return err + return 0, err } - return err + return invoiceAddIndex, err } // InvoicesAddedSince can be used by callers to seek into the event time series @@ -501,7 +508,7 @@ func (d *DB) InvoicesSettledSince(sinceSettleIndex uint64) ([]Invoice, error) { } func putInvoice(invoices, invoiceIndex, addIndex *bolt.Bucket, - i *Invoice, invoiceNum uint32) error { + i *Invoice, invoiceNum uint32) (uint64, error) { // Create the invoice key which is just the big-endian representation // of the invoice number. @@ -514,7 +521,7 @@ func putInvoice(invoices, invoiceIndex, addIndex *bolt.Bucket, invoiceCounter := invoiceNum + 1 byteOrder.PutUint32(scratch[:], invoiceCounter) if err := invoiceIndex.Put(numInvoicesKey, scratch[:]); err != nil { - return err + return 0, err } // Add the payment hash to the invoice index. This will let us quickly @@ -523,7 +530,7 @@ func putInvoice(invoices, invoiceIndex, addIndex *bolt.Bucket, paymentHash := sha256.Sum256(i.Terms.PaymentPreimage[:]) err := invoiceIndex.Put(paymentHash[:], invoiceKey[:]) if err != nil { - return err + return 0, err } // Next, we'll obtain the next add invoice index (sequence @@ -531,7 +538,7 @@ func putInvoice(invoices, invoiceIndex, addIndex *bolt.Bucket, // event stream. nextAddSeqNo, err := addIndex.NextSequence() if err != nil { - return err + return 0, err } // With the next sequence obtained, we'll updating the event series in @@ -540,7 +547,7 @@ func putInvoice(invoices, invoiceIndex, addIndex *bolt.Bucket, var seqNoBytes [8]byte byteOrder.PutUint64(seqNoBytes[:], nextAddSeqNo) if err := addIndex.Put(seqNoBytes[:], invoiceKey[:]); err != nil { - return err + return 0, err } i.AddIndex = nextAddSeqNo @@ -548,10 +555,14 @@ func putInvoice(invoices, invoiceIndex, addIndex *bolt.Bucket, // Finally, serialize the invoice itself to be written to the disk. var buf bytes.Buffer if err := serializeInvoice(&buf, i); err != nil { - return nil + return 0, nil } - return invoices.Put(invoiceKey[:], buf.Bytes()) + if err := invoices.Put(invoiceKey[:], buf.Bytes()); err != nil { + return 0, err + } + + return nextAddSeqNo, nil } func serializeInvoice(w io.Writer, i *Invoice) error { From 00de6f11b103d62a3335279e1caa7f0443899d06 Mon Sep 17 00:00:00 2001 From: Olaoluwa Osuntokun Date: Fri, 29 Jun 2018 18:06:10 -0700 Subject: [PATCH 19/26] lnrpc: the response to AddInvoice now contains the addIndex of new invoice --- lnrpc/rpc.pb.go | 251 ++++++++++++++++++++++------------------- lnrpc/rpc.proto | 8 ++ lnrpc/rpc.swagger.json | 5 + 3 files changed, 145 insertions(+), 119 deletions(-) diff --git a/lnrpc/rpc.pb.go b/lnrpc/rpc.pb.go index 6f1394ce..ff3912d0 100644 --- a/lnrpc/rpc.pb.go +++ b/lnrpc/rpc.pb.go @@ -4002,6 +4002,12 @@ type AddInvoiceResponse struct { // details of the invoice, the sender has all the data necessary to send a // payment to the recipient. PaymentRequest string `protobuf:"bytes,2,opt,name=payment_request" json:"payment_request,omitempty"` + // * + // The "add" index of this invoice. Each newly created invoice will increment + // this index making it monotonically increasing. Callers to the + // SubscribeInvoices call can use this to instantly get notified of all added + // invoices with an add_index greater than this one. + AddIndex uint64 `protobuf:"varint,16,opt,name=add_index" json:"add_index,omitempty"` } func (m *AddInvoiceResponse) Reset() { *m = AddInvoiceResponse{} } @@ -4023,6 +4029,13 @@ func (m *AddInvoiceResponse) GetPaymentRequest() string { return "" } +func (m *AddInvoiceResponse) GetAddIndex() uint64 { + if m != nil { + return m.AddIndex + } + return 0 +} + type PaymentHash struct { // * // The hex-encoded payment hash of the invoice to be looked up. The passed @@ -7082,7 +7095,7 @@ var _Lightning_serviceDesc = grpc.ServiceDesc{ func init() { proto.RegisterFile("rpc.proto", fileDescriptor0) } var fileDescriptor0 = []byte{ - // 6215 bytes of a gzipped FileDescriptorProto + // 6217 bytes of a gzipped FileDescriptorProto 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xbc, 0x5c, 0x4d, 0x6c, 0x1c, 0xc9, 0x75, 0x56, 0x0f, 0x87, 0x3f, 0xf3, 0x66, 0x48, 0x0e, 0x8b, 0x14, 0x35, 0x9a, 0xdd, 0xd5, 0x6a, 0xdb, 0xc2, 0x4a, 0x66, 0x36, 0x92, 0x96, 0xb6, 0x37, 0xeb, 0xdd, 0xc4, 0x8e, 0x44, 0x52, 0xa2, @@ -7354,122 +7367,122 @@ var fileDescriptor0 = []byte{ 0x33, 0x21, 0xb6, 0x09, 0x4d, 0x3a, 0xb2, 0xc9, 0xfd, 0x5c, 0xa2, 0xfd, 0x6c, 0x9b, 0x67, 0x3a, 0xda, 0x51, 0x93, 0xc8, 0xbc, 0x79, 0x58, 0xb6, 0x6f, 0x1e, 0x84, 0xd2, 0x94, 0x17, 0x36, 0x6d, 0xea, 0x2d, 0x07, 0xd0, 0x9a, 0xca, 0x05, 0x13, 0x04, 0x2b, 0x44, 0x60, 0x61, 0x28, 0xb5, 0x78, - 0xd0, 0x88, 0xfd, 0x60, 0xd0, 0x61, 0x42, 0x6a, 0x55, 0xd9, 0xfd, 0x32, 0xb0, 0x3b, 0x83, 0x81, - 0xe4, 0x26, 0x7d, 0x20, 0xcd, 0xf9, 0xc0, 0xb1, 0xf8, 0xa0, 0x62, 0x3f, 0x6a, 0x95, 0xfb, 0xe1, - 0xee, 0x40, 0x73, 0xdf, 0x48, 0x89, 0x26, 0xc6, 0x53, 0xc9, 0xd0, 0x92, 0x59, 0x0d, 0xc4, 0xe8, - 0xb0, 0x66, 0x76, 0xe8, 0xfe, 0x1c, 0xb0, 0xbd, 0x20, 0xcd, 0xf4, 0xf8, 0xf2, 0x1c, 0x6c, 0x15, - 0x17, 0xc8, 0xd3, 0x98, 0x9a, 0x12, 0xa3, 0xf4, 0xa2, 0x3b, 0x22, 0xff, 0xa9, 0x38, 0xb1, 0x0d, - 0x58, 0x08, 0x04, 0x54, 0x94, 0x33, 0x45, 0xa9, 0xeb, 0xdd, 0xa7, 0xb0, 0x2a, 0x41, 0xd3, 0x46, - 0xdb, 0xfb, 0xe1, 0x9c, 0xb7, 0x1f, 0xb5, 0xf2, 0x7e, 0xb8, 0xdf, 0x77, 0x60, 0x5e, 0x2e, 0x0e, - 0xd2, 0x97, 0xd2, 0xc9, 0x1b, 0x9e, 0x85, 0x55, 0x27, 0x12, 0x97, 0x65, 0x6c, 0xa6, 0x4a, 0xc6, - 0x18, 0xd4, 0x63, 0x3f, 0x3b, 0xa6, 0x03, 0x54, 0xc3, 0xa3, 0xdf, 0xac, 0x2d, 0x0e, 0xf5, 0x42, - 0x96, 0xe9, 0x40, 0x5f, 0x95, 0x4b, 0x2f, 0x4c, 0x46, 0x09, 0x47, 0x27, 0x99, 0xb2, 0x26, 0x04, - 0xae, 0x2f, 0x43, 0x64, 0x3e, 0x57, 0x0e, 0xe7, 0x2b, 0x2e, 0x9b, 0x28, 0xae, 0xb8, 0x24, 0xf5, - 0x74, 0xbd, 0xdb, 0x85, 0xce, 0x36, 0x1f, 0xf1, 0x8c, 0xdf, 0x19, 0x8d, 0x8a, 0xed, 0xbf, 0x06, - 0x97, 0x2b, 0xea, 0xa4, 0x53, 0x75, 0x0f, 0x56, 0xb6, 0xf9, 0xe1, 0x64, 0xb8, 0xc7, 0x4f, 0xf2, - 0x1b, 0x4b, 0x06, 0xf5, 0xf4, 0x38, 0x3a, 0x95, 0xdc, 0x41, 0xbf, 0xd9, 0x1b, 0x00, 0x23, 0xa4, - 0xe9, 0xa5, 0x31, 0xef, 0xab, 0x14, 0x5a, 0x42, 0x0e, 0x62, 0xde, 0x77, 0xdf, 0x03, 0x66, 0xb6, - 0x23, 0xa7, 0x80, 0x7a, 0x6a, 0x72, 0xd8, 0x4b, 0xa7, 0x69, 0xc6, 0xc7, 0x2a, 0x37, 0xd8, 0x84, - 0xdc, 0xeb, 0xd0, 0xda, 0xf7, 0xa7, 0x1e, 0xff, 0x86, 0xcc, 0xe8, 0xc7, 0xb3, 0xbb, 0x3f, 0x45, - 0x61, 0xd0, 0x67, 0x77, 0xaa, 0x76, 0xff, 0xab, 0x06, 0x73, 0x82, 0x12, 0x5b, 0x1d, 0xf0, 0x34, - 0x0b, 0x42, 0x71, 0x5b, 0x27, 0x5b, 0x35, 0xa0, 0x12, 0x6f, 0xd4, 0x2a, 0x78, 0x43, 0x7a, 0xd3, - 0x2a, 0x1d, 0x51, 0x32, 0x81, 0x85, 0x21, 0xc7, 0xe6, 0x59, 0x10, 0xe2, 0xf0, 0x98, 0x03, 0x85, - 0x60, 0x4e, 0xae, 0x0d, 0xc5, 0xf8, 0x14, 0xdb, 0x4b, 0x76, 0x30, 0xa1, 0x4a, 0x9d, 0x3b, 0x2f, - 0xb8, 0xa6, 0xa4, 0x73, 0x4b, 0xba, 0x75, 0xe1, 0x15, 0x74, 0xab, 0x70, 0xb1, 0x5f, 0xa6, 0x5b, - 0xe1, 0x15, 0x74, 0xab, 0xcb, 0xa0, 0x7d, 0x8f, 0x73, 0x8f, 0xa3, 0xd5, 0x56, 0xec, 0xf4, 0x1d, - 0x07, 0xda, 0xd2, 0xe1, 0xd0, 0x75, 0xec, 0x2d, 0xcb, 0x3b, 0xa9, 0x4c, 0x1a, 0xbc, 0x06, 0x8b, - 0xe4, 0x33, 0xe8, 0xa8, 0x95, 0x0c, 0xb1, 0x59, 0x20, 0xce, 0x43, 0x5d, 0x2d, 0x8c, 0x83, 0x91, - 0xdc, 0x14, 0x13, 0x52, 0x81, 0x2f, 0x3c, 0xbf, 0xd3, 0x96, 0x38, 0x9e, 0x2e, 0xbb, 0x7f, 0xe5, - 0xc0, 0x8a, 0x31, 0x60, 0xc9, 0x85, 0x1f, 0x82, 0xca, 0x92, 0x10, 0xc1, 0x2d, 0x21, 0x4c, 0x97, - 0x6c, 0xe7, 0x29, 0xff, 0xcc, 0x22, 0xa6, 0xcd, 0xf4, 0xa7, 0x34, 0xc0, 0x74, 0x32, 0x96, 0x5a, - 0xc9, 0x84, 0x90, 0x91, 0x4e, 0x39, 0x7f, 0xa6, 0x49, 0x66, 0x84, 0xe2, 0x32, 0x31, 0xba, 0x04, - 0x47, 0x5f, 0x47, 0x13, 0x89, 0xbc, 0x2f, 0x1b, 0x74, 0xff, 0xc9, 0x81, 0x55, 0xe1, 0xb4, 0xca, - 0x23, 0x81, 0xce, 0xe8, 0x9e, 0x13, 0x5e, 0xba, 0x90, 0xc8, 0xdd, 0x0b, 0x9e, 0x2c, 0xb3, 0xcf, - 0xbc, 0xa2, 0xa3, 0xad, 0x93, 0x1f, 0xce, 0xd8, 0x8b, 0x99, 0xaa, 0xbd, 0x78, 0xc9, 0x4a, 0x57, - 0x05, 0x73, 0x66, 0x2b, 0x83, 0x39, 0x77, 0xe7, 0x61, 0x36, 0xed, 0x47, 0x31, 0x77, 0xd7, 0x61, - 0xcd, 0x9e, 0x9c, 0x54, 0x41, 0xdf, 0x75, 0xa0, 0x73, 0x4f, 0x84, 0x36, 0x83, 0x70, 0xb8, 0x1b, - 0xa4, 0x59, 0x94, 0xe8, 0x27, 0x2c, 0x57, 0x00, 0xd2, 0xcc, 0x4f, 0x32, 0x91, 0x9c, 0x26, 0xc3, - 0x30, 0x39, 0x82, 0x63, 0xe4, 0xe1, 0x40, 0xd4, 0x8a, 0xbd, 0xd1, 0x65, 0xdc, 0x18, 0x32, 0x1b, - 0xbd, 0xe8, 0xe8, 0x28, 0xe5, 0xda, 0xad, 0x36, 0x31, 0x3c, 0x99, 0xa3, 0xc4, 0xe3, 0x59, 0x94, - 0x9f, 0x90, 0xaa, 0x15, 0xfe, 0x6a, 0x01, 0x75, 0xff, 0xc2, 0x81, 0xe5, 0x7c, 0x90, 0x3b, 0x08, - 0xda, 0xda, 0x41, 0xda, 0xb3, 0x5c, 0x3b, 0xa8, 0x00, 0x51, 0x80, 0x06, 0x4e, 0x8e, 0xcd, 0x40, - 0x48, 0x62, 0x65, 0x29, 0x9a, 0xa8, 0x44, 0x40, 0x13, 0x12, 0xb7, 0xfc, 0x19, 0x7e, 0x2d, 0xb2, - 0x00, 0x65, 0x89, 0x72, 0x0b, 0xc7, 0x19, 0x7d, 0x35, 0x27, 0x1c, 0x76, 0x59, 0x54, 0xf6, 0x69, - 0x9e, 0x50, 0xfc, 0xe9, 0xfe, 0xae, 0x03, 0x97, 0x2b, 0x16, 0x57, 0x4a, 0xc6, 0x36, 0xac, 0x1c, - 0xe9, 0x4a, 0xb5, 0x00, 0x42, 0x3c, 0xd6, 0x55, 0x2c, 0xde, 0x9e, 0xb4, 0x57, 0xfe, 0x00, 0xdd, - 0x77, 0x8a, 0x6b, 0x89, 0x25, 0xb5, 0x12, 0x64, 0xca, 0x15, 0x9b, 0xbf, 0x37, 0x03, 0x4b, 0xe2, - 0x8e, 0x46, 0x3c, 0x26, 0xe5, 0x09, 0xfb, 0x08, 0xe6, 0xe5, 0x63, 0x60, 0x76, 0x51, 0x76, 0x6b, - 0x3f, 0x3f, 0xee, 0xae, 0x17, 0x61, 0xc9, 0x3b, 0xab, 0xbf, 0xf1, 0x83, 0x7f, 0xf9, 0xfd, 0xda, - 0x22, 0x6b, 0xde, 0x3a, 0x79, 0xf7, 0xd6, 0x90, 0x87, 0x29, 0xb6, 0xf1, 0x2b, 0x00, 0xf9, 0x33, - 0x59, 0xd6, 0xd1, 0x6e, 0x4a, 0xe1, 0xfd, 0x6f, 0xf7, 0x72, 0x45, 0x8d, 0x6c, 0xf7, 0x32, 0xb5, - 0xbb, 0xea, 0x2e, 0x61, 0xbb, 0x41, 0x18, 0x64, 0xe2, 0xcd, 0xec, 0x07, 0xce, 0x06, 0x1b, 0x40, - 0xcb, 0x7c, 0x05, 0xcb, 0xd4, 0x91, 0xbe, 0xe2, 0x0d, 0x6e, 0xf7, 0xb5, 0xca, 0x3a, 0x15, 0xcf, - 0xa0, 0x3e, 0x2e, 0xba, 0x6d, 0xec, 0x63, 0x42, 0x14, 0x79, 0x2f, 0x23, 0x58, 0xb2, 0x1f, 0xbb, - 0xb2, 0xd7, 0x0d, 0xb1, 0x2e, 0x3d, 0xb5, 0xed, 0xbe, 0x71, 0x46, 0xad, 0xec, 0xeb, 0x0d, 0xea, - 0xeb, 0x92, 0xcb, 0xb0, 0xaf, 0x3e, 0xd1, 0xa8, 0xa7, 0xb6, 0x1f, 0x38, 0x1b, 0x9b, 0xdf, 0xba, - 0x02, 0x0d, 0x1d, 0x84, 0x63, 0x5f, 0x87, 0x45, 0xeb, 0x12, 0x8d, 0xa9, 0x69, 0x54, 0xdd, 0xb9, - 0x75, 0x5f, 0xaf, 0xae, 0x94, 0x1d, 0x5f, 0xa1, 0x8e, 0x3b, 0x6c, 0x1d, 0x3b, 0x96, 0xb7, 0x50, - 0xb7, 0xe8, 0xea, 0x50, 0x64, 0x2e, 0x3e, 0x13, 0xf3, 0xcc, 0x2f, 0xbe, 0xac, 0x79, 0x96, 0x2e, - 0xca, 0xac, 0x79, 0x96, 0x6f, 0xcb, 0xdc, 0xd7, 0xa9, 0xbb, 0x75, 0xb6, 0x66, 0x76, 0xa7, 0x83, - 0x63, 0x9c, 0x72, 0x4d, 0xcd, 0xb7, 0xb0, 0xec, 0x0d, 0xcd, 0x58, 0x55, 0x6f, 0x64, 0x35, 0x8b, - 0x94, 0x1f, 0xca, 0xba, 0x1d, 0xea, 0x8a, 0x31, 0xda, 0x3e, 0xf3, 0x29, 0x2c, 0xfb, 0x2a, 0x34, - 0xf4, 0xc3, 0x2f, 0x76, 0xc9, 0x78, 0x6d, 0x67, 0xbe, 0x46, 0xeb, 0x76, 0xca, 0x15, 0x55, 0x8c, - 0x61, 0xb6, 0x8c, 0x8c, 0xb1, 0x07, 0x17, 0xa5, 0x53, 0x7d, 0xc8, 0x7f, 0x94, 0x99, 0x54, 0xbc, - 0xe0, 0xbd, 0xed, 0xb0, 0x0f, 0x61, 0x41, 0xbd, 0xa7, 0x63, 0xeb, 0xd5, 0xef, 0x02, 0xbb, 0x97, - 0x4a, 0xb8, 0xd4, 0x1e, 0x77, 0x00, 0xf2, 0xb7, 0x60, 0x5a, 0xce, 0x4a, 0x2f, 0xd4, 0xf4, 0x22, - 0x56, 0x3c, 0x1c, 0x1b, 0xd2, 0xcb, 0x37, 0xfb, 0xa9, 0x19, 0x7b, 0x33, 0xa7, 0xaf, 0x7c, 0x84, - 0xf6, 0x92, 0x06, 0xdd, 0x75, 0x5a, 0xbb, 0x36, 0x23, 0xc1, 0x0d, 0xf9, 0xa9, 0xca, 0xba, 0xde, - 0x86, 0xa6, 0xf1, 0xbe, 0x8c, 0xa9, 0x16, 0xca, 0x6f, 0xd3, 0xba, 0xdd, 0xaa, 0x2a, 0x39, 0xdc, - 0x2f, 0xc0, 0xa2, 0xf5, 0x50, 0x4c, 0x4b, 0x46, 0xd5, 0x33, 0x34, 0x2d, 0x19, 0xd5, 0x6f, 0xcb, - 0xbe, 0x02, 0x4d, 0xe3, 0x59, 0x17, 0x33, 0xb2, 0xcd, 0x0a, 0x0f, 0xba, 0xf4, 0x88, 0xaa, 0x5e, - 0x81, 0xad, 0xd1, 0x7c, 0x97, 0xdc, 0x06, 0xce, 0x97, 0x52, 0x8f, 0x91, 0x49, 0xbe, 0x0e, 0x4b, - 0xf6, 0x43, 0x2f, 0x2d, 0x55, 0x95, 0x4f, 0xc6, 0xb4, 0x54, 0x9d, 0xf1, 0x3a, 0x4c, 0x32, 0xe4, - 0xc6, 0xaa, 0xee, 0xe4, 0xd6, 0xc7, 0xf2, 0x0a, 0xea, 0x05, 0xfb, 0x12, 0xaa, 0x0e, 0x99, 0x0b, - 0xce, 0xf2, 0xe7, 0x6d, 0x76, 0xc6, 0xb8, 0xe6, 0xf6, 0x52, 0xda, 0xb8, 0xbb, 0x42, 0x8d, 0x37, - 0x59, 0x3e, 0x03, 0x61, 0x0f, 0x28, 0x27, 0xdc, 0xb0, 0x07, 0x66, 0xda, 0xb8, 0x61, 0x0f, 0xac, - 0xd4, 0xf1, 0xa2, 0x3d, 0xc8, 0x02, 0x6c, 0x23, 0x84, 0xe5, 0x42, 0xba, 0x85, 0x16, 0x96, 0xea, - 0xfc, 0xb4, 0xee, 0x95, 0x97, 0x67, 0x69, 0xd8, 0x6a, 0x46, 0xa9, 0x97, 0x5b, 0x2a, 0x9d, 0xf0, - 0x57, 0xa1, 0x65, 0x3e, 0xd0, 0xd1, 0x16, 0xa2, 0xe2, 0x59, 0x91, 0xb6, 0x10, 0x55, 0x2f, 0x7a, - 0xd4, 0xe6, 0xb2, 0x96, 0xd9, 0x0d, 0x6e, 0xae, 0xfd, 0x9e, 0x21, 0x57, 0x99, 0x55, 0x0f, 0x35, - 0x72, 0x95, 0x59, 0xf9, 0x08, 0x42, 0x6d, 0x2e, 0x5b, 0xb5, 0xe6, 0x22, 0x62, 0x8f, 0xec, 0x2b, - 0xb0, 0x6c, 0xe4, 0x32, 0x1d, 0x4c, 0xc3, 0xbe, 0x66, 0xd4, 0x72, 0xa6, 0x6b, 0xb7, 0xca, 0xf3, - 0x74, 0x2f, 0x51, 0xfb, 0x2b, 0xae, 0x35, 0x09, 0x64, 0xd2, 0x2d, 0x68, 0x9a, 0x79, 0x52, 0x2f, - 0x69, 0xf7, 0x92, 0x51, 0x65, 0x26, 0x7d, 0xde, 0x76, 0xd8, 0x1f, 0x3a, 0xd0, 0xb2, 0xb2, 0x8e, - 0xac, 0x08, 0x7b, 0xa1, 0x9d, 0x8e, 0x59, 0x67, 0x36, 0xe4, 0x7a, 0x34, 0xc8, 0xbd, 0x8d, 0x2f, - 0x58, 0x8b, 0xf0, 0xb1, 0x75, 0x82, 0xb9, 0x59, 0x7c, 0xe7, 0xfd, 0xa2, 0x48, 0x60, 0x66, 0x03, - 0xbf, 0xb8, 0xed, 0xb0, 0x0f, 0xc4, 0x3f, 0x19, 0xa8, 0x88, 0x05, 0x33, 0x14, 0x69, 0x71, 0xc9, - 0xcc, 0x67, 0xfc, 0x37, 0x9c, 0xdb, 0x0e, 0xfb, 0x9a, 0x78, 0xce, 0x2d, 0xbf, 0xa5, 0x95, 0x7f, - 0xd5, 0xef, 0xdd, 0x6b, 0x34, 0x9b, 0x2b, 0xee, 0x65, 0x6b, 0x36, 0x45, 0x4b, 0x72, 0x47, 0x8c, - 0x4e, 0xbe, 0xd2, 0xcf, 0x55, 0x62, 0xe9, 0xe5, 0xfe, 0xd9, 0x83, 0x1c, 0x8b, 0x41, 0x4a, 0x72, - 0x8b, 0x3d, 0x5e, 0xb1, 0x19, 0x77, 0x83, 0xc6, 0x7a, 0xcd, 0x7d, 0xf3, 0xcc, 0xb1, 0xde, 0xa2, - 0x13, 0x29, 0x8e, 0x78, 0x1f, 0x20, 0x0f, 0xb9, 0xb1, 0x42, 0xfc, 0x49, 0x5b, 0x85, 0x72, 0x54, - 0xce, 0xe6, 0x41, 0x15, 0xa6, 0xc2, 0x16, 0xbf, 0x2a, 0x44, 0x55, 0xd2, 0xa7, 0x7a, 0xf4, 0xe5, - 0xd0, 0x59, 0xb7, 0x5b, 0x55, 0x55, 0x25, 0xa8, 0xaa, 0x7d, 0xf6, 0x04, 0x16, 0xf7, 0xa2, 0xe8, - 0xd9, 0x24, 0xd6, 0x21, 0x67, 0x3b, 0x7e, 0xb3, 0xeb, 0xa7, 0xc7, 0xdd, 0xc2, 0x2c, 0xdc, 0xab, - 0xd4, 0x54, 0x97, 0x75, 0x8c, 0xa6, 0x6e, 0x7d, 0x9c, 0x07, 0xfc, 0x5e, 0x30, 0x1f, 0x56, 0xb4, - 0x07, 0xa0, 0x07, 0xde, 0xb5, 0x9b, 0x31, 0xe3, 0x6e, 0xa5, 0x2e, 0x2c, 0x9f, 0x4c, 0x8d, 0xf6, - 0x56, 0xaa, 0xda, 0xbc, 0xed, 0xb0, 0x7d, 0x68, 0x6d, 0xf3, 0x7e, 0x34, 0xe0, 0x32, 0xe2, 0xb2, - 0x9a, 0x0f, 0x5c, 0x87, 0x6a, 0xba, 0x8b, 0x16, 0x68, 0xeb, 0xc4, 0xd8, 0x9f, 0x26, 0xfc, 0x1b, - 0xb7, 0x3e, 0x96, 0xb1, 0x9c, 0x17, 0x4a, 0x27, 0xaa, 0xf8, 0x93, 0xa5, 0x13, 0x0b, 0x01, 0x2b, - 0x4b, 0x27, 0x96, 0x02, 0x56, 0xd6, 0x52, 0xab, 0xf8, 0x17, 0x1b, 0xc1, 0x4a, 0x29, 0xc6, 0xa5, - 0xfd, 0x88, 0xb3, 0x22, 0x63, 0xdd, 0xab, 0x67, 0x13, 0xd8, 0xbd, 0x6d, 0xd8, 0xbd, 0x1d, 0xc0, - 0xe2, 0x36, 0x17, 0x8b, 0x25, 0xee, 0xb5, 0x0b, 0xcf, 0xc6, 0xcc, 0x3b, 0xf0, 0xa2, 0x52, 0xa4, - 0x3a, 0xdb, 0xe8, 0xd1, 0xa5, 0x32, 0xfb, 0x2a, 0x34, 0xef, 0xf3, 0x4c, 0x5d, 0x64, 0x6b, 0x6f, - 0xac, 0x70, 0xb3, 0xdd, 0xad, 0xb8, 0x07, 0xb7, 0x79, 0x86, 0x5a, 0xbb, 0xc5, 0x07, 0x43, 0x2e, - 0xd4, 0x53, 0x2f, 0x18, 0xbc, 0x60, 0xbf, 0x44, 0x8d, 0xeb, 0xdc, 0x97, 0x75, 0xe3, 0xfe, 0xd3, - 0x6c, 0x7c, 0xb9, 0x80, 0x57, 0xb5, 0x1c, 0x46, 0x03, 0x6e, 0x98, 0xff, 0x10, 0x9a, 0x46, 0x62, - 0x96, 0x16, 0xa0, 0x72, 0x92, 0x99, 0x16, 0xa0, 0x8a, 0x3c, 0x2e, 0xf7, 0x06, 0xf5, 0xe3, 0xb2, - 0xab, 0x79, 0x3f, 0x22, 0x77, 0x2b, 0xef, 0xe9, 0xd6, 0xc7, 0xfe, 0x38, 0x7b, 0xc1, 0x9e, 0xd2, - 0x13, 0x32, 0xf3, 0xb2, 0x3e, 0xf7, 0x06, 0x8b, 0xf7, 0xfa, 0x7a, 0xb1, 0x8c, 0x2a, 0xdb, 0x43, - 0x14, 0x5d, 0x91, 0x97, 0xf0, 0x19, 0x80, 0x83, 0x2c, 0x8a, 0xb7, 0x7d, 0x3e, 0x8e, 0xc2, 0x5c, - 0xd7, 0xe6, 0x17, 0xd2, 0xb9, 0xfe, 0x32, 0x6e, 0xa5, 0xd9, 0x53, 0xc3, 0x1f, 0xb7, 0x72, 0x1d, - 0x14, 0x73, 0x9d, 0x79, 0x67, 0xad, 0x17, 0xa4, 0xe2, 0xde, 0xfa, 0xb6, 0x83, 0xde, 0x75, 0x1e, - 0x51, 0xd5, 0xde, 0x75, 0x29, 0x58, 0xab, 0xd5, 0x5e, 0x45, 0xf8, 0x75, 0x1f, 0x1a, 0x79, 0x88, - 0xee, 0x52, 0x9e, 0x5c, 0x67, 0x05, 0xf4, 0xb4, 0x55, 0x2c, 0x05, 0xce, 0xdc, 0x36, 0x2d, 0x15, - 0xb0, 0x05, 0x5c, 0x2a, 0x8a, 0x86, 0x05, 0xb0, 0x2a, 0x06, 0xa8, 0x4d, 0x3c, 0x5d, 0xb1, 0xaa, - 0x99, 0x54, 0x04, 0xaf, 0xb4, 0x34, 0x57, 0xc6, 0x7e, 0xac, 0x73, 0x36, 0x72, 0xab, 0xb8, 0xde, - 0x45, 0xd5, 0x3c, 0x86, 0x95, 0x52, 0xe0, 0x42, 0x8b, 0xf4, 0x59, 0xf1, 0x22, 0x2d, 0xd2, 0x67, - 0xc6, 0x3c, 0xdc, 0x8b, 0xd4, 0xe5, 0xb2, 0x0b, 0xd8, 0x65, 0x7a, 0x1a, 0x64, 0xfd, 0xe3, 0x0f, - 0x9c, 0x8d, 0xc3, 0x39, 0xfa, 0xe7, 0xb3, 0x4f, 0xfd, 0x6f, 0x00, 0x00, 0x00, 0xff, 0xff, 0x29, - 0x75, 0x77, 0x36, 0x2b, 0x4d, 0x00, 0x00, + 0xd0, 0x88, 0xfd, 0x60, 0xd0, 0x61, 0x42, 0x6a, 0x55, 0xd9, 0xcd, 0x80, 0xdd, 0x19, 0x0c, 0x24, + 0x37, 0xe9, 0x03, 0x69, 0xce, 0x07, 0x8e, 0xc5, 0x07, 0x15, 0xfb, 0x51, 0xab, 0xde, 0x8f, 0x97, + 0x8e, 0xda, 0xdd, 0x81, 0xe6, 0xbe, 0x91, 0x30, 0x4d, 0x6c, 0xa9, 0x52, 0xa5, 0x25, 0x2b, 0x1b, + 0x88, 0x31, 0x9c, 0x9a, 0x39, 0x1c, 0xf7, 0xe7, 0x80, 0xed, 0x05, 0x69, 0xa6, 0x47, 0x9f, 0x67, + 0x68, 0xab, 0xa8, 0x41, 0x9e, 0xe4, 0xd4, 0x94, 0x18, 0x25, 0x1f, 0xdd, 0x11, 0xd9, 0x51, 0xc5, + 0x69, 0x6f, 0xc0, 0x42, 0x20, 0xa0, 0xa2, 0x14, 0x2a, 0x4a, 0x5d, 0xef, 0x3e, 0x85, 0x55, 0x09, + 0x9a, 0x16, 0xdc, 0x9e, 0xb7, 0x73, 0xde, 0x6e, 0xd5, 0xca, 0xbb, 0xe5, 0x7e, 0xdf, 0x81, 0x79, + 0xb9, 0x38, 0x48, 0x5f, 0x4a, 0x36, 0x6f, 0x78, 0x16, 0x56, 0x9d, 0x66, 0x5c, 0x96, 0xc0, 0x99, + 0x2a, 0x09, 0x64, 0x50, 0x8f, 0xfd, 0xec, 0x98, 0x8e, 0x57, 0x0d, 0x8f, 0x7e, 0xb3, 0xb6, 0x38, + 0xf2, 0x0b, 0x49, 0xa7, 0xe3, 0x7e, 0x55, 0xa6, 0xbd, 0x30, 0x28, 0x25, 0x1c, 0x5d, 0x68, 0xca, + 0xa9, 0x10, 0xb8, 0xbe, 0x2a, 0x91, 0xd9, 0x5e, 0x39, 0x9c, 0xaf, 0xb8, 0x6c, 0xa2, 0xb8, 0xe2, + 0x92, 0xd4, 0xd3, 0xf5, 0x6e, 0x17, 0x3a, 0xdb, 0x7c, 0xc4, 0x33, 0x7e, 0x67, 0x34, 0x2a, 0xb6, + 0xff, 0x1a, 0x5c, 0xae, 0xa8, 0x93, 0x2e, 0xd7, 0x3d, 0x58, 0xd9, 0xe6, 0x87, 0x93, 0xe1, 0x1e, + 0x3f, 0xc9, 0xef, 0x33, 0x19, 0xd4, 0xd3, 0xe3, 0xe8, 0x54, 0x72, 0x07, 0xfd, 0x66, 0x6f, 0x00, + 0x8c, 0x90, 0xa6, 0x97, 0xc6, 0xbc, 0xaf, 0x12, 0x6c, 0x09, 0x39, 0x88, 0x79, 0xdf, 0x7d, 0x0f, + 0x98, 0xd9, 0x8e, 0x9c, 0x02, 0x6a, 0xb1, 0xc9, 0x61, 0x2f, 0x9d, 0xa6, 0x19, 0x1f, 0xab, 0xcc, + 0x61, 0x13, 0x72, 0xaf, 0x43, 0x6b, 0xdf, 0x9f, 0x7a, 0xfc, 0x1b, 0x32, 0xdf, 0x1f, 0x4f, 0xf6, + 0xfe, 0x14, 0x45, 0x45, 0x9f, 0xec, 0xa9, 0xda, 0xfd, 0xaf, 0x1a, 0xcc, 0x09, 0x4a, 0x6c, 0x75, + 0xc0, 0xd3, 0x2c, 0x08, 0xc5, 0x5d, 0x9e, 0x6c, 0xd5, 0x80, 0x4a, 0xbc, 0x51, 0xab, 0xe0, 0x0d, + 0xe9, 0x6b, 0xab, 0x64, 0x45, 0xc9, 0x04, 0x16, 0x86, 0x1c, 0x9b, 0xe7, 0x48, 0x88, 0xa3, 0x65, + 0x0e, 0x14, 0x42, 0x3d, 0xb9, 0xae, 0x14, 0xe3, 0x53, 0x6c, 0x2f, 0xd9, 0xc1, 0x84, 0x2a, 0x35, + 0xf2, 0xbc, 0xe0, 0x9a, 0x92, 0x46, 0x2e, 0x69, 0xde, 0x85, 0x57, 0xd0, 0xbc, 0xc2, 0x01, 0x7f, + 0x99, 0xe6, 0x85, 0x57, 0xd0, 0xbc, 0x2e, 0x83, 0xf6, 0x3d, 0xce, 0x3d, 0x8e, 0x36, 0x5d, 0xb1, + 0xd3, 0x77, 0x1c, 0x68, 0x4b, 0x77, 0x44, 0xd7, 0xb1, 0xb7, 0x2c, 0xdf, 0xa5, 0x32, 0xa5, 0xf0, + 0x1a, 0x2c, 0x92, 0x47, 0xa1, 0x63, 0x5a, 0x32, 0x00, 0x67, 0x81, 0x38, 0x0f, 0x75, 0xf1, 0x30, + 0x0e, 0x46, 0x72, 0x53, 0x4c, 0x48, 0x85, 0xc5, 0xf0, 0x74, 0x4f, 0x5b, 0xe2, 0x78, 0xba, 0xec, + 0xfe, 0x95, 0x03, 0x2b, 0xc6, 0x80, 0x25, 0x17, 0x7e, 0x08, 0x2a, 0x87, 0x42, 0x84, 0xbe, 0x84, + 0x30, 0x5d, 0xb2, 0x5d, 0xab, 0xfc, 0x33, 0x8b, 0x98, 0x36, 0xd3, 0x9f, 0xd2, 0x00, 0xd3, 0xc9, + 0x58, 0x6a, 0x25, 0x13, 0x42, 0x46, 0x3a, 0xe5, 0xfc, 0x99, 0x26, 0x99, 0x11, 0x8a, 0xcb, 0xc4, + 0xe8, 0x8a, 0x1c, 0x3d, 0x21, 0x4d, 0x24, 0xb2, 0xc2, 0x6c, 0xd0, 0xfd, 0x27, 0x07, 0x56, 0x85, + 0x4b, 0x2b, 0x0f, 0x0c, 0x3a, 0xdf, 0x7b, 0x4e, 0xf8, 0xf0, 0x42, 0x22, 0x77, 0x2f, 0x78, 0xb2, + 0xcc, 0x3e, 0xf3, 0x8a, 0x6e, 0xb8, 0x4e, 0x8d, 0x38, 0x63, 0x2f, 0x66, 0xaa, 0xf6, 0xe2, 0x25, + 0x2b, 0x5d, 0x15, 0xea, 0x99, 0xad, 0x0c, 0xf5, 0xdc, 0x9d, 0x87, 0xd9, 0xb4, 0x1f, 0xc5, 0xdc, + 0x5d, 0x87, 0x35, 0x7b, 0x72, 0x52, 0x05, 0x7d, 0xd7, 0x81, 0xce, 0x3d, 0x11, 0xf8, 0x0c, 0xc2, + 0xe1, 0x6e, 0x90, 0x66, 0x51, 0xa2, 0x1f, 0xb8, 0x5c, 0x01, 0x48, 0x33, 0x3f, 0xc9, 0x44, 0xea, + 0x9a, 0x0c, 0xd2, 0xe4, 0x08, 0x8e, 0x91, 0x87, 0x03, 0x51, 0x2b, 0xf6, 0x46, 0x97, 0x71, 0x63, + 0xc8, 0x6c, 0xf4, 0xa2, 0xa3, 0xa3, 0x94, 0x6b, 0xa7, 0xdb, 0xc4, 0xf0, 0xdc, 0x8e, 0x12, 0x8f, + 0x27, 0x55, 0x7e, 0x42, 0xaa, 0x56, 0x78, 0xb3, 0x05, 0xd4, 0xfd, 0x0b, 0x07, 0x96, 0xf3, 0x41, + 0xee, 0x20, 0x68, 0x6b, 0x07, 0x69, 0xcf, 0x72, 0xed, 0xa0, 0xc2, 0x47, 0x01, 0x1a, 0x38, 0x39, + 0x36, 0x03, 0x21, 0x89, 0x95, 0xa5, 0x68, 0xa2, 0xd2, 0x04, 0x4d, 0x48, 0xe4, 0x00, 0x64, 0xf8, + 0xb5, 0xc8, 0x11, 0x94, 0x25, 0xca, 0x3c, 0x1c, 0x67, 0xf4, 0xd5, 0x9c, 0x70, 0xe7, 0x65, 0x51, + 0xd9, 0xa7, 0x79, 0x42, 0xf1, 0xa7, 0xfb, 0xbb, 0x0e, 0x5c, 0xae, 0x58, 0x5c, 0x29, 0x19, 0xdb, + 0xb0, 0x72, 0xa4, 0x2b, 0xd5, 0x02, 0x08, 0xf1, 0x58, 0x57, 0x91, 0x7a, 0x7b, 0xd2, 0x5e, 0xf9, + 0x03, 0x74, 0xee, 0x29, 0xea, 0x25, 0x96, 0xd4, 0x4a, 0x9f, 0x29, 0x57, 0x6c, 0xfe, 0xde, 0x0c, + 0x2c, 0x89, 0x1b, 0x1c, 0xf1, 0xd4, 0x94, 0x27, 0xec, 0x23, 0x98, 0x97, 0x4f, 0x85, 0xd9, 0x45, + 0xd9, 0xad, 0xfd, 0x38, 0xb9, 0xbb, 0x5e, 0x84, 0x25, 0xef, 0xac, 0xfe, 0xc6, 0x0f, 0xfe, 0xe5, + 0xf7, 0x6b, 0x8b, 0xac, 0x79, 0xeb, 0xe4, 0xdd, 0x5b, 0x43, 0x1e, 0xa6, 0xd8, 0xc6, 0xaf, 0x00, + 0xe4, 0x8f, 0x68, 0x59, 0x47, 0xbb, 0x29, 0x85, 0xd7, 0xc1, 0xdd, 0xcb, 0x15, 0x35, 0xb2, 0xdd, + 0xcb, 0xd4, 0xee, 0xaa, 0xbb, 0x84, 0xed, 0x06, 0x61, 0x90, 0x89, 0x17, 0xb5, 0x1f, 0x38, 0x1b, + 0x6c, 0x00, 0x2d, 0xf3, 0x8d, 0x2c, 0x53, 0x07, 0xfe, 0x8a, 0x17, 0xba, 0xdd, 0xd7, 0x2a, 0xeb, + 0x54, 0xb4, 0x83, 0xfa, 0xb8, 0xe8, 0xb6, 0xb1, 0x8f, 0x09, 0x51, 0xe4, 0xbd, 0x8c, 0x60, 0xc9, + 0x7e, 0x0a, 0xcb, 0x5e, 0x37, 0xc4, 0xba, 0xf4, 0x10, 0xb7, 0xfb, 0xc6, 0x19, 0xb5, 0xb2, 0xaf, + 0x37, 0xa8, 0xaf, 0x4b, 0x2e, 0xc3, 0xbe, 0xfa, 0x44, 0xa3, 0x1e, 0xe2, 0x7e, 0xe0, 0x6c, 0x6c, + 0x7e, 0xeb, 0x0a, 0x34, 0x74, 0x88, 0x8e, 0x7d, 0x1d, 0x16, 0xad, 0x2b, 0x36, 0xa6, 0xa6, 0x51, + 0x75, 0x23, 0xd7, 0x7d, 0xbd, 0xba, 0x52, 0x76, 0x7c, 0x85, 0x3a, 0xee, 0xb0, 0x75, 0xec, 0x58, + 0xde, 0x51, 0xdd, 0xa2, 0x8b, 0x45, 0x91, 0xd7, 0xf8, 0x4c, 0xcc, 0x33, 0xbf, 0x16, 0xb3, 0xe6, + 0x59, 0xba, 0x46, 0xb3, 0xe6, 0x59, 0xbe, 0x4b, 0x73, 0x5f, 0xa7, 0xee, 0xd6, 0xd9, 0x9a, 0xd9, + 0x9d, 0x0e, 0x9d, 0x71, 0xca, 0x44, 0x35, 0x5f, 0xca, 0xb2, 0x37, 0x34, 0x63, 0x55, 0xbd, 0xa0, + 0xd5, 0x2c, 0x52, 0x7e, 0x46, 0xeb, 0x76, 0xa8, 0x2b, 0xc6, 0x68, 0xfb, 0xcc, 0x87, 0xb2, 0xec, + 0xab, 0xd0, 0xd0, 0xcf, 0xc2, 0xd8, 0x25, 0xe3, 0x2d, 0x9e, 0xf9, 0x56, 0xad, 0xdb, 0x29, 0x57, + 0x54, 0x31, 0x86, 0xd9, 0x32, 0x32, 0xc6, 0x1e, 0x5c, 0x94, 0x4e, 0xf5, 0x21, 0xff, 0x51, 0x66, + 0x52, 0xf1, 0xbe, 0xf7, 0xb6, 0xc3, 0x3e, 0x84, 0x05, 0xf5, 0xda, 0x8e, 0xad, 0x57, 0xbf, 0x1a, + 0xec, 0x5e, 0x2a, 0xe1, 0x52, 0x7b, 0xdc, 0x01, 0xc8, 0x5f, 0x8a, 0x69, 0x39, 0x2b, 0xbd, 0x5f, + 0xd3, 0x8b, 0x58, 0xf1, 0xac, 0x6c, 0x48, 0xef, 0xe2, 0xec, 0x87, 0x68, 0xec, 0xcd, 0x9c, 0xbe, + 0xf2, 0x89, 0xda, 0x4b, 0x1a, 0x74, 0xd7, 0x69, 0xed, 0xda, 0x8c, 0x04, 0x37, 0xe4, 0xa7, 0x2a, + 0x27, 0x7b, 0x1b, 0x9a, 0xc6, 0xeb, 0x33, 0xa6, 0x5a, 0x28, 0xbf, 0x5c, 0xeb, 0x76, 0xab, 0xaa, + 0xe4, 0x70, 0xbf, 0x00, 0x8b, 0xd6, 0x33, 0x32, 0x2d, 0x19, 0x55, 0x8f, 0xd4, 0xb4, 0x64, 0x54, + 0xbf, 0x3c, 0xfb, 0x0a, 0x34, 0x8d, 0x47, 0x5f, 0xcc, 0xc8, 0x45, 0x2b, 0x3c, 0xf7, 0xd2, 0x23, + 0xaa, 0x7a, 0x23, 0xb6, 0x46, 0xf3, 0x5d, 0x72, 0x1b, 0x38, 0x5f, 0x4a, 0x4c, 0x46, 0x26, 0xf9, + 0x3a, 0x2c, 0xd9, 0xcf, 0xc0, 0xb4, 0x54, 0x55, 0x3e, 0x28, 0xd3, 0x52, 0x75, 0xc6, 0xdb, 0x31, + 0xc9, 0x90, 0x1b, 0xab, 0xba, 0x93, 0x5b, 0x1f, 0xcb, 0x0b, 0xaa, 0x17, 0xec, 0x4b, 0xa8, 0x3a, + 0x64, 0xa6, 0x38, 0xcb, 0x1f, 0xbf, 0xd9, 0xf9, 0xe4, 0x9a, 0xdb, 0x4b, 0x49, 0xe5, 0xee, 0x0a, + 0x35, 0xde, 0x64, 0xf9, 0x0c, 0x84, 0x3d, 0xa0, 0x8c, 0x71, 0xc3, 0x1e, 0x98, 0x49, 0xe5, 0x86, + 0x3d, 0xb0, 0x12, 0xcb, 0x8b, 0xf6, 0x20, 0x0b, 0xb0, 0x8d, 0x10, 0x96, 0x0b, 0xc9, 0x18, 0x5a, + 0x58, 0xaa, 0xb3, 0xd7, 0xba, 0x57, 0x5e, 0x9e, 0xc3, 0x61, 0xab, 0x19, 0xa5, 0x5e, 0x6e, 0xa9, + 0x64, 0xc3, 0x5f, 0x85, 0x96, 0xf9, 0x7c, 0x47, 0x5b, 0x88, 0x8a, 0x47, 0x47, 0xda, 0x42, 0x54, + 0xbd, 0xf7, 0x51, 0x9b, 0xcb, 0x5a, 0x66, 0x37, 0xb8, 0xb9, 0xf6, 0x6b, 0x87, 0x5c, 0x65, 0x56, + 0x3d, 0xe3, 0xc8, 0x55, 0x66, 0xe5, 0x13, 0x09, 0xb5, 0xb9, 0x6c, 0xd5, 0x9a, 0x8b, 0x88, 0x4c, + 0xb2, 0xaf, 0xc0, 0xb2, 0x91, 0xe9, 0x74, 0x30, 0x0d, 0xfb, 0x9a, 0x51, 0xcb, 0x79, 0xb0, 0xdd, + 0x2a, 0xcf, 0xd3, 0xbd, 0x44, 0xed, 0xaf, 0xb8, 0xd6, 0x24, 0x90, 0x49, 0xb7, 0xa0, 0x69, 0x66, + 0x51, 0xbd, 0xa4, 0xdd, 0x4b, 0x46, 0x95, 0x99, 0x12, 0x7a, 0xdb, 0x61, 0x7f, 0xe8, 0x40, 0xcb, + 0xca, 0x49, 0xb2, 0xe2, 0xef, 0x85, 0x76, 0x3a, 0x66, 0x9d, 0xd9, 0x90, 0xeb, 0xd1, 0x20, 0xf7, + 0x36, 0xbe, 0x60, 0x2d, 0xc2, 0xc7, 0xd6, 0x09, 0xe6, 0x66, 0xf1, 0x15, 0xf8, 0x8b, 0x22, 0x81, + 0x99, 0x2b, 0xfc, 0xe2, 0xb6, 0xc3, 0x3e, 0x10, 0xff, 0x73, 0xa0, 0x22, 0x16, 0xcc, 0x50, 0xa4, + 0xc5, 0x25, 0x33, 0x1f, 0xf9, 0xdf, 0x70, 0x6e, 0x3b, 0xec, 0x6b, 0xe2, 0xb1, 0xb7, 0xfc, 0x96, + 0x56, 0xfe, 0x55, 0xbf, 0x77, 0xaf, 0xd1, 0x6c, 0xae, 0xb8, 0x97, 0xad, 0xd9, 0x14, 0x2d, 0xc9, + 0x1d, 0x31, 0x3a, 0xf9, 0x86, 0x3f, 0x57, 0x89, 0xa5, 0x77, 0xfd, 0x67, 0x0f, 0x72, 0x2c, 0x06, + 0x29, 0xc9, 0x2d, 0xf6, 0x78, 0xc5, 0x66, 0xdc, 0x0d, 0x1a, 0xeb, 0x35, 0xf7, 0xcd, 0x33, 0xc7, + 0x7a, 0x8b, 0x4e, 0xa4, 0x38, 0xe2, 0x7d, 0x80, 0x3c, 0x20, 0xc7, 0x0a, 0xf1, 0x27, 0x6d, 0x15, + 0xca, 0x31, 0x3b, 0x9b, 0x07, 0x55, 0x98, 0x0a, 0x5b, 0xfc, 0xaa, 0x10, 0x55, 0x49, 0x9f, 0xea, + 0xd1, 0x97, 0x43, 0x67, 0xdd, 0x6e, 0x55, 0x55, 0x95, 0xa0, 0xaa, 0xf6, 0xd9, 0x13, 0x58, 0xdc, + 0x8b, 0xa2, 0x67, 0x93, 0x58, 0x07, 0xa4, 0xed, 0xf8, 0xcd, 0xae, 0x9f, 0x1e, 0x77, 0x0b, 0xb3, + 0x70, 0xaf, 0x52, 0x53, 0x5d, 0xd6, 0x31, 0x9a, 0xba, 0xf5, 0x71, 0x1e, 0xf0, 0x7b, 0xc1, 0x7c, + 0x58, 0xd1, 0x1e, 0x80, 0x1e, 0x78, 0xd7, 0x6e, 0xc6, 0x8c, 0xbb, 0x95, 0xba, 0xb0, 0x7c, 0x32, + 0x35, 0xda, 0x5b, 0xa9, 0x6a, 0xf3, 0xb6, 0xc3, 0xf6, 0xa1, 0xb5, 0xcd, 0xfb, 0xd1, 0x80, 0xcb, + 0x88, 0xcb, 0x6a, 0x3e, 0x70, 0x1d, 0xaa, 0xe9, 0x2e, 0x5a, 0xa0, 0xad, 0x13, 0x63, 0x7f, 0x9a, + 0xf0, 0x6f, 0xdc, 0xfa, 0x58, 0xc6, 0x72, 0x5e, 0x28, 0x9d, 0xa8, 0xe2, 0x4f, 0x96, 0x4e, 0x2c, + 0x04, 0xac, 0x2c, 0x9d, 0x58, 0x0a, 0x58, 0x59, 0x4b, 0xad, 0xe2, 0x5f, 0x6c, 0x04, 0x2b, 0xa5, + 0x18, 0x97, 0xf6, 0x23, 0xce, 0x8a, 0x8c, 0x75, 0xaf, 0x9e, 0x4d, 0x60, 0xf7, 0xb6, 0x61, 0xf7, + 0x76, 0x00, 0x8b, 0xdb, 0x5c, 0x2c, 0x96, 0xb8, 0xf5, 0x2e, 0x3c, 0x2a, 0x33, 0x6f, 0xc8, 0x8b, + 0x4a, 0x91, 0xea, 0x6c, 0xa3, 0x47, 0x57, 0xce, 0xec, 0xab, 0xd0, 0xbc, 0xcf, 0x33, 0x75, 0xcd, + 0xad, 0xbd, 0xb1, 0xc2, 0xbd, 0x77, 0xb7, 0xe2, 0x96, 0xdc, 0xe6, 0x19, 0x6a, 0xed, 0x16, 0x1f, + 0x0c, 0xb9, 0x50, 0x4f, 0xbd, 0x60, 0xf0, 0x82, 0xfd, 0x12, 0x35, 0xae, 0x33, 0x63, 0xd6, 0x8d, + 0xdb, 0x51, 0xb3, 0xf1, 0xe5, 0x02, 0x5e, 0xd5, 0x72, 0x18, 0x0d, 0xb8, 0x61, 0xfe, 0x43, 0x68, + 0x1a, 0x69, 0x5b, 0x5a, 0x80, 0xca, 0x29, 0x68, 0x5a, 0x80, 0x2a, 0xb2, 0xbc, 0xdc, 0x1b, 0xd4, + 0x8f, 0xcb, 0xae, 0xe6, 0xfd, 0x88, 0xcc, 0xae, 0xbc, 0xa7, 0x5b, 0x1f, 0xfb, 0xe3, 0xec, 0x05, + 0x7b, 0x4a, 0x0f, 0xcc, 0xcc, 0xab, 0xfc, 0xdc, 0x1b, 0x2c, 0xde, 0xfa, 0xeb, 0xc5, 0x32, 0xaa, + 0x6c, 0x0f, 0x51, 0x74, 0x45, 0x5e, 0xc2, 0x67, 0x00, 0x0e, 0xb2, 0x28, 0xde, 0xf6, 0xf9, 0x38, + 0x0a, 0x73, 0x5d, 0x9b, 0x5f, 0x57, 0xe7, 0xfa, 0xcb, 0xb8, 0xb3, 0x66, 0x4f, 0x0d, 0x7f, 0xdc, + 0xca, 0x84, 0x50, 0xcc, 0x75, 0xe6, 0x8d, 0xb6, 0x5e, 0x90, 0x8a, 0x5b, 0xed, 0xdb, 0x0e, 0x7a, + 0xd7, 0x79, 0x44, 0x55, 0x7b, 0xd7, 0xa5, 0x60, 0xad, 0x56, 0x7b, 0x15, 0xe1, 0xd7, 0x7d, 0x68, + 0xe4, 0x21, 0xba, 0x4b, 0x79, 0xea, 0x9d, 0x15, 0xd0, 0xd3, 0x56, 0xb1, 0x14, 0x38, 0x73, 0xdb, + 0xb4, 0x54, 0xc0, 0x16, 0x70, 0xa9, 0x28, 0x1a, 0x16, 0xc0, 0xaa, 0x18, 0xa0, 0x36, 0xf1, 0x74, + 0x01, 0xab, 0x66, 0x52, 0x11, 0xbc, 0xd2, 0xd2, 0x5c, 0x19, 0xfb, 0xb1, 0xce, 0xd9, 0xc8, 0xad, + 0xe2, 0xf2, 0x17, 0x55, 0xf3, 0x18, 0x56, 0x4a, 0x81, 0x0b, 0x2d, 0xd2, 0x67, 0xc5, 0x8b, 0xb4, + 0x48, 0x9f, 0x19, 0xf3, 0x70, 0x2f, 0x52, 0x97, 0xcb, 0x2e, 0x60, 0x97, 0xe9, 0x69, 0x90, 0xf5, + 0x8f, 0x3f, 0x70, 0x36, 0x0e, 0xe7, 0xe8, 0x7f, 0xd1, 0x3e, 0xf5, 0xbf, 0x01, 0x00, 0x00, 0xff, + 0xff, 0xb6, 0xb3, 0x40, 0xb7, 0x49, 0x4d, 0x00, 0x00, } diff --git a/lnrpc/rpc.proto b/lnrpc/rpc.proto index 19a3cf7e..de714045 100644 --- a/lnrpc/rpc.proto +++ b/lnrpc/rpc.proto @@ -1685,6 +1685,14 @@ message AddInvoiceResponse { payment to the recipient. */ string payment_request = 2 [json_name = "payment_request"]; + + /** + The "add" index of this invoice. Each newly created invoice will increment + this index making it monotonically increasing. Callers to the + SubscribeInvoices call can use this to instantly get notified of all added + invoices with an add_index greater than this one. + */ + uint64 add_index = 16 [json_name = "add_index"]; } message PaymentHash { /** diff --git a/lnrpc/rpc.swagger.json b/lnrpc/rpc.swagger.json index 3187fa0c..db37de97 100644 --- a/lnrpc/rpc.swagger.json +++ b/lnrpc/rpc.swagger.json @@ -1078,6 +1078,11 @@ "payment_request": { "type": "string", "description": "*\nA bare-bones invoice for a payment within the Lightning Network. With the\ndetails of the invoice, the sender has all the data necessary to send a\npayment to the recipient." + }, + "add_index": { + "type": "string", + "format": "uint64", + "description": "*\nThe \"add\" index of this invoice. Each newly created invoice will increment\nthis index making it monotonically increasing. Callers to the\nSubscribeInvoices call can use this to instantly get notified of all added\ninvoices with an add_index greater than this one." } } }, From 904342b9e8ee3e3512eac3869d84173759486c49 Mon Sep 17 00:00:00 2001 From: Olaoluwa Osuntokun Date: Fri, 29 Jun 2018 18:06:24 -0700 Subject: [PATCH 20/26] rpc: populate response of AddInvoice w/ add index of new invoice --- rpcserver.go | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/rpcserver.go b/rpcserver.go index df512269..be7844ab 100644 --- a/rpcserver.go +++ b/rpcserver.go @@ -2657,7 +2657,7 @@ func (r *rpcServer) AddInvoice(ctx context.Context, return nil, err } - i := &channeldb.Invoice{ + newInvoice := &channeldb.Invoice{ CreationDate: creationDate, Memo: []byte(invoice.Memo), Receipt: invoice.Receipt, @@ -2666,22 +2666,24 @@ func (r *rpcServer) AddInvoice(ctx context.Context, Value: amtMSat, }, } - copy(i.Terms.PaymentPreimage[:], paymentPreimage[:]) + copy(newInvoice.Terms.PaymentPreimage[:], paymentPreimage[:]) rpcsLog.Tracef("[addinvoice] adding new invoice %v", newLogClosure(func() string { - return spew.Sdump(i) + return spew.Sdump(newInvoice) }), ) // With all sanity checks passed, write the invoice to the database. - if err := r.server.invoices.AddInvoice(i); err != nil { + addIndex, err := r.server.invoices.AddInvoice(newInvoice) + if err != nil { return nil, err } return &lnrpc.AddInvoiceResponse{ RHash: rHash[:], PaymentRequest: payReqString, + AddIndex: addIndex, }, nil } From 9c1ed7fc7dd9bf290cc40c0ab4bd33a5cbe42795 Mon Sep 17 00:00:00 2001 From: Olaoluwa Osuntokun Date: Fri, 29 Jun 2018 18:06:49 -0700 Subject: [PATCH 21/26] invoiceregistry: update AddInvoice to return addIndex of new invoice --- invoiceregistry.go | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/invoiceregistry.go b/invoiceregistry.go index 1ed3e95b..61401999 100644 --- a/invoiceregistry.go +++ b/invoiceregistry.go @@ -252,8 +252,10 @@ func (i *invoiceRegistry) AddDebugInvoice(amt btcutil.Amount, preimage chainhash // the passed preimage. Additionally, any memo or receipt data provided will // also be stored on-disk. Once this invoice is added, subsystems within the // daemon add/forward HTLCs are able to obtain the proper preimage required for -// redemption in the case that we're the final destination. -func (i *invoiceRegistry) AddInvoice(invoice *channeldb.Invoice) error { +// redemption in the case that we're the final destination. We also return the +// addIndex of the newly created invoice which monotonically increases for each +// new invoice added. +func (i *invoiceRegistry) AddInvoice(invoice *channeldb.Invoice) (uint64, error) { i.Lock() defer i.Unlock() @@ -261,15 +263,16 @@ func (i *invoiceRegistry) AddInvoice(invoice *channeldb.Invoice) error { return spew.Sdump(invoice) })) - if err := i.cdb.AddInvoice(invoice); err != nil { - return err + addIndex, err := i.cdb.AddInvoice(invoice) + if err != nil { + return 0, err } // We'll launch a new goroutine to notify all of our active listeners // that a new invoice was just added. i.notifyClients(invoice, false) - return nil + return addIndex, nil } // LookupInvoice looks up an invoice by its payment hash (R-Hash), if found From 6891729b5cd9266a16a7a69a4e10d1d0b921e34a Mon Sep 17 00:00:00 2001 From: Olaoluwa Osuntokun Date: Fri, 29 Jun 2018 18:07:13 -0700 Subject: [PATCH 22/26] invoiceregistry: ensure we re-bind the loop variable within deliverBacklogEvents --- invoiceregistry.go | 34 ++++++++++++++++++++++++++++++---- 1 file changed, 30 insertions(+), 4 deletions(-) diff --git a/invoiceregistry.go b/invoiceregistry.go index 61401999..59c534d6 100644 --- a/invoiceregistry.go +++ b/invoiceregistry.go @@ -134,7 +134,7 @@ func (i *invoiceRegistry) invoiceEventNotifier() { // A sub-systems has just modified the invoice state, so we'll // dispatch notifications to all registered clients. case event := <-i.invoiceEvents: - for _, client := range i.notificationClients { + for clientID, client := range i.notificationClients { // Before we dispatch this event, we'll check // to ensure that this client hasn't already // received this notification in order to @@ -152,6 +152,24 @@ func (i *invoiceRegistry) invoiceEventNotifier() { case !event.isSettle && client.addIndex == invoice.AddIndex: continue + + // These two states should never happen, but we + // log them just in case so we can detect this + // instance. + case !event.isSettle && + client.addIndex+1 != invoice.AddIndex: + ltndLog.Warnf("client=%v for invoice "+ + "notifications missed an update, "+ + "add_index=%v, new add event index=%v", + clientID, client.addIndex, + invoice.AddIndex) + case event.isSettle && + client.settleIndex+1 != invoice.SettleIndex: + ltndLog.Warnf("client=%v for invoice "+ + "notifications missed an update, "+ + "settle_index=%v, new settle event index=%v", + clientID, client.settleIndex, + invoice.SettleIndex) } select { @@ -201,6 +219,10 @@ func (i *invoiceRegistry) deliverBacklogEvents(client *invoiceSubscription) erro // notification queue in order to catch up the client before delivering // any new notifications. for _, addEvent := range addEvents { + // We re-bind the loop variable to ensure we don't hold onto + // the loop reference causing is to point to the same item. + addEvent := addEvent + select { case client.ntfnQueue.ChanIn() <- &invoiceEvent{ isSettle: false, @@ -211,6 +233,10 @@ func (i *invoiceRegistry) deliverBacklogEvents(client *invoiceSubscription) erro } } for _, settleEvent := range settleEvents { + // We re-bind the loop variable to ensure we don't hold onto + // the loop reference causing is to point to the same item. + settleEvent := settleEvent + select { case client.ntfnQueue.ChanIn() <- &invoiceEvent{ isSettle: true, @@ -268,8 +294,8 @@ func (i *invoiceRegistry) AddInvoice(invoice *channeldb.Invoice) (uint64, error) return 0, err } - // We'll launch a new goroutine to notify all of our active listeners - // that a new invoice was just added. + // Now that we've added the invoice, we'll send dispatch a message to + // notify the clients of this new invoice. i.notifyClients(invoice, false) return addIndex, nil @@ -308,7 +334,7 @@ func (i *invoiceRegistry) LookupInvoice(rHash chainhash.Hash) (channeldb.Invoice return channeldb.Invoice{}, 0, err } - return *invoice, uint32(payReq.MinFinalCLTVExpiry()), nil + return invoice, uint32(payReq.MinFinalCLTVExpiry()), nil } // SettleInvoice attempts to mark an invoice as settled. If the invoice is a From 43d1d1f4f2a11dfacbc94cb31d3bebfb40fd8b20 Mon Sep 17 00:00:00 2001 From: Olaoluwa Osuntokun Date: Fri, 29 Jun 2018 18:07:55 -0700 Subject: [PATCH 23/26] test: extend testInvoiceSubscriptions to test historical notification dispatch --- lnd_test.go | 141 +++++++++++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 135 insertions(+), 6 deletions(-) diff --git a/lnd_test.go b/lnd_test.go index 47c07aa8..f232397f 100644 --- a/lnd_test.go +++ b/lnd_test.go @@ -19,6 +19,7 @@ import ( "reflect" "crypto/rand" + "crypto/sha256" prand "math/rand" "github.com/btcsuite/btclog" @@ -3452,10 +3453,11 @@ func testSendToRouteErrorPropagation(net *lntest.NetworkHarness, t *harnessTest) t.Fatalf("alice didn't advertise her channel: %v", err) } - // Create a new nodes (Carol and Charlie), load her with some funds, then establish - // a connection between Carol and Charlie with a channel that has - // identical capacity to the one created above.Then we will get route via queryroutes call - // which will be fake route for Alice -> Bob graph. + // Create a new nodes (Carol and Charlie), load her with some funds, + // then establish a connection between Carol and Charlie with a channel + // that has identical capacity to the one created above.Then we will + // get route via queryroutes call which will be fake route for Alice -> + // Bob graph. // // The network topology should now look like: Alice -> Bob; Carol -> Charlie. carol, err := net.NewNode("Carol", nil) @@ -4298,17 +4300,18 @@ func testInvoiceSubscriptions(net *lntest.NetworkHarness, t *harnessTest) { if err != nil { t.Fatalf("unable to add invoice: %v", err) } + lastAddIndex := invoiceResp.AddIndex // Create a new invoice subscription client for Bob, the notification // should be dispatched shortly below. req := &lnrpc.InvoiceSubscription{} - ctx, cancelFunc := context.WithCancel(context.Background()) + ctx, cancelInvoiceSubscription := context.WithCancel(context.Background()) bobInvoiceSubscription, err := net.Bob.SubscribeInvoices(ctx, req) if err != nil { t.Fatalf("unable to subscribe to bob's invoice updates: %v", err) } - defer cancelFunc() + var settleIndex uint64 quit := make(chan struct{}) updateSent := make(chan struct{}) go func() { @@ -4338,6 +4341,12 @@ func testInvoiceSubscriptions(net *lntest.NetworkHarness, t *harnessTest) { invoice.RPreimage, invoiceUpdate.RPreimage) } + if invoiceUpdate.SettleIndex == 0 { + t.Fatalf("invoice should have settle index") + } + + settleIndex = invoiceUpdate.SettleIndex + close(updateSent) }() @@ -4375,6 +4384,126 @@ func testInvoiceSubscriptions(net *lntest.NetworkHarness, t *harnessTest) { case <-updateSent: // Fall through on success } + // With the base case working, we'll now cancel Bob's current + // subscription in order to exercise the backlog fill behavior. + cancelInvoiceSubscription() + + // We'll now add 3 more invoices to Bob's invoice registry. + const numInvoices = 3 + newInvoices := make([]*lnrpc.Invoice, numInvoices) + payReqs := make([]string, numInvoices) + for i := 0; i < numInvoices; i++ { + preimage := bytes.Repeat([]byte{byte(90 + 1 + i)}, 32) + invoice := &lnrpc.Invoice{ + Memo: "testing", + RPreimage: preimage, + Value: paymentAmt, + } + resp, err := net.Bob.AddInvoice(ctxb, invoice) + if err != nil { + t.Fatalf("unable to add invoice: %v", err) + } + + newInvoices[i] = invoice + payReqs[i] = resp.PaymentRequest + } + + // Now that the set of invoices has been added, we'll re-register for + // streaming invoice notifications for Bob, this time specifying the + // add invoice of the last prior invoice. + req = &lnrpc.InvoiceSubscription{ + AddIndex: lastAddIndex, + } + ctx, cancelInvoiceSubscription = context.WithCancel(context.Background()) + bobInvoiceSubscription, err = net.Bob.SubscribeInvoices(ctx, req) + if err != nil { + t.Fatalf("unable to subscribe to bob's invoice updates: %v", err) + } + + // Since we specified a value of the prior add index above, we should + // now immediately get the invoices we just added as we should get the + // backlog of notifications. + for i := 0; i < numInvoices; i++ { + invoiceUpdate, err := bobInvoiceSubscription.Recv() + if err != nil { + t.Fatalf("unable to receive subscription") + } + + // We should now get the ith invoice we added, as they should + // be returned in order. + if invoiceUpdate.Settled { + t.Fatalf("should have only received add events") + } + originalInvoice := newInvoices[i] + rHash := sha256.Sum256(originalInvoice.RPreimage[:]) + if !bytes.Equal(invoiceUpdate.RHash, rHash[:]) { + t.Fatalf("invoices have mismatched payment hashes: "+ + "expected %x, got %x", rHash[:], + invoiceUpdate.RHash) + } + } + + cancelInvoiceSubscription() + + // We'll now have Bob settle out the remainder of these invoices so we + // can test that all settled invoices are properly notified. + ctxt, _ = context.WithTimeout(ctxb, timeout) + err = completePaymentRequests( + ctxt, net.Alice, payReqs, true, + ) + if err != nil { + t.Fatalf("unable to send payment: %v", err) + } + + // With the set of invoices paid, we'll now cancel the old + // subscription, and create a new one for Bob, this time using the + // settle index to obtain the backlog of settled invoices. + req = &lnrpc.InvoiceSubscription{ + SettleIndex: settleIndex, + } + ctx, cancelInvoiceSubscription = context.WithCancel(context.Background()) + bobInvoiceSubscription, err = net.Bob.SubscribeInvoices(ctx, req) + if err != nil { + t.Fatalf("unable to subscribe to bob's invoice updates: %v", err) + } + + defer cancelInvoiceSubscription() + + // As we specified the index of the past settle index, we should now + // receive notifications for the three HTLCs that we just settled. As + // the order that the HTLCs will be settled in is partially randomized, + // we'll use a map to assert that the proper set has been settled. + settledInvoices := make(map[[32]byte]struct{}) + for _, invoice := range newInvoices { + rHash := sha256.Sum256(invoice.RPreimage[:]) + settledInvoices[rHash] = struct{}{} + } + for i := 0; i < numInvoices; i++ { + invoiceUpdate, err := bobInvoiceSubscription.Recv() + if err != nil { + t.Fatalf("unable to receive subscription") + } + + // We should now get the ith invoice we added, as they should + // be returned in order. + if !invoiceUpdate.Settled { + t.Fatalf("should have only received settle events") + } + + var rHash [32]byte + copy(rHash[:], invoiceUpdate.RHash) + if _, ok := settledInvoices[rHash]; !ok { + t.Fatalf("unknown invoice settled: %x", rHash) + } + + delete(settledInvoices, rHash) + } + + // At this point, all the invoices should be fully settled. + if len(settledInvoices) != 0 { + t.Fatalf("not all invoices settled") + } + ctxt, _ = context.WithTimeout(ctxb, timeout) closeChannelAndAssert(ctxt, t, net, net.Alice, chanPoint, false) } From 4813da9a642c02fdb036a219caead243cf46b3f6 Mon Sep 17 00:00:00 2001 From: Olaoluwa Osuntokun Date: Thu, 5 Jul 2018 22:05:23 -0500 Subject: [PATCH 24/26] cmd/lncli: update ad-hoc addinvoice response json to display add_index --- cmd/lncli/commands.go | 30 ++++++++++++++++-------------- 1 file changed, 16 insertions(+), 14 deletions(-) diff --git a/cmd/lncli/commands.go b/cmd/lncli/commands.go index 23675cfb..fcd4aea3 100644 --- a/cmd/lncli/commands.go +++ b/cmd/lncli/commands.go @@ -1585,33 +1585,33 @@ var closedChannelsCommand = cli.Command{ Name: "closedchannels", Category: "Channels", Usage: "List all closed channels.", - Flags: []cli.Flag{ + Flags: []cli.Flag{ cli.BoolFlag{ Name: "cooperative", Usage: "list channels that were closed cooperatively", }, cli.BoolFlag{ - Name: "local_force", + Name: "local_force", Usage: "list channels that were force-closed " + - "by the local node", + "by the local node", }, cli.BoolFlag{ - Name: "remote_force", + Name: "remote_force", Usage: "list channels that were force-closed " + - "by the remote node", + "by the remote node", }, cli.BoolFlag{ - Name: "breach", + Name: "breach", Usage: "list channels for which the remote node " + - "attempted to broadcast a prior " + - "revoked channel state", + "attempted to broadcast a prior " + + "revoked channel state", }, cli.BoolFlag{ Name: "funding_canceled", Usage: "list channels that were never fully opened", }, }, - Action: actionDecorator(closedChannels), + Action: actionDecorator(closedChannels), } func closedChannels(ctx *cli.Context) error { @@ -1624,7 +1624,7 @@ func closedChannels(ctx *cli.Context) error { LocalForce: ctx.Bool("local_force"), RemoteForce: ctx.Bool("remote_force"), Breach: ctx.Bool("breach"), - FundingCanceled: ctx.Bool("funding_cancelled"), + FundingCanceled: ctx.Bool("funding_cancelled"), } resp, err := client.ClosedChannels(ctxb, req) @@ -2192,11 +2192,13 @@ func addInvoice(ctx *cli.Context) error { } printJSON(struct { - RHash string `json:"r_hash"` - PayReq string `json:"pay_req"` + RHash string `json:"r_hash"` + PayReq string `json:"pay_req"` + AddIndex uint64 `json:"add_index"` }{ - RHash: hex.EncodeToString(resp.RHash), - PayReq: resp.PaymentRequest, + RHash: hex.EncodeToString(resp.RHash), + PayReq: resp.PaymentRequest, + AddIndex: resp.AddIndex, }) return nil From db4a09d3b5b817b8e500c16258ca8dd19c910d25 Mon Sep 17 00:00:00 2001 From: Olaoluwa Osuntokun Date: Thu, 5 Jul 2018 22:07:40 -0500 Subject: [PATCH 25/26] channeldb: skip sub-buckets during invoice time series migration In this commit, we fix an existing bu gin the invoice time series migration code. Before this commit, the migration would fail as we would try to migrate an empty invoice. We now detect this case and skip all empty invoices. We also add a bit more logging on both the info and trace logging level. --- channeldb/migrations.go | 27 +++++++++++++++++++++++++-- 1 file changed, 25 insertions(+), 2 deletions(-) diff --git a/channeldb/migrations.go b/channeldb/migrations.go index a1f40228..75ee8f6e 100644 --- a/channeldb/migrations.go +++ b/channeldb/migrations.go @@ -136,10 +136,17 @@ func migrateInvoiceTimeSeries(tx *bolt.Tx) error { return err } + log.Infof("Migrating invoice database to new time series format") + // Now that we have all the buckets we need, we'll run through each // invoice in the database, and update it to reflect the new format // expected post migration. - return invoices.ForEach(func(invoiceNum, invoiceBytes []byte) error { + err = invoices.ForEach(func(invoiceNum, invoiceBytes []byte) error { + // If this is a sub bucket, then we'll skip it. + if invoiceBytes == nil { + return nil + } + // First, we'll make a copy of the encoded invoice bytes. invoiceBytesCopy := make([]byte, len(invoiceBytes)) copy(invoiceBytesCopy, invoiceBytes) @@ -153,7 +160,7 @@ func migrateInvoiceTimeSeries(tx *bolt.Tx) error { invoiceReader := bytes.NewReader(invoiceBytesCopy) invoice, err := deserializeInvoice(invoiceReader) if err != nil { - return err + return fmt.Errorf("unable to decode invoice: %v", err) } // Now that we have the fully decoded invoice, we can update @@ -173,6 +180,10 @@ func migrateInvoiceTimeSeries(tx *bolt.Tx) error { return err } + log.Tracef("Adding invoice (preimage=%v, add_index=%v) to add "+ + "time series", invoice.Terms.PaymentPreimage[:], + nextAddSeqNo) + // Next, we'll check if the invoice has been settled or not. If // so, then we'll also add it to the settle index. var nextSettleSeqNo uint64 @@ -190,6 +201,11 @@ func migrateInvoiceTimeSeries(tx *bolt.Tx) error { } invoice.AmtPaid = invoice.Terms.Value + + log.Tracef("Adding invoice (preimage=%v, "+ + "settle_index=%v) to add time series", + invoice.Terms.PaymentPreimage[:], + nextSettleSeqNo) } // Finally, we'll update the invoice itself with the new @@ -207,4 +223,11 @@ func migrateInvoiceTimeSeries(tx *bolt.Tx) error { return invoices.Put(invoiceNum, b.Bytes()) }) + if err != nil { + return err + } + + log.Infof("Migration to invoice time series index complete!") + + return nil } From 1189e703da1a77733efa2b92fd669d0d182b5884 Mon Sep 17 00:00:00 2001 From: Olaoluwa Osuntokun Date: Tue, 10 Jul 2018 19:37:24 -0700 Subject: [PATCH 26/26] invoiceregistry: enforce stricter check on duplicate ntfn avoidance --- invoiceregistry.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/invoiceregistry.go b/invoiceregistry.go index 59c534d6..be63ea26 100644 --- a/invoiceregistry.go +++ b/invoiceregistry.go @@ -144,13 +144,13 @@ func (i *invoiceRegistry) invoiceEventNotifier() { // If we've already sent this settle event to // the client, then we can skip this. case event.isSettle && - client.settleIndex == invoice.SettleIndex: + client.settleIndex >= invoice.SettleIndex: continue // Similarly, if we've already sent this add to // the client then we can skip this one. case !event.isSettle && - client.addIndex == invoice.AddIndex: + client.addIndex >= invoice.AddIndex: continue // These two states should never happen, but we