From f2cd668bcfb423fb212e78282c786dea166e0168 Mon Sep 17 00:00:00 2001 From: Olaoluwa Osuntokun Date: Tue, 27 Feb 2018 22:05:58 -0800 Subject: [PATCH 01/15] channeldb: add new ForwardingLog storage namespace MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit In this commit, we add a new storage namespace to channeldb: the ForwardingLog. This log will be used by higher level sub-systems to log each successfully completed HTLC. Each payment circuit will be summarized as a “ForwardingEvent”. A series of events can then be queried via a time slice query. In a time slice query, the caller specifies a time range, a number of events to skip, and the max number of events to return. Each query will return the index of the final item. As we have a max number of events we’ll return in a response, callers may need to use this last offset index to seek further by skipping that number of entries. Combining these fields, callers are able to query the time series, skipping an arbitrary amount of events, and capping the max number of returned events. --- channeldb/error.go | 4 + channeldb/forwarding_log.go | 274 ++++++++++++++++++++++++++++++++++++ 2 files changed, 278 insertions(+) create mode 100644 channeldb/forwarding_log.go diff --git a/channeldb/error.go b/channeldb/error.go index 953fd930..6ee24a87 100644 --- a/channeldb/error.go +++ b/channeldb/error.go @@ -85,4 +85,8 @@ var ( // ErrNoClosedChannels is returned when a node is queries for all the // channels it has closed, but it hasn't yet closed any channels. ErrNoClosedChannels = fmt.Errorf("no channel have been closed yet") + + // ErrNoForwardingEvents is returned in the case that a query fails due + // to the log not having any recorded events. + ErrNoForwardingEvents = fmt.Errorf("no recorded forwarding events") ) diff --git a/channeldb/forwarding_log.go b/channeldb/forwarding_log.go new file mode 100644 index 00000000..efcf3fd6 --- /dev/null +++ b/channeldb/forwarding_log.go @@ -0,0 +1,274 @@ +package channeldb + +import ( + "bytes" + "io" + "sort" + "time" + + "github.com/boltdb/bolt" + "github.com/lightningnetwork/lnd/lnwire" +) + +var ( + // forwardingLogBucket is the bucket that we'll use to store the + // forwarding log. The forwarding log contains a time series database + // of the forwarding history of a lightning daemon. Each key within the + // bucket is a timestamp (in nano seconds since the unix epoch), and + // the value a slice of a forwarding event for that timestamp. + forwardingLogBucket = []byte("circuit-fwd-log") +) + +const ( + // forwardingEventSize is the size of a forwarding event. The breakdown + // is as follows: + // + // * 8 byte incoming chan ID || 8 byte outgoing chan ID || 8 byte value in + // || 8 byte value out + // + // From the value in and value out, callers can easily compute the + // total fee extract from a forwarding event. + forwardingEventSize = 32 + + // MaxResponseEvents is the max number of forwarding events that will + // be returned by a single query response. This size was selected to + // safely remain under gRPC's 4MiB message size response limit. As each + // full forwarding event (including the timestamp) is 40 bytes, we can + // safely return 50k entries in a single response. + MaxResponseEvents = 50000 +) + +// ForwardingLog returns an instance of the ForwardingLog object backed by the +// target database instance. +func (d *DB) ForwardingLog() *ForwardingLog { + return &ForwardingLog{ + db: d, + } +} + +// ForwardingLog is a time series database that logs the fulfilment of payment +// circuits by a lightning network daemon. The log contains a series of +// forwarding events which map a timestamp to a forwarding event. A forwarding +// event describes which channels were used to create+settle a circuit, and the +// amount involved. Subtracting the outgoing amount from the incoming amount +// reveals the fee charged for the forwarding service. +type ForwardingLog struct { + db *DB +} + +// ForwardingEvent is an event in the forwarding log's time series. Each +// forwarding event logs the creation and tear-down of a payment circuit. A +// circuit is created once an incoming HTLC has been fully forwarded, and +// destroyed once the payment has been settled. +type ForwardingEvent struct { + // Timestamp is the settlement time of this payment circuit. + Timestamp time.Time + + // IncomingChanID is the incoming channel ID of the payment circuit. + IncomingChanID lnwire.ShortChannelID + + // OutgoingChanID is the outgoing channel ID of the payment circuit. + OutgoingChanID lnwire.ShortChannelID + + // AmtIn is the amount of the incoming HTLC. Subtracting this from the + // outgoing amount gives the total fees of this payment circuit. + AmtIn lnwire.MilliSatoshi + + // AmtOut is the amount of the outgoing HTLC. Subtracting the incoming + // amount from this gives the total fees for this payment circuit. + AmtOut lnwire.MilliSatoshi +} + +// encodeForwardingEvent writes out the target forwarding event to the passed +// io.Writer, using the expected DB format. Note that the timestamp isn't +// serialized as this will be the key value within the bucket. +func encodeForwardingEvent(w io.Writer, f *ForwardingEvent) error { + return writeElements( + w, f.IncomingChanID, f.OutgoingChanID, f.AmtIn, f.AmtOut, + ) +} + +// decodeForwardingEvent attempts to decode the raw bytes of a serialized +// forwarding event into the target ForwardingEvent. Note that the timestamp +// won't be decoded, as the caller is expected to set this due to the bucket +// structure of the forwarding log. +func decodeForwardingEvent(r io.Reader, f *ForwardingEvent) error { + return readElements( + r, &f.IncomingChanID, &f.OutgoingChanID, &f.AmtIn, &f.AmtOut, + ) +} + +// AddForwardingEvents adds a series of forwarding events to the database. +// Before inserting, the set of events will be sorted according to their +// timestamp. This ensures that all writes to disk are sequential. +func (f *ForwardingLog) AddForwardingEvents(events []ForwardingEvent) error { + // Before we create the database transaction, we'll ensure that the set + // of forwarding events are properly sorted according to their + // timestamp. + sort.Slice(events, func(i, j int) bool { + return events[i].Timestamp.Before(events[j].Timestamp) + }) + + var timestamp [8]byte + + return f.db.Batch(func(tx *bolt.Tx) error { + // First, we'll fetch the bucket that stores our time series + // log. + logBucket, err := tx.CreateBucketIfNotExists( + forwardingLogBucket, + ) + if err != nil { + return err + } + + // With the bucket obtained, we can now begin to write out the + // series of events. + for _, event := range events { + var eventBytes [forwardingEventSize]byte + eventBuf := bytes.NewBuffer(eventBytes[0:0:forwardingEventSize]) + + // First, we'll serialize this timestamp into our + // timestamp buffer. + byteOrder.PutUint64( + timestamp[:], uint64(event.Timestamp.UnixNano()), + ) + + // With the key encoded, we'll then encode the event + // into our buffer, then write it out to disk. + err := encodeForwardingEvent(eventBuf, &event) + if err != nil { + return err + } + err = logBucket.Put(timestamp[:], eventBuf.Bytes()) + if err != nil { + return err + } + } + + return nil + }) +} + +// ForwardingEventQuery represents a query to the forwarding log payment +// circuit time series database. The query allows a caller to retrieve all +// records for a particular time slice, offset in that time slice, limiting the +// total number of responses returned. +type ForwardingEventQuery struct { + // StartTime is the start time of the time slice. + StartTime time.Time + + // EndTime is the end time of the time slice. + EndTime time.Time + + // IndexOffset is the offset within the time slice to start at. This + // can be used to start the response at a particular record. + IndexOffset uint32 + + // NumMaxEvents is the max number of events to return. + NumMaxEvents uint32 +} + +// ForwardingLogTimeSlice is the response to a forwarding query. It includes +// the original query, the set events that match the query, and an integer +// which represents the offset index of the last item in the set of retuned +// events. This integer allows callers to resume their query using this offset +// in the event that the query's response exceeds the max number of returnable +// events. +type ForwardingLogTimeSlice struct { + ForwardingEventQuery + + // ForwardingEvents is the set of events in our time series that answer + // the query embedded above. + ForwardingEvents []ForwardingEvent + + // LastIndexOffset is the index of the last element in the set of + // returned ForwardingEvents above. Callers can use this to resume + // their query in the event that the time slice has too many events to + // fit into a single response. + LastIndexOffset uint32 +} + +// Query allows a caller to query the forwarding event time series for a +// particular time slice. The caller can control the precise time as well as +// the number of events to be returned. +// +// TODO(roasbeef): rename? +func (f *ForwardingLog) Query(q ForwardingEventQuery) (ForwardingLogTimeSlice, error) { + resp := ForwardingLogTimeSlice{ + ForwardingEventQuery: q, + } + + // If the user provided an index offset, then we'll not know how many + // records we need to skip. We'll also keep track of the record offset + // as that's part of the final return value. + recordsToSkip := q.IndexOffset + recordOffset := q.IndexOffset + + err := f.db.View(func(tx *bolt.Tx) error { + // If the bucket wasn't found, then there aren't any events to + // be returned. + logBucket := tx.Bucket(forwardingLogBucket) + if logBucket == nil { + return ErrNoForwardingEvents + } + + // We'll be using a cursor to seek into the database, so we'll + // populate byte slices that represent the start of the key + // space we're interested in, and the end. + var startTime, endTime [8]byte + byteOrder.PutUint64(startTime[:], uint64(q.StartTime.UnixNano())) + byteOrder.PutUint64(endTime[:], uint64(q.EndTime.UnixNano())) + + // If we know that a set of log events exists, then we'll begin + // our seek through the log in order to satisfy the query. + // We'll continue until either we reach the end of the range, + // or reach our max number of events. + logCursor := logBucket.Cursor() + timestamp, events := logCursor.Seek(startTime[:]) + for ; timestamp != nil && bytes.Compare(timestamp, endTime[:]) <= 0; timestamp, events = logCursor.Next() { + // If our current return payload exceeds the max number + // of events, then we'll exit now. + if uint32(len(resp.ForwardingEvents)) >= q.NumMaxEvents { + return nil + } + + // If we're not yet past the user defined offset, then + // we'll continue to seek forward. + if recordsToSkip > 0 { + recordsToSkip-- + continue + } + + currentTime := time.Unix( + 0, int64(byteOrder.Uint64(timestamp)), + ) + + // At this point, we've skipped enough records to start + // to collate our query. For each record, we'll + // increment the final record offset so the querier can + // utilize pagination to seek further. + readBuf := bytes.NewReader(events) + for readBuf.Len() != 0 { + var event ForwardingEvent + err := decodeForwardingEvent(readBuf, &event) + if err != nil { + return err + } + + event.Timestamp = currentTime + resp.ForwardingEvents = append(resp.ForwardingEvents, event) + + recordOffset++ + } + } + + return nil + }) + if err != nil && err != ErrNoForwardingEvents { + return ForwardingLogTimeSlice{}, err + } + + resp.LastIndexOffset = recordOffset + + return resp, nil +} From 744cfd2ce574a4b3cae3d5dc1c7cf6d89d09f5f6 Mon Sep 17 00:00:00 2001 From: Olaoluwa Osuntokun Date: Tue, 27 Feb 2018 22:06:27 -0800 Subject: [PATCH 02/15] channeldb: add a set of initial tests for the ForwardingLog --- channeldb/forwarding_log_test.go | 265 +++++++++++++++++++++++++++++++ 1 file changed, 265 insertions(+) create mode 100644 channeldb/forwarding_log_test.go diff --git a/channeldb/forwarding_log_test.go b/channeldb/forwarding_log_test.go new file mode 100644 index 00000000..cc06e886 --- /dev/null +++ b/channeldb/forwarding_log_test.go @@ -0,0 +1,265 @@ +package channeldb + +import ( + "math/rand" + "reflect" + "testing" + + "github.com/davecgh/go-spew/spew" + "github.com/lightningnetwork/lnd/lnwire" + + "time" +) + +// TestForwardingLogBasicStorageAndQuery tests that we're able to store and +// then query for items that have previously been added to the event log. +func TestForwardingLogBasicStorageAndQuery(t *testing.T) { + t.Parallel() + + // First, we'll set up a test database, and use that to instantiate the + // forwarding event log that we'll be using for the duration of the + // test. + db, cleanUp, err := makeTestDB() + defer cleanUp() + if err != nil { + t.Fatalf("unable to make test db: %v", err) + } + log := ForwardingLog{ + db: db, + } + + initialTime := time.Unix(1234, 0) + timestamp := time.Unix(1234, 0) + + // We'll create 100 random events, which each event being spaced 10 + // minutes after the prior event. + numEvents := 100 + events := make([]ForwardingEvent, numEvents) + for i := 0; i < numEvents; i++ { + events[i] = ForwardingEvent{ + Timestamp: timestamp, + IncomingChanID: lnwire.NewShortChanIDFromInt(uint64(rand.Int63())), + OutgoingChanID: lnwire.NewShortChanIDFromInt(uint64(rand.Int63())), + AmtIn: lnwire.MilliSatoshi(rand.Int63()), + AmtOut: lnwire.MilliSatoshi(rand.Int63()), + } + + timestamp = timestamp.Add(time.Minute * 10) + } + + // Now that all of our set of events constructed, we'll add them to the + // database in a batch manner. + if err := log.AddForwardingEvents(events); err != nil { + t.Fatalf("unable to add events: %v", err) + } + + // With our events added we'll now construct a basic query to retrieve + // all of the events. + eventQuery := ForwardingEventQuery{ + StartTime: initialTime, + EndTime: timestamp, + IndexOffset: 0, + NumMaxEvents: 1000, + } + timeSlice, err := log.Query(eventQuery) + if err != nil { + t.Fatalf("unable to query for events: %v", err) + } + + // The set of returned events should match identically, as they should + // be returned in sorted order. + if !reflect.DeepEqual(events, timeSlice.ForwardingEvents) { + t.Fatalf("event mismatch: expected %v vs %v", + spew.Sdump(events), spew.Sdump(timeSlice.ForwardingEvents)) + } + + // The offset index of the final entry should be numEvents, so the + // number of total events we've written. + if timeSlice.LastIndexOffset != uint32(numEvents) { + t.Fatalf("wrong final offset: expected %v, got %v", + timeSlice.LastIndexOffset, numEvents) + } +} + +// TestForwardingLogQueryOptions tests that the query offset works properly. So +// if we add a series of events, then we should be able to seek within the +// timeslice accordingly. This exercises the index offset and num max event +// field in the query, and also the last index offset field int he response. +func TestForwardingLogQueryOptions(t *testing.T) { + t.Parallel() + + // First, we'll set up a test database, and use that to instantiate the + // forwarding event log that we'll be using for the duration of the + // test. + db, cleanUp, err := makeTestDB() + defer cleanUp() + if err != nil { + t.Fatalf("unable to make test db: %v", err) + } + log := ForwardingLog{ + db: db, + } + + initialTime := time.Unix(1234, 0) + endTime := time.Unix(1234, 0) + + // We'll create 20 random events, which each event being spaced 10 + // minutes after the prior event. + numEvents := 20 + events := make([]ForwardingEvent, numEvents) + for i := 0; i < numEvents; i++ { + events[i] = ForwardingEvent{ + Timestamp: endTime, + IncomingChanID: lnwire.NewShortChanIDFromInt(uint64(rand.Int63())), + OutgoingChanID: lnwire.NewShortChanIDFromInt(uint64(rand.Int63())), + AmtIn: lnwire.MilliSatoshi(rand.Int63()), + AmtOut: lnwire.MilliSatoshi(rand.Int63()), + } + + endTime = endTime.Add(time.Minute * 10) + } + + // Now that all of our set of events constructed, we'll add them to the + // database in a batch manner. + if err := log.AddForwardingEvents(events); err != nil { + t.Fatalf("unable to add events: %v", err) + } + + // With all of our events added, we should be able to query for the + // first 10 events using the max event query field. + eventQuery := ForwardingEventQuery{ + StartTime: initialTime, + EndTime: endTime, + IndexOffset: 0, + NumMaxEvents: 10, + } + timeSlice, err := log.Query(eventQuery) + if err != nil { + t.Fatalf("unable to query for events: %v", err) + } + + // We should get exactly 10 events back. + if len(timeSlice.ForwardingEvents) != 10 { + t.Fatalf("wrong number of events: expected %v, got %v", 10, + len(timeSlice.ForwardingEvents)) + } + + // The set of events returned should be the first 10 events that we + // added. + if !reflect.DeepEqual(events[:10], timeSlice.ForwardingEvents) { + t.Fatalf("wrong response: expected %v, got %v", + spew.Sdump(events[:10]), + spew.Sdump(timeSlice.ForwardingEvents)) + } + + // The final offset should be the exact number of events returned. + if timeSlice.LastIndexOffset != 10 { + t.Fatalf("wrong index offset: expected %v, got %v", 10, + timeSlice.LastIndexOffset) + } + + // If we use the final offset to query again, then we should get 10 + // more events, that are the last 10 events we wrote. + eventQuery.IndexOffset = 10 + timeSlice, err = log.Query(eventQuery) + if err != nil { + t.Fatalf("unable to query for events: %v", err) + } + + // We should get exactly 10 events back once again. + if len(timeSlice.ForwardingEvents) != 10 { + t.Fatalf("wrong number of events: expected %v, got %v", 10, + len(timeSlice.ForwardingEvents)) + } + + // The events that we got back should be the last 10 events that we + // wrote out. + if !reflect.DeepEqual(events[10:], timeSlice.ForwardingEvents) { + t.Fatalf("wrong response: expected %v, got %v", + spew.Sdump(events[10:]), + spew.Sdump(timeSlice.ForwardingEvents)) + } + + // Finally, the last index offset should be 20, or the number of + // records we've written out. + if timeSlice.LastIndexOffset != 20 { + t.Fatalf("wrong index offset: expected %v, got %v", 20, + timeSlice.LastIndexOffset) + } +} + +// TestForwardingLogQueryLimit tests that we're able to properly limit the +// number of events that are returned as part of a query. +func TestForwardingLogQueryLimit(t *testing.T) { + t.Parallel() + + // First, we'll set up a test database, and use that to instantiate the + // forwarding event log that we'll be using for the duration of the + // test. + db, cleanUp, err := makeTestDB() + defer cleanUp() + if err != nil { + t.Fatalf("unable to make test db: %v", err) + } + log := ForwardingLog{ + db: db, + } + + initialTime := time.Unix(1234, 0) + endTime := time.Unix(1234, 0) + + // We'll create 200 random events, which each event being spaced 10 + // minutes after the prior event. + numEvents := 200 + events := make([]ForwardingEvent, numEvents) + for i := 0; i < numEvents; i++ { + events[i] = ForwardingEvent{ + Timestamp: endTime, + IncomingChanID: lnwire.NewShortChanIDFromInt(uint64(rand.Int63())), + OutgoingChanID: lnwire.NewShortChanIDFromInt(uint64(rand.Int63())), + AmtIn: lnwire.MilliSatoshi(rand.Int63()), + AmtOut: lnwire.MilliSatoshi(rand.Int63()), + } + + endTime = endTime.Add(time.Minute * 10) + } + + // Now that all of our set of events constructed, we'll add them to the + // database in a batch manner. + if err := log.AddForwardingEvents(events); err != nil { + t.Fatalf("unable to add events: %v", err) + } + + // Once the events have been written out, we'll issue a query over the + // entire range, but restrict the number of events to the first 100. + eventQuery := ForwardingEventQuery{ + StartTime: initialTime, + EndTime: endTime, + IndexOffset: 0, + NumMaxEvents: 100, + } + timeSlice, err := log.Query(eventQuery) + if err != nil { + t.Fatalf("unable to query for events: %v", err) + } + + // We should get exactly 100 events back. + if len(timeSlice.ForwardingEvents) != 100 { + t.Fatalf("wrong number of events: expected %v, got %v", 10, + len(timeSlice.ForwardingEvents)) + } + + // The set of events returned should be the first 100 events that we + // added. + if !reflect.DeepEqual(events[:100], timeSlice.ForwardingEvents) { + t.Fatalf("wrong response: expected %v, got %v", + spew.Sdump(events[:100]), + spew.Sdump(timeSlice.ForwardingEvents)) + } + + // The final offset should be the exact number of events returned. + if timeSlice.LastIndexOffset != 100 { + t.Fatalf("wrong index offset: expected %v, got %v", 100, + timeSlice.LastIndexOffset) + } +} From 2b8cad2f082c5da4eff34d621e87d32da144c84d Mon Sep 17 00:00:00 2001 From: Olaoluwa Osuntokun Date: Tue, 27 Feb 2018 22:11:20 -0800 Subject: [PATCH 03/15] htlcswitch: add incoming+outgoing amounts to the PaymentCircuit MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit In this commit, we add the incoming+outgoing amounts if the HTLC’s that the payment circuit consists of. With these new fields, we’ll be able to populate the forwarding event log once the payment circuit has been successfully completed. --- htlcswitch/circuit.go | 34 ++++++++++++++++++++++------------ 1 file changed, 22 insertions(+), 12 deletions(-) diff --git a/htlcswitch/circuit.go b/htlcswitch/circuit.go index da1b6792..06bce167 100644 --- a/htlcswitch/circuit.go +++ b/htlcswitch/circuit.go @@ -16,26 +16,36 @@ type PaymentCircuit struct { // PaymentHash used as unique identifier of payment. PaymentHash [32]byte - // IncomingChanID identifies the channel from which add HTLC request came - // and to which settle/fail HTLC request will be returned back. Once - // the switch forwards the settle/fail message to the src the circuit - // is considered to be completed. + // IncomingChanID identifies the channel from which add HTLC request + // came and to which settle/fail HTLC request will be returned back. + // Once the switch forwards the settle/fail message to the src the + // circuit is considered to be completed. IncomingChanID lnwire.ShortChannelID - // IncomingHTLCID is the ID in the update_add_htlc message we received from - // the incoming channel, which will be included in any settle/fail messages - // we send back. + // IncomingHTLCID is the ID in the update_add_htlc message we received + // from the incoming channel, which will be included in any settle/fail + // messages we send back. IncomingHTLCID uint64 - // OutgoingChanID identifies the channel to which we propagate the HTLC add - // update and from which we are expecting to receive HTLC settle/fail - // request back. + // IncomingAmt is the value of the incoming HTLC. If we take this and + // subtract it from the OutgoingAmt, then we'll compute the total fee + // attached to this payment circuit. + IncomingAmt lnwire.MilliSatoshi + + // OutgoingChanID identifies the channel to which we propagate the HTLC + // add update and from which we are expecting to receive HTLC + // settle/fail request back. OutgoingChanID lnwire.ShortChannelID - // OutgoingHTLCID is the ID in the update_add_htlc message we sent to the - // outgoing channel. + // OutgoingHTLCID is the ID in the update_add_htlc message we sent to + // the outgoing channel. OutgoingHTLCID uint64 + // OutgoingAmt is the value of the outgoing HTLC. If we subtract this + // from the IncomingAmt, then we'll compute the total fee attached to + // this payment circuit. + OutgoingAmt lnwire.MilliSatoshi + // ErrorEncrypter is used to re-encrypt the onion failure before // sending it back to the originator of the payment. ErrorEncrypter ErrorEncrypter From 157e05295816ada7375818eb20d3aa4e3f746ca0 Mon Sep 17 00:00:00 2001 From: Olaoluwa Osuntokun Date: Tue, 27 Feb 2018 22:12:19 -0800 Subject: [PATCH 04/15] htlcswitch: add the incoming payment amount to the htlcPacket MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit We’ll need this value within the link+switch in order to fully populate the forwarding event that will be generated if this HTLC circuit is successfully completed. --- htlcswitch/packet.go | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/htlcswitch/packet.go b/htlcswitch/packet.go index 262826b0..cdaccedb 100644 --- a/htlcswitch/packet.go +++ b/htlcswitch/packet.go @@ -23,6 +23,14 @@ type htlcPacket struct { // on the incoming channel. incomingHTLCID uint64 + // incomingHtlcAmt is the value of the *incoming* HTLC. This will be + // set by the link when it receives an incoming HTLC to be forwarded + // through the switch. Then the outgoing link will use this once it + // creates a full circuit add. This allows us to properly populate the + // forwarding event for this circuit/packet in the case the payment + // circuit is successful. + incomingHtlcAmt lnwire.MilliSatoshi + // outgoingHTLCID is the ID of the HTLC that we offered to the peer on the // outgoing channel. outgoingHTLCID uint64 From d377ffafddc8f5d5b5771e9087157daf3b4cd2ab Mon Sep 17 00:00:00 2001 From: Olaoluwa Osuntokun Date: Tue, 27 Feb 2018 22:13:59 -0800 Subject: [PATCH 05/15] htlcswitch: populate the incoming+outgoing in the payment circuit --- htlcswitch/link.go | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/htlcswitch/link.go b/htlcswitch/link.go index 89887a67..c0994d3f 100644 --- a/htlcswitch/link.go +++ b/htlcswitch/link.go @@ -881,14 +881,16 @@ func (l *channelLink) handleDownStreamPkt(pkt *htlcPacket, isReProcess bool) { "local_log_index=%v, batch_size=%v", htlc.PaymentHash[:], index, l.batchCounter+1) - // Create circuit (remember the path) in order to forward settle/fail - // packet back. + // Create circuit (remember the path) in order to forward + // settle/fail packet back. l.cfg.Switch.addCircuit(&PaymentCircuit{ PaymentHash: htlc.PaymentHash, IncomingChanID: pkt.incomingChanID, IncomingHTLCID: pkt.incomingHTLCID, + IncomingAmt: pkt.incomingHtlcAmt, OutgoingChanID: l.ShortChanID(), OutgoingHTLCID: index, + OutgoingAmt: htlc.Amount, ErrorEncrypter: pkt.obfuscator, }) From 6f11fee1a41460c8f62024e6290972572f063446 Mon Sep 17 00:00:00 2001 From: Olaoluwa Osuntokun Date: Tue, 27 Feb 2018 22:14:44 -0800 Subject: [PATCH 06/15] htlcswitch: when forwarding htlcs, set the incomingHtlcAmt --- htlcswitch/link.go | 25 +++++++++++++------------ 1 file changed, 13 insertions(+), 12 deletions(-) diff --git a/htlcswitch/link.go b/htlcswitch/link.go index c0994d3f..c0c3e8f3 100644 --- a/htlcswitch/link.go +++ b/htlcswitch/link.go @@ -1395,8 +1395,8 @@ func (l *channelLink) processLockedInHtlcs( switch pd.EntryType { // A settle for an HTLC we previously forwarded HTLC has been - // received. So we'll forward the HTLC to the switch which - // will handle propagating the settle to the prior hop. + // received. So we'll forward the HTLC to the switch which will + // handle propagating the settle to the prior hop. case lnwallet.Settle: settlePacket := &htlcPacket{ outgoingChanID: l.ShortChanID(), @@ -1413,10 +1413,10 @@ func (l *channelLink) processLockedInHtlcs( packetsToForward = append(packetsToForward, settlePacket) l.overflowQueue.SignalFreeSlot() - // A failureCode message for a previously forwarded HTLC has been - // received. As a result a new slot will be freed up in our - // commitment state, so we'll forward this to the switch so the - // backwards undo can continue. + // A failureCode message for a previously forwarded HTLC has + // been received. As a result a new slot will be freed up in + // our commitment state, so we'll forward this to the switch so + // the backwards undo can continue. case lnwallet.Fail: // Fetch the reason the HTLC was cancelled so we can // continue to propagate it. @@ -1820,12 +1820,13 @@ func (l *channelLink) processLockedInHtlcs( } updatePacket := &htlcPacket{ - incomingChanID: l.ShortChanID(), - incomingHTLCID: pd.HtlcIndex, - outgoingChanID: fwdInfo.NextHop, - amount: addMsg.Amount, - htlc: addMsg, - obfuscator: obfuscator, + incomingChanID: l.ShortChanID(), + incomingHTLCID: pd.HtlcIndex, + outgoingChanID: fwdInfo.NextHop, + incomingHtlcAmt: pd.Amount, + amount: addMsg.Amount, + htlc: addMsg, + obfuscator: obfuscator, } packetsToForward = append(packetsToForward, updatePacket) } From ad522a72c114a7940358c6cd2889e833d7f3527f Mon Sep 17 00:00:00 2001 From: Olaoluwa Osuntokun Date: Tue, 27 Feb 2018 22:18:52 -0800 Subject: [PATCH 07/15] htlcswitch: add new ticker in switch to batch log forwarding events MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit In this commit, we extend the switch as is, to record details concerning settled payment circuits. To do this, we introduce a new interface to the package: the ForwardingLog. This is a tiny interface that simply lets us abstract away the details of the storage backing of the forwarding log. Each time we receive a successful HTLC settle, we’ll log the full details (chans, fees, time) as a pending forwarding log entry. Every 15 seconds, we’ll then batch flush out these entries to disk. When we’re exiting, we’ll try to flush out all entries to ensure everything gets recorded to disk. --- htlcswitch/interfaces.go | 12 +++++ htlcswitch/mock.go | 16 ++++++ htlcswitch/switch.go | 111 +++++++++++++++++++++++++++++++++++---- 3 files changed, 130 insertions(+), 9 deletions(-) diff --git a/htlcswitch/interfaces.go b/htlcswitch/interfaces.go index 7e62e505..0d7ca2b4 100644 --- a/htlcswitch/interfaces.go +++ b/htlcswitch/interfaces.go @@ -121,3 +121,15 @@ type Peer interface { // properly handle. Disconnect(reason error) } + +// ForwardingLog is an interface that represents a time series database which +// keep track of all successfully completed payment circuits. Every few +// seconds, the switch will collate and flush out all the successful payment +// circuits during the last interval. +type ForwardingLog interface { + // AddForwardingEvents is a method that should write out the set of + // forwarding events in a batch to persistent storage. Outside + // sub-systems can then query the contents of the log for analysis, + // visualizations, etc. + AddForwardingEvents([]channeldb.ForwardingEvent) error +} diff --git a/htlcswitch/mock.go b/htlcswitch/mock.go index 55df1e49..b748c668 100644 --- a/htlcswitch/mock.go +++ b/htlcswitch/mock.go @@ -80,6 +80,22 @@ func (m *mockFeeEstimator) Stop() error { var _ lnwallet.FeeEstimator = (*mockFeeEstimator)(nil) +type mockForwardingLog struct { + sync.Mutex + events map[time.Time]channeldb.ForwardingEvent +} + +func (m *mockForwardingLog) AddForwardingEvents(events []channeldb.ForwardingEvent) error { + m.Lock() + defer m.Unlock() + + for _, event := range events { + m.events[event.Timestamp] = event + } + + return nil +} + type mockServer struct { started int32 shutdown int32 diff --git a/htlcswitch/switch.go b/htlcswitch/switch.go index f1656f12..029c1515 100644 --- a/htlcswitch/switch.go +++ b/htlcswitch/switch.go @@ -13,6 +13,7 @@ import ( "github.com/roasbeef/btcd/btcec" "github.com/go-errors/errors" + "github.com/lightningnetwork/lnd/channeldb" "github.com/lightningnetwork/lnd/contractcourt" "github.com/lightningnetwork/lnd/lnrpc" "github.com/lightningnetwork/lnd/lnwallet" @@ -99,6 +100,12 @@ type Config struct { // properly route around link./vertex failures. SelfKey *btcec.PublicKey + // FwdingLog is an interface that will be used by the switch to log + // forwarding events. A forwarding event happens each time a payment + // circuit is successfully completed. So when we forward an HTLC, and a + // settle is eventually received. + FwdingLog ForwardingLog + // LocalChannelClose kicks-off the workflow to execute a cooperative or // forced unilateral closure of the channel initiated by a local // subsystem. @@ -169,6 +176,12 @@ type Switch struct { // linkControl is a channel used to propagate add/remove/get htlc // switch handler commands. linkControl chan interface{} + + // pendingFwdingEvents is the set of forwarding events which have been + // collected during the current interval, but hasn't yet been written + // to the forwarding log. + fwdEventMtx sync.Mutex + pendingFwdingEvents []channeldb.ForwardingEvent } // New creates the new instance of htlc switch. @@ -553,8 +566,8 @@ func (s *Switch) handlePacketForward(packet *htlcPacket) error { // the ultimate settle message back latter. case *lnwire.UpdateAddHTLC: if packet.incomingChanID == (lnwire.ShortChannelID{}) { - // A blank incomingChanID indicates that this is a pending - // user-initiated payment. + // A blank incomingChanID indicates that this is a + // pending user-initiated payment. return s.handleLocalDispatch(packet) } @@ -606,8 +619,8 @@ func (s *Switch) handlePacketForward(packet *htlcPacket) error { } if link.Bandwidth() >= htlc.Amount { - destination = link + break } } @@ -665,9 +678,12 @@ func (s *Switch) handlePacketForward(packet *htlcPacket) error { return err } - // Remove circuit since we are about to complete the HTLC. - err := s.circuits.Remove(packet.outgoingChanID, - packet.outgoingHTLCID) + // Remove the circuit since we are about to complete + // the HTLC. + err := s.circuits.Remove( + packet.outgoingChanID, + packet.outgoingHTLCID, + ) if err != nil { log.Warnf("Failed to close completed onion circuit for %x: "+ "(%s, %d) <-> (%s, %d)", circuit.PaymentHash, @@ -683,6 +699,29 @@ func (s *Switch) handlePacketForward(packet *htlcPacket) error { packet.incomingChanID = circuit.IncomingChanID packet.incomingHTLCID = circuit.IncomingHTLCID + // If this is an HTLC settle, and it wasn't from a + // locally initiated HTLC, then we'll log a forwarding + // event so we can flush it to disk later. + // + // TODO(roasbeef): only do this once link actually + // fully settles? + _, isSettle := packet.htlc.(*lnwire.UpdateFulfillHTLC) + localHTLC := packet.incomingChanID == (lnwire.ShortChannelID{}) + if isSettle && !localHTLC { + s.fwdEventMtx.Lock() + s.pendingFwdingEvents = append( + s.pendingFwdingEvents, + channeldb.ForwardingEvent{ + Timestamp: time.Now(), + IncomingChanID: circuit.IncomingChanID, + OutgoingChanID: circuit.OutgoingChanID, + AmtIn: circuit.IncomingAmt, + AmtOut: circuit.OutgoingAmt, + }, + ) + s.fwdEventMtx.Unlock() + } + // Obfuscate the error message for fail updates before // sending back through the circuit unless the payment // was generated locally. @@ -715,9 +754,11 @@ func (s *Switch) handlePacketForward(packet *htlcPacket) error { } } - // A blank IncomingChanID in a circuit indicates that it is a - // pending user-initiated payment. - if packet.incomingChanID == (lnwire.ShortChannelID{}) { + // For local HTLC's we'll dispatch the settle event back to the + // caller, rather than to the peer that sent us the HTLC + // originally. + localHTLC := packet.incomingChanID == (lnwire.ShortChannelID{}) + if localHTLC { return s.handleLocalDispatch(packet) } @@ -790,6 +831,13 @@ func (s *Switch) htlcForwarder() { "channel link on stop: %v", err) } } + + // Before we exit fully, we'll attempt to flush out any + // forwarding events that may still be lingering since the last + // batch flush. + if err := s.FlushForwardingEvents(); err != nil { + log.Errorf("unable to flush forwarding events: %v", err) + } }() // TODO(roasbeef): cleared vs settled distinction @@ -801,6 +849,11 @@ func (s *Switch) htlcForwarder() { logTicker := time.NewTicker(10 * time.Second) defer logTicker.Stop() + // Every 15 seconds, we'll flush out the forwarding events that + // occurred during that period. + fwdEventTicker := time.NewTicker(15 * time.Second) + defer fwdEventTicker.Stop() + for { select { // A local close request has arrived, we'll forward this to the @@ -861,6 +914,17 @@ func (s *Switch) htlcForwarder() { case cmd := <-s.htlcPlex: cmd.err <- s.handlePacketForward(cmd.pkt) + // When this time ticks, then it indicates that we should + // collect all the forwarding events since the last internal, + // and write them out to our log. + case <-fwdEventTicker.C: + go func() { + if err := s.FlushForwardingEvents(); err != nil { + log.Errorf("unable to flush "+ + "forwarding events: %v", err) + } + }() + // The log ticker has fired, so we'll calculate some forwarding // stats for the last 10 seconds to display within the logs to // users. @@ -1307,3 +1371,32 @@ func (s *Switch) numPendingPayments() int { func (s *Switch) addCircuit(circuit *PaymentCircuit) { s.circuits.Add(circuit) } + +// FlushForwardingEvents flushes out the set of pending forwarding events to +// the persistent log. This will be used by the switch to periodically flush +// out the set of forwarding events to disk. External callers can also use this +// method to ensure all data is flushed to dis before querying the log. +func (s *Switch) FlushForwardingEvents() error { + // First, we'll obtain a copy of the current set of pending forwarding + // events. + s.fwdEventMtx.Lock() + + // If we won't have any forwarding events, then we can exit early. + if len(s.pendingFwdingEvents) == 0 { + s.fwdEventMtx.Unlock() + return nil + } + + events := make([]channeldb.ForwardingEvent, len(s.pendingFwdingEvents)) + copy(events[:], s.pendingFwdingEvents[:]) + + // With the copy obtained, we can now clear out the header pointer of + // the current slice. This way, we can re-use the underlying storage + // allocated for the slice. + s.pendingFwdingEvents = s.pendingFwdingEvents[:0] + s.fwdEventMtx.Unlock() + + // Finally, we'll write out the copied events to the persistent + // forwarding log. + return s.cfg.FwdingLog.AddForwardingEvents(events) +} From 473dfd115be3b5a241e2c4c44e6474fb43302ce1 Mon Sep 17 00:00:00 2001 From: Olaoluwa Osuntokun Date: Tue, 27 Feb 2018 22:19:21 -0800 Subject: [PATCH 08/15] htlcswitch: add set of tests for the forwarding log --- htlcswitch/link.go | 2 + htlcswitch/mock.go | 19 ++-- htlcswitch/switch.go | 2 +- htlcswitch/switch_test.go | 201 ++++++++++++++++++++++++++++++++++++-- 4 files changed, 210 insertions(+), 14 deletions(-) diff --git a/htlcswitch/link.go b/htlcswitch/link.go index c0c3e8f3..ae923277 100644 --- a/htlcswitch/link.go +++ b/htlcswitch/link.go @@ -683,6 +683,8 @@ out: // carried out by the remote peer. In the case of such an // event, we'll wipe the channel state from the peer, and mark // the contract as fully settled. Afterwards we can exit. + // + // TODO(roasbeef): add force closure? also breach? case <-l.cfg.ChainEvents.UnilateralClosure: log.Warnf("Remote peer has closed ChannelPoint(%v) on-chain", l.channel.ChannelPoint()) diff --git a/htlcswitch/mock.go b/htlcswitch/mock.go index b748c668..7b6e5803 100644 --- a/htlcswitch/mock.go +++ b/htlcswitch/mock.go @@ -82,6 +82,7 @@ var _ lnwallet.FeeEstimator = (*mockFeeEstimator)(nil) type mockForwardingLog struct { sync.Mutex + events map[time.Time]channeldb.ForwardingEvent } @@ -124,13 +125,17 @@ func newMockServer(t testing.TB, name string) *mockServer { copy(id[:], h[:]) return &mockServer{ - t: t, - id: id, - name: name, - messages: make(chan lnwire.Message, 3000), - quit: make(chan struct{}), - registry: newMockRegistry(), - htlcSwitch: New(Config{}), + t: t, + id: id, + name: name, + messages: make(chan lnwire.Message, 3000), + quit: make(chan struct{}), + registry: newMockRegistry(), + htlcSwitch: New(Config{ + FwdingLog: &mockForwardingLog{ + events: make(map[time.Time]channeldb.ForwardingEvent), + }, + }), interceptorFuncs: make([]messageInterceptor, 0), } } diff --git a/htlcswitch/switch.go b/htlcswitch/switch.go index 029c1515..9086700b 100644 --- a/htlcswitch/switch.go +++ b/htlcswitch/switch.go @@ -754,7 +754,7 @@ func (s *Switch) handlePacketForward(packet *htlcPacket) error { } } - // For local HTLC's we'll dispatch the settle event back to the + // For local HTLCs we'll dispatch the settle event back to the // caller, rather than to the peer that sent us the HTLC // originally. localHTLC := packet.incomingChanID == (lnwire.ShortChannelID{}) diff --git a/htlcswitch/switch_test.go b/htlcswitch/switch_test.go index 6fbd489f..95d95797 100644 --- a/htlcswitch/switch_test.go +++ b/htlcswitch/switch_test.go @@ -7,10 +7,13 @@ import ( "time" "github.com/btcsuite/fastsha256" + "github.com/davecgh/go-spew/spew" "github.com/go-errors/errors" + "github.com/lightningnetwork/lnd/channeldb" "github.com/lightningnetwork/lnd/lnwire" "github.com/roasbeef/btcd/chaincfg/chainhash" "github.com/roasbeef/btcd/wire" + "github.com/roasbeef/btcutil" ) var ( @@ -35,7 +38,11 @@ func TestSwitchForward(t *testing.T) { alicePeer := newMockServer(t, "alice") bobPeer := newMockServer(t, "bob") - s := New(Config{}) + s := New(Config{ + FwdingLog: &mockForwardingLog{ + events: make(map[time.Time]channeldb.ForwardingEvent), + }, + }) s.Start() aliceChannelLink := newMockChannelLink( @@ -122,7 +129,11 @@ func TestSkipIneligibleLinksMultiHopForward(t *testing.T) { alicePeer := newMockServer(t, "alice") bobPeer := newMockServer(t, "bob") - s := New(Config{}) + s := New(Config{ + FwdingLog: &mockForwardingLog{ + events: make(map[time.Time]channeldb.ForwardingEvent), + }, + }) s.Start() aliceChannelLink := newMockChannelLink( @@ -177,7 +188,11 @@ func TestSkipIneligibleLinksLocalForward(t *testing.T) { // to forward form the get go. alicePeer := newMockServer(t, "alice") - s := New(Config{}) + s := New(Config{ + FwdingLog: &mockForwardingLog{ + events: make(map[time.Time]channeldb.ForwardingEvent), + }, + }) s.Start() aliceChannelLink := newMockChannelLink( @@ -216,7 +231,11 @@ func TestSwitchCancel(t *testing.T) { alicePeer := newMockServer(t, "alice") bobPeer := newMockServer(t, "bob") - s := New(Config{}) + s := New(Config{ + FwdingLog: &mockForwardingLog{ + events: make(map[time.Time]channeldb.ForwardingEvent), + }, + }) s.Start() aliceChannelLink := newMockChannelLink( @@ -298,7 +317,11 @@ func TestSwitchAddSamePayment(t *testing.T) { alicePeer := newMockServer(t, "alice") bobPeer := newMockServer(t, "bob") - s := New(Config{}) + s := New(Config{ + FwdingLog: &mockForwardingLog{ + events: make(map[time.Time]channeldb.ForwardingEvent), + }, + }) s.Start() aliceChannelLink := newMockChannelLink( @@ -422,7 +445,11 @@ func TestSwitchSendPayment(t *testing.T) { alicePeer := newMockServer(t, "alice") - s := New(Config{}) + s := New(Config{ + FwdingLog: &mockForwardingLog{ + events: make(map[time.Time]channeldb.ForwardingEvent), + }, + }) s.Start() aliceChannelLink := newMockChannelLink( @@ -542,3 +569,165 @@ func TestSwitchSendPayment(t *testing.T) { t.Fatal("wrong amount of pending payments") } } + +// TestLocalPaymentNoForwardingEvents tests that if we send a series of locally +// initiated payments, then they aren't reflected in the forwarding log. +func TestLocalPaymentNoForwardingEvents(t *testing.T) { + t.Parallel() + + // First, we'll create our traditional three hop network. We'll only be + // interacting with and asserting the state of the first end point for + // this test. + channels, cleanUp, _, err := createClusterChannels( + btcutil.SatoshiPerBitcoin*3, + btcutil.SatoshiPerBitcoin*5) + if err != nil { + t.Fatalf("unable to create channel: %v", err) + } + defer cleanUp() + + n := newThreeHopNetwork(t, channels.aliceToBob, channels.bobToAlice, + channels.bobToCarol, channels.carolToBob, testStartingHeight) + if err := n.start(); err != nil { + t.Fatalf("unable to start three hop network: %v", err) + } + + // We'll now craft and send a payment from Alice to Bob. + amount := lnwire.NewMSatFromSatoshis(btcutil.SatoshiPerBitcoin) + htlcAmt, totalTimelock, hops := generateHops( + amount, testStartingHeight, n.firstBobChannelLink, + ) + + // With the payment crafted, we'll send it from Alice to Bob. We'll + // wait for Alice to receive the preimage for the payment before + // proceeding. + receiver := n.bobServer + _, err = n.makePayment( + n.aliceServer, receiver, n.bobServer.PubKey(), hops, amount, + htlcAmt, totalTimelock, + ).Wait(30 * time.Second) + if err != nil { + t.Fatalf("unable to make the payment: %v", err) + } + + // At this point, we'll forcibly stop the three hop network. Doing + // this will cause any pending forwarding events to be flushed by the + // various switches in the network. + n.stop() + + // With all the switches stopped, we'll fetch Alice's mock forwarding + // event log. + log, ok := n.aliceServer.htlcSwitch.cfg.FwdingLog.(*mockForwardingLog) + if !ok { + t.Fatalf("mockForwardingLog assertion failed") + } + + // If we examine the memory of the forwarding log, then it should be + // blank. + if len(log.events) != 0 { + t.Fatalf("log should have no events, instead has: %v", + spew.Sdump(log.events)) + } +} + +// TestMultiHopPaymentForwardingEvents tests that if we send a series of +// multi-hop payments via Alice->Bob->Carol. Then Bob properly logs forwarding +// events, while Alice and Carol don't. +func TestMultiHopPaymentForwardingEvents(t *testing.T) { + t.Parallel() + + // First, we'll create our traditional three hop network. + channels, cleanUp, _, err := createClusterChannels( + btcutil.SatoshiPerBitcoin*3, + btcutil.SatoshiPerBitcoin*5) + if err != nil { + t.Fatalf("unable to create channel: %v", err) + } + defer cleanUp() + + n := newThreeHopNetwork(t, channels.aliceToBob, channels.bobToAlice, + channels.bobToCarol, channels.carolToBob, testStartingHeight) + if err := n.start(); err != nil { + t.Fatalf("unable to start three hop network: %v", err) + } + + // We'll make now 10 payments, of 100k satoshis each from Alice to + // Carol via Bob. + const numPayments = 10 + finalAmt := lnwire.NewMSatFromSatoshis(100000) + htlcAmt, totalTimelock, hops := generateHops( + finalAmt, testStartingHeight, n.firstBobChannelLink, + n.carolChannelLink, + ) + for i := 0; i < numPayments; i++ { + _, err := n.makePayment( + n.aliceServer, n.carolServer, n.bobServer.PubKey(), + hops, finalAmt, htlcAmt, totalTimelock, + ).Wait(30 * time.Second) + if err != nil { + t.Fatalf("unable to send payment: %v", err) + } + } + + time.Sleep(time.Millisecond * 200) + + // With all 10 payments sent. We'll now manually stop each of the + // switches so we can examine their end state. + n.stop() + + // Alice and Carol shouldn't have any recorded forwarding events, as + // they were the source and the sink for these payment flows. + aliceLog, ok := n.aliceServer.htlcSwitch.cfg.FwdingLog.(*mockForwardingLog) + if !ok { + t.Fatalf("mockForwardingLog assertion failed") + } + if len(aliceLog.events) != 0 { + t.Fatalf("log should have no events, instead has: %v", + spew.Sdump(aliceLog.events)) + } + carolLog, ok := n.carolServer.htlcSwitch.cfg.FwdingLog.(*mockForwardingLog) + if !ok { + t.Fatalf("mockForwardingLog assertion failed") + } + if len(carolLog.events) != 0 { + t.Fatalf("log should have no events, instead has: %v", + spew.Sdump(carolLog.events)) + } + + // Bob on the other hand, should have 10 events. + bobLog, ok := n.bobServer.htlcSwitch.cfg.FwdingLog.(*mockForwardingLog) + if !ok { + t.Fatalf("mockForwardingLog assertion failed") + } + if len(bobLog.events) != 10 { + t.Fatalf("log should have 10 events, instead has: %v", + spew.Sdump(bobLog.events)) + } + + // Each of the 10 events should have had all fields set properly. + for _, event := range bobLog.events { + // The incoming and outgoing channels should properly be set for + // the event. + if event.IncomingChanID != n.aliceChannelLink.ShortChanID() { + t.Fatalf("chan id mismatch: expected %v, got %v", + event.IncomingChanID, + n.aliceChannelLink.ShortChanID()) + } + if event.OutgoingChanID != n.carolChannelLink.ShortChanID() { + t.Fatalf("chan id mismatch: expected %v, got %v", + event.OutgoingChanID, + n.carolChannelLink.ShortChanID()) + } + + // Additionally, the incoming and outgoing amounts should also + // be properly set. + if event.AmtIn != htlcAmt { + t.Fatalf("incoming amt mismatch: expected %v, got %v", + event.AmtIn, htlcAmt) + } + if event.AmtOut != finalAmt { + t.Fatalf("outgoing amt mismatch: expected %v, got %v", + event.AmtOut, finalAmt) + } + } +} From ff720e1252cd4ef774713ff4c6585bd23eb5fb0c Mon Sep 17 00:00:00 2001 From: Olaoluwa Osuntokun Date: Tue, 27 Feb 2018 22:20:46 -0800 Subject: [PATCH 09/15] lnrpc: extend the FeeReportResponse to include fee revenue sums --- lnrpc/rpc.proto | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/lnrpc/rpc.proto b/lnrpc/rpc.proto index d7c19967..1baac903 100644 --- a/lnrpc/rpc.proto +++ b/lnrpc/rpc.proto @@ -1488,6 +1488,15 @@ message ChannelFeeReport { message FeeReportResponse { /// An array of channel fee reports which describes the current fee schedule for each channel. repeated ChannelFeeReport channel_fees = 1 [json_name = "channel_fees"]; + + /// The total amount of fee revenue (in satoshis) the switch has collected over the past 24 hrs. + uint64 day_fee_sum = 2 [json_name = "day_fee_sum"]; + + /// The total amount of fee revenue (in satoshis) the switch has collected over the past 1 week. + uint64 week_fee_sum = 3 [json_name = "week_fee_sum"]; + + /// The total amount of fee revenue (in satoshis) the switch has collected over the past 1 month. + uint64 month_fee_sum = 4 [json_name = "month_fee_sum"]; } message PolicyUpdateRequest { From 36f214f48cb14933e83984b79d7ca897d5c601eb Mon Sep 17 00:00:00 2001 From: Olaoluwa Osuntokun Date: Tue, 27 Feb 2018 22:22:06 -0800 Subject: [PATCH 10/15] lnrpc: add ForwardingHistory for querying payment circuit time series In this commit, we add a new method to the RPC service: ForwardingHistory. This method will allow callers to query for the completed payment circuits in a particular time slice, skip a series of events, and also paginate within a time slice. --- lnrpc/rpc.pb.go | 871 ++++++++++++++++++++++++++--------------- lnrpc/rpc.pb.gw.go | 46 +++ lnrpc/rpc.proto | 63 +++ lnrpc/rpc.swagger.json | 120 ++++++ 4 files changed, 782 insertions(+), 318 deletions(-) diff --git a/lnrpc/rpc.pb.go b/lnrpc/rpc.pb.go index 644b4fa7..8970c9f0 100644 --- a/lnrpc/rpc.pb.go +++ b/lnrpc/rpc.pb.go @@ -102,6 +102,9 @@ It has these top-level messages: FeeReportResponse PolicyUpdateRequest PolicyUpdateResponse + ForwardingHistoryRequest + ForwardingEvent + ForwardingHistoryResponse */ package lnrpc @@ -3664,6 +3667,12 @@ func (m *ChannelFeeReport) GetFeeRate() float64 { type FeeReportResponse struct { // / An array of channel fee reports which describes the current fee schedule for each channel. ChannelFees []*ChannelFeeReport `protobuf:"bytes,1,rep,name=channel_fees" json:"channel_fees,omitempty"` + // / The total amount of fee revenue (in satoshis) the switch has collected over the past 24 hrs. + DayFeeSum uint64 `protobuf:"varint,2,opt,name=day_fee_sum" json:"day_fee_sum,omitempty"` + // / The total amount of fee revenue (in satoshis) the switch has collected over the past 1 week. + WeekFeeSum uint64 `protobuf:"varint,3,opt,name=week_fee_sum" json:"week_fee_sum,omitempty"` + // / The total amount of fee revenue (in satoshis) the switch has collected over the past 1 month. + MonthFeeSum uint64 `protobuf:"varint,4,opt,name=month_fee_sum" json:"month_fee_sum,omitempty"` } func (m *FeeReportResponse) Reset() { *m = FeeReportResponse{} } @@ -3678,6 +3687,27 @@ func (m *FeeReportResponse) GetChannelFees() []*ChannelFeeReport { return nil } +func (m *FeeReportResponse) GetDayFeeSum() uint64 { + if m != nil { + return m.DayFeeSum + } + return 0 +} + +func (m *FeeReportResponse) GetWeekFeeSum() uint64 { + if m != nil { + return m.WeekFeeSum + } + return 0 +} + +func (m *FeeReportResponse) GetMonthFeeSum() uint64 { + if m != nil { + return m.MonthFeeSum + } + return 0 +} + type PolicyUpdateRequest struct { // Types that are valid to be assigned to Scope: // *PolicyUpdateRequest_Global @@ -3833,6 +3863,138 @@ func (m *PolicyUpdateResponse) String() string { return proto.Compact func (*PolicyUpdateResponse) ProtoMessage() {} func (*PolicyUpdateResponse) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{93} } +type ForwardingHistoryRequest struct { + // / Start time is the starting point of the forwarding history request. All records beyond this point will be included, respecting the end time, and the index offset. + StartTime uint64 `protobuf:"varint,1,opt,name=start_time" json:"start_time,omitempty"` + // / End time is the end point of the forwarding history request. The response will carry at most 50k records between the start time and the end time. The index offset can be used to implement pagination. + EndTime uint64 `protobuf:"varint,2,opt,name=end_time" json:"end_time,omitempty"` + // / Index offset is the offset in the time series to start at. As each response can only contain 50k records, callers can use this to skip around within a packed time series. + IndexOffset uint32 `protobuf:"varint,3,opt,name=index_offset" json:"index_offset,omitempty"` + // / The max number of events to return in the response to this query. + NumMaxEvents uint32 `protobuf:"varint,4,opt,name=num_max_events" json:"num_max_events,omitempty"` +} + +func (m *ForwardingHistoryRequest) Reset() { *m = ForwardingHistoryRequest{} } +func (m *ForwardingHistoryRequest) String() string { return proto.CompactTextString(m) } +func (*ForwardingHistoryRequest) ProtoMessage() {} +func (*ForwardingHistoryRequest) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{94} } + +func (m *ForwardingHistoryRequest) GetStartTime() uint64 { + if m != nil { + return m.StartTime + } + return 0 +} + +func (m *ForwardingHistoryRequest) GetEndTime() uint64 { + if m != nil { + return m.EndTime + } + return 0 +} + +func (m *ForwardingHistoryRequest) GetIndexOffset() uint32 { + if m != nil { + return m.IndexOffset + } + return 0 +} + +func (m *ForwardingHistoryRequest) GetNumMaxEvents() uint32 { + if m != nil { + return m.NumMaxEvents + } + return 0 +} + +type ForwardingEvent struct { + // / Timestamp is the time (unix epoch offset) that this circuit was completed. + Timestamp uint64 `protobuf:"varint,1,opt,name=timestamp" json:"timestamp,omitempty"` + // / The incoming channel ID that carried the HTLC that created the circuit. + ChanIdIn uint64 `protobuf:"varint,2,opt,name=chan_id_in" json:"chan_id_in,omitempty"` + // / The outgoing channel ID that carried the preimage that completed the circuit. + ChanIdOut uint64 `protobuf:"varint,4,opt,name=chan_id_out" json:"chan_id_out,omitempty"` + // / The total amount of the incoming HTLC that created half the circuit. + AmtIn uint64 `protobuf:"varint,5,opt,name=amt_in" json:"amt_in,omitempty"` + // / The total amount of the outgoign HTLC that created the second half of the circuit. + AmtOut uint64 `protobuf:"varint,6,opt,name=amt_out" json:"amt_out,omitempty"` + // / The total fee that this payment circuit carried. + Fee uint64 `protobuf:"varint,7,opt,name=fee" json:"fee,omitempty"` +} + +func (m *ForwardingEvent) Reset() { *m = ForwardingEvent{} } +func (m *ForwardingEvent) String() string { return proto.CompactTextString(m) } +func (*ForwardingEvent) ProtoMessage() {} +func (*ForwardingEvent) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{95} } + +func (m *ForwardingEvent) GetTimestamp() uint64 { + if m != nil { + return m.Timestamp + } + return 0 +} + +func (m *ForwardingEvent) GetChanIdIn() uint64 { + if m != nil { + return m.ChanIdIn + } + return 0 +} + +func (m *ForwardingEvent) GetChanIdOut() uint64 { + if m != nil { + return m.ChanIdOut + } + return 0 +} + +func (m *ForwardingEvent) GetAmtIn() uint64 { + if m != nil { + return m.AmtIn + } + return 0 +} + +func (m *ForwardingEvent) GetAmtOut() uint64 { + if m != nil { + return m.AmtOut + } + return 0 +} + +func (m *ForwardingEvent) GetFee() uint64 { + if m != nil { + return m.Fee + } + return 0 +} + +type ForwardingHistoryResponse struct { + // / A list of forwarding events from the time slice of the time series specified in the request. + ForwardingEvents []*ForwardingEvent `protobuf:"bytes,1,rep,name=forwarding_events" json:"forwarding_events,omitempty"` + // / The index of the last time in the set of returned forwarding events. Can be used to seek further, pagination style. + LastOffsetIndex uint32 `protobuf:"varint,2,opt,name=last_offset_index" json:"last_offset_index,omitempty"` +} + +func (m *ForwardingHistoryResponse) Reset() { *m = ForwardingHistoryResponse{} } +func (m *ForwardingHistoryResponse) String() string { return proto.CompactTextString(m) } +func (*ForwardingHistoryResponse) ProtoMessage() {} +func (*ForwardingHistoryResponse) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{96} } + +func (m *ForwardingHistoryResponse) GetForwardingEvents() []*ForwardingEvent { + if m != nil { + return m.ForwardingEvents + } + return nil +} + +func (m *ForwardingHistoryResponse) GetLastOffsetIndex() uint32 { + if m != nil { + return m.LastOffsetIndex + } + return 0 +} + func init() { proto.RegisterType((*GenSeedRequest)(nil), "lnrpc.GenSeedRequest") proto.RegisterType((*GenSeedResponse)(nil), "lnrpc.GenSeedResponse") @@ -3932,6 +4094,9 @@ func init() { proto.RegisterType((*FeeReportResponse)(nil), "lnrpc.FeeReportResponse") proto.RegisterType((*PolicyUpdateRequest)(nil), "lnrpc.PolicyUpdateRequest") proto.RegisterType((*PolicyUpdateResponse)(nil), "lnrpc.PolicyUpdateResponse") + proto.RegisterType((*ForwardingHistoryRequest)(nil), "lnrpc.ForwardingHistoryRequest") + proto.RegisterType((*ForwardingEvent)(nil), "lnrpc.ForwardingEvent") + proto.RegisterType((*ForwardingHistoryResponse)(nil), "lnrpc.ForwardingHistoryResponse") proto.RegisterEnum("lnrpc.NewAddressRequest_AddressType", NewAddressRequest_AddressType_name, NewAddressRequest_AddressType_value) } @@ -4321,6 +4486,18 @@ type LightningClient interface { // UpdateChannelPolicy allows the caller to update the fee schedule and // channel policies for all channels globally, or a particular channel. UpdateChannelPolicy(ctx context.Context, in *PolicyUpdateRequest, opts ...grpc.CallOption) (*PolicyUpdateResponse, error) + // * lncli: `forwardinghistory` + // ForwardingHistory allows the caller to query the htlcswitch for a record of + // all HTLC's forwarded within the target time range, and integer offset + // within that time range. If no time-range is specified, then the first chunk + // of the past 24 hrs of forwarding history are returned. + // + // A list of forwarding events are returned. The size of each forwarding event + // is 40 bytes, and the max message size able to be returned in gRPC is 4 MiB. + // As a result each message can only contain 50k entries. Each response has + // the index offset of the last entry. The index offset can be provided to the + // request to allow the caller to skip a series of records. + ForwardingHistory(ctx context.Context, in *ForwardingHistoryRequest, opts ...grpc.CallOption) (*ForwardingHistoryResponse, error) } type lightningClient struct { @@ -4810,6 +4987,15 @@ func (c *lightningClient) UpdateChannelPolicy(ctx context.Context, in *PolicyUpd return out, nil } +func (c *lightningClient) ForwardingHistory(ctx context.Context, in *ForwardingHistoryRequest, opts ...grpc.CallOption) (*ForwardingHistoryResponse, error) { + out := new(ForwardingHistoryResponse) + err := grpc.Invoke(ctx, "/lnrpc.Lightning/ForwardingHistory", in, out, c.cc, opts...) + if err != nil { + return nil, err + } + return out, nil +} + // Server API for Lightning service type LightningServer interface { @@ -5008,6 +5194,18 @@ type LightningServer interface { // UpdateChannelPolicy allows the caller to update the fee schedule and // channel policies for all channels globally, or a particular channel. UpdateChannelPolicy(context.Context, *PolicyUpdateRequest) (*PolicyUpdateResponse, error) + // * lncli: `forwardinghistory` + // ForwardingHistory allows the caller to query the htlcswitch for a record of + // all HTLC's forwarded within the target time range, and integer offset + // within that time range. If no time-range is specified, then the first chunk + // of the past 24 hrs of forwarding history are returned. + // + // A list of forwarding events are returned. The size of each forwarding event + // is 40 bytes, and the max message size able to be returned in gRPC is 4 MiB. + // As a result each message can only contain 50k entries. Each response has + // the index offset of the last entry. The index offset can be provided to the + // request to allow the caller to skip a series of records. + ForwardingHistory(context.Context, *ForwardingHistoryRequest) (*ForwardingHistoryResponse, error) } func RegisterLightningServer(s *grpc.Server, srv LightningServer) { @@ -5721,6 +5919,24 @@ func _Lightning_UpdateChannelPolicy_Handler(srv interface{}, ctx context.Context return interceptor(ctx, in, info, handler) } +func _Lightning_ForwardingHistory_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(ForwardingHistoryRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(LightningServer).ForwardingHistory(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/lnrpc.Lightning/ForwardingHistory", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(LightningServer).ForwardingHistory(ctx, req.(*ForwardingHistoryRequest)) + } + return interceptor(ctx, in, info, handler) +} + var _Lightning_serviceDesc = grpc.ServiceDesc{ ServiceName: "lnrpc.Lightning", HandlerType: (*LightningServer)(nil), @@ -5853,6 +6069,10 @@ var _Lightning_serviceDesc = grpc.ServiceDesc{ MethodName: "UpdateChannelPolicy", Handler: _Lightning_UpdateChannelPolicy_Handler, }, + { + MethodName: "ForwardingHistory", + Handler: _Lightning_ForwardingHistory_Handler, + }, }, Streams: []grpc.StreamDesc{ { @@ -5893,322 +6113,337 @@ var _Lightning_serviceDesc = grpc.ServiceDesc{ func init() { proto.RegisterFile("rpc.proto", fileDescriptor0) } var fileDescriptor0 = []byte{ - // 5069 bytes of a gzipped FileDescriptorProto - 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xbc, 0x5b, 0xcd, 0x93, 0x1c, 0xc9, - 0x55, 0x57, 0xf5, 0xf4, 0x7c, 0xf4, 0xeb, 0x8f, 0x99, 0xc9, 0x19, 0xcd, 0xb4, 0x7a, 0x65, 0x59, - 0x5b, 0x6c, 0xac, 0x84, 0x58, 0x34, 0xda, 0xb1, 0xbd, 0xac, 0x57, 0x60, 0x87, 0xbe, 0x56, 0xb3, - 0xf6, 0xac, 0x3c, 0xae, 0x91, 0xbc, 0xe0, 0x05, 0xda, 0x35, 0xdd, 0x39, 0x3d, 0x65, 0x55, 0x57, - 0xd5, 0x56, 0x55, 0xcf, 0xa8, 0x77, 0x51, 0x04, 0x1f, 0x11, 0x9c, 0x20, 0x38, 0x40, 0x04, 0x61, - 0x08, 0x73, 0xc0, 0x17, 0x38, 0xf0, 0x07, 0x10, 0x8e, 0x80, 0xbb, 0x23, 0x08, 0x0e, 0x3e, 0x11, - 0x1c, 0xe1, 0x64, 0x8e, 0x04, 0x17, 0x4e, 0xc4, 0x7b, 0xf9, 0x51, 0x99, 0x55, 0x35, 0x92, 0x6c, - 0x03, 0xb7, 0xce, 0x5f, 0x66, 0xbd, 0xcc, 0x7c, 0xf9, 0xf2, 0x7d, 0xe4, 0x7b, 0x0d, 0xad, 0x34, - 0x19, 0xdd, 0x4c, 0xd2, 0x38, 0x8f, 0xd9, 0x62, 0x18, 0xa5, 0xc9, 0x68, 0x70, 0x79, 0x12, 0xc7, - 0x93, 0x90, 0xef, 0xf8, 0x49, 0xb0, 0xe3, 0x47, 0x51, 0x9c, 0xfb, 0x79, 0x10, 0x47, 0x99, 0x18, - 0xe4, 0x7e, 0x07, 0x7a, 0x0f, 0x79, 0x74, 0xc8, 0xf9, 0xd8, 0xe3, 0x9f, 0xcc, 0x78, 0x96, 0xb3, - 0x5f, 0x82, 0x75, 0x9f, 0x7f, 0xca, 0xf9, 0x78, 0x98, 0xf8, 0x59, 0x96, 0x9c, 0xa4, 0x7e, 0xc6, - 0xfb, 0xce, 0x55, 0xe7, 0x7a, 0xc7, 0x5b, 0x13, 0x1d, 0x07, 0x1a, 0x67, 0xaf, 0x43, 0x27, 0xc3, - 0xa1, 0x3c, 0xca, 0xd3, 0x38, 0x99, 0xf7, 0x1b, 0x34, 0xae, 0x8d, 0xd8, 0x03, 0x01, 0xb9, 0x21, - 0xac, 0xea, 0x19, 0xb2, 0x24, 0x8e, 0x32, 0xce, 0x6e, 0xc1, 0xe6, 0x28, 0x48, 0x4e, 0x78, 0x3a, - 0xa4, 0x8f, 0xa7, 0x11, 0x9f, 0xc6, 0x51, 0x30, 0xea, 0x3b, 0x57, 0x17, 0xae, 0xb7, 0x3c, 0x26, - 0xfa, 0xf0, 0x8b, 0x0f, 0x65, 0x0f, 0xbb, 0x06, 0xab, 0x3c, 0x12, 0x38, 0x1f, 0xd3, 0x57, 0x72, - 0xaa, 0x5e, 0x01, 0xe3, 0x07, 0xee, 0x5f, 0x3a, 0xb0, 0xfe, 0x41, 0x14, 0xe4, 0x1f, 0xf9, 0x61, - 0xc8, 0x73, 0xb5, 0xa7, 0x6b, 0xb0, 0x7a, 0x46, 0x00, 0xed, 0xe9, 0x2c, 0x4e, 0xc7, 0x72, 0x47, - 0x3d, 0x01, 0x1f, 0x48, 0xf4, 0xdc, 0x95, 0x35, 0xce, 0x5d, 0x59, 0x2d, 0xbb, 0x16, 0xea, 0xd9, - 0xe5, 0x6e, 0x02, 0x33, 0x17, 0x27, 0xd8, 0xe1, 0x7e, 0x05, 0x36, 0x9e, 0x44, 0x61, 0x3c, 0x7a, - 0xfa, 0xb3, 0x2d, 0xda, 0xdd, 0x82, 0x4d, 0xfb, 0x7b, 0x49, 0xf7, 0x7b, 0x0d, 0x68, 0x3f, 0x4e, - 0xfd, 0x28, 0xf3, 0x47, 0x78, 0xe4, 0xac, 0x0f, 0xcb, 0xf9, 0xb3, 0xe1, 0x89, 0x9f, 0x9d, 0x10, - 0xa1, 0x96, 0xa7, 0x9a, 0x6c, 0x0b, 0x96, 0xfc, 0x69, 0x3c, 0x8b, 0x72, 0xe2, 0xea, 0x82, 0x27, - 0x5b, 0xec, 0x2d, 0x58, 0x8f, 0x66, 0xd3, 0xe1, 0x28, 0x8e, 0x8e, 0x83, 0x74, 0x2a, 0x04, 0x87, - 0x36, 0xb7, 0xe8, 0x55, 0x3b, 0xd8, 0x15, 0x80, 0x23, 0x5c, 0x86, 0x98, 0xa2, 0x49, 0x53, 0x18, - 0x08, 0x73, 0xa1, 0x23, 0x5b, 0x3c, 0x98, 0x9c, 0xe4, 0xfd, 0x45, 0x22, 0x64, 0x61, 0x48, 0x23, - 0x0f, 0xa6, 0x7c, 0x98, 0xe5, 0xfe, 0x34, 0xe9, 0x2f, 0xd1, 0x6a, 0x0c, 0x84, 0xfa, 0xe3, 0xdc, - 0x0f, 0x87, 0xc7, 0x9c, 0x67, 0xfd, 0x65, 0xd9, 0xaf, 0x11, 0xf6, 0x26, 0xf4, 0xc6, 0x3c, 0xcb, - 0x87, 0xfe, 0x78, 0x9c, 0xf2, 0x2c, 0xe3, 0x59, 0x7f, 0x85, 0x8e, 0xae, 0x84, 0xba, 0x7d, 0xd8, - 0x7a, 0xc8, 0x73, 0x83, 0x3b, 0x99, 0x64, 0xbb, 0xbb, 0x0f, 0xcc, 0x80, 0xef, 0xf3, 0xdc, 0x0f, - 0xc2, 0x8c, 0xbd, 0x03, 0x9d, 0xdc, 0x18, 0x4c, 0xa2, 0xda, 0xde, 0x65, 0x37, 0xe9, 0x8e, 0xdd, - 0x34, 0x3e, 0xf0, 0xac, 0x71, 0xee, 0x7f, 0x3b, 0xd0, 0x3e, 0xe4, 0x91, 0xbe, 0x5d, 0x0c, 0x9a, - 0xb8, 0x12, 0x79, 0x92, 0xf4, 0x9b, 0x7d, 0x1e, 0xda, 0xb4, 0xba, 0x2c, 0x4f, 0x83, 0x68, 0x42, - 0x47, 0xd0, 0xf2, 0x00, 0xa1, 0x43, 0x42, 0xd8, 0x1a, 0x2c, 0xf8, 0xd3, 0x9c, 0x18, 0xbf, 0xe0, - 0xe1, 0x4f, 0xbc, 0x77, 0x89, 0x3f, 0x9f, 0xf2, 0x28, 0x2f, 0x98, 0xdd, 0xf1, 0xda, 0x12, 0xdb, - 0x43, 0x6e, 0xdf, 0x84, 0x0d, 0x73, 0x88, 0xa2, 0xbe, 0x48, 0xd4, 0xd7, 0x8d, 0x91, 0x72, 0x92, - 0x6b, 0xb0, 0xaa, 0xc6, 0xa7, 0x62, 0xb1, 0xc4, 0xfe, 0x96, 0xd7, 0x93, 0xb0, 0xda, 0xc2, 0x75, - 0x58, 0x3b, 0x0e, 0x22, 0x3f, 0x1c, 0x8e, 0xc2, 0xfc, 0x74, 0x38, 0xe6, 0x61, 0xee, 0xd3, 0x41, - 0x2c, 0x7a, 0x3d, 0xc2, 0xef, 0x85, 0xf9, 0xe9, 0x7d, 0x44, 0xdd, 0x3f, 0x73, 0xa0, 0x23, 0x36, - 0x2f, 0x2f, 0xfe, 0x1b, 0xd0, 0x55, 0x73, 0xf0, 0x34, 0x8d, 0x53, 0x29, 0x87, 0x36, 0xc8, 0x6e, - 0xc0, 0x9a, 0x02, 0x92, 0x94, 0x07, 0x53, 0x7f, 0xc2, 0xe5, 0x6d, 0xaf, 0xe0, 0x6c, 0xb7, 0xa0, - 0x98, 0xc6, 0xb3, 0x5c, 0x5c, 0xbd, 0xf6, 0x6e, 0x47, 0x1e, 0x8c, 0x87, 0x98, 0x67, 0x0f, 0x71, - 0xff, 0xda, 0x81, 0xce, 0xbd, 0x13, 0x3f, 0x8a, 0x78, 0x78, 0x10, 0x07, 0x51, 0xce, 0x6e, 0x01, - 0x3b, 0x9e, 0x45, 0xe3, 0x20, 0x9a, 0x0c, 0xf3, 0x67, 0xc1, 0x78, 0x78, 0x34, 0xcf, 0x79, 0x26, - 0x8e, 0x68, 0xef, 0x82, 0x57, 0xd3, 0xc7, 0xde, 0x82, 0x35, 0x0b, 0xcd, 0xf2, 0x54, 0x9c, 0xdb, - 0xde, 0x05, 0xaf, 0xd2, 0x83, 0x82, 0x1f, 0xcf, 0xf2, 0x64, 0x96, 0x0f, 0x83, 0x68, 0xcc, 0x9f, - 0xd1, 0x1a, 0xbb, 0x9e, 0x85, 0xdd, 0xed, 0x41, 0xc7, 0xfc, 0xce, 0xfd, 0x0a, 0xac, 0xed, 0xe3, - 0x8d, 0x88, 0x82, 0x68, 0x72, 0x47, 0x88, 0x2d, 0x5e, 0xd3, 0x64, 0x76, 0xf4, 0x94, 0xcf, 0x25, - 0xdf, 0x64, 0x0b, 0x85, 0xea, 0x24, 0xce, 0x72, 0x29, 0x39, 0xf4, 0xdb, 0xfd, 0x37, 0x07, 0x56, - 0x91, 0xf7, 0x1f, 0xfa, 0xd1, 0x5c, 0x9d, 0xdc, 0x3e, 0x74, 0x90, 0xd4, 0xe3, 0xf8, 0x8e, 0xb8, - 0xec, 0x42, 0x88, 0xaf, 0x4b, 0x5e, 0x95, 0x46, 0xdf, 0x34, 0x87, 0xa2, 0x32, 0x9f, 0x7b, 0xd6, - 0xd7, 0x28, 0xb6, 0xb9, 0x9f, 0x4e, 0x78, 0x4e, 0x6a, 0x40, 0xaa, 0x05, 0x10, 0xd0, 0xbd, 0x38, - 0x3a, 0x66, 0x57, 0xa1, 0x93, 0xf9, 0xf9, 0x30, 0xe1, 0x29, 0x71, 0x8d, 0x44, 0x6f, 0xc1, 0x83, - 0xcc, 0xcf, 0x0f, 0x78, 0x7a, 0x77, 0x9e, 0xf3, 0xc1, 0x57, 0x61, 0xbd, 0x32, 0x0b, 0x4a, 0x7b, - 0xb1, 0x45, 0xfc, 0xc9, 0x36, 0x61, 0xf1, 0xd4, 0x0f, 0x67, 0x5c, 0x6a, 0x27, 0xd1, 0x78, 0xaf, - 0xf1, 0xae, 0xe3, 0xbe, 0x09, 0x6b, 0xc5, 0xb2, 0xa5, 0x90, 0x31, 0x68, 0x22, 0x07, 0x25, 0x01, - 0xfa, 0xed, 0xfe, 0x9e, 0x23, 0x06, 0xde, 0x8b, 0x03, 0x7d, 0xd3, 0x71, 0x20, 0x2a, 0x04, 0x35, - 0x10, 0x7f, 0x9f, 0xab, 0x09, 0x7f, 0xfe, 0xcd, 0xba, 0xd7, 0x60, 0xdd, 0x58, 0xc2, 0x0b, 0x16, - 0xfb, 0x57, 0x0e, 0xac, 0x3f, 0xe2, 0x67, 0xf2, 0xd4, 0xd5, 0x6a, 0xdf, 0x85, 0x66, 0x3e, 0x4f, - 0x84, 0x29, 0xee, 0xed, 0xbe, 0x21, 0x0f, 0xad, 0x32, 0xee, 0xa6, 0x6c, 0x3e, 0x9e, 0x27, 0xdc, - 0xa3, 0x2f, 0xdc, 0x6f, 0x40, 0xdb, 0x00, 0xd9, 0x36, 0x6c, 0x7c, 0xf4, 0xc1, 0xe3, 0x47, 0x0f, - 0x0e, 0x0f, 0x87, 0x07, 0x4f, 0xee, 0x7e, 0xfd, 0xc1, 0x6f, 0x0c, 0xf7, 0xee, 0x1c, 0xee, 0xad, - 0x5d, 0x60, 0x5b, 0xc0, 0x1e, 0x3d, 0x38, 0x7c, 0xfc, 0xe0, 0xbe, 0x85, 0x3b, 0x6c, 0x15, 0xda, - 0x26, 0xd0, 0x70, 0x07, 0xd0, 0x7f, 0xc4, 0xcf, 0x3e, 0x0a, 0xf2, 0x88, 0x67, 0x99, 0x3d, 0xbd, - 0x7b, 0x13, 0x98, 0xb9, 0x26, 0xb9, 0xcd, 0x3e, 0x2c, 0x4b, 0xdd, 0xab, 0x4c, 0x8f, 0x6c, 0xba, - 0x6f, 0x02, 0x3b, 0x0c, 0x26, 0xd1, 0x87, 0x3c, 0xcb, 0xfc, 0x09, 0x57, 0x9b, 0x5d, 0x83, 0x85, - 0x69, 0x36, 0x91, 0x5a, 0x12, 0x7f, 0xba, 0x5f, 0x80, 0x0d, 0x6b, 0x9c, 0x24, 0x7c, 0x19, 0x5a, - 0x59, 0x30, 0x89, 0xfc, 0x7c, 0x96, 0x72, 0x49, 0xba, 0x00, 0xdc, 0xf7, 0x61, 0xf3, 0x5b, 0x3c, - 0x0d, 0x8e, 0xe7, 0x2f, 0x23, 0x6f, 0xd3, 0x69, 0x94, 0xe9, 0x3c, 0x80, 0x8b, 0x25, 0x3a, 0x72, - 0x7a, 0x21, 0x99, 0xf2, 0xfc, 0x56, 0x3c, 0xd1, 0x30, 0xee, 0x69, 0xc3, 0xbc, 0xa7, 0xee, 0x13, - 0x60, 0xf7, 0xe2, 0x28, 0xe2, 0xa3, 0xfc, 0x80, 0xf3, 0xb4, 0x70, 0xb8, 0x0a, 0x31, 0x6c, 0xef, - 0x6e, 0xcb, 0x83, 0x2d, 0x5f, 0x7e, 0x29, 0x9f, 0x0c, 0x9a, 0x09, 0x4f, 0xa7, 0x44, 0x78, 0xc5, - 0xa3, 0xdf, 0xee, 0x45, 0xd8, 0xb0, 0xc8, 0x4a, 0xf3, 0xff, 0x36, 0x5c, 0xbc, 0x1f, 0x64, 0xa3, - 0xea, 0x84, 0x7d, 0x58, 0x4e, 0x66, 0x47, 0xc3, 0xe2, 0x92, 0xa9, 0x26, 0x5a, 0xc5, 0xf2, 0x27, - 0x92, 0xd8, 0x1f, 0x3a, 0xd0, 0xdc, 0x7b, 0xbc, 0x7f, 0x8f, 0x0d, 0x60, 0x25, 0x88, 0x46, 0xf1, - 0x14, 0x6d, 0x89, 0xd8, 0xb4, 0x6e, 0x9f, 0x7b, 0x79, 0x2e, 0x43, 0x8b, 0x4c, 0x10, 0x1a, 0x7a, - 0xe9, 0x1b, 0x15, 0x00, 0x3a, 0x19, 0xfc, 0x59, 0x12, 0xa4, 0xe4, 0x45, 0x28, 0xdf, 0xa0, 0x49, - 0x2a, 0xb2, 0xda, 0xe1, 0xfe, 0xa4, 0x09, 0xdd, 0x3b, 0xa3, 0x3c, 0x38, 0xe5, 0x52, 0x85, 0xd3, - 0xac, 0x04, 0xc8, 0xf5, 0xc8, 0x16, 0x1a, 0x9b, 0x94, 0x4f, 0xe3, 0x9c, 0x0f, 0xad, 0xc3, 0xb0, - 0x41, 0x1c, 0x35, 0x12, 0x84, 0x86, 0x09, 0x1a, 0x03, 0x5a, 0x5f, 0xcb, 0xb3, 0x41, 0x64, 0x19, - 0x02, 0xc3, 0x60, 0x4c, 0x2b, 0x6b, 0x7a, 0xaa, 0x89, 0xfc, 0x18, 0xf9, 0x89, 0x3f, 0x0a, 0xf2, - 0xb9, 0xbc, 0xf3, 0xba, 0x8d, 0xb4, 0xc3, 0x78, 0xe4, 0x87, 0xc3, 0x23, 0x3f, 0xf4, 0xa3, 0x11, - 0x97, 0xfe, 0x8c, 0x0d, 0xa2, 0xcb, 0x22, 0x97, 0xa4, 0x86, 0x09, 0xb7, 0xa6, 0x84, 0xa2, 0xeb, - 0x33, 0x8a, 0xa7, 0xd3, 0x20, 0x47, 0x4f, 0xa7, 0xbf, 0x22, 0xf4, 0x4b, 0x81, 0xd0, 0x4e, 0x44, - 0xeb, 0x4c, 0xf0, 0xb0, 0x25, 0x66, 0xb3, 0x40, 0xa4, 0x72, 0xcc, 0x39, 0xe9, 0xa9, 0xa7, 0x67, - 0x7d, 0x10, 0x54, 0x0a, 0x04, 0x4f, 0x63, 0x16, 0x65, 0x3c, 0xcf, 0x43, 0x3e, 0xd6, 0x0b, 0x6a, - 0xd3, 0xb0, 0x6a, 0x07, 0xbb, 0x05, 0x1b, 0xc2, 0xf9, 0xca, 0xfc, 0x3c, 0xce, 0x4e, 0x82, 0x6c, - 0x98, 0xf1, 0x28, 0xef, 0x77, 0x68, 0x7c, 0x5d, 0x17, 0x7b, 0x17, 0xb6, 0x4b, 0x70, 0xca, 0x47, - 0x3c, 0x38, 0xe5, 0xe3, 0x7e, 0x97, 0xbe, 0x3a, 0xaf, 0x9b, 0x5d, 0x85, 0x36, 0xfa, 0x9c, 0xb3, - 0x64, 0xec, 0xa3, 0x79, 0xee, 0xd1, 0x39, 0x98, 0x10, 0x7b, 0x1b, 0xba, 0x09, 0x17, 0x36, 0xf4, - 0x24, 0x0f, 0x47, 0x59, 0x7f, 0x95, 0x0c, 0x5c, 0x5b, 0x5e, 0x29, 0x94, 0x5f, 0xcf, 0x1e, 0x81, - 0xa2, 0x39, 0xca, 0xc8, 0x8b, 0xf1, 0xe7, 0xfd, 0x35, 0x12, 0xba, 0x02, 0xc0, 0x9b, 0xb5, 0x1f, - 0x64, 0xb9, 0x94, 0x34, 0xad, 0xe3, 0xf6, 0x60, 0xd3, 0x86, 0x75, 0x5c, 0xb3, 0x22, 0xc5, 0x26, - 0xeb, 0xb7, 0x69, 0xea, 0x4d, 0x39, 0xb5, 0x25, 0xb1, 0x9e, 0x1e, 0xe5, 0xfe, 0xc4, 0x81, 0x26, - 0xde, 0xb3, 0xf3, 0xef, 0xa4, 0xa9, 0x3a, 0x17, 0x2c, 0xd5, 0x49, 0xfe, 0x36, 0x7a, 0x23, 0x82, - 0xe7, 0x42, 0x2e, 0x0d, 0xa4, 0xe8, 0x4f, 0xf9, 0xe8, 0x94, 0x84, 0x53, 0xf7, 0x23, 0x82, 0xa2, - 0x8b, 0x26, 0x8b, 0xbe, 0x16, 0x92, 0xa9, 0xdb, 0xaa, 0x8f, 0xbe, 0x5c, 0x2e, 0xfa, 0xe8, 0xbb, - 0x3e, 0x2c, 0x07, 0xd1, 0x51, 0x3c, 0x8b, 0xc6, 0x24, 0x85, 0x2b, 0x9e, 0x6a, 0x22, 0x37, 0x13, - 0xf2, 0x60, 0x82, 0x29, 0x97, 0xe2, 0x57, 0x00, 0x2e, 0x43, 0x97, 0x26, 0x23, 0xbd, 0xa2, 0x59, - 0xf9, 0x0e, 0xac, 0x1b, 0x98, 0xe4, 0xe3, 0xeb, 0xb0, 0x98, 0x20, 0x20, 0x1d, 0x14, 0x75, 0x7e, - 0xa4, 0x90, 0x44, 0x8f, 0xbb, 0x86, 0x71, 0x6b, 0xfe, 0x41, 0x74, 0x1c, 0x2b, 0x4a, 0xff, 0xb8, - 0x80, 0x81, 0xa6, 0x84, 0x24, 0xa1, 0xeb, 0xb0, 0x1a, 0x8c, 0x79, 0x94, 0x07, 0xf9, 0x7c, 0x68, - 0x79, 0x4e, 0x65, 0x18, 0x15, 0xb9, 0x1f, 0x06, 0x7e, 0x26, 0x95, 0x84, 0x68, 0xb0, 0x5d, 0xd8, - 0x44, 0xf9, 0x52, 0x22, 0xa3, 0x0f, 0x57, 0x38, 0x70, 0xb5, 0x7d, 0x78, 0x25, 0x10, 0x17, 0x4a, - 0xa8, 0xf8, 0x44, 0x28, 0xb4, 0xba, 0x2e, 0xe4, 0x9a, 0xa0, 0x84, 0x5b, 0x5e, 0x14, 0x32, 0xa8, - 0x81, 0x4a, 0xd4, 0xb4, 0x24, 0x9c, 0xc7, 0x72, 0xd4, 0x64, 0x44, 0x5e, 0x2b, 0x95, 0xc8, 0xeb, - 0x3a, 0xac, 0x66, 0xf3, 0x68, 0xc4, 0xc7, 0xc3, 0x3c, 0xc6, 0x79, 0x83, 0x88, 0x4e, 0x67, 0xc5, - 0x2b, 0xc3, 0x14, 0x23, 0xf2, 0x2c, 0x8f, 0x78, 0x4e, 0xba, 0x61, 0xc5, 0x53, 0x4d, 0x54, 0xb3, - 0x34, 0x44, 0x88, 0x76, 0xcb, 0x93, 0x2d, 0xb4, 0x48, 0xb3, 0x34, 0xc8, 0xfa, 0x1d, 0x42, 0xe9, - 0x37, 0xfb, 0x22, 0x5c, 0x3c, 0xc2, 0x88, 0xe6, 0x84, 0xfb, 0x63, 0x9e, 0xd2, 0xe9, 0x8b, 0x80, - 0x4e, 0x5c, 0xf1, 0xfa, 0x4e, 0xf7, 0x53, 0x32, 0x8f, 0x3a, 0xa0, 0x7c, 0x42, 0xb7, 0x9a, 0xbd, - 0x06, 0x2d, 0xb1, 0x93, 0xec, 0xc4, 0x97, 0x16, 0x7b, 0x85, 0x80, 0xc3, 0x13, 0x1f, 0xe3, 0x20, - 0x8b, 0x39, 0x0d, 0xf2, 0xcb, 0xda, 0x84, 0xed, 0x09, 0xde, 0xbc, 0x01, 0x3d, 0x15, 0xaa, 0x66, - 0xc3, 0x90, 0x1f, 0xe7, 0xca, 0xfd, 0x8e, 0x66, 0x53, 0x9c, 0x2e, 0xdb, 0xe7, 0xc7, 0xb9, 0xfb, - 0x08, 0xd6, 0xe5, 0xed, 0xfc, 0x46, 0xc2, 0xd5, 0xd4, 0x5f, 0x2e, 0xdb, 0x06, 0x61, 0xa2, 0x37, - 0xa4, 0x3c, 0x9a, 0x31, 0x44, 0xc9, 0x60, 0xb8, 0x1e, 0x30, 0xd9, 0x7d, 0x2f, 0x8c, 0x33, 0x2e, - 0x09, 0xba, 0xd0, 0x19, 0x85, 0x71, 0xa6, 0x9c, 0x7c, 0xb9, 0x1d, 0x0b, 0xc3, 0x13, 0xc8, 0x66, - 0xa3, 0x11, 0xde, 0x77, 0x61, 0xe4, 0x55, 0xd3, 0xfd, 0x1b, 0x07, 0x36, 0x88, 0x9a, 0xd2, 0x23, - 0xda, 0x33, 0x7c, 0xf5, 0x65, 0x76, 0x46, 0x66, 0xe0, 0xb3, 0x09, 0x8b, 0xc7, 0x71, 0x3a, 0xe2, - 0x72, 0x26, 0xd1, 0xf8, 0xe9, 0x7d, 0xdd, 0x66, 0xc5, 0xd7, 0xfd, 0x17, 0x07, 0xd6, 0x69, 0xa9, - 0x87, 0xb9, 0x9f, 0xcf, 0x32, 0xb9, 0xfd, 0x5f, 0x85, 0x2e, 0x6e, 0x95, 0xab, 0x4b, 0x23, 0x17, - 0xba, 0xa9, 0xef, 0x37, 0xa1, 0x62, 0xf0, 0xde, 0x05, 0xcf, 0x1e, 0xcc, 0xbe, 0x0a, 0x1d, 0xf3, - 0xbd, 0x81, 0xd6, 0xdc, 0xde, 0xbd, 0xa4, 0x76, 0x59, 0x91, 0x9c, 0xbd, 0x0b, 0x9e, 0xf5, 0x01, - 0xbb, 0x0d, 0x40, 0x56, 0x9b, 0xc8, 0xca, 0x40, 0xf1, 0x92, 0xcd, 0x24, 0xe3, 0xb0, 0xf6, 0x2e, - 0x78, 0xc6, 0xf0, 0xbb, 0x2b, 0xb0, 0x24, 0xcc, 0x8c, 0xfb, 0x10, 0xba, 0xd6, 0x4a, 0x2d, 0x1f, - 0xbe, 0x23, 0x7c, 0xf8, 0x4a, 0xc8, 0xd7, 0xa8, 0x86, 0x7c, 0xee, 0xdf, 0x37, 0x80, 0xa1, 0xb4, - 0x95, 0x8e, 0x13, 0xed, 0x5c, 0x3c, 0xb6, 0xbc, 0x96, 0x8e, 0x67, 0x42, 0xec, 0x26, 0x30, 0xa3, - 0xa9, 0x22, 0x7b, 0x61, 0x1d, 0x6a, 0x7a, 0x50, 0x8d, 0x09, 0x97, 0x43, 0x45, 0x98, 0xd2, 0x4b, - 0x13, 0xe7, 0x56, 0xdb, 0x87, 0x06, 0x20, 0x99, 0x65, 0x27, 0x68, 0x87, 0x95, 0x5f, 0xa3, 0xda, - 0x65, 0x01, 0x59, 0x7a, 0xa9, 0x80, 0x2c, 0x97, 0x05, 0x84, 0xec, 0x5d, 0x1a, 0x9c, 0xfa, 0x39, - 0x57, 0x36, 0x44, 0x36, 0xd1, 0x8d, 0x99, 0x06, 0x11, 0x99, 0xe7, 0xe1, 0x14, 0x67, 0x97, 0x6e, - 0x8c, 0x05, 0xba, 0x3f, 0x76, 0x60, 0x0d, 0x79, 0x67, 0xc9, 0xd7, 0x7b, 0x40, 0xe2, 0xfd, 0x8a, - 0xe2, 0x65, 0x8d, 0xfd, 0xf9, 0xa5, 0xeb, 0x5d, 0x68, 0x11, 0xc1, 0x38, 0xe1, 0x91, 0x14, 0xae, - 0xbe, 0x2d, 0x5c, 0x85, 0x66, 0xd9, 0xbb, 0xe0, 0x15, 0x83, 0x0d, 0xd1, 0xfa, 0x67, 0x07, 0xda, - 0x72, 0x99, 0x3f, 0xb3, 0xb3, 0x3d, 0x80, 0x15, 0x94, 0x32, 0xc3, 0x97, 0xd5, 0x6d, 0xb4, 0x03, - 0x53, 0x8c, 0x68, 0xd0, 0xf0, 0x59, 0x8e, 0x76, 0x19, 0x46, 0x2b, 0x46, 0x4a, 0x34, 0x1b, 0xe6, - 0x41, 0x38, 0x54, 0xbd, 0xf2, 0xc9, 0xae, 0xae, 0x0b, 0x75, 0x49, 0x96, 0xfb, 0x13, 0x2e, 0x0d, - 0x94, 0x68, 0x60, 0x44, 0x21, 0x37, 0x54, 0x76, 0xa2, 0x7e, 0x04, 0xb0, 0x5d, 0xe9, 0xd2, 0x8e, - 0x94, 0xf4, 0x1d, 0xc3, 0x60, 0x7a, 0x14, 0x6b, 0x37, 0xd4, 0x31, 0xdd, 0x4a, 0xab, 0x8b, 0x4d, - 0xe0, 0xa2, 0xb2, 0xc4, 0xc8, 0xd3, 0xc2, 0xee, 0x36, 0xc8, 0x85, 0x78, 0xdb, 0x96, 0x81, 0xf2, - 0x84, 0x0a, 0x37, 0x6f, 0x63, 0x3d, 0x3d, 0x76, 0x02, 0x7d, 0x6d, 0xf2, 0xa5, 0xda, 0x36, 0xdc, - 0x02, 0x9c, 0xeb, 0xad, 0x97, 0xcc, 0x45, 0x3a, 0x66, 0xac, 0xa6, 0x39, 0x97, 0x1a, 0x9b, 0xc3, - 0x15, 0xd5, 0x47, 0x7a, 0xb9, 0x3a, 0x5f, 0xf3, 0x95, 0xf6, 0xf6, 0x3e, 0x7e, 0x6c, 0x4f, 0xfa, - 0x12, 0xc2, 0x83, 0x1f, 0x39, 0xd0, 0xb3, 0xc9, 0xa1, 0xe8, 0xc8, 0x78, 0x44, 0x29, 0x18, 0xe5, - 0x4a, 0x95, 0xe0, 0x6a, 0x44, 0xd5, 0xa8, 0x8b, 0xa8, 0xcc, 0xb8, 0x69, 0xe1, 0x65, 0x71, 0x53, - 0xf3, 0xd5, 0xe2, 0xa6, 0xc5, 0xba, 0xb8, 0x69, 0xf0, 0x5f, 0x0e, 0xb0, 0xea, 0xf9, 0xb2, 0x87, - 0x22, 0xa4, 0x8b, 0x78, 0x28, 0xf5, 0xc4, 0x2f, 0xbf, 0x9a, 0x8c, 0x28, 0x1e, 0xaa, 0xaf, 0x51, - 0x58, 0x4d, 0x45, 0x60, 0xba, 0x22, 0x5d, 0xaf, 0xae, 0xab, 0x14, 0xc9, 0x35, 0x5f, 0x1e, 0xc9, - 0x2d, 0xbe, 0x3c, 0x92, 0x5b, 0x2a, 0x47, 0x72, 0x83, 0xdf, 0x81, 0xae, 0x75, 0xea, 0xff, 0x7b, - 0x3b, 0x2e, 0xbb, 0x31, 0xe2, 0x80, 0x2d, 0x6c, 0xf0, 0x1f, 0x0d, 0x60, 0x55, 0xc9, 0xfb, 0x7f, - 0x5d, 0x03, 0xc9, 0x91, 0xa5, 0x40, 0x16, 0xa4, 0x1c, 0x59, 0xaa, 0xe3, 0xff, 0x52, 0x29, 0xbe, - 0x05, 0xeb, 0x29, 0x1f, 0xc5, 0xa7, 0x94, 0xb6, 0xb2, 0x5f, 0x01, 0xaa, 0x1d, 0xe8, 0xc8, 0xd9, - 0xf1, 0xeb, 0x8a, 0x95, 0x65, 0x30, 0x2c, 0x43, 0x29, 0x8c, 0x75, 0xbf, 0x0c, 0x9b, 0x22, 0xf9, - 0x73, 0x57, 0x90, 0x52, 0xbe, 0xc4, 0xeb, 0xd0, 0x39, 0x13, 0xcf, 0x74, 0xc3, 0x38, 0x0a, 0xe7, - 0xd2, 0x88, 0xb4, 0x25, 0xf6, 0x8d, 0x28, 0x9c, 0xbb, 0xdf, 0x77, 0xe0, 0x62, 0xe9, 0xdb, 0xe2, - 0xb5, 0x5e, 0xa8, 0x5a, 0x5b, 0xff, 0xda, 0x20, 0x6e, 0x51, 0xca, 0xb8, 0xb1, 0x45, 0x61, 0x92, - 0xaa, 0x1d, 0xc8, 0xc2, 0x59, 0x54, 0x1d, 0x2f, 0x0e, 0xa6, 0xae, 0xcb, 0xdd, 0x86, 0x8b, 0xf2, - 0xf0, 0xed, 0xbd, 0xb9, 0xbb, 0xb0, 0x55, 0xee, 0x28, 0x5e, 0x1b, 0xed, 0x25, 0xab, 0xa6, 0xfb, - 0xdb, 0xc0, 0xbe, 0x39, 0xe3, 0xe9, 0x9c, 0xf2, 0x02, 0xfa, 0x69, 0x75, 0xbb, 0x1c, 0x7c, 0x2f, - 0x25, 0xb3, 0xa3, 0xaf, 0xf3, 0xb9, 0x4a, 0xbc, 0x34, 0x8a, 0xc4, 0xcb, 0xe7, 0x00, 0x30, 0x9a, - 0xa0, 0x44, 0x82, 0x4a, 0x85, 0x61, 0xb0, 0x26, 0x08, 0xba, 0xb7, 0x61, 0xc3, 0xa2, 0xaf, 0x39, - 0xb9, 0x24, 0xbf, 0x10, 0x11, 0xad, 0x9d, 0x9e, 0x90, 0x7d, 0xee, 0x9f, 0x3b, 0xb0, 0xb0, 0x17, - 0x27, 0xe6, 0x63, 0x93, 0x63, 0x3f, 0x36, 0x49, 0xd5, 0x3a, 0xd4, 0x9a, 0xb3, 0x21, 0x15, 0x83, - 0x09, 0xa2, 0x62, 0xf4, 0xa7, 0x39, 0xc6, 0x74, 0xc7, 0x71, 0x7a, 0xe6, 0xa7, 0x63, 0xc9, 0xde, - 0x12, 0x8a, 0xbb, 0x2b, 0xf4, 0x0f, 0xfe, 0x44, 0x9f, 0x82, 0x5e, 0xdc, 0xe6, 0x32, 0x0c, 0x95, - 0x2d, 0xf7, 0x4f, 0x1c, 0x58, 0xa4, 0xb5, 0xe2, 0x65, 0x11, 0xc7, 0x4f, 0x39, 0x39, 0x7a, 0xd0, - 0x73, 0xc4, 0x65, 0x29, 0xc1, 0xa5, 0x4c, 0x5d, 0xa3, 0x92, 0xa9, 0xbb, 0x0c, 0x2d, 0xd1, 0x2a, - 0x52, 0x5b, 0x05, 0xc0, 0xae, 0x40, 0xf3, 0x24, 0x4e, 0x94, 0x89, 0x03, 0xf5, 0x82, 0x13, 0x27, - 0x1e, 0xe1, 0xee, 0x0d, 0x58, 0x7d, 0x14, 0x8f, 0xb9, 0xf1, 0x00, 0x70, 0xee, 0x29, 0xba, 0xbf, - 0xeb, 0xc0, 0x8a, 0x1a, 0xcc, 0xae, 0x43, 0x13, 0x2d, 0x55, 0xc9, 0x37, 0xd4, 0xaf, 0xad, 0x38, - 0xce, 0xa3, 0x11, 0xa8, 0x61, 0x28, 0x70, 0x2c, 0x3c, 0x09, 0x15, 0x36, 0x16, 0x36, 0xfa, 0x4d, - 0xe8, 0x89, 0x35, 0x97, 0x6c, 0x59, 0x09, 0x75, 0xff, 0xd6, 0x81, 0xae, 0x35, 0x07, 0x7a, 0xf9, - 0xa1, 0x9f, 0xe5, 0xf2, 0xed, 0x4a, 0x32, 0xd1, 0x84, 0xcc, 0x27, 0xa1, 0x86, 0xfd, 0x24, 0xa4, - 0x1f, 0x2b, 0x16, 0xcc, 0xc7, 0x8a, 0x5b, 0xd0, 0x2a, 0xb2, 0x9e, 0x4d, 0x4b, 0x73, 0xe0, 0x8c, - 0xea, 0x1d, 0xb9, 0x18, 0x84, 0x74, 0x46, 0x71, 0x18, 0xa7, 0x32, 0x29, 0x28, 0x1a, 0xee, 0x6d, - 0x68, 0x1b, 0xe3, 0x71, 0x19, 0x11, 0xcf, 0xcf, 0xe2, 0xf4, 0xa9, 0x7a, 0x99, 0x92, 0x4d, 0x9d, - 0x3f, 0x69, 0x14, 0xf9, 0x13, 0xf7, 0xef, 0x1c, 0xe8, 0xa2, 0xa4, 0x04, 0xd1, 0xe4, 0x20, 0x0e, - 0x83, 0xd1, 0x9c, 0x24, 0x46, 0x09, 0x85, 0xcc, 0x16, 0x2a, 0x89, 0xb1, 0x61, 0x74, 0x09, 0x94, - 0x93, 0x2f, 0xe5, 0x45, 0xb7, 0x51, 0xf2, 0xd1, 0xb4, 0x1d, 0xf9, 0x19, 0x17, 0x51, 0x81, 0x54, - 0xe5, 0x16, 0x88, 0xda, 0x05, 0x81, 0xd4, 0xcf, 0xf9, 0x70, 0x1a, 0x84, 0x61, 0x20, 0xc6, 0x0a, - 0x09, 0xaf, 0xeb, 0x72, 0x7f, 0xd8, 0x80, 0xb6, 0xd4, 0x22, 0x0f, 0xc6, 0x13, 0xf1, 0xc8, 0x2a, - 0xfd, 0x14, 0x7d, 0xfd, 0x0c, 0x44, 0xf5, 0x5b, 0x9e, 0x8d, 0x81, 0x94, 0x8f, 0x75, 0xa1, 0x7a, - 0xac, 0x97, 0xa1, 0x85, 0xe2, 0xf5, 0x36, 0xb9, 0x50, 0x22, 0x49, 0x5e, 0x00, 0xaa, 0x77, 0x97, - 0x7a, 0x17, 0x8b, 0x5e, 0x02, 0x2c, 0xa7, 0x69, 0xa9, 0xe4, 0x34, 0xbd, 0x0b, 0x1d, 0x49, 0x86, - 0xf8, 0x4e, 0x31, 0x57, 0x21, 0xe0, 0xd6, 0x99, 0x78, 0xd6, 0x48, 0xf5, 0xe5, 0xae, 0xfa, 0x72, - 0xe5, 0x65, 0x5f, 0xaa, 0x91, 0x94, 0x79, 0x10, 0xbc, 0x79, 0x98, 0xfa, 0xc9, 0x89, 0xd2, 0xcc, - 0x63, 0x9d, 0x5f, 0x25, 0x98, 0xdd, 0x80, 0x45, 0xfc, 0x4c, 0x69, 0xbf, 0xfa, 0x4b, 0x27, 0x86, - 0xb0, 0xeb, 0xb0, 0xc8, 0xc7, 0x13, 0xae, 0x1c, 0x77, 0x66, 0x87, 0x50, 0x78, 0x46, 0x9e, 0x18, - 0x80, 0x2a, 0x00, 0xd1, 0x92, 0x0a, 0xb0, 0x35, 0xe7, 0x12, 0x36, 0x3f, 0x18, 0xbb, 0x9b, 0xc0, - 0x1e, 0x09, 0xa9, 0x35, 0x9f, 0x0c, 0xff, 0x60, 0x01, 0xda, 0x06, 0x8c, 0xb7, 0x79, 0x82, 0x0b, - 0x1e, 0x8e, 0x03, 0x7f, 0xca, 0x73, 0x9e, 0x4a, 0x49, 0x2d, 0xa1, 0xa4, 0x60, 0x4f, 0x27, 0xc3, - 0x78, 0x96, 0x0f, 0xc7, 0x7c, 0x92, 0x72, 0x61, 0xef, 0x1c, 0xaf, 0x84, 0xe2, 0xb8, 0xa9, 0xff, - 0xcc, 0x1c, 0x27, 0xe4, 0xa1, 0x84, 0xaa, 0x07, 0x40, 0xc1, 0xa3, 0x66, 0xf1, 0x00, 0x28, 0x38, - 0x52, 0xd6, 0x43, 0x8b, 0x35, 0x7a, 0xe8, 0x1d, 0xd8, 0x12, 0x1a, 0x47, 0xde, 0xcd, 0x61, 0x49, - 0x4c, 0xce, 0xe9, 0x65, 0x37, 0x60, 0x0d, 0xd7, 0xac, 0x04, 0x3c, 0x0b, 0x3e, 0x15, 0xc1, 0xba, - 0xe3, 0x55, 0x70, 0x1c, 0x8b, 0xd7, 0xd1, 0x1a, 0x2b, 0xb2, 0x10, 0x15, 0x9c, 0xc6, 0xfa, 0xcf, - 0xec, 0xb1, 0x2d, 0x39, 0xb6, 0x84, 0xbb, 0x5d, 0x68, 0x1f, 0xe6, 0x71, 0xa2, 0x0e, 0xa5, 0x07, - 0x1d, 0xd1, 0x94, 0x99, 0xa7, 0xd7, 0xe0, 0x12, 0x49, 0xd1, 0xe3, 0x38, 0x89, 0xc3, 0x78, 0x32, - 0x3f, 0x9c, 0x1d, 0x65, 0xa3, 0x34, 0x48, 0xd0, 0xa1, 0x76, 0xff, 0xc9, 0x81, 0x0d, 0xab, 0x57, - 0xbe, 0x04, 0x7c, 0x51, 0x88, 0xb4, 0x4e, 0x16, 0x08, 0xc1, 0x5b, 0x37, 0xd4, 0xa1, 0x18, 0x28, - 0xde, 0x55, 0x9e, 0xc8, 0xfc, 0xc1, 0x1d, 0x58, 0x55, 0x2b, 0x53, 0x1f, 0x0a, 0x29, 0xec, 0x57, - 0xa5, 0x50, 0x7e, 0xdf, 0x93, 0x1f, 0x28, 0x12, 0xbf, 0x26, 0xdc, 0x52, 0x3e, 0xa6, 0x3d, 0xaa, - 0x90, 0x70, 0xa0, 0xbe, 0x37, 0x7d, 0x61, 0xb5, 0x82, 0x91, 0x06, 0x33, 0xf7, 0x8f, 0x1c, 0x80, - 0x62, 0x75, 0x28, 0x18, 0x85, 0x4a, 0x17, 0xd5, 0x51, 0x86, 0xfa, 0x7e, 0x1d, 0x3a, 0xfa, 0x19, - 0xbb, 0xb0, 0x12, 0x6d, 0x85, 0xa1, 0x03, 0x73, 0x0d, 0x56, 0x27, 0x61, 0x7c, 0x44, 0x36, 0x97, - 0x52, 0x99, 0x99, 0xcc, 0xbf, 0xf5, 0x04, 0xfc, 0xbe, 0x44, 0x0b, 0x93, 0xd2, 0x34, 0x4c, 0x8a, - 0xfb, 0xc7, 0x0d, 0xfd, 0x2c, 0x5a, 0xec, 0xf9, 0xdc, 0x5b, 0xc6, 0x76, 0x2b, 0xca, 0xf1, 0x9c, - 0x57, 0x48, 0x7a, 0xfc, 0x38, 0x78, 0x69, 0x1c, 0x78, 0x1b, 0x7a, 0xa9, 0xd0, 0x3e, 0x4a, 0x35, - 0x35, 0x5f, 0xa0, 0x9a, 0xba, 0xa9, 0x65, 0x77, 0x7e, 0x11, 0xd6, 0xfc, 0xf1, 0x29, 0x4f, 0xf3, - 0x80, 0x02, 0x02, 0x32, 0xfa, 0x42, 0xa1, 0xae, 0x1a, 0x38, 0xd9, 0xe2, 0x6b, 0xb0, 0x2a, 0x73, - 0x9e, 0x7a, 0xa4, 0x2c, 0x7d, 0x29, 0x60, 0x1c, 0xe8, 0xfe, 0x40, 0xbd, 0xc0, 0xda, 0x67, 0x78, - 0x3e, 0x47, 0xcc, 0xdd, 0x35, 0x4a, 0xbb, 0xfb, 0x05, 0xf9, 0x1a, 0x3a, 0x56, 0x51, 0x87, 0x7c, - 0x97, 0x16, 0xa0, 0x7c, 0xbd, 0xb6, 0x59, 0xda, 0x7c, 0x15, 0x96, 0xba, 0xdf, 0x5f, 0x80, 0xe5, - 0x0f, 0xa2, 0xd3, 0x38, 0x18, 0xd1, 0xdb, 0xe4, 0x94, 0x4f, 0x63, 0x55, 0x5f, 0x80, 0xbf, 0xd1, - 0xa2, 0x53, 0x52, 0x2d, 0xc9, 0xe5, 0xe3, 0xa2, 0x6a, 0xa2, 0x75, 0x4b, 0x8b, 0x9a, 0x1b, 0x21, - 0x29, 0x06, 0x82, 0xfe, 0x61, 0x6a, 0x16, 0x1c, 0xc9, 0x56, 0x51, 0xa0, 0xb1, 0x68, 0x14, 0x68, - 0xd0, 0x4b, 0xb6, 0xc8, 0x17, 0x12, 0x3b, 0x57, 0x3c, 0xd5, 0x24, 0x3f, 0x36, 0xe5, 0x22, 0x26, - 0x26, 0x3b, 0xb9, 0x2c, 0xfd, 0x58, 0x13, 0x44, 0x5b, 0x2a, 0x3e, 0x10, 0x63, 0x84, 0xae, 0x31, - 0x21, 0xf4, 0x2d, 0xca, 0x35, 0x4b, 0x2d, 0x71, 0xc4, 0x25, 0x18, 0x15, 0xd2, 0x98, 0x6b, 0xbd, - 0x21, 0xf6, 0x00, 0xa2, 0xa6, 0xa8, 0x8c, 0x1b, 0x5e, 0xb0, 0xc8, 0x7b, 0xca, 0x16, 0xf9, 0x20, - 0x7e, 0x18, 0x1e, 0xf9, 0xa3, 0xa7, 0x54, 0x49, 0x46, 0x69, 0xce, 0x96, 0x67, 0x83, 0xb8, 0x6a, - 0x2a, 0x8c, 0x92, 0x24, 0xba, 0x22, 0x4d, 0x69, 0x40, 0xee, 0xb7, 0x80, 0xdd, 0x19, 0x8f, 0xe5, - 0x09, 0xe9, 0x18, 0xa1, 0xe0, 0xad, 0x63, 0xf1, 0xb6, 0x66, 0x8f, 0x8d, 0xda, 0x3d, 0xba, 0x0f, - 0xa0, 0x7d, 0x60, 0x14, 0x80, 0xd1, 0x61, 0xaa, 0xd2, 0x2f, 0x29, 0x00, 0x06, 0x62, 0x4c, 0xd8, - 0x30, 0x27, 0x74, 0x7f, 0x05, 0xd8, 0x7e, 0x90, 0xe5, 0x7a, 0x7d, 0x3a, 0x92, 0xd4, 0x0f, 0x62, - 0x46, 0x24, 0x29, 0x31, 0x8a, 0x24, 0xef, 0x88, 0x6c, 0x69, 0x79, 0x63, 0x37, 0x60, 0x25, 0x10, - 0x90, 0xd2, 0xc3, 0x3d, 0x29, 0xc0, 0x6a, 0xa4, 0xee, 0x47, 0x87, 0x42, 0x82, 0x96, 0x9a, 0xff, - 0xa1, 0x03, 0xcb, 0x72, 0x6b, 0x68, 0x0e, 0xad, 0xd2, 0x37, 0xb1, 0x31, 0x0b, 0xab, 0x2f, 0x18, - 0xaa, 0x4a, 0xdd, 0x42, 0x9d, 0xd4, 0x31, 0x68, 0x26, 0x7e, 0x7e, 0x42, 0x1e, 0x74, 0xcb, 0xa3, - 0xdf, 0x2a, 0x52, 0x5a, 0x2c, 0x22, 0xa5, 0xba, 0x1a, 0x35, 0xa1, 0x33, 0x2a, 0xb8, 0xca, 0x22, - 0xcb, 0x0d, 0xe8, 0x07, 0xd0, 0xbb, 0x22, 0x8b, 0x5c, 0xc0, 0x05, 0xbf, 0x24, 0x89, 0x32, 0xbf, - 0xe4, 0x50, 0x4f, 0xf7, 0xbb, 0x03, 0xe8, 0xdf, 0xe7, 0x21, 0xcf, 0xf9, 0x9d, 0x30, 0x2c, 0xd3, - 0x7f, 0x0d, 0x2e, 0xd5, 0xf4, 0x49, 0xab, 0xfa, 0x3e, 0xac, 0xdf, 0xe7, 0x47, 0xb3, 0xc9, 0x3e, - 0x3f, 0x2d, 0x32, 0x0f, 0x0c, 0x9a, 0xd9, 0x49, 0x7c, 0x26, 0xcf, 0x96, 0x7e, 0x63, 0xc0, 0x1b, - 0xe2, 0x98, 0x61, 0x96, 0xf0, 0x91, 0xaa, 0x8c, 0x21, 0xe4, 0x30, 0xe1, 0x23, 0xf7, 0x1d, 0x60, - 0x26, 0x1d, 0xb9, 0x05, 0xbc, 0xb9, 0xb3, 0xa3, 0x61, 0x36, 0xcf, 0x72, 0x3e, 0x55, 0x25, 0x3f, - 0x26, 0xe4, 0x5e, 0x83, 0xce, 0x81, 0x3f, 0xf7, 0xf8, 0x27, 0xb2, 0xfa, 0x10, 0x83, 0x37, 0x7f, - 0x8e, 0xa2, 0xac, 0x83, 0x37, 0xea, 0x76, 0xff, 0xa1, 0x01, 0x4b, 0x62, 0x24, 0x52, 0x1d, 0xf3, - 0x2c, 0x0f, 0x22, 0xf1, 0x42, 0x2f, 0xa9, 0x1a, 0x50, 0x45, 0x36, 0x1a, 0x35, 0xb2, 0x21, 0xdd, - 0x29, 0x55, 0x5f, 0x20, 0x85, 0xc0, 0xc2, 0x28, 0x36, 0xd5, 0x39, 0xcb, 0xa6, 0x8c, 0x4d, 0x15, - 0x50, 0x8a, 0x92, 0x0b, 0xfd, 0x20, 0xd6, 0xa7, 0x84, 0x56, 0x8a, 0x83, 0x09, 0xd5, 0x6a, 0xa1, - 0x65, 0x21, 0x35, 0x15, 0x2d, 0x54, 0xd1, 0x36, 0x2b, 0xaf, 0xa0, 0x6d, 0x84, 0x8f, 0x65, 0x69, - 0x1b, 0x06, 0x6b, 0xef, 0x73, 0xee, 0xf1, 0x24, 0x4e, 0x55, 0x09, 0xa7, 0xfb, 0x3d, 0x07, 0xd6, - 0xa4, 0xf5, 0xd0, 0x7d, 0xec, 0x75, 0xcb, 0xd4, 0x38, 0x75, 0x8f, 0xb6, 0x6f, 0x40, 0x97, 0x82, - 0x2d, 0x8c, 0xa4, 0x28, 0xb2, 0x92, 0xef, 0x0f, 0x16, 0x88, 0x6b, 0x52, 0xcf, 0x90, 0xd3, 0x20, - 0x94, 0x0c, 0x36, 0x21, 0x34, 0x8b, 0x2a, 0x18, 0x23, 0xf6, 0x3a, 0x9e, 0x6e, 0xbb, 0x07, 0xb0, - 0x6e, 0xac, 0x57, 0x0a, 0xd4, 0x6d, 0x50, 0x89, 0x4b, 0xf1, 0x9c, 0x20, 0xee, 0xc5, 0xb6, 0x6d, - 0x08, 0x8b, 0xcf, 0xac, 0xc1, 0xee, 0xbf, 0x3a, 0xb0, 0x21, 0x9c, 0x02, 0xe9, 0x72, 0xe9, 0x3a, - 0xa8, 0x25, 0xe1, 0x05, 0x09, 0x81, 0xdf, 0xbb, 0xe0, 0xc9, 0x36, 0xfb, 0xd2, 0x2b, 0x3a, 0x32, - 0x3a, 0x47, 0x78, 0x0e, 0x7b, 0x16, 0xea, 0xd8, 0xf3, 0x82, 0xcd, 0xd7, 0x05, 0xcb, 0x8b, 0xb5, - 0xc1, 0xf2, 0xdd, 0x65, 0x58, 0xcc, 0x46, 0x71, 0xc2, 0xdd, 0x2d, 0xd8, 0xb4, 0x37, 0x27, 0x58, - 0xb6, 0xfb, 0x83, 0x06, 0xf4, 0xc4, 0xbb, 0x9e, 0x28, 0x0e, 0xe7, 0x29, 0xfb, 0x10, 0x96, 0x65, - 0x29, 0x3e, 0xbb, 0x28, 0x77, 0x63, 0x17, 0xff, 0x0f, 0xb6, 0xca, 0xb0, 0x54, 0x17, 0x1b, 0xbf, - 0xff, 0xe3, 0x7f, 0xff, 0xd3, 0x46, 0x97, 0xb5, 0x77, 0x4e, 0xdf, 0xde, 0x99, 0xf0, 0x28, 0x43, - 0x1a, 0xbf, 0x09, 0x50, 0x54, 0xb3, 0xb3, 0xbe, 0x56, 0xea, 0xa5, 0xea, 0xfb, 0xc1, 0xa5, 0x9a, - 0x1e, 0x49, 0xf7, 0x12, 0xd1, 0xdd, 0x70, 0x7b, 0x48, 0x37, 0x88, 0x82, 0x5c, 0x94, 0xb6, 0xbf, - 0xe7, 0xdc, 0x60, 0x63, 0xe8, 0x98, 0x55, 0xed, 0x4c, 0xf9, 0xd0, 0x35, 0xa5, 0xf2, 0x83, 0xd7, - 0x6a, 0xfb, 0x54, 0x00, 0x41, 0x73, 0x5c, 0x74, 0xd7, 0x70, 0x8e, 0x19, 0x8d, 0xd0, 0xb3, 0xec, - 0xfe, 0xe7, 0x00, 0x5a, 0x3a, 0x0e, 0x65, 0xdf, 0x85, 0xae, 0xf5, 0x14, 0xca, 0x14, 0xe1, 0xba, - 0xc7, 0xd5, 0xc1, 0xe5, 0xfa, 0x4e, 0x39, 0xed, 0x15, 0x9a, 0xb6, 0xcf, 0xb6, 0x70, 0x5a, 0xf9, - 0xfe, 0xb8, 0x43, 0x6f, 0xc4, 0xa2, 0xd2, 0xe2, 0x29, 0xf4, 0xec, 0xe7, 0x4b, 0x76, 0xd9, 0x96, - 0xb0, 0xd2, 0x6c, 0x9f, 0x3b, 0xa7, 0x57, 0x4e, 0x77, 0x99, 0xa6, 0xdb, 0x62, 0x9b, 0xe6, 0x74, - 0x3a, 0x3e, 0xe4, 0x54, 0x1b, 0x63, 0x96, 0xbb, 0xb3, 0xcf, 0xe9, 0xa3, 0xae, 0x2b, 0x83, 0xd7, - 0x87, 0x56, 0xad, 0x85, 0x77, 0xfb, 0x34, 0x15, 0x63, 0xc4, 0x50, 0xb3, 0xda, 0x9d, 0x7d, 0x0c, - 0x2d, 0x5d, 0xe2, 0xca, 0xb6, 0x8d, 0xba, 0x62, 0xb3, 0xee, 0x76, 0xd0, 0xaf, 0x76, 0xd4, 0x1d, - 0x95, 0x49, 0x19, 0x05, 0x62, 0x1f, 0x2e, 0x4a, 0xa7, 0xe0, 0x88, 0xff, 0x34, 0x3b, 0xa9, 0x29, - 0xd2, 0xbf, 0xe5, 0xb0, 0xdb, 0xb0, 0xa2, 0x2a, 0x87, 0xd9, 0x56, 0x7d, 0x05, 0xf4, 0x60, 0xbb, - 0x82, 0x4b, 0x75, 0x74, 0x07, 0xa0, 0x28, 0x72, 0xd5, 0x92, 0x5f, 0xa9, 0xc5, 0xd5, 0x4c, 0xac, - 0xa9, 0x88, 0x9d, 0x50, 0x8d, 0xaf, 0x5d, 0x43, 0xcb, 0x3e, 0x5f, 0x8c, 0xaf, 0xad, 0xae, 0x7d, - 0x01, 0x41, 0x77, 0x8b, 0x78, 0xb7, 0xc6, 0xe8, 0x2a, 0x45, 0xfc, 0x4c, 0x55, 0x89, 0xdd, 0x87, - 0xb6, 0x51, 0x38, 0xcb, 0x14, 0x85, 0x6a, 0xd1, 0xed, 0x60, 0x50, 0xd7, 0x25, 0x97, 0xfb, 0x35, - 0xe8, 0x5a, 0x15, 0xb0, 0xfa, 0x66, 0xd4, 0xd5, 0xd7, 0xea, 0x9b, 0x51, 0x5f, 0x34, 0xfb, 0x6d, - 0x68, 0x1b, 0xf5, 0xaa, 0xcc, 0xc8, 0xb1, 0x97, 0x2a, 0x55, 0xf5, 0x8a, 0xea, 0xca, 0x5b, 0x37, - 0x69, 0xbf, 0x3d, 0xb7, 0x85, 0xfb, 0xa5, 0x52, 0x29, 0x14, 0x92, 0xef, 0x42, 0xcf, 0xae, 0x60, - 0xd5, 0xb7, 0xaa, 0xb6, 0x16, 0x56, 0xdf, 0xaa, 0x73, 0xca, 0x5e, 0xa5, 0x40, 0xde, 0xd8, 0xd0, - 0x93, 0xec, 0x7c, 0x26, 0x5f, 0x61, 0x9f, 0xb3, 0x6f, 0xa2, 0xea, 0x90, 0xb5, 0x6b, 0xac, 0xa8, - 0xdb, 0xb5, 0x2b, 0xdc, 0xb4, 0xb4, 0x57, 0xca, 0xdc, 0xdc, 0x75, 0x22, 0xde, 0x66, 0xc5, 0x0e, - 0x84, 0x86, 0xa6, 0x1a, 0x36, 0x43, 0x43, 0x9b, 0x65, 0x6e, 0x86, 0x86, 0xb6, 0x4a, 0xdd, 0xca, - 0x1a, 0x3a, 0x0f, 0x90, 0x46, 0x04, 0xab, 0xa5, 0xbc, 0x9a, 0xbe, 0x2c, 0xf5, 0x59, 0xf9, 0xc1, - 0x95, 0x17, 0xa7, 0xe3, 0x6c, 0x35, 0xa3, 0xd4, 0xcb, 0x8e, 0x2a, 0xa2, 0xf8, 0x2d, 0xe8, 0x98, - 0x85, 0x91, 0x5a, 0x67, 0xd7, 0x14, 0x51, 0x6a, 0x9d, 0x5d, 0x57, 0x49, 0xa9, 0x0e, 0x97, 0x75, - 0xcc, 0x69, 0xd8, 0xb7, 0x61, 0xd5, 0xc8, 0xe0, 0x1e, 0xce, 0xa3, 0x91, 0x16, 0x9e, 0x6a, 0x1d, - 0xcd, 0xa0, 0xce, 0x60, 0xbb, 0xdb, 0x44, 0x78, 0xdd, 0xb5, 0x08, 0xa3, 0xe0, 0xdc, 0x83, 0xb6, - 0x99, 0x1d, 0x7e, 0x01, 0xdd, 0x6d, 0xa3, 0xcb, 0x2c, 0x3f, 0xb9, 0xe5, 0xb0, 0xbf, 0x70, 0xa0, - 0x63, 0x56, 0x68, 0x31, 0xeb, 0xe1, 0xa7, 0x44, 0xa7, 0x6f, 0xf6, 0x99, 0x84, 0x5c, 0x8f, 0x16, - 0xb9, 0x7f, 0xe3, 0x6b, 0x16, 0x93, 0x3f, 0xb3, 0x7c, 0xb1, 0x9b, 0xe5, 0x7f, 0x99, 0x3c, 0x2f, - 0x0f, 0x30, 0x6b, 0x8d, 0x9e, 0xdf, 0x72, 0xd8, 0x7b, 0xe2, 0x9f, 0x48, 0x2a, 0x8e, 0x62, 0x86, - 0x72, 0x2b, 0xb3, 0xcc, 0xfc, 0xd3, 0xce, 0x75, 0xe7, 0x96, 0xc3, 0xbe, 0x23, 0xfe, 0x4c, 0x22, - 0xbf, 0x25, 0xce, 0xbf, 0xea, 0xf7, 0xee, 0x1b, 0xb4, 0x9b, 0x2b, 0xee, 0x25, 0x6b, 0x37, 0x65, - 0xed, 0x7e, 0x00, 0x50, 0x04, 0xc5, 0xac, 0x14, 0x21, 0x6a, 0xbd, 0x57, 0x8d, 0x9b, 0xed, 0x13, - 0x55, 0x81, 0x24, 0x52, 0xfc, 0x58, 0x08, 0xa3, 0x1c, 0x9f, 0xe9, 0x23, 0xad, 0x06, 0xb7, 0x83, - 0x41, 0x5d, 0x57, 0x9d, 0x28, 0x2a, 0xfa, 0xec, 0x09, 0x74, 0xf7, 0xe3, 0xf8, 0xe9, 0x2c, 0xd1, - 0x0f, 0x2d, 0x76, 0x8c, 0x86, 0x11, 0xf8, 0xa0, 0xb4, 0x0b, 0xf7, 0x2a, 0x91, 0x1a, 0xb0, 0xbe, - 0x41, 0x6a, 0xe7, 0xb3, 0x22, 0x24, 0x7f, 0xce, 0x7c, 0x58, 0xd7, 0x36, 0x4e, 0x2f, 0x7c, 0x60, - 0x93, 0x31, 0x23, 0xe3, 0xca, 0x14, 0x96, 0xd7, 0xa1, 0x56, 0xbb, 0x93, 0x29, 0x9a, 0xb7, 0x1c, - 0x76, 0x00, 0x9d, 0xfb, 0x7c, 0x14, 0x8f, 0xb9, 0x8c, 0xaa, 0x36, 0x8a, 0x85, 0xeb, 0x70, 0x6c, - 0xd0, 0xb5, 0x40, 0xfb, 0xd6, 0x27, 0xfe, 0x3c, 0xe5, 0x9f, 0xec, 0x7c, 0x26, 0xe3, 0xb5, 0xe7, - 0xea, 0xd6, 0xab, 0x18, 0xd3, 0xba, 0xf5, 0xa5, 0xa0, 0xd4, 0xba, 0xf5, 0x95, 0xa0, 0xd4, 0x62, - 0xb5, 0x8a, 0x71, 0x59, 0x88, 0xa1, 0x6a, 0x29, 0x8e, 0xd5, 0x96, 0xf2, 0xbc, 0xe8, 0x77, 0x70, - 0xf5, 0xfc, 0x01, 0xf6, 0x6c, 0x37, 0xec, 0xd9, 0x0e, 0xa1, 0x7b, 0x9f, 0x0b, 0x66, 0x89, 0xe4, - 0xc5, 0xc0, 0x56, 0x23, 0x66, 0xa2, 0xa3, 0xac, 0x62, 0xa8, 0xcf, 0x56, 0xeb, 0x94, 0x39, 0x60, - 0x1f, 0x43, 0xfb, 0x21, 0xcf, 0x55, 0xb6, 0x42, 0xfb, 0x1b, 0xa5, 0xf4, 0xc5, 0xa0, 0x26, 0xd9, - 0x61, 0xcb, 0x0c, 0x51, 0xdb, 0xe1, 0xe3, 0x09, 0x17, 0x97, 0x7d, 0x18, 0x8c, 0x9f, 0xb3, 0x5f, - 0x27, 0xe2, 0x3a, 0xc1, 0xb9, 0x65, 0x3c, 0x72, 0x9b, 0xc4, 0x57, 0x4b, 0x78, 0x1d, 0xe5, 0x28, - 0x1e, 0x73, 0xc3, 0xc0, 0x45, 0xd0, 0x36, 0xb2, 0xd9, 0xfa, 0x02, 0x55, 0x33, 0xe8, 0xfa, 0x02, - 0xd5, 0x24, 0xbf, 0xdd, 0xeb, 0x34, 0x8f, 0xcb, 0xae, 0x16, 0xf3, 0x88, 0x84, 0x77, 0x31, 0xd3, - 0xce, 0x67, 0xfe, 0x34, 0x7f, 0xce, 0x3e, 0xa2, 0xa2, 0x6e, 0x33, 0x23, 0x53, 0xf8, 0x3b, 0xe5, - 0xe4, 0x8d, 0x66, 0x96, 0xd1, 0x65, 0xfb, 0x40, 0x62, 0x2a, 0xb2, 0x83, 0x5f, 0x02, 0x38, 0xcc, - 0xe3, 0xe4, 0xbe, 0xcf, 0xa7, 0x71, 0x54, 0x68, 0xae, 0x22, 0xeb, 0x50, 0x68, 0x2e, 0x23, 0xf5, - 0xc0, 0x3e, 0x32, 0x3c, 0x4e, 0x2b, 0xa1, 0xa5, 0x84, 0xeb, 0xdc, 0xc4, 0x84, 0x66, 0x48, 0x4d, - 0x72, 0xe2, 0x96, 0x83, 0xfe, 0x63, 0xf1, 0x6a, 0xa2, 0xfd, 0xc7, 0xca, 0x83, 0x8c, 0x56, 0x7b, - 0x35, 0x4f, 0x2c, 0x07, 0xd0, 0x2a, 0x42, 0x77, 0x65, 0x92, 0xca, 0x81, 0xbe, 0xb6, 0x31, 0x95, - 0x88, 0xda, 0x5d, 0x23, 0x56, 0x01, 0x5b, 0x41, 0x56, 0x51, 0x42, 0x3e, 0x80, 0x0d, 0xb1, 0x40, - 0x6d, 0x30, 0xe9, 0x1d, 0x5d, 0xed, 0xa4, 0x26, 0x82, 0xd6, 0xb7, 0xb9, 0x2e, 0x00, 0xb5, 0x63, - 0x3b, 0x94, 0x56, 0xf1, 0x86, 0xff, 0x9e, 0x73, 0xe3, 0x68, 0x89, 0xfe, 0x7c, 0xfe, 0x85, 0xff, - 0x09, 0x00, 0x00, 0xff, 0xff, 0xce, 0xee, 0xff, 0xf6, 0xae, 0x3e, 0x00, 0x00, + // 5312 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xbc, 0x5c, 0x4b, 0x90, 0x1c, 0xc9, + 0x59, 0x56, 0xf5, 0xf4, 0x3c, 0xfa, 0xef, 0x9e, 0x57, 0xce, 0x68, 0xd4, 0xea, 0x95, 0x65, 0x6d, + 0xb1, 0xb1, 0x12, 0x62, 0xd1, 0x68, 0xc7, 0xf6, 0xb2, 0x5e, 0x81, 0x1d, 0x7a, 0xcf, 0xda, 0xb3, + 0xf2, 0xb8, 0x46, 0xf2, 0x82, 0x17, 0x68, 0xd7, 0x74, 0xe7, 0xf4, 0x94, 0xd5, 0x5d, 0x55, 0x5b, + 0x95, 0x3d, 0xa3, 0xde, 0x45, 0x11, 0x3c, 0x22, 0x38, 0x41, 0xf8, 0x00, 0x11, 0x84, 0x21, 0xcc, + 0xc1, 0xbe, 0xc0, 0x81, 0x23, 0x07, 0xc2, 0x04, 0xdc, 0x1d, 0x41, 0x70, 0xf0, 0x89, 0xe0, 0x06, + 0x9c, 0xcc, 0x99, 0x0b, 0x27, 0xe2, 0xff, 0xf3, 0x51, 0x99, 0x55, 0x35, 0x92, 0x6c, 0x03, 0xb7, + 0xce, 0x2f, 0x33, 0xff, 0x7c, 0xfd, 0xf9, 0xbf, 0xf2, 0xaf, 0x86, 0x56, 0x96, 0x0e, 0x6e, 0xa4, + 0x59, 0x22, 0x12, 0x36, 0x3f, 0x8e, 0xb3, 0x74, 0xd0, 0xbb, 0x34, 0x4a, 0x92, 0xd1, 0x98, 0x6f, + 0x87, 0x69, 0xb4, 0x1d, 0xc6, 0x71, 0x22, 0x42, 0x11, 0x25, 0x71, 0x2e, 0x1b, 0xf9, 0xdf, 0x82, + 0x95, 0x87, 0x3c, 0x3e, 0xe0, 0x7c, 0x18, 0xf0, 0x8f, 0xa7, 0x3c, 0x17, 0xec, 0x97, 0x60, 0x3d, + 0xe4, 0x9f, 0x70, 0x3e, 0xec, 0xa7, 0x61, 0x9e, 0xa7, 0xc7, 0x59, 0x98, 0xf3, 0xae, 0x77, 0xc5, + 0xbb, 0xd6, 0x09, 0xd6, 0x64, 0xc5, 0xbe, 0xc1, 0xd9, 0xeb, 0xd0, 0xc9, 0xb1, 0x29, 0x8f, 0x45, + 0x96, 0xa4, 0xb3, 0x6e, 0x83, 0xda, 0xb5, 0x11, 0xbb, 0x2f, 0x21, 0x7f, 0x0c, 0xab, 0x66, 0x84, + 0x3c, 0x4d, 0xe2, 0x9c, 0xb3, 0x9b, 0xb0, 0x39, 0x88, 0xd2, 0x63, 0x9e, 0xf5, 0xa9, 0xf3, 0x24, + 0xe6, 0x93, 0x24, 0x8e, 0x06, 0x5d, 0xef, 0xca, 0xdc, 0xb5, 0x56, 0xc0, 0x64, 0x1d, 0xf6, 0xf8, + 0x40, 0xd5, 0xb0, 0xab, 0xb0, 0xca, 0x63, 0x89, 0xf3, 0x21, 0xf5, 0x52, 0x43, 0xad, 0x14, 0x30, + 0x76, 0xf0, 0xff, 0xc2, 0x83, 0xf5, 0xf7, 0xe3, 0x48, 0x7c, 0x18, 0x8e, 0xc7, 0x5c, 0xe8, 0x35, + 0x5d, 0x85, 0xd5, 0x53, 0x02, 0x68, 0x4d, 0xa7, 0x49, 0x36, 0x54, 0x2b, 0x5a, 0x91, 0xf0, 0xbe, + 0x42, 0xcf, 0x9c, 0x59, 0xe3, 0xcc, 0x99, 0xd5, 0x6e, 0xd7, 0x5c, 0xfd, 0x76, 0xf9, 0x9b, 0xc0, + 0xec, 0xc9, 0xc9, 0xed, 0xf0, 0xbf, 0x04, 0x1b, 0x4f, 0xe2, 0x71, 0x32, 0x78, 0xfa, 0xb3, 0x4d, + 0xda, 0xdf, 0x82, 0x4d, 0xb7, 0xbf, 0xa2, 0xfb, 0xdd, 0x06, 0xb4, 0x1f, 0x67, 0x61, 0x9c, 0x87, + 0x03, 0x3c, 0x72, 0xd6, 0x85, 0x45, 0xf1, 0xac, 0x7f, 0x1c, 0xe6, 0xc7, 0x44, 0xa8, 0x15, 0xe8, + 0x22, 0xdb, 0x82, 0x85, 0x70, 0x92, 0x4c, 0x63, 0x41, 0xbb, 0x3a, 0x17, 0xa8, 0x12, 0x7b, 0x0b, + 0xd6, 0xe3, 0xe9, 0xa4, 0x3f, 0x48, 0xe2, 0xa3, 0x28, 0x9b, 0x48, 0xc6, 0xa1, 0xc5, 0xcd, 0x07, + 0xd5, 0x0a, 0x76, 0x19, 0xe0, 0x10, 0xa7, 0x21, 0x87, 0x68, 0xd2, 0x10, 0x16, 0xc2, 0x7c, 0xe8, + 0xa8, 0x12, 0x8f, 0x46, 0xc7, 0xa2, 0x3b, 0x4f, 0x84, 0x1c, 0x0c, 0x69, 0x88, 0x68, 0xc2, 0xfb, + 0xb9, 0x08, 0x27, 0x69, 0x77, 0x81, 0x66, 0x63, 0x21, 0x54, 0x9f, 0x88, 0x70, 0xdc, 0x3f, 0xe2, + 0x3c, 0xef, 0x2e, 0xaa, 0x7a, 0x83, 0xb0, 0x37, 0x61, 0x65, 0xc8, 0x73, 0xd1, 0x0f, 0x87, 0xc3, + 0x8c, 0xe7, 0x39, 0xcf, 0xbb, 0x4b, 0x74, 0x74, 0x25, 0xd4, 0xef, 0xc2, 0xd6, 0x43, 0x2e, 0xac, + 0xdd, 0xc9, 0xd5, 0xb6, 0xfb, 0x7b, 0xc0, 0x2c, 0xf8, 0x1e, 0x17, 0x61, 0x34, 0xce, 0xd9, 0x3b, + 0xd0, 0x11, 0x56, 0x63, 0x62, 0xd5, 0xf6, 0x0e, 0xbb, 0x41, 0x77, 0xec, 0x86, 0xd5, 0x21, 0x70, + 0xda, 0xf9, 0xff, 0xed, 0x41, 0xfb, 0x80, 0xc7, 0xe6, 0x76, 0x31, 0x68, 0xe2, 0x4c, 0xd4, 0x49, + 0xd2, 0x6f, 0xf6, 0x59, 0x68, 0xd3, 0xec, 0x72, 0x91, 0x45, 0xf1, 0x88, 0x8e, 0xa0, 0x15, 0x00, + 0x42, 0x07, 0x84, 0xb0, 0x35, 0x98, 0x0b, 0x27, 0x82, 0x36, 0x7e, 0x2e, 0xc0, 0x9f, 0x78, 0xef, + 0xd2, 0x70, 0x36, 0xe1, 0xb1, 0x28, 0x36, 0xbb, 0x13, 0xb4, 0x15, 0xb6, 0x8b, 0xbb, 0x7d, 0x03, + 0x36, 0xec, 0x26, 0x9a, 0xfa, 0x3c, 0x51, 0x5f, 0xb7, 0x5a, 0xaa, 0x41, 0xae, 0xc2, 0xaa, 0x6e, + 0x9f, 0xc9, 0xc9, 0xd2, 0xf6, 0xb7, 0x82, 0x15, 0x05, 0xeb, 0x25, 0x5c, 0x83, 0xb5, 0xa3, 0x28, + 0x0e, 0xc7, 0xfd, 0xc1, 0x58, 0x9c, 0xf4, 0x87, 0x7c, 0x2c, 0x42, 0x3a, 0x88, 0xf9, 0x60, 0x85, + 0xf0, 0xbb, 0x63, 0x71, 0x72, 0x0f, 0x51, 0xff, 0x4f, 0x3d, 0xe8, 0xc8, 0xc5, 0xab, 0x8b, 0xff, + 0x06, 0x2c, 0xeb, 0x31, 0x78, 0x96, 0x25, 0x99, 0xe2, 0x43, 0x17, 0x64, 0xd7, 0x61, 0x4d, 0x03, + 0x69, 0xc6, 0xa3, 0x49, 0x38, 0xe2, 0xea, 0xb6, 0x57, 0x70, 0xb6, 0x53, 0x50, 0xcc, 0x92, 0xa9, + 0x90, 0x57, 0xaf, 0xbd, 0xd3, 0x51, 0x07, 0x13, 0x20, 0x16, 0xb8, 0x4d, 0xfc, 0xef, 0x7b, 0xd0, + 0xb9, 0x7b, 0x1c, 0xc6, 0x31, 0x1f, 0xef, 0x27, 0x51, 0x2c, 0xd8, 0x4d, 0x60, 0x47, 0xd3, 0x78, + 0x18, 0xc5, 0xa3, 0xbe, 0x78, 0x16, 0x0d, 0xfb, 0x87, 0x33, 0xc1, 0x73, 0x79, 0x44, 0xbb, 0xe7, + 0x82, 0x9a, 0x3a, 0xf6, 0x16, 0xac, 0x39, 0x68, 0x2e, 0x32, 0x79, 0x6e, 0xbb, 0xe7, 0x82, 0x4a, + 0x0d, 0x32, 0x7e, 0x32, 0x15, 0xe9, 0x54, 0xf4, 0xa3, 0x78, 0xc8, 0x9f, 0xd1, 0x1c, 0x97, 0x03, + 0x07, 0xbb, 0xb3, 0x02, 0x1d, 0xbb, 0x9f, 0xff, 0x25, 0x58, 0xdb, 0xc3, 0x1b, 0x11, 0x47, 0xf1, + 0xe8, 0xb6, 0x64, 0x5b, 0xbc, 0xa6, 0xe9, 0xf4, 0xf0, 0x29, 0x9f, 0xa9, 0x7d, 0x53, 0x25, 0x64, + 0xaa, 0xe3, 0x24, 0x17, 0x8a, 0x73, 0xe8, 0xb7, 0xff, 0xef, 0x1e, 0xac, 0xe2, 0xde, 0x7f, 0x10, + 0xc6, 0x33, 0x7d, 0x72, 0x7b, 0xd0, 0x41, 0x52, 0x8f, 0x93, 0xdb, 0xf2, 0xb2, 0x4b, 0x26, 0xbe, + 0xa6, 0xf6, 0xaa, 0xd4, 0xfa, 0x86, 0xdd, 0x14, 0x85, 0xf9, 0x2c, 0x70, 0x7a, 0x23, 0xdb, 0x8a, + 0x30, 0x1b, 0x71, 0x41, 0x62, 0x40, 0x89, 0x05, 0x90, 0xd0, 0xdd, 0x24, 0x3e, 0x62, 0x57, 0xa0, + 0x93, 0x87, 0xa2, 0x9f, 0xf2, 0x8c, 0x76, 0x8d, 0x58, 0x6f, 0x2e, 0x80, 0x3c, 0x14, 0xfb, 0x3c, + 0xbb, 0x33, 0x13, 0xbc, 0xf7, 0x65, 0x58, 0xaf, 0x8c, 0x82, 0xdc, 0x5e, 0x2c, 0x11, 0x7f, 0xb2, + 0x4d, 0x98, 0x3f, 0x09, 0xc7, 0x53, 0xae, 0xa4, 0x93, 0x2c, 0xbc, 0xd7, 0x78, 0xd7, 0xf3, 0xdf, + 0x84, 0xb5, 0x62, 0xda, 0x8a, 0xc9, 0x18, 0x34, 0x71, 0x07, 0x15, 0x01, 0xfa, 0xed, 0xff, 0x9e, + 0x27, 0x1b, 0xde, 0x4d, 0x22, 0x73, 0xd3, 0xb1, 0x21, 0x0a, 0x04, 0xdd, 0x10, 0x7f, 0x9f, 0x29, + 0x09, 0x7f, 0xfe, 0xc5, 0xfa, 0x57, 0x61, 0xdd, 0x9a, 0xc2, 0x0b, 0x26, 0xfb, 0x97, 0x1e, 0xac, + 0x3f, 0xe2, 0xa7, 0xea, 0xd4, 0xf5, 0x6c, 0xdf, 0x85, 0xa6, 0x98, 0xa5, 0x52, 0x15, 0xaf, 0xec, + 0xbc, 0xa1, 0x0e, 0xad, 0xd2, 0xee, 0x86, 0x2a, 0x3e, 0x9e, 0xa5, 0x3c, 0xa0, 0x1e, 0xfe, 0xd7, + 0xa0, 0x6d, 0x81, 0xec, 0x02, 0x6c, 0x7c, 0xf8, 0xfe, 0xe3, 0x47, 0xf7, 0x0f, 0x0e, 0xfa, 0xfb, + 0x4f, 0xee, 0x7c, 0xf5, 0xfe, 0x6f, 0xf4, 0x77, 0x6f, 0x1f, 0xec, 0xae, 0x9d, 0x63, 0x5b, 0xc0, + 0x1e, 0xdd, 0x3f, 0x78, 0x7c, 0xff, 0x9e, 0x83, 0x7b, 0x6c, 0x15, 0xda, 0x36, 0xd0, 0xf0, 0x7b, + 0xd0, 0x7d, 0xc4, 0x4f, 0x3f, 0x8c, 0x44, 0xcc, 0xf3, 0xdc, 0x1d, 0xde, 0xbf, 0x01, 0xcc, 0x9e, + 0x93, 0x5a, 0x66, 0x17, 0x16, 0x95, 0xec, 0xd5, 0xaa, 0x47, 0x15, 0xfd, 0x37, 0x81, 0x1d, 0x44, + 0xa3, 0xf8, 0x03, 0x9e, 0xe7, 0xe1, 0x88, 0xeb, 0xc5, 0xae, 0xc1, 0xdc, 0x24, 0x1f, 0x29, 0x29, + 0x89, 0x3f, 0xfd, 0xcf, 0xc1, 0x86, 0xd3, 0x4e, 0x11, 0xbe, 0x04, 0xad, 0x3c, 0x1a, 0xc5, 0xa1, + 0x98, 0x66, 0x5c, 0x91, 0x2e, 0x00, 0xff, 0x01, 0x6c, 0x7e, 0x83, 0x67, 0xd1, 0xd1, 0xec, 0x65, + 0xe4, 0x5d, 0x3a, 0x8d, 0x32, 0x9d, 0xfb, 0x70, 0xbe, 0x44, 0x47, 0x0d, 0x2f, 0x39, 0x53, 0x9d, + 0xdf, 0x52, 0x20, 0x0b, 0xd6, 0x3d, 0x6d, 0xd8, 0xf7, 0xd4, 0x7f, 0x02, 0xec, 0x6e, 0x12, 0xc7, + 0x7c, 0x20, 0xf6, 0x39, 0xcf, 0x0a, 0x83, 0xab, 0x60, 0xc3, 0xf6, 0xce, 0x05, 0x75, 0xb0, 0xe5, + 0xcb, 0xaf, 0xf8, 0x93, 0x41, 0x33, 0xe5, 0xd9, 0x84, 0x08, 0x2f, 0x05, 0xf4, 0xdb, 0x3f, 0x0f, + 0x1b, 0x0e, 0x59, 0xa5, 0xfe, 0xdf, 0x86, 0xf3, 0xf7, 0xa2, 0x7c, 0x50, 0x1d, 0xb0, 0x0b, 0x8b, + 0xe9, 0xf4, 0xb0, 0x5f, 0x5c, 0x32, 0x5d, 0x44, 0xad, 0x58, 0xee, 0xa2, 0x88, 0xfd, 0xa1, 0x07, + 0xcd, 0xdd, 0xc7, 0x7b, 0x77, 0x59, 0x0f, 0x96, 0xa2, 0x78, 0x90, 0x4c, 0x50, 0x97, 0xc8, 0x45, + 0x9b, 0xf2, 0x99, 0x97, 0xe7, 0x12, 0xb4, 0x48, 0x05, 0xa1, 0xa2, 0x57, 0xb6, 0x51, 0x01, 0xa0, + 0x91, 0xc1, 0x9f, 0xa5, 0x51, 0x46, 0x56, 0x84, 0xb6, 0x0d, 0x9a, 0x24, 0x22, 0xab, 0x15, 0xfe, + 0x4f, 0x9a, 0xb0, 0x7c, 0x7b, 0x20, 0xa2, 0x13, 0xae, 0x44, 0x38, 0x8d, 0x4a, 0x80, 0x9a, 0x8f, + 0x2a, 0xa1, 0xb2, 0xc9, 0xf8, 0x24, 0x11, 0xbc, 0xef, 0x1c, 0x86, 0x0b, 0x62, 0xab, 0x81, 0x24, + 0xd4, 0x4f, 0x51, 0x19, 0xd0, 0xfc, 0x5a, 0x81, 0x0b, 0xe2, 0x96, 0x21, 0xd0, 0x8f, 0x86, 0x34, + 0xb3, 0x66, 0xa0, 0x8b, 0xb8, 0x1f, 0x83, 0x30, 0x0d, 0x07, 0x91, 0x98, 0xa9, 0x3b, 0x6f, 0xca, + 0x48, 0x7b, 0x9c, 0x0c, 0xc2, 0x71, 0xff, 0x30, 0x1c, 0x87, 0xf1, 0x80, 0x2b, 0x7b, 0xc6, 0x05, + 0xd1, 0x64, 0x51, 0x53, 0xd2, 0xcd, 0xa4, 0x59, 0x53, 0x42, 0xd1, 0xf4, 0x19, 0x24, 0x93, 0x49, + 0x24, 0xd0, 0xd2, 0xe9, 0x2e, 0x49, 0xf9, 0x52, 0x20, 0xb4, 0x12, 0x59, 0x3a, 0x95, 0x7b, 0xd8, + 0x92, 0xa3, 0x39, 0x20, 0x52, 0x39, 0xe2, 0x9c, 0xe4, 0xd4, 0xd3, 0xd3, 0x2e, 0x48, 0x2a, 0x05, + 0x82, 0xa7, 0x31, 0x8d, 0x73, 0x2e, 0xc4, 0x98, 0x0f, 0xcd, 0x84, 0xda, 0xd4, 0xac, 0x5a, 0xc1, + 0x6e, 0xc2, 0x86, 0x34, 0xbe, 0xf2, 0x50, 0x24, 0xf9, 0x71, 0x94, 0xf7, 0x73, 0x1e, 0x8b, 0x6e, + 0x87, 0xda, 0xd7, 0x55, 0xb1, 0x77, 0xe1, 0x42, 0x09, 0xce, 0xf8, 0x80, 0x47, 0x27, 0x7c, 0xd8, + 0x5d, 0xa6, 0x5e, 0x67, 0x55, 0xb3, 0x2b, 0xd0, 0x46, 0x9b, 0x73, 0x9a, 0x0e, 0x43, 0x54, 0xcf, + 0x2b, 0x74, 0x0e, 0x36, 0xc4, 0xde, 0x86, 0xe5, 0x94, 0x4b, 0x1d, 0x7a, 0x2c, 0xc6, 0x83, 0xbc, + 0xbb, 0x4a, 0x0a, 0xae, 0xad, 0xae, 0x14, 0xf2, 0x6f, 0xe0, 0xb6, 0x40, 0xd6, 0x1c, 0xe4, 0x64, + 0xc5, 0x84, 0xb3, 0xee, 0x1a, 0x31, 0x5d, 0x01, 0xe0, 0xcd, 0xda, 0x8b, 0x72, 0xa1, 0x38, 0xcd, + 0xc8, 0xb8, 0x5d, 0xd8, 0x74, 0x61, 0xe3, 0xd7, 0x2c, 0x29, 0xb6, 0xc9, 0xbb, 0x6d, 0x1a, 0x7a, + 0x53, 0x0d, 0xed, 0x70, 0x6c, 0x60, 0x5a, 0xf9, 0x3f, 0xf1, 0xa0, 0x89, 0xf7, 0xec, 0xec, 0x3b, + 0x69, 0x8b, 0xce, 0x39, 0x47, 0x74, 0x92, 0xbd, 0x8d, 0xd6, 0x88, 0xdc, 0x73, 0xc9, 0x97, 0x16, + 0x52, 0xd4, 0x67, 0x7c, 0x70, 0x42, 0xcc, 0x69, 0xea, 0x11, 0x41, 0xd6, 0x45, 0x95, 0x45, 0xbd, + 0x25, 0x67, 0x9a, 0xb2, 0xae, 0xa3, 0x9e, 0x8b, 0x45, 0x1d, 0xf5, 0xeb, 0xc2, 0x62, 0x14, 0x1f, + 0x26, 0xd3, 0x78, 0x48, 0x5c, 0xb8, 0x14, 0xe8, 0x22, 0xee, 0x66, 0x4a, 0x16, 0x4c, 0x34, 0xe1, + 0x8a, 0xfd, 0x0a, 0xc0, 0x67, 0x68, 0xd2, 0xe4, 0x24, 0x57, 0xcc, 0x56, 0xbe, 0x03, 0xeb, 0x16, + 0xa6, 0xf6, 0xf1, 0x75, 0x98, 0x4f, 0x11, 0x50, 0x06, 0x8a, 0x3e, 0x3f, 0x12, 0x48, 0xb2, 0xc6, + 0x5f, 0x43, 0xbf, 0x55, 0xbc, 0x1f, 0x1f, 0x25, 0x9a, 0xd2, 0x3f, 0xce, 0xa1, 0xa3, 0xa9, 0x20, + 0x45, 0xe8, 0x1a, 0xac, 0x46, 0x43, 0x1e, 0x8b, 0x48, 0xcc, 0xfa, 0x8e, 0xe5, 0x54, 0x86, 0x51, + 0x90, 0x87, 0xe3, 0x28, 0xcc, 0x95, 0x90, 0x90, 0x05, 0xb6, 0x03, 0x9b, 0xc8, 0x5f, 0x9a, 0x65, + 0xcc, 0xe1, 0x4a, 0x03, 0xae, 0xb6, 0x0e, 0xaf, 0x04, 0xe2, 0x52, 0x08, 0x15, 0x5d, 0xa4, 0x40, + 0xab, 0xab, 0xc2, 0x5d, 0x93, 0x94, 0x70, 0xc9, 0xf3, 0x92, 0x07, 0x0d, 0x50, 0xf1, 0x9a, 0x16, + 0xa4, 0xf1, 0x58, 0xf6, 0x9a, 0x2c, 0xcf, 0x6b, 0xa9, 0xe2, 0x79, 0x5d, 0x83, 0xd5, 0x7c, 0x16, + 0x0f, 0xf8, 0xb0, 0x2f, 0x12, 0x1c, 0x37, 0x8a, 0xe9, 0x74, 0x96, 0x82, 0x32, 0x4c, 0x3e, 0x22, + 0xcf, 0x45, 0xcc, 0x05, 0xc9, 0x86, 0xa5, 0x40, 0x17, 0x51, 0xcc, 0x52, 0x13, 0xc9, 0xda, 0xad, + 0x40, 0x95, 0x50, 0x23, 0x4d, 0xb3, 0x28, 0xef, 0x76, 0x08, 0xa5, 0xdf, 0xec, 0xf3, 0x70, 0xfe, + 0x10, 0x3d, 0x9a, 0x63, 0x1e, 0x0e, 0x79, 0x46, 0xa7, 0x2f, 0x1d, 0x3a, 0x79, 0xc5, 0xeb, 0x2b, + 0xfd, 0x4f, 0x48, 0x3d, 0x1a, 0x87, 0xf2, 0x09, 0xdd, 0x6a, 0xf6, 0x1a, 0xb4, 0xe4, 0x4a, 0xf2, + 0xe3, 0x50, 0x69, 0xec, 0x25, 0x02, 0x0e, 0x8e, 0x43, 0xf4, 0x83, 0x9c, 0xcd, 0x69, 0x90, 0x5d, + 0xd6, 0x26, 0x6c, 0x57, 0xee, 0xcd, 0x1b, 0xb0, 0xa2, 0x5d, 0xd5, 0xbc, 0x3f, 0xe6, 0x47, 0x42, + 0x9b, 0xdf, 0xf1, 0x74, 0x82, 0xc3, 0xe5, 0x7b, 0xfc, 0x48, 0xf8, 0x8f, 0x60, 0x5d, 0xdd, 0xce, + 0xaf, 0xa5, 0x5c, 0x0f, 0xfd, 0xc5, 0xb2, 0x6e, 0x90, 0x2a, 0x7a, 0x43, 0xf1, 0xa3, 0xed, 0x43, + 0x94, 0x14, 0x86, 0x1f, 0x00, 0x53, 0xd5, 0x77, 0xc7, 0x49, 0xce, 0x15, 0x41, 0x1f, 0x3a, 0x83, + 0x71, 0x92, 0x6b, 0x23, 0x5f, 0x2d, 0xc7, 0xc1, 0xf0, 0x04, 0xf2, 0xe9, 0x60, 0x80, 0xf7, 0x5d, + 0x2a, 0x79, 0x5d, 0xf4, 0xff, 0xca, 0x83, 0x0d, 0xa2, 0xa6, 0xe5, 0x88, 0xb1, 0x0c, 0x5f, 0x7d, + 0x9a, 0x9d, 0x81, 0xed, 0xf8, 0x6c, 0xc2, 0xfc, 0x51, 0x92, 0x0d, 0xb8, 0x1a, 0x49, 0x16, 0x7e, + 0x7a, 0x5b, 0xb7, 0x59, 0xb1, 0x75, 0xff, 0xc5, 0x83, 0x75, 0x9a, 0xea, 0x81, 0x08, 0xc5, 0x34, + 0x57, 0xcb, 0xff, 0x55, 0x58, 0xc6, 0xa5, 0x72, 0x7d, 0x69, 0xd4, 0x44, 0x37, 0xcd, 0xfd, 0x26, + 0x54, 0x36, 0xde, 0x3d, 0x17, 0xb8, 0x8d, 0xd9, 0x97, 0xa1, 0x63, 0xc7, 0x1b, 0x68, 0xce, 0xed, + 0x9d, 0x8b, 0x7a, 0x95, 0x15, 0xce, 0xd9, 0x3d, 0x17, 0x38, 0x1d, 0xd8, 0x2d, 0x00, 0xd2, 0xda, + 0x44, 0x56, 0x39, 0x8a, 0x17, 0xdd, 0x4d, 0xb2, 0x0e, 0x6b, 0xf7, 0x5c, 0x60, 0x35, 0xbf, 0xb3, + 0x04, 0x0b, 0x52, 0xcd, 0xf8, 0x0f, 0x61, 0xd9, 0x99, 0xa9, 0x63, 0xc3, 0x77, 0xa4, 0x0d, 0x5f, + 0x71, 0xf9, 0x1a, 0x55, 0x97, 0xcf, 0xff, 0xbb, 0x06, 0x30, 0xe4, 0xb6, 0xd2, 0x71, 0xa2, 0x9e, + 0x4b, 0x86, 0x8e, 0xd5, 0xd2, 0x09, 0x6c, 0x88, 0xdd, 0x00, 0x66, 0x15, 0xb5, 0x67, 0x2f, 0xb5, + 0x43, 0x4d, 0x0d, 0x8a, 0x31, 0x69, 0x72, 0x68, 0x0f, 0x53, 0x59, 0x69, 0xf2, 0xdc, 0x6a, 0xeb, + 0x50, 0x01, 0xa4, 0xd3, 0xfc, 0x18, 0xf5, 0xb0, 0xb6, 0x6b, 0x74, 0xb9, 0xcc, 0x20, 0x0b, 0x2f, + 0x65, 0x90, 0xc5, 0x32, 0x83, 0x90, 0xbe, 0xcb, 0xa2, 0x93, 0x50, 0x70, 0xad, 0x43, 0x54, 0x11, + 0xcd, 0x98, 0x49, 0x14, 0x93, 0x7a, 0xee, 0x4f, 0x70, 0x74, 0x65, 0xc6, 0x38, 0xa0, 0xff, 0x63, + 0x0f, 0xd6, 0x70, 0xef, 0x1c, 0xfe, 0x7a, 0x0f, 0x88, 0xbd, 0x5f, 0x91, 0xbd, 0x9c, 0xb6, 0x3f, + 0x3f, 0x77, 0xbd, 0x0b, 0x2d, 0x22, 0x98, 0xa4, 0x3c, 0x56, 0xcc, 0xd5, 0x75, 0x99, 0xab, 0x90, + 0x2c, 0xbb, 0xe7, 0x82, 0xa2, 0xb1, 0xc5, 0x5a, 0xff, 0xec, 0x41, 0x5b, 0x4d, 0xf3, 0x67, 0x36, + 0xb6, 0x7b, 0xb0, 0x84, 0x5c, 0x66, 0xd9, 0xb2, 0xa6, 0x8c, 0x7a, 0x60, 0x82, 0x1e, 0x0d, 0x2a, + 0x3e, 0xc7, 0xd0, 0x2e, 0xc3, 0xa8, 0xc5, 0x48, 0x88, 0xe6, 0x7d, 0x11, 0x8d, 0xfb, 0xba, 0x56, + 0x85, 0xec, 0xea, 0xaa, 0x50, 0x96, 0xe4, 0x22, 0x1c, 0x71, 0xa5, 0xa0, 0x64, 0x01, 0x3d, 0x0a, + 0xb5, 0xa0, 0xb2, 0x11, 0xf5, 0x23, 0x80, 0x0b, 0x95, 0x2a, 0x63, 0x48, 0x29, 0xdb, 0x71, 0x1c, + 0x4d, 0x0e, 0x13, 0x63, 0x86, 0x7a, 0xb6, 0x59, 0xe9, 0x54, 0xb1, 0x11, 0x9c, 0xd7, 0x9a, 0x18, + 0xf7, 0xb4, 0xd0, 0xbb, 0x0d, 0x32, 0x21, 0xde, 0x76, 0x79, 0xa0, 0x3c, 0xa0, 0xc6, 0xed, 0xdb, + 0x58, 0x4f, 0x8f, 0x1d, 0x43, 0xd7, 0xa8, 0x7c, 0x25, 0xb6, 0x2d, 0xb3, 0x00, 0xc7, 0x7a, 0xeb, + 0x25, 0x63, 0x91, 0x8c, 0x19, 0xea, 0x61, 0xce, 0xa4, 0xc6, 0x66, 0x70, 0x59, 0xd7, 0x91, 0x5c, + 0xae, 0x8e, 0xd7, 0x7c, 0xa5, 0xb5, 0x3d, 0xc0, 0xce, 0xee, 0xa0, 0x2f, 0x21, 0xdc, 0xfb, 0x91, + 0x07, 0x2b, 0x2e, 0x39, 0x64, 0x1d, 0xe5, 0x8f, 0x68, 0x01, 0xa3, 0x4d, 0xa9, 0x12, 0x5c, 0xf5, + 0xa8, 0x1a, 0x75, 0x1e, 0x95, 0xed, 0x37, 0xcd, 0xbd, 0xcc, 0x6f, 0x6a, 0xbe, 0x9a, 0xdf, 0x34, + 0x5f, 0xe7, 0x37, 0xf5, 0xfe, 0xcb, 0x03, 0x56, 0x3d, 0x5f, 0xf6, 0x50, 0xba, 0x74, 0x31, 0x1f, + 0x2b, 0x39, 0xf1, 0xcb, 0xaf, 0xc6, 0x23, 0x7a, 0x0f, 0x75, 0x6f, 0x64, 0x56, 0x5b, 0x10, 0xd8, + 0xa6, 0xc8, 0x72, 0x50, 0x57, 0x55, 0xf2, 0xe4, 0x9a, 0x2f, 0xf7, 0xe4, 0xe6, 0x5f, 0xee, 0xc9, + 0x2d, 0x94, 0x3d, 0xb9, 0xde, 0xef, 0xc0, 0xb2, 0x73, 0xea, 0xff, 0x7b, 0x2b, 0x2e, 0x9b, 0x31, + 0xf2, 0x80, 0x1d, 0xac, 0xf7, 0x9f, 0x0d, 0x60, 0x55, 0xce, 0xfb, 0x7f, 0x9d, 0x03, 0xf1, 0x91, + 0x23, 0x40, 0xe6, 0x14, 0x1f, 0x39, 0xa2, 0xe3, 0xff, 0x52, 0x28, 0xbe, 0x05, 0xeb, 0x19, 0x1f, + 0x24, 0x27, 0xf4, 0x6c, 0xe5, 0x46, 0x01, 0xaa, 0x15, 0x68, 0xc8, 0xb9, 0xfe, 0xeb, 0x92, 0xf3, + 0xca, 0x60, 0x69, 0x86, 0x92, 0x1b, 0xeb, 0x7f, 0x11, 0x36, 0xe5, 0xe3, 0xcf, 0x1d, 0x49, 0x4a, + 0xdb, 0x12, 0xaf, 0x43, 0xe7, 0x54, 0x86, 0xe9, 0xfa, 0x49, 0x3c, 0x9e, 0x29, 0x25, 0xd2, 0x56, + 0xd8, 0xd7, 0xe2, 0xf1, 0xcc, 0xff, 0x9e, 0x07, 0xe7, 0x4b, 0x7d, 0x8b, 0x68, 0xbd, 0x14, 0xb5, + 0xae, 0xfc, 0x75, 0x41, 0x5c, 0xa2, 0xe2, 0x71, 0x6b, 0x89, 0x52, 0x25, 0x55, 0x2b, 0x70, 0x0b, + 0xa7, 0x71, 0xb5, 0xbd, 0x3c, 0x98, 0xba, 0x2a, 0xff, 0x02, 0x9c, 0x57, 0x87, 0xef, 0xae, 0xcd, + 0xdf, 0x81, 0xad, 0x72, 0x45, 0x11, 0x6d, 0x74, 0xa7, 0xac, 0x8b, 0xfe, 0x6f, 0x03, 0xfb, 0xfa, + 0x94, 0x67, 0x33, 0x7a, 0x17, 0x30, 0xa1, 0xd5, 0x0b, 0x65, 0xe7, 0x7b, 0x21, 0x9d, 0x1e, 0x7e, + 0x95, 0xcf, 0xf4, 0xc3, 0x4b, 0xa3, 0x78, 0x78, 0xf9, 0x0c, 0x00, 0x7a, 0x13, 0xf4, 0x90, 0xa0, + 0x9f, 0xc2, 0xd0, 0x59, 0x93, 0x04, 0xfd, 0x5b, 0xb0, 0xe1, 0xd0, 0x37, 0x3b, 0xb9, 0xa0, 0x7a, + 0x48, 0x8f, 0xd6, 0x7d, 0x9e, 0x50, 0x75, 0xfe, 0x9f, 0x79, 0x30, 0xb7, 0x9b, 0xa4, 0x76, 0xb0, + 0xc9, 0x73, 0x83, 0x4d, 0x4a, 0xb4, 0xf6, 0x8d, 0xe4, 0x6c, 0x28, 0xc1, 0x60, 0x83, 0x28, 0x18, + 0xc3, 0x89, 0x40, 0x9f, 0xee, 0x28, 0xc9, 0x4e, 0xc3, 0x6c, 0xa8, 0xb6, 0xb7, 0x84, 0xe2, 0xea, + 0x0a, 0xf9, 0x83, 0x3f, 0xd1, 0xa6, 0xa0, 0x88, 0xdb, 0x4c, 0xb9, 0xa1, 0xaa, 0xe4, 0x7f, 0xc7, + 0x83, 0x79, 0x9a, 0x2b, 0x5e, 0x16, 0x79, 0xfc, 0xf4, 0x26, 0x47, 0x01, 0x3d, 0x4f, 0x5e, 0x96, + 0x12, 0x5c, 0x7a, 0xa9, 0x6b, 0x54, 0x5e, 0xea, 0x2e, 0x41, 0x4b, 0x96, 0x8a, 0xa7, 0xad, 0x02, + 0x60, 0x97, 0xa1, 0x79, 0x9c, 0xa4, 0x5a, 0xc5, 0x81, 0x8e, 0xe0, 0x24, 0x69, 0x40, 0xb8, 0x7f, + 0x1d, 0x56, 0x1f, 0x25, 0x43, 0x6e, 0x05, 0x00, 0xce, 0x3c, 0x45, 0xff, 0x77, 0x3d, 0x58, 0xd2, + 0x8d, 0xd9, 0x35, 0x68, 0xa2, 0xa6, 0x2a, 0xd9, 0x86, 0x26, 0xda, 0x8a, 0xed, 0x02, 0x6a, 0x81, + 0x12, 0x86, 0x1c, 0xc7, 0xc2, 0x92, 0xd0, 0x6e, 0x63, 0xa1, 0xa3, 0xdf, 0x84, 0x15, 0x39, 0xe7, + 0x92, 0x2e, 0x2b, 0xa1, 0xfe, 0x5f, 0x7b, 0xb0, 0xec, 0x8c, 0x81, 0x56, 0xfe, 0x38, 0xcc, 0x85, + 0x8a, 0x5d, 0xa9, 0x4d, 0xb4, 0x21, 0x3b, 0x24, 0xd4, 0x70, 0x43, 0x42, 0x26, 0x58, 0x31, 0x67, + 0x07, 0x2b, 0x6e, 0x42, 0xab, 0x78, 0xf5, 0x6c, 0x3a, 0x92, 0x03, 0x47, 0xd4, 0x71, 0xe4, 0xa2, + 0x11, 0xd2, 0x19, 0x24, 0xe3, 0x24, 0x53, 0x8f, 0x82, 0xb2, 0xe0, 0xdf, 0x82, 0xb6, 0xd5, 0x1e, + 0xa7, 0x11, 0x73, 0x71, 0x9a, 0x64, 0x4f, 0x75, 0x64, 0x4a, 0x15, 0xcd, 0xfb, 0x49, 0xa3, 0x78, + 0x3f, 0xf1, 0xff, 0xc6, 0x83, 0x65, 0xe4, 0x94, 0x28, 0x1e, 0xed, 0x27, 0xe3, 0x68, 0x30, 0x23, + 0x8e, 0xd1, 0x4c, 0xa1, 0x5e, 0x0b, 0x35, 0xc7, 0xb8, 0x30, 0x9a, 0x04, 0xda, 0xc8, 0x57, 0xfc, + 0x62, 0xca, 0xc8, 0xf9, 0xa8, 0xda, 0x0e, 0xc3, 0x9c, 0x4b, 0xaf, 0x40, 0x89, 0x72, 0x07, 0x44, + 0xe9, 0x82, 0x40, 0x16, 0x0a, 0xde, 0x9f, 0x44, 0xe3, 0x71, 0x24, 0xdb, 0x4a, 0x0e, 0xaf, 0xab, + 0xf2, 0x7f, 0xd8, 0x80, 0xb6, 0x92, 0x22, 0xf7, 0x87, 0x23, 0x19, 0x64, 0x55, 0x76, 0x8a, 0xb9, + 0x7e, 0x16, 0xa2, 0xeb, 0x1d, 0xcb, 0xc6, 0x42, 0xca, 0xc7, 0x3a, 0x57, 0x3d, 0xd6, 0x4b, 0xd0, + 0x42, 0xf6, 0x7a, 0x9b, 0x4c, 0x28, 0xf9, 0x48, 0x5e, 0x00, 0xba, 0x76, 0x87, 0x6a, 0xe7, 0x8b, + 0x5a, 0x02, 0x1c, 0xa3, 0x69, 0xa1, 0x64, 0x34, 0xbd, 0x0b, 0x1d, 0x45, 0x86, 0xf6, 0x9d, 0x7c, + 0xae, 0x82, 0xc1, 0x9d, 0x33, 0x09, 0x9c, 0x96, 0xba, 0xe7, 0x8e, 0xee, 0xb9, 0xf4, 0xb2, 0x9e, + 0xba, 0x25, 0xbd, 0x3c, 0xc8, 0xbd, 0x79, 0x98, 0x85, 0xe9, 0xb1, 0x96, 0xcc, 0x43, 0xf3, 0xbe, + 0x4a, 0x30, 0xbb, 0x0e, 0xf3, 0xd8, 0x4d, 0x4b, 0xbf, 0xfa, 0x4b, 0x27, 0x9b, 0xb0, 0x6b, 0x30, + 0xcf, 0x87, 0x23, 0xae, 0x0d, 0x77, 0xe6, 0xba, 0x50, 0x78, 0x46, 0x81, 0x6c, 0x80, 0x22, 0x00, + 0xd1, 0x92, 0x08, 0x70, 0x25, 0xe7, 0x02, 0x16, 0xdf, 0x1f, 0xfa, 0x9b, 0xc0, 0x1e, 0x49, 0xae, + 0xb5, 0x43, 0x86, 0x7f, 0x30, 0x07, 0x6d, 0x0b, 0xc6, 0xdb, 0x3c, 0xc2, 0x09, 0xf7, 0x87, 0x51, + 0x38, 0xe1, 0x82, 0x67, 0x8a, 0x53, 0x4b, 0x28, 0x09, 0xd8, 0x93, 0x51, 0x3f, 0x99, 0x8a, 0xfe, + 0x90, 0x8f, 0x32, 0x2e, 0xf5, 0x9d, 0x17, 0x94, 0x50, 0x6c, 0x37, 0x09, 0x9f, 0xd9, 0xed, 0x24, + 0x3f, 0x94, 0x50, 0x1d, 0x00, 0x94, 0x7b, 0xd4, 0x2c, 0x02, 0x80, 0x72, 0x47, 0xca, 0x72, 0x68, + 0xbe, 0x46, 0x0e, 0xbd, 0x03, 0x5b, 0x52, 0xe2, 0xa8, 0xbb, 0xd9, 0x2f, 0xb1, 0xc9, 0x19, 0xb5, + 0xec, 0x3a, 0xac, 0xe1, 0x9c, 0x35, 0x83, 0xe7, 0xd1, 0x27, 0xd2, 0x59, 0xf7, 0x82, 0x0a, 0x8e, + 0x6d, 0xf1, 0x3a, 0x3a, 0x6d, 0xe5, 0x2b, 0x44, 0x05, 0xa7, 0xb6, 0xe1, 0x33, 0xb7, 0x6d, 0x4b, + 0xb5, 0x2d, 0xe1, 0xfe, 0x32, 0xb4, 0x0f, 0x44, 0x92, 0xea, 0x43, 0x59, 0x81, 0x8e, 0x2c, 0xaa, + 0x97, 0xa7, 0xd7, 0xe0, 0x22, 0x71, 0xd1, 0xe3, 0x24, 0x4d, 0xc6, 0xc9, 0x68, 0x76, 0x30, 0x3d, + 0xcc, 0x07, 0x59, 0x94, 0xa2, 0x41, 0xed, 0xff, 0x93, 0x07, 0x1b, 0x4e, 0xad, 0x8a, 0x04, 0x7c, + 0x5e, 0xb2, 0xb4, 0x79, 0x2c, 0x90, 0x8c, 0xb7, 0x6e, 0x89, 0x43, 0xd9, 0x50, 0xc6, 0x55, 0x9e, + 0xa8, 0xf7, 0x83, 0xdb, 0xb0, 0xaa, 0x67, 0xa6, 0x3b, 0x4a, 0x2e, 0xec, 0x56, 0xb9, 0x50, 0xf5, + 0x5f, 0x51, 0x1d, 0x34, 0x89, 0x5f, 0x93, 0x66, 0x29, 0x1f, 0xd2, 0x1a, 0xb5, 0x4b, 0xd8, 0xd3, + 0xfd, 0x6d, 0x5b, 0x58, 0xcf, 0x60, 0x60, 0xc0, 0xdc, 0xff, 0x23, 0x0f, 0xa0, 0x98, 0x1d, 0x32, + 0x46, 0x21, 0xd2, 0x65, 0x76, 0x94, 0x25, 0xbe, 0x5f, 0x87, 0x8e, 0x09, 0x63, 0x17, 0x5a, 0xa2, + 0xad, 0x31, 0x34, 0x60, 0xae, 0xc2, 0xea, 0x68, 0x9c, 0x1c, 0x92, 0xce, 0xa5, 0xa7, 0xcc, 0x5c, + 0xbd, 0xbf, 0xad, 0x48, 0xf8, 0x81, 0x42, 0x0b, 0x95, 0xd2, 0xb4, 0x54, 0x8a, 0xff, 0xc7, 0x0d, + 0x13, 0x16, 0x2d, 0xd6, 0x7c, 0xe6, 0x2d, 0x63, 0x3b, 0x15, 0xe1, 0x78, 0x46, 0x14, 0x92, 0x82, + 0x1f, 0xfb, 0x2f, 0xf5, 0x03, 0x6f, 0xc1, 0x4a, 0x26, 0xa5, 0x8f, 0x16, 0x4d, 0xcd, 0x17, 0x88, + 0xa6, 0xe5, 0xcc, 0xd1, 0x3b, 0xbf, 0x08, 0x6b, 0xe1, 0xf0, 0x84, 0x67, 0x22, 0x22, 0x87, 0x80, + 0x94, 0xbe, 0x14, 0xa8, 0xab, 0x16, 0x4e, 0xba, 0xf8, 0x2a, 0xac, 0xaa, 0x37, 0x4f, 0xd3, 0x52, + 0xa5, 0xbe, 0x14, 0x30, 0x36, 0xf4, 0x7f, 0xa0, 0x23, 0xb0, 0xee, 0x19, 0x9e, 0xbd, 0x23, 0xf6, + 0xea, 0x1a, 0xa5, 0xd5, 0xfd, 0x82, 0x8a, 0x86, 0x0e, 0xb5, 0xd7, 0xa1, 0xe2, 0xd2, 0x12, 0x54, + 0xd1, 0x6b, 0x77, 0x4b, 0x9b, 0xaf, 0xb2, 0xa5, 0xfe, 0xf7, 0xe6, 0x60, 0xf1, 0xfd, 0xf8, 0x24, + 0x89, 0x06, 0x14, 0x9b, 0x9c, 0xf0, 0x49, 0xa2, 0xf3, 0x0b, 0xf0, 0x37, 0x6a, 0x74, 0x7a, 0x54, + 0x4b, 0x85, 0x0a, 0x2e, 0xea, 0x22, 0x6a, 0xb7, 0xac, 0xc8, 0xb9, 0x91, 0x9c, 0x62, 0x21, 0x68, + 0x1f, 0x66, 0x76, 0xc2, 0x91, 0x2a, 0x15, 0x09, 0x1a, 0xf3, 0x56, 0x82, 0x06, 0x45, 0xb2, 0xe5, + 0x7b, 0x21, 0x6d, 0xe7, 0x52, 0xa0, 0x8b, 0x64, 0xc7, 0x66, 0x5c, 0xfa, 0xc4, 0xa4, 0x27, 0x17, + 0x95, 0x1d, 0x6b, 0x83, 0xa8, 0x4b, 0x65, 0x07, 0xd9, 0x46, 0xca, 0x1a, 0x1b, 0x42, 0xdb, 0xa2, + 0x9c, 0xb3, 0xd4, 0x92, 0x47, 0x5c, 0x82, 0x51, 0x20, 0x0d, 0xb9, 0x91, 0x1b, 0x72, 0x0d, 0x20, + 0x73, 0x8a, 0xca, 0xb8, 0x65, 0x05, 0xcb, 0x77, 0x4f, 0x55, 0x22, 0x1b, 0x24, 0x1c, 0x8f, 0x0f, + 0xc3, 0xc1, 0x53, 0xca, 0x24, 0xa3, 0x67, 0xce, 0x56, 0xe0, 0x82, 0x38, 0x6b, 0x4a, 0x8c, 0x52, + 0x24, 0x96, 0xe5, 0x33, 0xa5, 0x05, 0xf9, 0xdf, 0x00, 0x76, 0x7b, 0x38, 0x54, 0x27, 0x64, 0x7c, + 0x84, 0x62, 0x6f, 0x3d, 0x67, 0x6f, 0x6b, 0xd6, 0xd8, 0xa8, 0x5d, 0xa3, 0x7f, 0x1f, 0xda, 0xfb, + 0x56, 0x02, 0x18, 0x1d, 0xa6, 0x4e, 0xfd, 0x52, 0x0c, 0x60, 0x21, 0xd6, 0x80, 0x0d, 0x7b, 0x40, + 0xff, 0x57, 0x80, 0xed, 0x45, 0xb9, 0x30, 0xf3, 0x33, 0x9e, 0xa4, 0x09, 0x88, 0x59, 0x9e, 0xa4, + 0xc2, 0xc8, 0x93, 0xbc, 0x2d, 0x5f, 0x4b, 0xcb, 0x0b, 0xbb, 0x0e, 0x4b, 0x91, 0x84, 0xb4, 0x1c, + 0x5e, 0x51, 0x0c, 0xac, 0x5b, 0x9a, 0x7a, 0x34, 0x28, 0x14, 0xe8, 0x88, 0xf9, 0x1f, 0x7a, 0xb0, + 0xa8, 0x96, 0x86, 0xea, 0xd0, 0x49, 0x7d, 0x93, 0x0b, 0x73, 0xb0, 0xfa, 0x84, 0xa1, 0x2a, 0xd7, + 0xcd, 0xd5, 0x71, 0x1d, 0x83, 0x66, 0x1a, 0x8a, 0x63, 0xb2, 0xa0, 0x5b, 0x01, 0xfd, 0xd6, 0x9e, + 0xd2, 0x7c, 0xe1, 0x29, 0xd5, 0xe5, 0xa8, 0x49, 0x99, 0x51, 0xc1, 0xf5, 0x2b, 0xb2, 0x5a, 0x80, + 0x09, 0x80, 0xde, 0x91, 0xaf, 0xc8, 0x05, 0x5c, 0xec, 0x97, 0x22, 0x51, 0xde, 0x2f, 0xd5, 0x34, + 0x30, 0xf5, 0x7e, 0x0f, 0xba, 0xf7, 0xf8, 0x98, 0x0b, 0x7e, 0x7b, 0x3c, 0x2e, 0xd3, 0x7f, 0x0d, + 0x2e, 0xd6, 0xd4, 0x29, 0xad, 0xfa, 0x00, 0xd6, 0xef, 0xf1, 0xc3, 0xe9, 0x68, 0x8f, 0x9f, 0x14, + 0x2f, 0x0f, 0x0c, 0x9a, 0xf9, 0x71, 0x72, 0xaa, 0xce, 0x96, 0x7e, 0xa3, 0xc3, 0x3b, 0xc6, 0x36, + 0xfd, 0x3c, 0xe5, 0x03, 0x9d, 0x19, 0x43, 0xc8, 0x41, 0xca, 0x07, 0xfe, 0x3b, 0xc0, 0x6c, 0x3a, + 0x6a, 0x09, 0x78, 0x73, 0xa7, 0x87, 0xfd, 0x7c, 0x96, 0x0b, 0x3e, 0xd1, 0x29, 0x3f, 0x36, 0xe4, + 0x5f, 0x85, 0xce, 0x7e, 0x38, 0x0b, 0xf8, 0xc7, 0x2a, 0xfb, 0x10, 0x9d, 0xb7, 0x70, 0x86, 0xac, + 0x6c, 0x9c, 0x37, 0xaa, 0xf6, 0xff, 0xa1, 0x01, 0x0b, 0xb2, 0x25, 0x52, 0x1d, 0xf2, 0x5c, 0x44, + 0xb1, 0x8c, 0xd0, 0x2b, 0xaa, 0x16, 0x54, 0xe1, 0x8d, 0x46, 0x0d, 0x6f, 0x28, 0x73, 0x4a, 0xe7, + 0x17, 0x28, 0x26, 0x70, 0x30, 0xf2, 0x4d, 0xcd, 0x9b, 0x65, 0x53, 0xf9, 0xa6, 0x1a, 0x28, 0x79, + 0xc9, 0x85, 0x7c, 0x90, 0xf3, 0xd3, 0x4c, 0xab, 0xd8, 0xc1, 0x86, 0x6a, 0xa5, 0xd0, 0xa2, 0xe4, + 0x9a, 0x8a, 0x14, 0xaa, 0x48, 0x9b, 0xa5, 0x57, 0x90, 0x36, 0xd2, 0xc6, 0x72, 0xa4, 0x0d, 0x83, + 0xb5, 0x07, 0x9c, 0x07, 0x3c, 0x4d, 0x32, 0x9d, 0xc2, 0xe9, 0x7f, 0xd7, 0x83, 0x35, 0xa5, 0x3d, + 0x4c, 0x1d, 0x7b, 0xdd, 0x51, 0x35, 0x5e, 0x5d, 0xd0, 0xf6, 0x0d, 0x58, 0x26, 0x67, 0x0b, 0x3d, + 0x29, 0xf2, 0xac, 0x54, 0xfc, 0xc1, 0x01, 0x71, 0x4e, 0x3a, 0x0c, 0x39, 0x89, 0xc6, 0x6a, 0x83, + 0x6d, 0x08, 0xd5, 0xa2, 0x76, 0xc6, 0x68, 0x7b, 0xbd, 0xc0, 0x94, 0xfd, 0xbf, 0xf7, 0x60, 0xdd, + 0x9a, 0xb0, 0xe2, 0xa8, 0x5b, 0xa0, 0x5f, 0x2e, 0x65, 0x3c, 0x41, 0x5e, 0x8c, 0x0b, 0xae, 0x26, + 0x2c, 0xba, 0x39, 0x8d, 0xe9, 0x60, 0xc2, 0x19, 0x4d, 0x30, 0x9f, 0xca, 0xdc, 0xa9, 0x66, 0x60, + 0x43, 0xc8, 0x14, 0xa7, 0x9c, 0x3f, 0x35, 0x4d, 0xe6, 0xa8, 0x89, 0x83, 0xd1, 0xc3, 0x54, 0x12, + 0x8b, 0x63, 0xd3, 0x48, 0x66, 0x5c, 0xb8, 0xa0, 0xff, 0xaf, 0x1e, 0x6c, 0x48, 0x0b, 0x44, 0xd9, + 0x77, 0x26, 0xe9, 0x6a, 0x41, 0x9a, 0x5c, 0xf2, 0x76, 0xed, 0x9e, 0x0b, 0x54, 0x99, 0x7d, 0xe1, + 0x15, 0xad, 0x26, 0xf3, 0x20, 0x79, 0xc6, 0x59, 0xcc, 0xd5, 0x9d, 0xc5, 0x0b, 0x76, 0xba, 0xce, + 0x33, 0x9f, 0xaf, 0xf5, 0xcc, 0xef, 0x2c, 0xc2, 0x7c, 0x3e, 0x48, 0x52, 0xee, 0x6f, 0xc1, 0xa6, + 0xbb, 0x38, 0x25, 0x4e, 0xbe, 0xef, 0x41, 0xf7, 0x81, 0x0c, 0x2b, 0x45, 0xf1, 0x68, 0x37, 0xca, + 0x45, 0x92, 0x99, 0xb4, 0xd3, 0xcb, 0x00, 0xb9, 0x08, 0x33, 0x21, 0xd3, 0x42, 0x94, 0x4f, 0x5d, + 0x20, 0x38, 0x47, 0x1e, 0x0f, 0x65, 0xad, 0x3c, 0x1b, 0x53, 0xc6, 0x83, 0xa1, 0xc7, 0xd2, 0x7e, + 0x72, 0x74, 0x94, 0x73, 0x63, 0x23, 0xd9, 0x18, 0xba, 0x59, 0x78, 0x7b, 0xd1, 0xb1, 0xe0, 0x27, + 0x24, 0x36, 0xa5, 0x0f, 0x55, 0x42, 0xfd, 0xbf, 0xf5, 0x60, 0xb5, 0x98, 0xe4, 0x7d, 0x04, 0xdd, + 0x9b, 0x2e, 0xa7, 0x66, 0xdd, 0x74, 0xed, 0xed, 0x47, 0xc3, 0x7e, 0x14, 0xab, 0xb9, 0x59, 0x08, + 0xdd, 0x3e, 0x55, 0x4a, 0xa6, 0x3a, 0x05, 0xc7, 0x86, 0xe4, 0x2b, 0x9d, 0xc0, 0xde, 0x32, 0xff, + 0x46, 0x95, 0x28, 0xab, 0x67, 0x22, 0xa8, 0xd7, 0x82, 0x8c, 0xf1, 0xa9, 0xa2, 0xd6, 0x35, 0x8b, + 0x84, 0xe2, 0x4f, 0xff, 0x3b, 0x1e, 0x5c, 0xac, 0xd9, 0x5c, 0x75, 0x33, 0xee, 0xc1, 0xfa, 0x91, + 0xa9, 0xd4, 0x1b, 0x20, 0xaf, 0xc7, 0x96, 0xe2, 0xa2, 0xd2, 0xa2, 0x83, 0x6a, 0x07, 0xf6, 0x16, + 0xac, 0x53, 0x90, 0x42, 0x6e, 0xa9, 0xf3, 0x68, 0x5d, 0xad, 0xd8, 0xf9, 0x41, 0x03, 0x56, 0x64, + 0xcc, 0x58, 0x7e, 0x78, 0xc0, 0x33, 0xf6, 0x01, 0x2c, 0xaa, 0xcf, 0x3c, 0xd8, 0x79, 0x35, 0xac, + 0xfb, 0x61, 0x49, 0x6f, 0xab, 0x0c, 0x2b, 0xde, 0xd9, 0xf8, 0xfd, 0x1f, 0xff, 0xc7, 0x9f, 0x34, + 0x96, 0x59, 0x7b, 0xfb, 0xe4, 0xed, 0xed, 0x11, 0x8f, 0x73, 0xa4, 0xf1, 0x9b, 0x00, 0xc5, 0x97, + 0x12, 0xac, 0x6b, 0x0c, 0x86, 0xd2, 0x97, 0x1d, 0xbd, 0x8b, 0x35, 0x35, 0x8a, 0xee, 0x45, 0xa2, + 0xbb, 0xe1, 0xaf, 0x20, 0xdd, 0x28, 0x8e, 0x84, 0xfc, 0x6c, 0xe2, 0x3d, 0xef, 0x3a, 0x1b, 0x42, + 0xc7, 0xfe, 0x62, 0x82, 0x69, 0xff, 0xac, 0xe6, 0x33, 0x8c, 0xde, 0x6b, 0xb5, 0x75, 0xda, 0x39, + 0xa5, 0x31, 0xce, 0xfb, 0x6b, 0x38, 0xc6, 0x94, 0x5a, 0x98, 0x51, 0x76, 0xfe, 0xed, 0x35, 0x68, + 0x99, 0x18, 0x07, 0xfb, 0x36, 0x2c, 0x3b, 0x61, 0x76, 0xa6, 0x09, 0xd7, 0x05, 0xee, 0x7b, 0x97, + 0xea, 0x2b, 0xd5, 0xb0, 0x97, 0x69, 0xd8, 0x2e, 0xdb, 0xc2, 0x61, 0x55, 0x6c, 0x7b, 0x9b, 0xde, + 0x1f, 0x64, 0x16, 0xcf, 0x53, 0x58, 0x71, 0x43, 0xe3, 0xec, 0x92, 0x2b, 0x50, 0x4a, 0xa3, 0x7d, + 0xe6, 0x8c, 0x5a, 0x35, 0xdc, 0x25, 0x1a, 0x6e, 0x8b, 0x6d, 0xda, 0xc3, 0x99, 0xd8, 0x03, 0xa7, + 0xbc, 0x2b, 0xfb, 0x53, 0x0a, 0xf6, 0x19, 0x73, 0xd4, 0x75, 0x9f, 0x58, 0x98, 0x43, 0xab, 0x7e, + 0x67, 0xe1, 0x77, 0x69, 0x28, 0xc6, 0x68, 0x43, 0xed, 0x2f, 0x29, 0xd8, 0x47, 0xd0, 0x32, 0xe9, + 0xd3, 0xec, 0x82, 0x95, 0xb3, 0x6e, 0xe7, 0x74, 0xf7, 0xba, 0xd5, 0x8a, 0xba, 0xa3, 0xb2, 0x29, + 0x23, 0x43, 0xec, 0xc1, 0x79, 0x65, 0x70, 0x1e, 0xf2, 0x9f, 0x66, 0x25, 0x35, 0x1f, 0x80, 0xdc, + 0xf4, 0xd8, 0x2d, 0x58, 0xd2, 0x59, 0xe9, 0x6c, 0xab, 0x3e, 0xbb, 0xbe, 0x77, 0xa1, 0x82, 0xab, + 0xfb, 0x7c, 0x1b, 0xa0, 0x48, 0xa0, 0x36, 0x9c, 0x5f, 0xc9, 0xf3, 0x36, 0x9b, 0x58, 0x93, 0x6d, + 0x3d, 0xa2, 0xfc, 0x71, 0x37, 0x3f, 0x9b, 0x7d, 0xb6, 0x68, 0x5f, 0x9b, 0xb9, 0xfd, 0x02, 0x82, + 0xfe, 0x16, 0xed, 0xdd, 0x1a, 0xa3, 0xab, 0x14, 0xf3, 0x53, 0x9d, 0x81, 0x78, 0x0f, 0xda, 0x56, + 0x52, 0x36, 0xd3, 0x14, 0xaa, 0x09, 0xdd, 0xbd, 0x5e, 0x5d, 0x95, 0x9a, 0xee, 0x57, 0x60, 0xd9, + 0xc9, 0xae, 0x36, 0x37, 0xa3, 0x2e, 0x77, 0xdb, 0xdc, 0x8c, 0xfa, 0x84, 0xec, 0x6f, 0x42, 0xdb, + 0xca, 0x85, 0x66, 0x56, 0xfe, 0x46, 0x29, 0x0b, 0xda, 0xcc, 0xa8, 0x2e, 0x75, 0x7a, 0x93, 0xd6, + 0xbb, 0xe2, 0xb7, 0x70, 0xbd, 0x94, 0x86, 0x87, 0x4c, 0xf2, 0x6d, 0x58, 0x71, 0xb3, 0xa3, 0xcd, + 0xad, 0xaa, 0xcd, 0xb3, 0x36, 0xb7, 0xea, 0x8c, 0x94, 0x6a, 0xc5, 0x90, 0xd7, 0x37, 0xcc, 0x20, + 0xdb, 0x9f, 0xaa, 0x08, 0xff, 0x73, 0xf6, 0x75, 0x14, 0x1d, 0x2a, 0x2f, 0x92, 0x15, 0x39, 0xe1, + 0x6e, 0xf6, 0xa4, 0xe1, 0xf6, 0x4a, 0x0a, 0xa5, 0xbf, 0x4e, 0xc4, 0xdb, 0xac, 0x58, 0x81, 0x94, + 0xd0, 0x94, 0x1f, 0x69, 0x49, 0x68, 0x3b, 0x85, 0xd2, 0x92, 0xd0, 0x4e, 0x1a, 0x65, 0x59, 0x42, + 0x8b, 0x08, 0x69, 0xc4, 0xb0, 0x5a, 0x7a, 0xb3, 0x35, 0x97, 0xa5, 0x3e, 0xe3, 0xa3, 0x77, 0xf9, + 0xc5, 0x4f, 0xbd, 0xae, 0x98, 0xd1, 0xe2, 0x65, 0x5b, 0x27, 0xe8, 0xfc, 0x16, 0x74, 0xec, 0xa4, + 0x5b, 0x23, 0xb3, 0x6b, 0x12, 0x74, 0x8d, 0xcc, 0xae, 0xcb, 0xd2, 0xd5, 0x87, 0xcb, 0x3a, 0xf6, + 0x30, 0xec, 0x9b, 0xb0, 0x6a, 0x65, 0x07, 0x1c, 0xcc, 0xe2, 0x81, 0x61, 0x9e, 0x6a, 0x8e, 0x56, + 0xaf, 0xce, 0x3e, 0xf3, 0x2f, 0x10, 0xe1, 0x75, 0xdf, 0x21, 0x8c, 0x8c, 0x73, 0x17, 0xda, 0x76, + 0xe6, 0xc1, 0x0b, 0xe8, 0x5e, 0xb0, 0xaa, 0xec, 0xd4, 0xa6, 0x9b, 0x1e, 0xfb, 0x73, 0x0f, 0x3a, + 0x76, 0xf6, 0x1f, 0x73, 0x82, 0x8a, 0x25, 0x3a, 0x5d, 0xbb, 0xce, 0x26, 0xe4, 0x07, 0x34, 0xc9, + 0xbd, 0xeb, 0x5f, 0x71, 0x36, 0xf9, 0x53, 0xc7, 0xce, 0xbf, 0x51, 0xfe, 0x82, 0xe9, 0x79, 0xb9, + 0x81, 0x9d, 0xc7, 0xf6, 0xfc, 0xa6, 0xc7, 0xde, 0x93, 0x5f, 0xb9, 0x69, 0x1f, 0x9d, 0x59, 0xc2, + 0xad, 0xbc, 0x65, 0xf6, 0x07, 0x61, 0xd7, 0xbc, 0x9b, 0x1e, 0xfb, 0x96, 0xfc, 0x50, 0x49, 0xf5, + 0xa5, 0x9d, 0x7f, 0xd5, 0xfe, 0xfe, 0x1b, 0xb4, 0x9a, 0xcb, 0xfe, 0x45, 0x67, 0x35, 0x65, 0xe9, + 0xbe, 0x0f, 0x50, 0x04, 0x5c, 0x58, 0x29, 0xfa, 0x60, 0xe4, 0x5e, 0x35, 0x26, 0xe3, 0x9e, 0xa8, + 0x0e, 0x52, 0x20, 0xc5, 0x8f, 0x24, 0x33, 0xaa, 0xf6, 0xb9, 0x39, 0xd2, 0x6a, 0xe0, 0xa4, 0xd7, + 0xab, 0xab, 0xaa, 0x63, 0x45, 0x4d, 0x9f, 0x3d, 0x81, 0xe5, 0xbd, 0x24, 0x79, 0x3a, 0x4d, 0x4d, + 0x10, 0xcf, 0xf5, 0xff, 0x77, 0xc3, 0xfc, 0xb8, 0x57, 0x5a, 0x85, 0x7f, 0x85, 0x48, 0xf5, 0x58, + 0xd7, 0x22, 0xb5, 0xfd, 0x69, 0x11, 0xee, 0x79, 0xce, 0x42, 0x58, 0x37, 0x3a, 0xce, 0x4c, 0xbc, + 0xe7, 0x92, 0xb1, 0xa3, 0x2e, 0x95, 0x21, 0x1c, 0xab, 0x43, 0xcf, 0x76, 0x3b, 0xd7, 0x34, 0x6f, + 0x7a, 0x6c, 0x1f, 0x3a, 0xf7, 0xf8, 0x20, 0x19, 0x72, 0xe5, 0xb1, 0x6f, 0x14, 0x13, 0x37, 0xae, + 0x7e, 0x6f, 0xd9, 0x01, 0xdd, 0x5b, 0x9f, 0x86, 0xb3, 0x8c, 0x7f, 0xbc, 0xfd, 0xa9, 0x8a, 0x05, + 0x3c, 0xd7, 0xb7, 0x5e, 0xc7, 0x2f, 0x9c, 0x5b, 0x5f, 0x0a, 0x78, 0x38, 0xb7, 0xbe, 0x12, 0xf0, + 0x70, 0xb6, 0x5a, 0xc7, 0x4f, 0xd8, 0x18, 0xd6, 0x2b, 0x31, 0x12, 0xa3, 0x29, 0xcf, 0x8a, 0xac, + 0xf4, 0xae, 0x9c, 0xdd, 0xc0, 0x1d, 0xed, 0xba, 0x3b, 0xda, 0x01, 0x2c, 0xdf, 0xe3, 0x72, 0xb3, + 0xe4, 0xc3, 0x58, 0xcf, 0x15, 0x23, 0xf6, 0x23, 0x5a, 0x59, 0xc4, 0x50, 0x9d, 0x2b, 0xd6, 0xe9, + 0x55, 0x8a, 0x7d, 0x04, 0xed, 0x87, 0x5c, 0xe8, 0x97, 0x30, 0x63, 0x6f, 0x94, 0x9e, 0xc6, 0x7a, + 0x35, 0x0f, 0x69, 0x2e, 0xcf, 0x10, 0xb5, 0x6d, 0x3e, 0x1c, 0x71, 0x79, 0xd9, 0xfb, 0xd1, 0xf0, + 0x39, 0xfb, 0x75, 0x22, 0x6e, 0x1e, 0xcf, 0xb7, 0xac, 0x07, 0x14, 0x9b, 0xf8, 0x6a, 0x09, 0xaf, + 0xa3, 0x1c, 0x27, 0x43, 0x6e, 0x29, 0xb8, 0x18, 0xda, 0x56, 0xa6, 0x84, 0xb9, 0x40, 0xd5, 0xec, + 0x0c, 0x73, 0x81, 0x6a, 0x12, 0x2b, 0xfc, 0x6b, 0x34, 0x8e, 0xcf, 0xae, 0x14, 0xe3, 0xc8, 0x64, + 0x8a, 0x62, 0xa4, 0xed, 0x4f, 0xc3, 0x89, 0x78, 0xce, 0x3e, 0xa4, 0x0f, 0x06, 0xec, 0xd7, 0xbe, + 0xc2, 0xde, 0x29, 0x3f, 0x0c, 0x9a, 0xcd, 0xb2, 0xaa, 0x5c, 0x1b, 0x48, 0x0e, 0x45, 0x7a, 0xf0, + 0x0b, 0x00, 0x07, 0x22, 0x49, 0xef, 0x85, 0x7c, 0x92, 0xc4, 0x85, 0xe4, 0x2a, 0x5e, 0xb4, 0x0a, + 0xc9, 0x65, 0x3d, 0x6b, 0xb1, 0x0f, 0x2d, 0x8b, 0xd3, 0x79, 0x2c, 0xd5, 0xcc, 0x75, 0xe6, 0xa3, + 0x97, 0xd9, 0x90, 0x9a, 0x87, 0xaf, 0x9b, 0x1e, 0xda, 0x8f, 0x45, 0x44, 0xce, 0xd8, 0x8f, 0x95, + 0x60, 0x9f, 0x11, 0x7b, 0x35, 0xe1, 0xbb, 0x7d, 0x68, 0x15, 0x61, 0x21, 0xad, 0x92, 0xca, 0x41, + 0x24, 0xa3, 0x63, 0x2a, 0xc1, 0x1a, 0x7f, 0x8d, 0xb6, 0x0a, 0xd8, 0x12, 0x6e, 0x15, 0x45, 0x60, + 0x22, 0xd8, 0x90, 0x13, 0x34, 0x0a, 0x93, 0xde, 0x68, 0xf4, 0x4a, 0x6a, 0x02, 0x26, 0xe6, 0x36, + 0xd7, 0xc6, 0x1b, 0x1c, 0xdf, 0x0e, 0xb9, 0x55, 0xbe, 0x0f, 0xa1, 0x68, 0x9e, 0xc0, 0x7a, 0xc5, + 0x59, 0x36, 0x57, 0xfa, 0xac, 0x18, 0x85, 0xb9, 0xd2, 0x67, 0xfa, 0xd9, 0xfe, 0x79, 0x1a, 0x72, + 0xd5, 0x07, 0x1c, 0x32, 0x3f, 0x8d, 0xc4, 0xe0, 0xf8, 0x3d, 0xef, 0xfa, 0xe1, 0x02, 0xfd, 0x8f, + 0xc2, 0xe7, 0xfe, 0x27, 0x00, 0x00, 0xff, 0xff, 0x2a, 0xe2, 0xf7, 0xd2, 0x79, 0x41, 0x00, 0x00, } diff --git a/lnrpc/rpc.pb.gw.go b/lnrpc/rpc.pb.gw.go index b3fa5e8c..e5116aa7 100644 --- a/lnrpc/rpc.pb.gw.go +++ b/lnrpc/rpc.pb.gw.go @@ -551,6 +551,19 @@ func request_Lightning_UpdateChannelPolicy_0(ctx context.Context, marshaler runt } +func request_Lightning_ForwardingHistory_0(ctx context.Context, marshaler runtime.Marshaler, client LightningClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var protoReq ForwardingHistoryRequest + var metadata runtime.ServerMetadata + + if err := marshaler.NewDecoder(req.Body).Decode(&protoReq); err != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) + } + + msg, err := client.ForwardingHistory(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD)) + return msg, metadata, err + +} + // RegisterWalletUnlockerHandlerFromEndpoint is same as RegisterWalletUnlockerHandler but // automatically dials to "endpoint" and closes the connection when "ctx" gets done. func RegisterWalletUnlockerHandlerFromEndpoint(ctx context.Context, mux *runtime.ServeMux, endpoint string, opts []grpc.DialOption) (err error) { @@ -1529,6 +1542,35 @@ func RegisterLightningHandler(ctx context.Context, mux *runtime.ServeMux, conn * }) + mux.Handle("POST", pattern_Lightning_ForwardingHistory_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + ctx, cancel := context.WithCancel(ctx) + defer cancel() + if cn, ok := w.(http.CloseNotifier); ok { + go func(done <-chan struct{}, closed <-chan bool) { + select { + case <-done: + case <-closed: + cancel() + } + }(ctx.Done(), cn.CloseNotify()) + } + inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) + rctx, err := runtime.AnnotateContext(ctx, mux, req) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + resp, md, err := request_Lightning_ForwardingHistory_0(rctx, inboundMarshaler, client, req, pathParams) + ctx = runtime.NewServerMetadataContext(ctx, md) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + + forward_Lightning_ForwardingHistory_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + + }) + return nil } @@ -1588,6 +1630,8 @@ var ( pattern_Lightning_FeeReport_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1}, []string{"v1", "fees"}, "")) pattern_Lightning_UpdateChannelPolicy_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1}, []string{"v1", "chanpolicy"}, "")) + + pattern_Lightning_ForwardingHistory_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1}, []string{"v1", "switch"}, "")) ) var ( @@ -1646,4 +1690,6 @@ var ( forward_Lightning_FeeReport_0 = runtime.ForwardResponseMessage forward_Lightning_UpdateChannelPolicy_0 = runtime.ForwardResponseMessage + + forward_Lightning_ForwardingHistory_0 = runtime.ForwardResponseMessage ) diff --git a/lnrpc/rpc.proto b/lnrpc/rpc.proto index 1baac903..23f4cb12 100644 --- a/lnrpc/rpc.proto +++ b/lnrpc/rpc.proto @@ -531,6 +531,25 @@ service Lightning { body: "*" }; } + + /** lncli: `forwardinghistory` + ForwardingHistory allows the caller to query the htlcswitch for a record of + all HTLC's forwarded within the target time range, and integer offset + within that time range. If no time-range is specified, then the first chunk + of the past 24 hrs of forwarding history are returned. + + A list of forwarding events are returned. The size of each forwarding event + is 40 bytes, and the max message size able to be returned in gRPC is 4 MiB. + As a result each message can only contain 50k entries. Each response has + the index offset of the last entry. The index offset can be provided to the + request to allow the caller to skip a series of records. + */ + rpc ForwardingHistory(ForwardingHistoryRequest) returns (ForwardingHistoryResponse) { + option (google.api.http) = { + post: "/v1/switch" + body: "*" + }; + }; } message Transaction { @@ -1519,3 +1538,47 @@ message PolicyUpdateRequest { } message PolicyUpdateResponse { } + +message ForwardingHistoryRequest { + /// Start time is the starting point of the forwarding history request. All records beyond this point will be included, respecting the end time, and the index offset. + uint64 start_time = 1 [json_name = "start_time"]; + + /// End time is the end point of the forwarding history request. The response will carry at most 50k records between the start time and the end time. The index offset can be used to implement pagination. + uint64 end_time = 2 [json_name = "end_time"]; + + /// Index offset is the offset in the time series to start at. As each response can only contain 50k records, callers can use this to skip around within a packed time series. + uint32 index_offset = 3 [json_name = "index_offset"]; + + /// The max number of events to return in the response to this query. + uint32 num_max_events = 4 [json_name = "num_max_events"]; +} +message ForwardingEvent { + /// Timestamp is the time (unix epoch offset) that this circuit was completed. + uint64 timestamp = 1 [json_name = "timestamp"]; + + /// The incoming channel ID that carried the HTLC that created the circuit. + uint64 chan_id_in = 2 [json_name = "chan_id_in"]; + + /// The outgoing channel ID that carried the preimage that completed the circuit. + uint64 chan_id_out = 4 [json_name = "chan_id_out"]; + + /// The total amount of the incoming HTLC that created half the circuit. + uint64 amt_in = 5 [json_name = "amt_in"]; + + /// The total amount of the outgoign HTLC that created the second half of the circuit. + uint64 amt_out = 6 [json_name = "amt_out"]; + + /// The total fee that this payment circuit carried. + uint64 fee = 7 [json_name = "fee"]; + + // TODO(roasbeef): add settlement latency? + // * use FPE on the chan id? + // * also list failures? +} +message ForwardingHistoryResponse { + /// A list of forwarding events from the time slice of the time series specified in the request. + repeated ForwardingEvent forwarding_events = 1 [json_name = "forwarding_events"]; + + /// The index of the last time in the set of returned forwarding events. Can be used to seek further, pagination style. + uint32 last_offset_index = 2 [json_name = "last_offset_index"]; +} diff --git a/lnrpc/rpc.swagger.json b/lnrpc/rpc.swagger.json index 31f20465..9eda8943 100644 --- a/lnrpc/rpc.swagger.json +++ b/lnrpc/rpc.swagger.json @@ -670,6 +670,34 @@ ] } }, + "/v1/switch": { + "post": { + "summary": "* lncli: `forwardinghistory`\nForwardingHistory allows the caller to query the htlcswitch for a record of\nall HTLC's forwarded within the target time range, and integer offset\nwithin that time range. If no time-range is specified, then the first chunk\nof the past 24 hrs of forwarding history are returned.", + "description": "A list of forwarding events are returned. The size of each forwarding event\nis 40 bytes, and the max message size able to be returned in gRPC is 4 MiB.\nAs a result each message can only contain 50k entries. Each response has\nthe index offset of the last entry. The index offset can be provided to the\nrequest to allow the caller to skip a series of records.", + "operationId": "ForwardingHistory", + "responses": { + "200": { + "description": "", + "schema": { + "$ref": "#/definitions/lnrpcForwardingHistoryResponse" + } + } + }, + "parameters": [ + { + "name": "body", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/lnrpcForwardingHistoryRequest" + } + } + ], + "tags": [ + "Lightning" + ] + } + }, "/v1/transactions": { "get": { "summary": "* lncli: `listchaintxns`\nGetTransactions returns a list describing all the known transactions\nrelevant to the wallet.", @@ -1189,6 +1217,98 @@ "$ref": "#/definitions/lnrpcChannelFeeReport" }, "description": "/ An array of channel fee reports which describes the current fee schedule for each channel." + }, + "day_fee_sum": { + "type": "string", + "format": "uint64", + "description": "/ The total amount of fee revenue (in satoshis) the switch has collected over the past 24 hrs." + }, + "week_fee_sum": { + "type": "string", + "format": "uint64", + "description": "/ The total amount of fee revenue (in satoshis) the switch has collected over the past 1 week." + }, + "month_fee_sum": { + "type": "string", + "format": "uint64", + "description": "/ The total amount of fee revenue (in satoshis) the switch has collected over the past 1 month." + } + } + }, + "lnrpcForwardingEvent": { + "type": "object", + "properties": { + "timestamp": { + "type": "string", + "format": "uint64", + "description": "/ Timestamp is the time (unix epoch offset) that this circuit was completed." + }, + "chan_id_in": { + "type": "string", + "format": "uint64", + "description": "/ The incoming channel ID that carried the HTLC that created the circuit." + }, + "chan_id_out": { + "type": "string", + "format": "uint64", + "description": "/ The outgoing channel ID that carried the preimage that completed the circuit." + }, + "amt_in": { + "type": "string", + "format": "uint64", + "description": "/ The total amount of the incoming HTLC that created half the circuit." + }, + "amt_out": { + "type": "string", + "format": "uint64", + "description": "/ The total amount of the outgoign HTLC that created the second half of the circuit." + }, + "fee": { + "type": "string", + "format": "uint64", + "description": "/ The total fee that this payment circuit carried." + } + } + }, + "lnrpcForwardingHistoryRequest": { + "type": "object", + "properties": { + "start_time": { + "type": "string", + "format": "uint64", + "description": "/ Start time is the starting point of the forwarding history request. All records beyond this point will be included, respecting the end time, and the index offset." + }, + "end_time": { + "type": "string", + "format": "uint64", + "description": "/ End time is the end point of the forwarding history request. The response will carry at most 50k records between the start time and the end time. The index offset can be used to implement pagination." + }, + "index_offset": { + "type": "integer", + "format": "int64", + "description": "/ Index offset is the offset in the time series to start at. As each response can only contain 50k records, callers can use this to skip around within a packed time series." + }, + "num_max_events": { + "type": "integer", + "format": "int64", + "description": "/ The max number of events to return in the response to this query." + } + } + }, + "lnrpcForwardingHistoryResponse": { + "type": "object", + "properties": { + "forwarding_events": { + "type": "array", + "items": { + "$ref": "#/definitions/lnrpcForwardingEvent" + }, + "description": "/ A list of forwarding events from the time slice of the time series specified in the request." + }, + "last_offset_index": { + "type": "integer", + "format": "int64", + "description": "/ The index of the last time in the set of returned forwarding events. Can be used to seek further, pagination style." } } }, From a540003bb925e225767fba8d7e30a52769864c91 Mon Sep 17 00:00:00 2001 From: Olaoluwa Osuntokun Date: Tue, 27 Feb 2018 22:22:31 -0800 Subject: [PATCH 11/15] server: set the FwdingLog attribute in the switch's config --- server.go | 1 + 1 file changed, 1 insertion(+) diff --git a/server.go b/server.go index 8573b7d4..e9bec4e9 100644 --- a/server.go +++ b/server.go @@ -221,6 +221,7 @@ func newServer(listenAddrs []string, chanDB *channeldb.DB, cc *chainControl, pubKey[:], err) } }, + FwdingLog: chanDB.ForwardingLog(), }) // If external IP addresses have been specified, add those to the list From 06c1030999ff2a7f0334fe7ee559de6441cdc7f8 Mon Sep 17 00:00:00 2001 From: Olaoluwa Osuntokun Date: Tue, 27 Feb 2018 22:23:09 -0800 Subject: [PATCH 12/15] rpc: populate new day, week, and month fee sums in FeeReport --- rpcserver.go | 88 ++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 88 insertions(+) diff --git a/rpcserver.go b/rpcserver.go index 80d1d8d8..58f87973 100644 --- a/rpcserver.go +++ b/rpcserver.go @@ -3042,6 +3042,8 @@ func (r *rpcServer) FeeReport(ctx context.Context, // TODO(roasbeef): use UnaryInterceptor to add automated logging + rpcsLog.Debugf("[feereport]") + channelGraph := r.server.chanDB.ChannelGraph() selfNode, err := channelGraph.SourceNode() if err != nil { @@ -3074,8 +3076,94 @@ func (r *rpcServer) FeeReport(ctx context.Context, return nil, err } + fwdEventLog := r.server.chanDB.ForwardingLog() + + // computeFeeSum is a helper function that computes the total fees for + // a particular time slice described by a forwarding event query. + computeFeeSum := func(query channeldb.ForwardingEventQuery) (lnwire.MilliSatoshi, error) { + + var totalFees lnwire.MilliSatoshi + + // We'll continue to fetch the next query and accumulate the + // fees until the next query returns no events. + for { + timeSlice, err := fwdEventLog.Query(query) + if err != nil { + return 0, nil + } + + // If the timeslice is empty, then we'll return as + // we've retrieved all the entries in this range. + if len(timeSlice.ForwardingEvents) == 0 { + break + } + + // Otherwise, we'll tally up an accumulate the total + // fees for this time slice. + for _, event := range timeSlice.ForwardingEvents { + fee := event.AmtIn - event.AmtOut + totalFees += fee + } + + // We'll now take the last offset index returned as + // part of this response, and modify our query to start + // at this index. This has a pagination effect in the + // case that our query bounds has more than 100k + // entries. + query.IndexOffset = timeSlice.LastIndexOffset + } + + return totalFees, nil + } + + now := time.Now() + + // Before we perform the queries below, we'll instruct the switch to + // flush any pending events to disk. This ensure we get a complete + // snapshot at this particular time. + if r.server.htlcSwitch.FlushForwardingEvents(); err != nil { + return nil, fmt.Errorf("unable to flush forwarding "+ + "events: %v", err) + } + + // In addition to returning the current fee schedule for each channel. + // We'll also perform a series of queries to obtain the total fees + // earned over the past day, week, and month. + dayQuery := channeldb.ForwardingEventQuery{ + StartTime: now.Add(-time.Hour * 24), + EndTime: now, + NumMaxEvents: 1000, + } + dayFees, err := computeFeeSum(dayQuery) + if err != nil { + return nil, fmt.Errorf("unable to retrieve day fees: %v", err) + } + + weekQuery := channeldb.ForwardingEventQuery{ + StartTime: now.Add(-time.Hour * 24 * 7), + EndTime: now, + NumMaxEvents: 1000, + } + weekFees, err := computeFeeSum(weekQuery) + if err != nil { + return nil, fmt.Errorf("unable to retrieve day fees: %v", err) + } + + monthQuery := channeldb.ForwardingEventQuery{ + StartTime: now.Add(-time.Hour * 24 * 30), + EndTime: now, + NumMaxEvents: 1000, + } + monthFees, err := computeFeeSum(monthQuery) + if err != nil { + return nil, fmt.Errorf("unable to retrieve day fees: %v", err) + } + return &lnrpc.FeeReportResponse{ ChannelFees: feeReports, + DayFeeSum: uint64(dayFees.ToSatoshis()), + WeekFeeSum: uint64(weekFees.ToSatoshis()), + MonthFeeSum: uint64(monthFees.ToSatoshis()), }, nil } From 02852cdc3b6d95fb2f62f29bcd5bdbb3edf283a4 Mon Sep 17 00:00:00 2001 From: Olaoluwa Osuntokun Date: Tue, 27 Feb 2018 22:23:27 -0800 Subject: [PATCH 13/15] rpc: implement new ForwardingHistory command --- rpcserver.go | 93 ++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 93 insertions(+) diff --git a/rpcserver.go b/rpcserver.go index 58f87973..2416a31c 100644 --- a/rpcserver.go +++ b/rpcserver.go @@ -263,6 +263,10 @@ var ( Entity: "offchain", Action: "write", }}, + "/lnrpc.Lightning/ForwardingHistory": {{ + Entity: "offchain", + Action: "read", + }}, } ) @@ -3269,3 +3273,92 @@ func (r *rpcServer) UpdateChannelPolicy(ctx context.Context, return &lnrpc.PolicyUpdateResponse{}, nil } + +// ForwardingHistory allows the caller to query the htlcswitch for a record of +// all HTLC's forwarded within the target time range, and integer offset within +// that time range. If no time-range is specified, then the first chunk of the +// past 24 hrs of forwarding history are returned. + +// A list of forwarding events are returned. The size of each forwarding event +// is 40 bytes, and the max message size able to be returned in gRPC is 4 MiB. +// In order to safely stay under this max limit, we'll return 50k events per +// response. Each response has the index offset of the last entry. The index +// offset can be provided to the request to allow the caller to skip a series +// of records. +func (r *rpcServer) ForwardingHistory(ctx context.Context, + req *lnrpc.ForwardingHistoryRequest) (*lnrpc.ForwardingHistoryResponse, error) { + + rpcsLog.Debugf("[forwardinghistory]") + + // Before we perform the queries below, we'll instruct the switch to + // flush any pending events to disk. This ensure we get a complete + // snapshot at this particular time. + if err := r.server.htlcSwitch.FlushForwardingEvents(); err != nil { + return nil, fmt.Errorf("unable to flush forwarding "+ + "events: %v", err) + } + + var ( + startTime, endTime time.Time + + numEvents uint32 + ) + + // If the start and end time were not set, then we'll just return the + // records over the past 24 hours. + if req.StartTime == 0 && req.EndTime == 0 { + now := time.Now() + startTime = now.Add(-time.Hour * 24) + endTime = now + } else { + startTime = time.Unix(int64(req.StartTime), 0) + endTime = time.Unix(int64(req.EndTime), 0) + } + + // If the number of events wasn't specified, then we'll default to + // returning the last 100 events. + numEvents = req.NumMaxEvents + if numEvents == 0 { + numEvents = 100 + } + + // Next, we'll map the proto request into a format the is understood by + // the forwarding log. + eventQuery := channeldb.ForwardingEventQuery{ + StartTime: startTime, + EndTime: endTime, + IndexOffset: req.IndexOffset, + NumMaxEvents: numEvents, + } + timeSlice, err := r.server.chanDB.ForwardingLog().Query(eventQuery) + if err != nil { + return nil, fmt.Errorf("unable to query forwarding log: %v", err) + } + + // TODO(roasbeef): add settlement latency? + // * use FPE on all records? + + // With the events retrieved, we'll now map them into the proper proto + // response. + // + // TODO(roasbeef): show in ns for the outside? + resp := &lnrpc.ForwardingHistoryResponse{ + ForwardingEvents: make([]*lnrpc.ForwardingEvent, len(timeSlice.ForwardingEvents)), + LastOffsetIndex: timeSlice.LastIndexOffset, + } + for i, event := range timeSlice.ForwardingEvents { + amtInSat := event.AmtIn.ToSatoshis() + amtOutSat := event.AmtOut.ToSatoshis() + + resp.ForwardingEvents[i] = &lnrpc.ForwardingEvent{ + Timestamp: uint64(event.Timestamp.Unix()), + ChanIdIn: event.IncomingChanID.ToUint64(), + ChanIdOut: event.OutgoingChanID.ToUint64(), + AmtIn: uint64(amtInSat), + AmtOut: uint64(amtOutSat), + Fee: uint64(amtInSat - amtOutSat), + } + } + + return resp, nil +} From 643159caff1d30de810969f6f7d5d16213701922 Mon Sep 17 00:00:00 2001 From: Olaoluwa Osuntokun Date: Tue, 27 Feb 2018 22:24:15 -0800 Subject: [PATCH 14/15] test: extend testMultiHopPayments to check feereport and forwarding history --- lnd_test.go | 55 +++++++++++++++++++++++++++++++++++++++++++++++------ 1 file changed, 49 insertions(+), 6 deletions(-) diff --git a/lnd_test.go b/lnd_test.go index 28406603..e96810a5 100644 --- a/lnd_test.go +++ b/lnd_test.go @@ -117,9 +117,7 @@ func assertTxInBlock(t *harnessTest, block *wire.MsgBlock, txid *chainhash.Hash) // mineBlocks mine 'num' of blocks and check that blocks are present in // node blockchain. -func mineBlocks(t *harnessTest, net *lntest.NetworkHarness, num uint32, -) []*wire.MsgBlock { - +func mineBlocks(t *harnessTest, net *lntest.NetworkHarness, num uint32) []*wire.MsgBlock { blocks := make([]*wire.MsgBlock, num) blockHashes, err := net.Miner.Node.Generate(num) @@ -2497,6 +2495,51 @@ func testMultiHopPayments(net *lntest.NetworkHarness, t *harnessTest) { assertAmountPaid(t, ctxb, "Carol(local) => Dave(remote)", carol, carolFundPoint, amountPaid+(baseFee*numPayments)*2, int64(0)) + // Now that we know all the balances have been settled out properly, + // we'll ensure that our internal record keeping for completed circuits + // was properly updated. + + // First, check that the FeeReport response shows the proper fees + // accrued over each time range. Dave should've earned 1 satoshi for + // each of the forwarded payments. + feeReport, err := dave.FeeReport(ctxb, &lnrpc.FeeReportRequest{}) + if err != nil { + t.Fatalf("unable to query for fee report: %v", err) + } + const exectedFees = 5 + if feeReport.DayFeeSum != exectedFees { + t.Fatalf("fee mismatch: expected %v, got %v", 5, + feeReport.DayFeeSum) + } + if feeReport.WeekFeeSum != exectedFees { + t.Fatalf("fee mismatch: expected %v, got %v", 5, + feeReport.WeekFeeSum) + } + if feeReport.MonthFeeSum != exectedFees { + t.Fatalf("fee mismatch: expected %v, got %v", 5, + feeReport.MonthFeeSum) + } + + // Next, ensure that if we issue the vanilla query for the forwarding + // history, it returns 5 values, and each entry is formatted properly. + fwdingHistory, err := dave.ForwardingHistory( + ctxb, &lnrpc.ForwardingHistoryRequest{}, + ) + if err != nil { + t.Fatalf("unable to query for fee report: %v", err) + } + if len(fwdingHistory.ForwardingEvents) != 5 { + t.Fatalf("wrong number of forwarding event: expected %v, "+ + "got %v", 5, len(fwdingHistory.ForwardingEvents)) + } + for _, event := range fwdingHistory.ForwardingEvents { + // Each event should show a fee of 1 satoshi. + if event.Fee != 1 { + t.Fatalf("fee mismatch: expected %v, got %v", 1, + event.Fee) + } + } + ctxt, _ = context.WithTimeout(ctxb, timeout) closeChannelAndAssert(ctxt, t, net, net.Alice, chanPointAlice, false) ctxt, _ = context.WithTimeout(ctxb, timeout) @@ -2504,9 +2547,9 @@ func testMultiHopPayments(net *lntest.NetworkHarness, t *harnessTest) { ctxt, _ = context.WithTimeout(ctxb, timeout) closeChannelAndAssert(ctxt, t, net, carol, chanPointCarol, false) - // Finally, shutdown the nodes we created for the duration of the tests, - // only leaving the two seed nodes (Alice and Bob) within our test - // network. + // Finally, shutdown the nodes we created for the duration of the + // tests, only leaving the two seed nodes (Alice and Bob) within our + // test network. if err := net.ShutdownNode(carol); err != nil { t.Fatalf("unable to shutdown carol: %v", err) } From 8306273329ef2bde2c13605f5ae54ed1a07a24e6 Mon Sep 17 00:00:00 2001 From: Olaoluwa Osuntokun Date: Tue, 27 Feb 2018 22:24:37 -0800 Subject: [PATCH 15/15] cmd/lncli: add new fwdinghistory command --- cmd/lncli/commands.go | 114 ++++++++++++++++++++++++++++++++++++++++++ cmd/lncli/main.go | 1 + 2 files changed, 115 insertions(+) diff --git a/cmd/lncli/commands.go b/cmd/lncli/commands.go index 4092a2f3..20936a44 100644 --- a/cmd/lncli/commands.go +++ b/cmd/lncli/commands.go @@ -2427,3 +2427,117 @@ func updateChannelPolicy(ctx *cli.Context) error { printRespJSON(resp) return nil } + +var forwardingHistoryCommand = cli.Command{ + Name: "fwdinghistory", + Usage: "Query the history of all forwarded htlcs", + ArgsUsage: "start_time [end_time] [index_offset] [max_events]", + Description: ` + Query the htlc switch's internal forwarding log for all completed + payment circuits (HTLCs) over a particular time range (--start_time and + --end_time). The start and end times are meant to be expressed in + seconds since the Unix epoch. If a start and end time aren't provided, + then events over the past 24 hours are queried for. + + The max number of events returned is 50k. The default number is 100, + callers can use the --max_events param to modify this value. + + Finally, callers can skip a series of events using the --index_offset + parameter. Each response will contain the offset index of the last + entry. Using this callers can manually paginate within a time slice. + `, + Flags: []cli.Flag{ + cli.Int64Flag{ + Name: "start_time", + Usage: "the starting time for the query, expressed in " + + "seconds since the unix epoch", + }, + cli.Int64Flag{ + Name: "end_time", + Usage: "the end time for the query, expressed in " + + "seconds since the unix epoch", + }, + cli.Int64Flag{ + Name: "index_offset", + Usage: "the number of events to skip", + }, + cli.Int64Flag{ + Name: "max_events", + Usage: "the max number of events to return", + }, + }, + Action: actionDecorator(forwardingHistory), +} + +func forwardingHistory(ctx *cli.Context) error { + ctxb := context.Background() + client, cleanUp := getClient(ctx) + defer cleanUp() + + var ( + startTime, endTime uint64 + indexOffset, maxEvents uint32 + err error + ) + args := ctx.Args() + + switch { + case ctx.IsSet("start_time"): + startTime = ctx.Uint64("start_time") + case args.Present(): + startTime, err = strconv.ParseUint(args.First(), 10, 64) + if err != nil { + return fmt.Errorf("unable to decode start_time %v", err) + } + args = args.Tail() + } + + switch { + case ctx.IsSet("end_time"): + endTime = ctx.Uint64("end_time") + case args.Present(): + endTime, err = strconv.ParseUint(args.First(), 10, 64) + if err != nil { + return fmt.Errorf("unable to decode end_time: %v", err) + } + args = args.Tail() + } + + switch { + case ctx.IsSet("index_offset"): + indexOffset = uint32(ctx.Int64("index_offset")) + case args.Present(): + i, err := strconv.ParseInt(args.First(), 10, 64) + if err != nil { + return fmt.Errorf("unable to decode index_offset: %v", err) + } + indexOffset = uint32(i) + args = args.Tail() + } + + switch { + case ctx.IsSet("max_events"): + maxEvents = uint32(ctx.Int64("max_events")) + case args.Present(): + m, err := strconv.ParseInt(args.First(), 10, 64) + if err != nil { + return fmt.Errorf("unable to decode max_events: %v", err) + } + maxEvents = uint32(m) + args = args.Tail() + } + + req := &lnrpc.ForwardingHistoryRequest{ + StartTime: startTime, + EndTime: endTime, + IndexOffset: indexOffset, + NumMaxEvents: maxEvents, + } + resp, err := client.ForwardingHistory(ctxb, req) + if err != nil { + return err + } + + printRespJSON(resp) + return nil +} diff --git a/cmd/lncli/main.go b/cmd/lncli/main.go index 56b0450a..8e4794a1 100644 --- a/cmd/lncli/main.go +++ b/cmd/lncli/main.go @@ -195,6 +195,7 @@ func main() { verifyMessageCommand, feeReportCommand, updateChannelPolicyCommand, + forwardingHistoryCommand, } if err := app.Run(os.Args); err != nil {