From 8b213c76403fda6feaec58c1cb2582fd8fa15163 Mon Sep 17 00:00:00 2001 From: Elle Mouton Date: Wed, 9 Jun 2021 11:18:19 +0200 Subject: [PATCH 01/12] multi: rename USDPrice struct to Price Rename the USDPrice struct to Price so that it can be used to represent more than 1 currency. --- accounting/conversions.go | 8 ++++---- accounting/entries_test.go | 4 ++-- accounting/report.go | 2 +- fiat/coincap_api.go | 12 ++++++------ fiat/coincap_api_test.go | 4 ++-- fiat/coindesk_api.go | 10 +++++----- fiat/coindesk_api_test.go | 2 +- fiat/fiat.go | 9 +++++---- fiat/fiat_test.go | 2 +- fiat/prices.go | 12 ++++++------ fiat/prices_test.go | 16 ++++++++-------- frdrpc/exchange_rate.go | 2 +- 12 files changed, 42 insertions(+), 41 deletions(-) diff --git a/accounting/conversions.go b/accounting/conversions.go index 5c18897..24d3db1 100644 --- a/accounting/conversions.go +++ b/accounting/conversions.go @@ -11,7 +11,7 @@ import ( ) // usdPrice is a function which gets the USD price of bitcoin at a given time. -type usdPrice func(timestamp time.Time) (*fiat.USDPrice, error) +type usdPrice func(timestamp time.Time) (*fiat.Price, error) // satsToMsat converts an amount expressed in sats to msat. func satsToMsat(sats btcutil.Amount) int64 { @@ -39,8 +39,8 @@ func getConversion(ctx context.Context, startTime, endTime time.Time, // If we don't want fiat values, just return a price which will yield // a zero price and timestamp. if disableFiat { - return func(_ time.Time) (*fiat.USDPrice, error) { - return &fiat.USDPrice{}, nil + return func(_ time.Time) (*fiat.Price, error) { + return &fiat.Price{}, nil }, nil } @@ -64,7 +64,7 @@ func getConversion(ctx context.Context, startTime, endTime time.Time, // Create a wrapper function which can be used to get individual price // points from our set of price data as we create our report. - return func(ts time.Time) (*fiat.USDPrice, error) { + return func(ts time.Time) (*fiat.Price, error) { return fiat.GetPrice(prices, ts) }, nil } diff --git a/accounting/entries_test.go b/accounting/entries_test.go index 36d9087..fe45ef1 100644 --- a/accounting/entries_test.go +++ b/accounting/entries_test.go @@ -163,7 +163,7 @@ var ( mockPriceTimestamp = time.Unix(1594306589, 0) - mockBTCPrice = &fiat.USDPrice{ + mockBTCPrice = &fiat.Price{ Timestamp: mockPriceTimestamp, Price: decimal.NewFromInt(100000), } @@ -178,7 +178,7 @@ var ( ) // mockPrice is a mocked price function which returns mockPrice * amount. -func mockPrice(_ time.Time) (*fiat.USDPrice, error) { +func mockPrice(_ time.Time) (*fiat.Price, error) { return mockBTCPrice, nil } diff --git a/accounting/report.go b/accounting/report.go index f2b15de..00eac2f 100644 --- a/accounting/report.go +++ b/accounting/report.go @@ -52,7 +52,7 @@ type HarmonyEntry struct { // BTCPrice is the timestamped bitcoin price we used to get our fiat // value. - BTCPrice *fiat.USDPrice + BTCPrice *fiat.Price } // newHarmonyEntry produces a harmony entry. If provided with a negative amount, diff --git a/fiat/coincap_api.go b/fiat/coincap_api.go index 497da65..b4423d9 100644 --- a/fiat/coincap_api.go +++ b/fiat/coincap_api.go @@ -127,7 +127,7 @@ type coinCapAPI struct { // convert produces usd prices from the output of the query function. // It is set within the struct so that it can be mocked for testing. - convert func([]byte) ([]*USDPrice, error) + convert func([]byte) ([]*Price, error) } // newCoinCapAPI returns a coin cap api struct which can be used to query @@ -176,13 +176,13 @@ type coinCapDataPoint struct { // parseCoinCapData parses http response data to usc price structs, using // intermediary structs to get around parsing. -func parseCoinCapData(data []byte) ([]*USDPrice, error) { +func parseCoinCapData(data []byte) ([]*Price, error) { var priceEntries coinCapResponse if err := json.Unmarshal(data, &priceEntries); err != nil { return nil, err } - var usdRecords = make([]*USDPrice, len(priceEntries.Data)) + var usdRecords = make([]*Price, len(priceEntries.Data)) // Convert each entry from the api to a usable record with a converted // time and parsed price. @@ -193,7 +193,7 @@ func parseCoinCapData(data []byte) ([]*USDPrice, error) { } ns := time.Duration(entry.Timestamp) * time.Millisecond - usdRecords[i] = &USDPrice{ + usdRecords[i] = &Price{ Timestamp: time.Unix(0, ns.Nanoseconds()), Price: decPrice, } @@ -206,7 +206,7 @@ func parseCoinCapData(data []byte) ([]*USDPrice, error) { // requested is more than coincap will serve us in a single request, we break // our queries up into multiple chunks. func (c *coinCapAPI) rawPriceData(ctx context.Context, startTime, - endTime time.Time) ([]*USDPrice, error) { + endTime time.Time) ([]*Price, error) { // When we query prices over a range, it is likely that the first data // point we get is after our starting point, since we have discrete @@ -217,7 +217,7 @@ func (c *coinCapAPI) rawPriceData(ctx context.Context, startTime, // so that we do not have overlapping data across queries. startTime = startTime.Add(c.granularity.aggregation * -1) - var historicalRecords []*USDPrice + var historicalRecords []*Price // Create start and end vars to query one maximum length at a time. maxPeriod := c.granularity.maximumQuery diff --git a/fiat/coincap_api_test.go b/fiat/coincap_api_test.go index 2163acc..0e79693 100644 --- a/fiat/coincap_api_test.go +++ b/fiat/coincap_api_test.go @@ -74,7 +74,7 @@ func TestCoinCapGetPrices(t *testing.T) { } // Create a mocked convert function. - convert := func([]byte) ([]*USDPrice, error) { + convert := func([]byte) ([]*Price, error) { return nil, nil } @@ -189,7 +189,7 @@ func TestParseCoinCapData(t *testing.T) { prices, err := parseCoinCapData(bytes) require.NoError(t, err) - expectedPrices := []*USDPrice{ + expectedPrices := []*Price{ { Price: price1, Timestamp: time1, diff --git a/fiat/coindesk_api.go b/fiat/coindesk_api.go index 37011e0..aeeff6b 100644 --- a/fiat/coindesk_api.go +++ b/fiat/coindesk_api.go @@ -46,15 +46,15 @@ func queryCoinDesk(start, end time.Time) ([]byte, error) { return ioutil.ReadAll(response.Body) } -// parseCoinDeskData parses http response data from coindesk into USDPrice +// parseCoinDeskData parses http response data from coindesk into Price // structs. -func parseCoinDeskData(data []byte) ([]*USDPrice, error) { +func parseCoinDeskData(data []byte) ([]*Price, error) { var priceEntries coinDeskResponse if err := json.Unmarshal(data, &priceEntries); err != nil { return nil, err } - var usdRecords = make([]*USDPrice, 0, len(priceEntries.Data)) + var usdRecords = make([]*Price, 0, len(priceEntries.Data)) for date, price := range priceEntries.Data { timestamp, err := time.Parse(coinDeskTimeFormat, date) @@ -62,7 +62,7 @@ func parseCoinDeskData(data []byte) ([]*USDPrice, error) { return nil, err } - usdRecords = append(usdRecords, &USDPrice{ + usdRecords = append(usdRecords, &Price{ Timestamp: timestamp, Price: decimal.NewFromFloat(price), }) @@ -74,7 +74,7 @@ func parseCoinDeskData(data []byte) ([]*USDPrice, error) { // rawPriceData retrieves price information from coindesks's api for the given // time range. func (c *coinDeskAPI) rawPriceData(ctx context.Context, start, - end time.Time) ([]*USDPrice, error) { + end time.Time) ([]*Price, error) { query := func() ([]byte, error) { return queryCoinDesk(start, end) diff --git a/fiat/coindesk_api_test.go b/fiat/coindesk_api_test.go index 11a5c2e..0c3a612 100644 --- a/fiat/coindesk_api_test.go +++ b/fiat/coindesk_api_test.go @@ -55,7 +55,7 @@ func TestParseCoinDeskData(t *testing.T) { prices, err := parseCoinDeskData(bytes) require.NoError(t, err) - expectedPrices := []*USDPrice{ + expectedPrices := []*Price{ { Price: price, Timestamp: timestamp, diff --git a/fiat/fiat.go b/fiat/fiat.go index 87d8450..798c9cc 100644 --- a/fiat/fiat.go +++ b/fiat/fiat.go @@ -23,12 +23,13 @@ var ( errRetriesFailed = errors.New("could not get data within max retries") ) -// USDPrice represents the Bitcoin price in USD at a certain time. -type USDPrice struct { +// Price represents the Bitcoin price in USD at a certain time. +type Price struct { // Timestamp is the time at which the BTC price is quoted. Timestamp time.Time - // Price is the price in USD for 1 BTC at the given timestamp. + // Price is the fiat price for the given currency for 1 BTC at the + // given timestamp. Price decimal.Decimal } @@ -37,7 +38,7 @@ type USDPrice struct { // context passed in. It takes query and convert functions as parameters for // testing purposes. func retryQuery(ctx context.Context, queryAPI func() ([]byte, error), - convert func([]byte) ([]*USDPrice, error)) ([]*USDPrice, error) { + convert func([]byte) ([]*Price, error)) ([]*Price, error) { for i := 0; i < maxRetries; i++ { // If our request fails, log the error, sleep for the retry diff --git a/fiat/fiat_test.go b/fiat/fiat_test.go index 3f5b5ee..8de4e67 100644 --- a/fiat/fiat_test.go +++ b/fiat/fiat_test.go @@ -101,7 +101,7 @@ func TestRetryQuery(t *testing.T) { } // Create a mocked parse call which acts as a nop. - parse := func([]byte) ([]*USDPrice, error) { + parse := func([]byte) ([]*Price, error) { return nil, nil } diff --git a/fiat/prices.go b/fiat/prices.go index b92e83c..7a3c66e 100644 --- a/fiat/prices.go +++ b/fiat/prices.go @@ -28,7 +28,7 @@ var ( // is used to fetch fiat price information. type fiatBackend interface { rawPriceData(ctx context.Context, startTime, - endTime time.Time) ([]*USDPrice, error) + endTime time.Time) ([]*Price, error) } // PriceSource holds a fiatBackend that can be used to fetch fiat price @@ -41,7 +41,7 @@ type PriceSource struct { // fiatBackend implementation. GetPrices also validates the time parameters and // sorts the results. func (p PriceSource) GetPrices(ctx context.Context, startTime, - endTime time.Time) ([]*USDPrice, error) { + endTime time.Time) ([]*Price, error) { // First, check that we have a valid start and end time, and that the // range specified is not in the future. @@ -134,7 +134,7 @@ type PriceRequest struct { // GetPrices gets a set of prices for a set of timestamps. func GetPrices(ctx context.Context, timestamps []time.Time, backend PriceBackend, granularity Granularity) ( - map[time.Time]*USDPrice, error) { + map[time.Time]*Price, error) { if len(timestamps) == 0 { return nil, nil @@ -163,7 +163,7 @@ func GetPrices(ctx context.Context, timestamps []time.Time, } // Prices will map transaction timestamps to their USD prices. - var prices = make(map[time.Time]*USDPrice, len(timestamps)) + var prices = make(map[time.Time]*Price, len(timestamps)) for _, ts := range timestamps { price, err := GetPrice(priceData, ts) @@ -195,12 +195,12 @@ func MsatToUSD(price decimal.Decimal, amt lnwire.MilliSatoshi) decimal.Decimal { // querying. The last datapoint's timestamp may be before the timestamp we are // querying. If a request lies between two price points, we just return the // earlier price. -func GetPrice(prices []*USDPrice, timestamp time.Time) (*USDPrice, error) { +func GetPrice(prices []*Price, timestamp time.Time) (*Price, error) { if len(prices) == 0 { return nil, errNoPrices } - var lastPrice *USDPrice + var lastPrice *Price // Run through our prices until we find a timestamp that our price // point lies before. Since we always return the previous price, this diff --git a/fiat/prices_test.go b/fiat/prices_test.go index f9bc13b..5344b9c 100644 --- a/fiat/prices_test.go +++ b/fiat/prices_test.go @@ -18,22 +18,22 @@ func TestGetPrice(t *testing.T) { price10K := decimal.New(10000, 1) price20K := decimal.New(20000, 1) - now10k := &USDPrice{ + now10k := &Price{ Timestamp: now, Price: price10K, } - hourAgo20K := &USDPrice{ + hourAgo20K := &Price{ Timestamp: oneHourAgo, Price: price20K, } tests := []struct { name string - prices []*USDPrice + prices []*Price request time.Time expectedErr error - expectedPrice *USDPrice + expectedPrice *Price }{ { name: "no prices", @@ -43,21 +43,21 @@ func TestGetPrice(t *testing.T) { }, { name: "timestamp before range", - prices: []*USDPrice{now10k}, + prices: []*Price{now10k}, request: oneHourAgo, expectedErr: errPriceOutOfRange, expectedPrice: nil, }, { name: "timestamp equals data point timestamp", - prices: []*USDPrice{hourAgo20K, now10k}, + prices: []*Price{hourAgo20K, now10k}, request: now, expectedErr: nil, expectedPrice: now10k, }, { name: "timestamp after range", - prices: []*USDPrice{ + prices: []*Price{ { Timestamp: twoHoursAgo, Price: price10K, @@ -70,7 +70,7 @@ func TestGetPrice(t *testing.T) { }, { name: "timestamp between prices, pick earlier", - prices: []*USDPrice{hourAgo20K, now10k}, + prices: []*Price{hourAgo20K, now10k}, request: now.Add(time.Minute * -30), expectedErr: nil, expectedPrice: hourAgo20K, diff --git a/frdrpc/exchange_rate.go b/frdrpc/exchange_rate.go index 18d73ca..a1690ff 100644 --- a/frdrpc/exchange_rate.go +++ b/frdrpc/exchange_rate.go @@ -115,7 +115,7 @@ func parseExchangeRateRequest(req *ExchangeRateRequest) ([]time.Time, return timestamps, fiatBackend, granularity, nil } -func exchangeRateResponse(prices map[time.Time]*fiat.USDPrice) *ExchangeRateResponse { +func exchangeRateResponse(prices map[time.Time]*fiat.Price) *ExchangeRateResponse { fiatVals := make([]*ExchangeRate, 0, len(prices)) for ts, price := range prices { From b6404b7c400e913af139e186b2a1c8e39fc3cb66 Mon Sep 17 00:00:00 2001 From: Elle Mouton Date: Wed, 9 Jun 2021 11:25:38 +0200 Subject: [PATCH 02/12] accounting: rename usdPrice func type to fiatPrice Rename usdPrice func type to fiatPrice so that the name is appropriate for multiple fiat currencies. --- accounting/conversions.go | 6 +++--- accounting/entries.go | 4 ++-- accounting/off_chain.go | 2 +- accounting/on_chain.go | 2 +- accounting/report.go | 2 +- 5 files changed, 8 insertions(+), 8 deletions(-) diff --git a/accounting/conversions.go b/accounting/conversions.go index 24d3db1..320547b 100644 --- a/accounting/conversions.go +++ b/accounting/conversions.go @@ -10,8 +10,8 @@ import ( "github.com/lightninglabs/faraday/utils" ) -// usdPrice is a function which gets the USD price of bitcoin at a given time. -type usdPrice func(timestamp time.Time) (*fiat.Price, error) +// fiatPrice is a function which gets the fiat price of bitcoin at a given time. +type fiatPrice func(timestamp time.Time) (*fiat.Price, error) // satsToMsat converts an amount expressed in sats to msat. func satsToMsat(sats btcutil.Amount) int64 { @@ -34,7 +34,7 @@ func invertMsat(msat int64) int64 { // individual price points from this data. func getConversion(ctx context.Context, startTime, endTime time.Time, disableFiat bool, fiatBackend fiat.PriceBackend, - granularity *fiat.Granularity) (usdPrice, error) { + granularity *fiat.Granularity) (fiatPrice, error) { // If we don't want fiat values, just return a price which will yield // a zero price and timestamp. diff --git a/accounting/entries.go b/accounting/entries.go index c5db138..df3f5b0 100644 --- a/accounting/entries.go +++ b/accounting/entries.go @@ -17,9 +17,9 @@ type entryUtils struct { // may be nil. getFee getFeeFunc - // getFiat provides a USD price for the btc value provided at its + // getFiat provides a fiat price for the btc value provided at its // timestamp. - getFiat usdPrice + getFiat fiatPrice // customCategories is a set of custom categories which are set for the // report. diff --git a/accounting/off_chain.go b/accounting/off_chain.go index 557cf21..b498317 100644 --- a/accounting/off_chain.go +++ b/accounting/off_chain.go @@ -57,7 +57,7 @@ func OffChainReport(ctx context.Context, cfg *OffChainConfig) (Report, error) { // offChainReportWithPrices produces off chain reports using the getPrice // function provided. This allows testing of our report creation without calling // the actual price API. -func offChainReportWithPrices(cfg *OffChainConfig, getPrice usdPrice) (Report, +func offChainReportWithPrices(cfg *OffChainConfig, getPrice fiatPrice) (Report, error) { invoices, err := cfg.ListInvoices() diff --git a/accounting/on_chain.go b/accounting/on_chain.go index 9e90ae0..54c3e4c 100644 --- a/accounting/on_chain.go +++ b/accounting/on_chain.go @@ -77,7 +77,7 @@ func newChannelInfo(id lnwire.ShortChannelID, chanPoint *wire.OutPoint, // getOnChainInfo queries lnd for all transactions relevant to our on chain // transactions, and produces the set of information that we will need to create // an on chain report. -func getOnChainInfo(cfg *OnChainConfig, getPrice usdPrice) (*onChainInformation, +func getOnChainInfo(cfg *OnChainConfig, getPrice fiatPrice) (*onChainInformation, error) { // Create an info struct to hold all the elements we need. diff --git a/accounting/report.go b/accounting/report.go index 00eac2f..19115ac 100644 --- a/accounting/report.go +++ b/accounting/report.go @@ -63,7 +63,7 @@ type HarmonyEntry struct { // a credit. func newHarmonyEntry(ts time.Time, amountMsat int64, e EntryType, txid, reference, note, category string, onChain bool, - convert usdPrice) (*HarmonyEntry, + convert fiatPrice) (*HarmonyEntry, error) { From 4a7199f38d0936f7fb99c9703adc49acb2962bb9 Mon Sep 17 00:00:00 2001 From: Elle Mouton Date: Wed, 9 Jun 2021 11:28:35 +0200 Subject: [PATCH 03/12] multi: rename MsatToUSD func to MsatToFiat Rename the MsatToUSD functino to MsatToFiat so that the name is appropriate for multiple currencies. --- accounting/entries_test.go | 26 +++++++++++++------------- accounting/report.go | 2 +- cmd/frcli/fiat_estimate.go | 2 +- fiat/prices.go | 4 ++-- fiat/prices_test.go | 6 +++--- 5 files changed, 20 insertions(+), 20 deletions(-) diff --git a/accounting/entries_test.go b/accounting/entries_test.go index fe45ef1..71818e8 100644 --- a/accounting/entries_test.go +++ b/accounting/entries_test.go @@ -213,7 +213,7 @@ func TestChannelOpenEntry(t *testing.T) { return &HarmonyEntry{ Timestamp: transactionTimestamp, Amount: amtMsat, - FiatValue: fiat.MsatToUSD(mockBTCPrice.Price, amtMsat), + FiatValue: fiat.MsatToFiat(mockBTCPrice.Price, amtMsat), TxID: openChannelTx, Reference: fmt.Sprintf("%v", channelID), Note: note, @@ -232,7 +232,7 @@ func TestChannelOpenEntry(t *testing.T) { feeEntry := &HarmonyEntry{ Timestamp: transactionTimestamp, Amount: msatAmt, - FiatValue: fiat.MsatToUSD(mockBTCPrice.Price, msatAmt), + FiatValue: fiat.MsatToFiat(mockBTCPrice.Price, msatAmt), TxID: openChannelTx, Reference: FeeReference(openChannelTx), Note: channelOpenFeeNote(channelID), @@ -315,7 +315,7 @@ func TestChannelCloseEntry(t *testing.T) { chanEntry := &HarmonyEntry{ Timestamp: closeTimestamp, Amount: amtMsat, - FiatValue: fiat.MsatToUSD(mockBTCPrice.Price, amtMsat), + FiatValue: fiat.MsatToFiat(mockBTCPrice.Price, amtMsat), TxID: closeTx, Reference: closeTx, Note: note, @@ -332,7 +332,7 @@ func TestChannelCloseEntry(t *testing.T) { feeEntry := &HarmonyEntry{ Timestamp: closeTimestamp, Amount: mockFeeMSat, - FiatValue: fiat.MsatToUSD(mockBTCPrice.Price, mockFeeMSat), + FiatValue: fiat.MsatToFiat(mockBTCPrice.Price, mockFeeMSat), TxID: closeTx, Reference: FeeReference(closeTx), Note: "", @@ -432,7 +432,7 @@ func TestSweepEntry(t *testing.T) { sweepEntry := &HarmonyEntry{ Timestamp: onChainTimestamp, Amount: amtMsat, - FiatValue: fiat.MsatToUSD(mockBTCPrice.Price, amtMsat), + FiatValue: fiat.MsatToFiat(mockBTCPrice.Price, amtMsat), TxID: onChainTxID, Reference: onChainTxID, Note: "", @@ -447,7 +447,7 @@ func TestSweepEntry(t *testing.T) { { Timestamp: onChainTimestamp, Amount: mockFeeMSat, - FiatValue: fiat.MsatToUSD(mockBTCPrice.Price, mockFeeMSat), + FiatValue: fiat.MsatToFiat(mockBTCPrice.Price, mockFeeMSat), TxID: onChainTxID, Reference: FeeReference(onChainTxID), Note: "", @@ -527,7 +527,7 @@ func TestOnChainEntry(t *testing.T) { entry := &HarmonyEntry{ Timestamp: onChainTimestamp, Amount: amtMsat, - FiatValue: fiat.MsatToUSD(mockBTCPrice.Price, amtMsat), + FiatValue: fiat.MsatToFiat(mockBTCPrice.Price, amtMsat), TxID: onChainTxID, Reference: onChainTxID, Note: label, @@ -548,7 +548,7 @@ func TestOnChainEntry(t *testing.T) { feeEntry := &HarmonyEntry{ Timestamp: onChainTimestamp, Amount: feeMsat, - FiatValue: fiat.MsatToUSD(mockBTCPrice.Price, feeMsat), + FiatValue: fiat.MsatToFiat(mockBTCPrice.Price, feeMsat), TxID: onChainTxID, Reference: FeeReference(onChainTxID), Note: "", @@ -646,7 +646,7 @@ func TestInvoiceEntry(t *testing.T) { expectedEntry := &HarmonyEntry{ Timestamp: invoiceSettleTime, Amount: invoiceOverpaidAmt, - FiatValue: fiat.MsatToUSD( + FiatValue: fiat.MsatToFiat( mockBTCPrice.Price, invoiceOverpaidAmt, ), TxID: invoiceHash, @@ -713,7 +713,7 @@ func TestPaymentEntry(t *testing.T) { paymentEntry := &HarmonyEntry{ Timestamp: paymentTime, Amount: amtMsat, - FiatValue: fiat.MsatToUSD(mockBTCPrice.Price, amtMsat), + FiatValue: fiat.MsatToFiat(mockBTCPrice.Price, amtMsat), TxID: paymentHash, Reference: paymentRef, Note: paymentNote(&otherPubkey), @@ -728,7 +728,7 @@ func TestPaymentEntry(t *testing.T) { feeEntry := &HarmonyEntry{ Timestamp: paymentTime, Amount: feeMsat, - FiatValue: fiat.MsatToUSD(mockBTCPrice.Price, feeMsat), + FiatValue: fiat.MsatToFiat(mockBTCPrice.Price, feeMsat), TxID: paymentHash, Reference: FeeReference(paymentRef), Note: paymentNote(&otherPubkey), @@ -789,7 +789,7 @@ func TestForwardingEntry(t *testing.T) { fwdEntry := &HarmonyEntry{ Timestamp: forwardTs, Amount: 0, - FiatValue: fiat.MsatToUSD(mockBTCPrice.Price, 0), + FiatValue: fiat.MsatToFiat(mockBTCPrice.Price, 0), TxID: txid, Reference: "", Note: note, @@ -802,7 +802,7 @@ func TestForwardingEntry(t *testing.T) { feeEntry := &HarmonyEntry{ Timestamp: forwardTs, Amount: fwdFeeMsat, - FiatValue: fiat.MsatToUSD(mockBTCPrice.Price, fwdFeeMsat), + FiatValue: fiat.MsatToFiat(mockBTCPrice.Price, fwdFeeMsat), TxID: txid, Reference: "", Note: "", diff --git a/accounting/report.go b/accounting/report.go index 19115ac..156971e 100644 --- a/accounting/report.go +++ b/accounting/report.go @@ -86,7 +86,7 @@ func newHarmonyEntry(ts time.Time, amountMsat int64, e EntryType, txid, return &HarmonyEntry{ Timestamp: ts, Amount: amtMsat, - FiatValue: fiat.MsatToUSD(btcPrice.Price, amtMsat), + FiatValue: fiat.MsatToFiat(btcPrice.Price, amtMsat), TxID: txid, Reference: reference, Note: note, diff --git a/cmd/frcli/fiat_estimate.go b/cmd/frcli/fiat_estimate.go index 8db2917..afc4647 100644 --- a/cmd/frcli/fiat_estimate.go +++ b/cmd/frcli/fiat_estimate.go @@ -85,7 +85,7 @@ func queryFiatEstimate(ctx *cli.Context) error { return err } - usdVal := fiat.MsatToUSD(bitcoinPrice, lnwire.MilliSatoshi(amt)) + usdVal := fiat.MsatToFiat(bitcoinPrice, lnwire.MilliSatoshi(amt)) priceTs := time.Unix(int64(estimate.BtcPrice.PriceTimestamp), 0) fmt.Printf("%v msat = %v USD, priced at %v\n", amt, usdVal, priceTs) diff --git a/fiat/prices.go b/fiat/prices.go index 7a3c66e..b903118 100644 --- a/fiat/prices.go +++ b/fiat/prices.go @@ -177,9 +177,9 @@ func GetPrices(ctx context.Context, timestamps []time.Time, return prices, nil } -// MsatToUSD converts a msat amount to usd. Note that this function coverts +// MsatToFiat converts a msat amount to fiat. Note that this function converts // values to Bitcoin values, then gets the fiat price for that BTC value. -func MsatToUSD(price decimal.Decimal, amt lnwire.MilliSatoshi) decimal.Decimal { +func MsatToFiat(price decimal.Decimal, amt lnwire.MilliSatoshi) decimal.Decimal { msatDecimal := decimal.NewFromInt(int64(amt)) // We are quoted price per whole bitcoin. We need to scale this price diff --git a/fiat/prices_test.go b/fiat/prices_test.go index 5344b9c..be3705b 100644 --- a/fiat/prices_test.go +++ b/fiat/prices_test.go @@ -92,8 +92,8 @@ func TestGetPrice(t *testing.T) { } } -// TestMSatToUsd tests conversion of msat to usd. This -func TestMSatToUsd(t *testing.T) { +// TestMSatToFiat tests conversion of msat to fiat. This +func TestMSatToFiat(t *testing.T) { tests := []struct { name string amount lnwire.MilliSatoshi @@ -126,7 +126,7 @@ func TestMSatToUsd(t *testing.T) { t.Run(test.name, func(t *testing.T) { t.Parallel() - amt := MsatToUSD(test.price, test.amount) + amt := MsatToFiat(test.price, test.amount) if !amt.Equals(test.expectedFiat) { t.Fatalf("expected: %v, got: %v", test.expectedFiat, amt) From dffb00929fd6a5ad67087f112002ea8e0e25fdad Mon Sep 17 00:00:00 2001 From: Elle Mouton Date: Wed, 9 Jun 2021 11:40:50 +0200 Subject: [PATCH 04/12] fiat: add Currency field to the Price struct Add Currency field to the price struct so that Price can be used to represent any currency. --- fiat/coincap_api.go | 5 +++++ fiat/coincap_api_test.go | 2 ++ fiat/coindesk_api.go | 5 +++++ fiat/coindesk_api_test.go | 2 ++ fiat/fiat.go | 5 ++++- 5 files changed, 18 insertions(+), 1 deletion(-) diff --git a/fiat/coincap_api.go b/fiat/coincap_api.go index b4423d9..78cad35 100644 --- a/fiat/coincap_api.go +++ b/fiat/coincap_api.go @@ -15,6 +15,10 @@ import ( const ( // coinCapHistoryAPI is the endpoint we hit for historical price data. coinCapHistoryAPI = "https://api.coincap.io/v2/assets/bitcoin/history" + + // coinCapDefaultCurrency is the currency that the price data returned + // by the Coin Cap API is quoted in. + coinCapDefaultCurrency = "USD" ) // ErrQueryTooLong is returned when we cannot get a granularity level for a @@ -196,6 +200,7 @@ func parseCoinCapData(data []byte) ([]*Price, error) { usdRecords[i] = &Price{ Timestamp: time.Unix(0, ns.Nanoseconds()), Price: decPrice, + Currency: coinCapDefaultCurrency, } } diff --git a/fiat/coincap_api_test.go b/fiat/coincap_api_test.go index 0e79693..8cd97d4 100644 --- a/fiat/coincap_api_test.go +++ b/fiat/coincap_api_test.go @@ -193,10 +193,12 @@ func TestParseCoinCapData(t *testing.T) { { Price: price1, Timestamp: time1, + Currency: coinCapDefaultCurrency, }, { Price: price2, Timestamp: time2, + Currency: coinCapDefaultCurrency, }, } diff --git a/fiat/coindesk_api.go b/fiat/coindesk_api.go index aeeff6b..894c3f3 100644 --- a/fiat/coindesk_api.go +++ b/fiat/coindesk_api.go @@ -17,6 +17,10 @@ const ( // coinDeskTimeFormat is the date format used by coindesk. coinDeskTimeFormat = "2006-01-02" + + // coinDeskDefaultCurrency is the default currency that the price data + // returned by the Coin Desk API is quoted in. + coinDeskDefaultCurrency = "USD" ) // coinDeskAPI implements the fiatBackend interface. @@ -65,6 +69,7 @@ func parseCoinDeskData(data []byte) ([]*Price, error) { usdRecords = append(usdRecords, &Price{ Timestamp: timestamp, Price: decimal.NewFromFloat(price), + Currency: coinDeskDefaultCurrency, }) } diff --git a/fiat/coindesk_api_test.go b/fiat/coindesk_api_test.go index 0c3a612..a648210 100644 --- a/fiat/coindesk_api_test.go +++ b/fiat/coindesk_api_test.go @@ -59,11 +59,13 @@ func TestParseCoinDeskData(t *testing.T) { { Price: price, Timestamp: timestamp, + Currency: coinDeskDefaultCurrency, }, } require.True(t, expectedPrices[0].Price.Equal(prices[0].Price)) require.True(t, expectedPrices[0].Timestamp.Equal(prices[0].Timestamp)) + require.Equal(t, expectedPrices[0].Currency, prices[0].Currency) }) } } diff --git a/fiat/fiat.go b/fiat/fiat.go index 798c9cc..2fb0edc 100644 --- a/fiat/fiat.go +++ b/fiat/fiat.go @@ -23,7 +23,7 @@ var ( errRetriesFailed = errors.New("could not get data within max retries") ) -// Price represents the Bitcoin price in USD at a certain time. +// Price represents the Bitcoin price in the given currency at a certain time. type Price struct { // Timestamp is the time at which the BTC price is quoted. Timestamp time.Time @@ -31,6 +31,9 @@ type Price struct { // Price is the fiat price for the given currency for 1 BTC at the // given timestamp. Price decimal.Decimal + + // Currency is the code of the currency that the Price is quoted in. + Currency string } // retryQuery calls an api until it succeeds, or we hit our maximum retries. From 36188ebdfe4ba4b64ab2958749565614a39e2692 Mon Sep 17 00:00:00 2001 From: Elle Mouton Date: Wed, 9 Jun 2021 12:13:03 +0200 Subject: [PATCH 05/12] multi: update RPC to show the fiat currency Update the RPC methods (exchange rate and node report) so that they show the fiat currency that the bitcoin price is quoted in. --- cmd/frcli/csv.go | 2 +- cmd/frcli/fiat_estimate.go | 5 +- cmd/frcli/node_audit.go | 10 +- docs/accounting.md | 2 +- fiat/prices.go | 2 +- frdrpc/exchange_rate.go | 1 + frdrpc/faraday.pb.go | 320 +++++++++++++++++++----------------- frdrpc/faraday.proto | 8 +- frdrpc/faraday.swagger.json | 8 +- frdrpc/node_audit.go | 3 +- 10 files changed, 196 insertions(+), 165 deletions(-) diff --git a/cmd/frcli/csv.go b/cmd/frcli/csv.go index d483620..9b21a52 100644 --- a/cmd/frcli/csv.go +++ b/cmd/frcli/csv.go @@ -8,7 +8,7 @@ import ( ) // CSVHeaders returns the headers used for harmony csv records. -var CSVHeaders = "Timestamp,OnChain,Type,Category,Amount(Msat),Amount(USD),TxID,Reference,BTCPrice,BTCTimestamp,Note" +var CSVHeaders = "Timestamp,OnChain,Type,Category,Amount(Msat),Amount(%v),TxID,Reference,BTCPrice,BTCTimestamp,Note" // csv returns a csv string of the values contained in a rpc entry. For ease // of use, the credit field is used to set a negative sign (-) on the amount diff --git a/cmd/frcli/fiat_estimate.go b/cmd/frcli/fiat_estimate.go index afc4647..798c039 100644 --- a/cmd/frcli/fiat_estimate.go +++ b/cmd/frcli/fiat_estimate.go @@ -85,10 +85,11 @@ func queryFiatEstimate(ctx *cli.Context) error { return err } - usdVal := fiat.MsatToFiat(bitcoinPrice, lnwire.MilliSatoshi(amt)) + fiatVal := fiat.MsatToFiat(bitcoinPrice, lnwire.MilliSatoshi(amt)) priceTs := time.Unix(int64(estimate.BtcPrice.PriceTimestamp), 0) - fmt.Printf("%v msat = %v USD, priced at %v\n", amt, usdVal, priceTs) + fmt.Printf("%v msat = %v %s, priced at %v\n", + amt, fiatVal, estimate.BtcPrice.Currency, priceTs) return nil } diff --git a/cmd/frcli/node_audit.go b/cmd/frcli/node_audit.go index 38311c7..cd7874d 100644 --- a/cmd/frcli/node_audit.go +++ b/cmd/frcli/node_audit.go @@ -194,7 +194,15 @@ func queryOnChainReport(ctx *cli.Context) error { } }() - csvStrs := []string{CSVHeaders} + var headers string + if len(report.Reports) > 0 { + headers = fmt.Sprintf( + CSVHeaders, + report.Reports[0].BtcPrice.Currency, + ) + } + + csvStrs := []string{headers} for _, report := range report.Reports { csvStrs = append(csvStrs, csv(report)) } diff --git a/docs/accounting.md b/docs/accounting.md index 382f22e..891d687 100644 --- a/docs/accounting.md +++ b/docs/accounting.md @@ -9,7 +9,7 @@ It is strongly recommended that Faraday is run with a connection to a Bitcon nod ## Common Fields For brevity, the following fields which have the same meaning for each entry will be omitted: - Timestamp: The timestamp of the block that the channel open transaction appeared in. -- Fiat: The value of the amount field in USD. Note that values less than one satoshi will be rounded down to zero. +- Fiat: The value of the amount field in specified currency. Note that values less than one satoshi will be rounded down to zero. - OnChain: Whether the transaction occurred off chain, or on chain. - Credit: True when an entry increased our balances, false when an entry decreased our balances. diff --git a/fiat/prices.go b/fiat/prices.go index b903118..88dc3e5 100644 --- a/fiat/prices.go +++ b/fiat/prices.go @@ -162,7 +162,7 @@ func GetPrices(ctx context.Context, timestamps []time.Time, return nil, err } - // Prices will map transaction timestamps to their USD prices. + // Prices will map transaction timestamps to their fiat prices. var prices = make(map[time.Time]*Price, len(timestamps)) for _, ts := range timestamps { diff --git a/frdrpc/exchange_rate.go b/frdrpc/exchange_rate.go index a1690ff..ff25e73 100644 --- a/frdrpc/exchange_rate.go +++ b/frdrpc/exchange_rate.go @@ -124,6 +124,7 @@ func exchangeRateResponse(prices map[time.Time]*fiat.Price) *ExchangeRateRespons BtcPrice: &BitcoinPrice{ Price: price.Price.String(), PriceTimestamp: uint64(price.Timestamp.Unix()), + Currency: price.Currency, }, }) } diff --git a/frdrpc/faraday.pb.go b/frdrpc/faraday.pb.go index 51cc088..e044750 100644 --- a/frdrpc/faraday.pb.go +++ b/frdrpc/faraday.pb.go @@ -1264,6 +1264,8 @@ type BitcoinPrice struct { Price string `protobuf:"bytes,1,opt,name=price,proto3" json:"price,omitempty"` // The timestamp for this price price provided. PriceTimestamp uint64 `protobuf:"varint,2,opt,name=price_timestamp,json=priceTimestamp,proto3" json:"price_timestamp,omitempty"` + // The currency that the price is denoted in. + Currency string `protobuf:"bytes,3,opt,name=currency,proto3" json:"currency,omitempty"` } func (x *BitcoinPrice) Reset() { @@ -1312,6 +1314,13 @@ func (x *BitcoinPrice) GetPriceTimestamp() uint64 { return 0 } +func (x *BitcoinPrice) GetCurrency() string { + if x != nil { + return x.Currency + } + return "" +} + type ExchangeRate struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache @@ -1585,13 +1594,14 @@ type ReportEntry struct { CustomCategory string `protobuf:"bytes,12,opt,name=custom_category,json=customCategory,proto3" json:"custom_category,omitempty"` // The transaction id of the entry. Txid string `protobuf:"bytes,7,opt,name=txid,proto3" json:"txid,omitempty"` - // The fiat amount of the entry's amount in USD. + // The fiat amount of the entry's amount in the currency specified in the + // btc_price field. Fiat string `protobuf:"bytes,8,opt,name=fiat,proto3" json:"fiat,omitempty"` // A unique identifier for the entry, if available. Reference string `protobuf:"bytes,9,opt,name=reference,proto3" json:"reference,omitempty"` // An additional note for the entry, providing additional context. Note string `protobuf:"bytes,10,opt,name=note,proto3" json:"note,omitempty"` - // The bitcoin price and timestamp used to calcualte our fiat value. + // The bitcoin price and timestamp used to calculate our fiat value. BtcPrice *BitcoinPrice `protobuf:"bytes,11,opt,name=btc_price,json=btcPrice,proto3" json:"btc_price,omitempty"` } @@ -2046,163 +2056,165 @@ var file_faraday_proto_rawDesc = []byte{ 0x12, 0x2a, 0x0a, 0x05, 0x72, 0x61, 0x74, 0x65, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x14, 0x2e, 0x66, 0x72, 0x64, 0x72, 0x70, 0x63, 0x2e, 0x45, 0x78, 0x63, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x52, 0x61, 0x74, 0x65, 0x52, 0x05, 0x72, 0x61, 0x74, 0x65, 0x73, 0x4a, 0x04, 0x08, 0x01, - 0x10, 0x02, 0x22, 0x4d, 0x0a, 0x0c, 0x42, 0x69, 0x74, 0x63, 0x6f, 0x69, 0x6e, 0x50, 0x72, 0x69, + 0x10, 0x02, 0x22, 0x69, 0x0a, 0x0c, 0x42, 0x69, 0x74, 0x63, 0x6f, 0x69, 0x6e, 0x50, 0x72, 0x69, 0x63, 0x65, 0x12, 0x14, 0x0a, 0x05, 0x70, 0x72, 0x69, 0x63, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x70, 0x72, 0x69, 0x63, 0x65, 0x12, 0x27, 0x0a, 0x0f, 0x70, 0x72, 0x69, 0x63, 0x65, 0x5f, 0x74, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x18, 0x02, 0x20, 0x01, 0x28, 0x04, 0x52, 0x0e, 0x70, 0x72, 0x69, 0x63, 0x65, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, - 0x70, 0x22, 0x5f, 0x0a, 0x0c, 0x45, 0x78, 0x63, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x52, 0x61, 0x74, - 0x65, 0x12, 0x1c, 0x0a, 0x09, 0x74, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x18, 0x01, - 0x20, 0x01, 0x28, 0x04, 0x52, 0x09, 0x74, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x12, - 0x31, 0x0a, 0x09, 0x62, 0x74, 0x63, 0x5f, 0x70, 0x72, 0x69, 0x63, 0x65, 0x18, 0x02, 0x20, 0x01, - 0x28, 0x0b, 0x32, 0x14, 0x2e, 0x66, 0x72, 0x64, 0x72, 0x70, 0x63, 0x2e, 0x42, 0x69, 0x74, 0x63, - 0x6f, 0x69, 0x6e, 0x50, 0x72, 0x69, 0x63, 0x65, 0x52, 0x08, 0x62, 0x74, 0x63, 0x50, 0x72, 0x69, - 0x63, 0x65, 0x22, 0xa9, 0x02, 0x0a, 0x10, 0x4e, 0x6f, 0x64, 0x65, 0x41, 0x75, 0x64, 0x69, 0x74, - 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x1d, 0x0a, 0x0a, 0x73, 0x74, 0x61, 0x72, 0x74, - 0x5f, 0x74, 0x69, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x04, 0x52, 0x09, 0x73, 0x74, 0x61, - 0x72, 0x74, 0x54, 0x69, 0x6d, 0x65, 0x12, 0x19, 0x0a, 0x08, 0x65, 0x6e, 0x64, 0x5f, 0x74, 0x69, - 0x6d, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x04, 0x52, 0x07, 0x65, 0x6e, 0x64, 0x54, 0x69, 0x6d, - 0x65, 0x12, 0x21, 0x0a, 0x0c, 0x64, 0x69, 0x73, 0x61, 0x62, 0x6c, 0x65, 0x5f, 0x66, 0x69, 0x61, - 0x74, 0x18, 0x04, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0b, 0x64, 0x69, 0x73, 0x61, 0x62, 0x6c, 0x65, - 0x46, 0x69, 0x61, 0x74, 0x12, 0x35, 0x0a, 0x0b, 0x67, 0x72, 0x61, 0x6e, 0x75, 0x6c, 0x61, 0x72, - 0x69, 0x74, 0x79, 0x18, 0x05, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x13, 0x2e, 0x66, 0x72, 0x64, 0x72, - 0x70, 0x63, 0x2e, 0x47, 0x72, 0x61, 0x6e, 0x75, 0x6c, 0x61, 0x72, 0x69, 0x74, 0x79, 0x52, 0x0b, - 0x67, 0x72, 0x61, 0x6e, 0x75, 0x6c, 0x61, 0x72, 0x69, 0x74, 0x79, 0x12, 0x43, 0x0a, 0x11, 0x63, - 0x75, 0x73, 0x74, 0x6f, 0x6d, 0x5f, 0x63, 0x61, 0x74, 0x65, 0x67, 0x6f, 0x72, 0x69, 0x65, 0x73, - 0x18, 0x06, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x16, 0x2e, 0x66, 0x72, 0x64, 0x72, 0x70, 0x63, 0x2e, - 0x43, 0x75, 0x73, 0x74, 0x6f, 0x6d, 0x43, 0x61, 0x74, 0x65, 0x67, 0x6f, 0x72, 0x79, 0x52, 0x10, - 0x63, 0x75, 0x73, 0x74, 0x6f, 0x6d, 0x43, 0x61, 0x74, 0x65, 0x67, 0x6f, 0x72, 0x69, 0x65, 0x73, - 0x12, 0x36, 0x0a, 0x0c, 0x66, 0x69, 0x61, 0x74, 0x5f, 0x62, 0x61, 0x63, 0x6b, 0x65, 0x6e, 0x64, - 0x18, 0x07, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x13, 0x2e, 0x66, 0x72, 0x64, 0x72, 0x70, 0x63, 0x2e, - 0x46, 0x69, 0x61, 0x74, 0x42, 0x61, 0x63, 0x6b, 0x65, 0x6e, 0x64, 0x52, 0x0b, 0x66, 0x69, 0x61, - 0x74, 0x42, 0x61, 0x63, 0x6b, 0x65, 0x6e, 0x64, 0x4a, 0x04, 0x08, 0x03, 0x10, 0x04, 0x22, 0x83, - 0x01, 0x0a, 0x0e, 0x43, 0x75, 0x73, 0x74, 0x6f, 0x6d, 0x43, 0x61, 0x74, 0x65, 0x67, 0x6f, 0x72, - 0x79, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, - 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x19, 0x0a, 0x08, 0x6f, 0x6e, 0x5f, 0x63, 0x68, 0x61, 0x69, - 0x6e, 0x18, 0x02, 0x20, 0x01, 0x28, 0x08, 0x52, 0x07, 0x6f, 0x6e, 0x43, 0x68, 0x61, 0x69, 0x6e, - 0x12, 0x1b, 0x0a, 0x09, 0x6f, 0x66, 0x66, 0x5f, 0x63, 0x68, 0x61, 0x69, 0x6e, 0x18, 0x03, 0x20, - 0x01, 0x28, 0x08, 0x52, 0x08, 0x6f, 0x66, 0x66, 0x43, 0x68, 0x61, 0x69, 0x6e, 0x12, 0x25, 0x0a, - 0x0e, 0x6c, 0x61, 0x62, 0x65, 0x6c, 0x5f, 0x70, 0x61, 0x74, 0x74, 0x65, 0x72, 0x6e, 0x73, 0x18, - 0x05, 0x20, 0x03, 0x28, 0x09, 0x52, 0x0d, 0x6c, 0x61, 0x62, 0x65, 0x6c, 0x50, 0x61, 0x74, 0x74, - 0x65, 0x72, 0x6e, 0x73, 0x22, 0xe9, 0x02, 0x0a, 0x0b, 0x52, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x45, - 0x6e, 0x74, 0x72, 0x79, 0x12, 0x1c, 0x0a, 0x09, 0x74, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, - 0x70, 0x18, 0x01, 0x20, 0x01, 0x28, 0x04, 0x52, 0x09, 0x74, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, - 0x6d, 0x70, 0x12, 0x19, 0x0a, 0x08, 0x6f, 0x6e, 0x5f, 0x63, 0x68, 0x61, 0x69, 0x6e, 0x18, 0x02, - 0x20, 0x01, 0x28, 0x08, 0x52, 0x07, 0x6f, 0x6e, 0x43, 0x68, 0x61, 0x69, 0x6e, 0x12, 0x16, 0x0a, - 0x06, 0x61, 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x03, 0x20, 0x01, 0x28, 0x04, 0x52, 0x06, 0x61, - 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x16, 0x0a, 0x06, 0x63, 0x72, 0x65, 0x64, 0x69, 0x74, 0x18, - 0x04, 0x20, 0x01, 0x28, 0x08, 0x52, 0x06, 0x63, 0x72, 0x65, 0x64, 0x69, 0x74, 0x12, 0x14, 0x0a, - 0x05, 0x61, 0x73, 0x73, 0x65, 0x74, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x61, 0x73, - 0x73, 0x65, 0x74, 0x12, 0x25, 0x0a, 0x04, 0x74, 0x79, 0x70, 0x65, 0x18, 0x06, 0x20, 0x01, 0x28, - 0x0e, 0x32, 0x11, 0x2e, 0x66, 0x72, 0x64, 0x72, 0x70, 0x63, 0x2e, 0x45, 0x6e, 0x74, 0x72, 0x79, - 0x54, 0x79, 0x70, 0x65, 0x52, 0x04, 0x74, 0x79, 0x70, 0x65, 0x12, 0x27, 0x0a, 0x0f, 0x63, 0x75, - 0x73, 0x74, 0x6f, 0x6d, 0x5f, 0x63, 0x61, 0x74, 0x65, 0x67, 0x6f, 0x72, 0x79, 0x18, 0x0c, 0x20, - 0x01, 0x28, 0x09, 0x52, 0x0e, 0x63, 0x75, 0x73, 0x74, 0x6f, 0x6d, 0x43, 0x61, 0x74, 0x65, 0x67, - 0x6f, 0x72, 0x79, 0x12, 0x12, 0x0a, 0x04, 0x74, 0x78, 0x69, 0x64, 0x18, 0x07, 0x20, 0x01, 0x28, - 0x09, 0x52, 0x04, 0x74, 0x78, 0x69, 0x64, 0x12, 0x12, 0x0a, 0x04, 0x66, 0x69, 0x61, 0x74, 0x18, - 0x08, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x66, 0x69, 0x61, 0x74, 0x12, 0x1c, 0x0a, 0x09, 0x72, - 0x65, 0x66, 0x65, 0x72, 0x65, 0x6e, 0x63, 0x65, 0x18, 0x09, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, - 0x72, 0x65, 0x66, 0x65, 0x72, 0x65, 0x6e, 0x63, 0x65, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x6f, 0x74, - 0x65, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, 0x6f, 0x74, 0x65, 0x12, 0x31, 0x0a, - 0x09, 0x62, 0x74, 0x63, 0x5f, 0x70, 0x72, 0x69, 0x63, 0x65, 0x18, 0x0b, 0x20, 0x01, 0x28, 0x0b, - 0x32, 0x14, 0x2e, 0x66, 0x72, 0x64, 0x72, 0x70, 0x63, 0x2e, 0x42, 0x69, 0x74, 0x63, 0x6f, 0x69, - 0x6e, 0x50, 0x72, 0x69, 0x63, 0x65, 0x52, 0x08, 0x62, 0x74, 0x63, 0x50, 0x72, 0x69, 0x63, 0x65, - 0x22, 0x42, 0x0a, 0x11, 0x4e, 0x6f, 0x64, 0x65, 0x41, 0x75, 0x64, 0x69, 0x74, 0x52, 0x65, 0x73, - 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x2d, 0x0a, 0x07, 0x72, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x73, - 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x13, 0x2e, 0x66, 0x72, 0x64, 0x72, 0x70, 0x63, 0x2e, - 0x52, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x52, 0x07, 0x72, 0x65, 0x70, - 0x6f, 0x72, 0x74, 0x73, 0x22, 0x39, 0x0a, 0x12, 0x43, 0x6c, 0x6f, 0x73, 0x65, 0x52, 0x65, 0x70, - 0x6f, 0x72, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x23, 0x0a, 0x0d, 0x63, 0x68, - 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x5f, 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, - 0x09, 0x52, 0x0c, 0x63, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x50, 0x6f, 0x69, 0x6e, 0x74, 0x22, - 0xdd, 0x01, 0x0a, 0x13, 0x43, 0x6c, 0x6f, 0x73, 0x65, 0x52, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x52, - 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x23, 0x0a, 0x0d, 0x63, 0x68, 0x61, 0x6e, 0x6e, - 0x65, 0x6c, 0x5f, 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0c, - 0x63, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x50, 0x6f, 0x69, 0x6e, 0x74, 0x12, 0x2b, 0x0a, 0x11, - 0x63, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x5f, 0x69, 0x6e, 0x69, 0x74, 0x69, 0x61, 0x74, 0x6f, - 0x72, 0x18, 0x02, 0x20, 0x01, 0x28, 0x08, 0x52, 0x10, 0x63, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, - 0x49, 0x6e, 0x69, 0x74, 0x69, 0x61, 0x74, 0x6f, 0x72, 0x12, 0x1d, 0x0a, 0x0a, 0x63, 0x6c, 0x6f, - 0x73, 0x65, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x63, - 0x6c, 0x6f, 0x73, 0x65, 0x54, 0x79, 0x70, 0x65, 0x12, 0x1d, 0x0a, 0x0a, 0x63, 0x6c, 0x6f, 0x73, - 0x65, 0x5f, 0x74, 0x78, 0x69, 0x64, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x63, 0x6c, - 0x6f, 0x73, 0x65, 0x54, 0x78, 0x69, 0x64, 0x12, 0x19, 0x0a, 0x08, 0x6f, 0x70, 0x65, 0x6e, 0x5f, - 0x66, 0x65, 0x65, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x6f, 0x70, 0x65, 0x6e, 0x46, - 0x65, 0x65, 0x12, 0x1b, 0x0a, 0x09, 0x63, 0x6c, 0x6f, 0x73, 0x65, 0x5f, 0x66, 0x65, 0x65, 0x18, - 0x06, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x63, 0x6c, 0x6f, 0x73, 0x65, 0x46, 0x65, 0x65, 0x2a, - 0xa1, 0x01, 0x0a, 0x0b, 0x47, 0x72, 0x61, 0x6e, 0x75, 0x6c, 0x61, 0x72, 0x69, 0x74, 0x79, 0x12, - 0x17, 0x0a, 0x13, 0x55, 0x4e, 0x4b, 0x4e, 0x4f, 0x57, 0x4e, 0x5f, 0x47, 0x52, 0x41, 0x4e, 0x55, - 0x4c, 0x41, 0x52, 0x49, 0x54, 0x59, 0x10, 0x00, 0x12, 0x0a, 0x0a, 0x06, 0x4d, 0x49, 0x4e, 0x55, - 0x54, 0x45, 0x10, 0x01, 0x12, 0x10, 0x0a, 0x0c, 0x46, 0x49, 0x56, 0x45, 0x5f, 0x4d, 0x49, 0x4e, - 0x55, 0x54, 0x45, 0x53, 0x10, 0x02, 0x12, 0x13, 0x0a, 0x0f, 0x46, 0x49, 0x46, 0x54, 0x45, 0x45, - 0x4e, 0x5f, 0x4d, 0x49, 0x4e, 0x55, 0x54, 0x45, 0x53, 0x10, 0x03, 0x12, 0x12, 0x0a, 0x0e, 0x54, - 0x48, 0x49, 0x52, 0x54, 0x59, 0x5f, 0x4d, 0x49, 0x4e, 0x55, 0x54, 0x45, 0x53, 0x10, 0x04, 0x12, - 0x08, 0x0a, 0x04, 0x48, 0x4f, 0x55, 0x52, 0x10, 0x05, 0x12, 0x0d, 0x0a, 0x09, 0x53, 0x49, 0x58, - 0x5f, 0x48, 0x4f, 0x55, 0x52, 0x53, 0x10, 0x06, 0x12, 0x10, 0x0a, 0x0c, 0x54, 0x57, 0x45, 0x4c, - 0x56, 0x45, 0x5f, 0x48, 0x4f, 0x55, 0x52, 0x53, 0x10, 0x07, 0x12, 0x07, 0x0a, 0x03, 0x44, 0x41, - 0x59, 0x10, 0x08, 0x2a, 0x41, 0x0a, 0x0b, 0x46, 0x69, 0x61, 0x74, 0x42, 0x61, 0x63, 0x6b, 0x65, - 0x6e, 0x64, 0x12, 0x17, 0x0a, 0x13, 0x55, 0x4e, 0x4b, 0x4e, 0x4f, 0x57, 0x4e, 0x5f, 0x46, 0x49, - 0x41, 0x54, 0x42, 0x41, 0x43, 0x4b, 0x45, 0x4e, 0x44, 0x10, 0x00, 0x12, 0x0b, 0x0a, 0x07, 0x43, - 0x4f, 0x49, 0x4e, 0x43, 0x41, 0x50, 0x10, 0x01, 0x12, 0x0c, 0x0a, 0x08, 0x43, 0x4f, 0x49, 0x4e, - 0x44, 0x45, 0x53, 0x4b, 0x10, 0x02, 0x2a, 0xa2, 0x02, 0x0a, 0x09, 0x45, 0x6e, 0x74, 0x72, 0x79, - 0x54, 0x79, 0x70, 0x65, 0x12, 0x0b, 0x0a, 0x07, 0x55, 0x4e, 0x4b, 0x4e, 0x4f, 0x57, 0x4e, 0x10, - 0x00, 0x12, 0x16, 0x0a, 0x12, 0x4c, 0x4f, 0x43, 0x41, 0x4c, 0x5f, 0x43, 0x48, 0x41, 0x4e, 0x4e, - 0x45, 0x4c, 0x5f, 0x4f, 0x50, 0x45, 0x4e, 0x10, 0x01, 0x12, 0x17, 0x0a, 0x13, 0x52, 0x45, 0x4d, - 0x4f, 0x54, 0x45, 0x5f, 0x43, 0x48, 0x41, 0x4e, 0x4e, 0x45, 0x4c, 0x5f, 0x4f, 0x50, 0x45, 0x4e, - 0x10, 0x02, 0x12, 0x14, 0x0a, 0x10, 0x43, 0x48, 0x41, 0x4e, 0x4e, 0x45, 0x4c, 0x5f, 0x4f, 0x50, - 0x45, 0x4e, 0x5f, 0x46, 0x45, 0x45, 0x10, 0x03, 0x12, 0x11, 0x0a, 0x0d, 0x43, 0x48, 0x41, 0x4e, - 0x4e, 0x45, 0x4c, 0x5f, 0x43, 0x4c, 0x4f, 0x53, 0x45, 0x10, 0x04, 0x12, 0x0b, 0x0a, 0x07, 0x52, - 0x45, 0x43, 0x45, 0x49, 0x50, 0x54, 0x10, 0x05, 0x12, 0x0b, 0x0a, 0x07, 0x50, 0x41, 0x59, 0x4d, - 0x45, 0x4e, 0x54, 0x10, 0x06, 0x12, 0x07, 0x0a, 0x03, 0x46, 0x45, 0x45, 0x10, 0x07, 0x12, 0x14, - 0x0a, 0x10, 0x43, 0x49, 0x52, 0x43, 0x55, 0x4c, 0x41, 0x52, 0x5f, 0x52, 0x45, 0x43, 0x45, 0x49, - 0x50, 0x54, 0x10, 0x08, 0x12, 0x0b, 0x0a, 0x07, 0x46, 0x4f, 0x52, 0x57, 0x41, 0x52, 0x44, 0x10, - 0x09, 0x12, 0x0f, 0x0a, 0x0b, 0x46, 0x4f, 0x52, 0x57, 0x41, 0x52, 0x44, 0x5f, 0x46, 0x45, 0x45, - 0x10, 0x0a, 0x12, 0x14, 0x0a, 0x10, 0x43, 0x49, 0x52, 0x43, 0x55, 0x4c, 0x41, 0x52, 0x5f, 0x50, - 0x41, 0x59, 0x4d, 0x45, 0x4e, 0x54, 0x10, 0x0b, 0x12, 0x10, 0x0a, 0x0c, 0x43, 0x49, 0x52, 0x43, - 0x55, 0x4c, 0x41, 0x52, 0x5f, 0x46, 0x45, 0x45, 0x10, 0x0c, 0x12, 0x09, 0x0a, 0x05, 0x53, 0x57, - 0x45, 0x45, 0x50, 0x10, 0x0d, 0x12, 0x0d, 0x0a, 0x09, 0x53, 0x57, 0x45, 0x45, 0x50, 0x5f, 0x46, - 0x45, 0x45, 0x10, 0x0e, 0x12, 0x15, 0x0a, 0x11, 0x43, 0x48, 0x41, 0x4e, 0x4e, 0x45, 0x4c, 0x5f, - 0x43, 0x4c, 0x4f, 0x53, 0x45, 0x5f, 0x46, 0x45, 0x45, 0x10, 0x0f, 0x32, 0xd8, 0x04, 0x0a, 0x0d, - 0x46, 0x61, 0x72, 0x61, 0x64, 0x61, 0x79, 0x53, 0x65, 0x72, 0x76, 0x65, 0x72, 0x12, 0x65, 0x0a, - 0x16, 0x4f, 0x75, 0x74, 0x6c, 0x69, 0x65, 0x72, 0x52, 0x65, 0x63, 0x6f, 0x6d, 0x6d, 0x65, 0x6e, - 0x64, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x12, 0x25, 0x2e, 0x66, 0x72, 0x64, 0x72, 0x70, 0x63, - 0x2e, 0x4f, 0x75, 0x74, 0x6c, 0x69, 0x65, 0x72, 0x52, 0x65, 0x63, 0x6f, 0x6d, 0x6d, 0x65, 0x6e, - 0x64, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x24, - 0x2e, 0x66, 0x72, 0x64, 0x72, 0x70, 0x63, 0x2e, 0x43, 0x6c, 0x6f, 0x73, 0x65, 0x52, 0x65, 0x63, - 0x6f, 0x6d, 0x6d, 0x65, 0x6e, 0x64, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x52, 0x65, 0x73, 0x70, - 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x69, 0x0a, 0x18, 0x54, 0x68, 0x72, 0x65, 0x73, 0x68, 0x6f, 0x6c, - 0x64, 0x52, 0x65, 0x63, 0x6f, 0x6d, 0x6d, 0x65, 0x6e, 0x64, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, - 0x12, 0x27, 0x2e, 0x66, 0x72, 0x64, 0x72, 0x70, 0x63, 0x2e, 0x54, 0x68, 0x72, 0x65, 0x73, 0x68, - 0x6f, 0x6c, 0x64, 0x52, 0x65, 0x63, 0x6f, 0x6d, 0x6d, 0x65, 0x6e, 0x64, 0x61, 0x74, 0x69, 0x6f, - 0x6e, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x24, 0x2e, 0x66, 0x72, 0x64, 0x72, - 0x70, 0x63, 0x2e, 0x43, 0x6c, 0x6f, 0x73, 0x65, 0x52, 0x65, 0x63, 0x6f, 0x6d, 0x6d, 0x65, 0x6e, - 0x64, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, - 0x4c, 0x0a, 0x0d, 0x52, 0x65, 0x76, 0x65, 0x6e, 0x75, 0x65, 0x52, 0x65, 0x70, 0x6f, 0x72, 0x74, - 0x12, 0x1c, 0x2e, 0x66, 0x72, 0x64, 0x72, 0x70, 0x63, 0x2e, 0x52, 0x65, 0x76, 0x65, 0x6e, 0x75, - 0x65, 0x52, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1d, - 0x2e, 0x66, 0x72, 0x64, 0x72, 0x70, 0x63, 0x2e, 0x52, 0x65, 0x76, 0x65, 0x6e, 0x75, 0x65, 0x52, - 0x65, 0x70, 0x6f, 0x72, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x52, 0x0a, - 0x0f, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x49, 0x6e, 0x73, 0x69, 0x67, 0x68, 0x74, 0x73, - 0x12, 0x1e, 0x2e, 0x66, 0x72, 0x64, 0x72, 0x70, 0x63, 0x2e, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, - 0x6c, 0x49, 0x6e, 0x73, 0x69, 0x67, 0x68, 0x74, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, - 0x1a, 0x1f, 0x2e, 0x66, 0x72, 0x64, 0x72, 0x70, 0x63, 0x2e, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, - 0x6c, 0x49, 0x6e, 0x73, 0x69, 0x67, 0x68, 0x74, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, - 0x65, 0x12, 0x49, 0x0a, 0x0c, 0x45, 0x78, 0x63, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x52, 0x61, 0x74, - 0x65, 0x12, 0x1b, 0x2e, 0x66, 0x72, 0x64, 0x72, 0x70, 0x63, 0x2e, 0x45, 0x78, 0x63, 0x68, 0x61, - 0x6e, 0x67, 0x65, 0x52, 0x61, 0x74, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1c, - 0x2e, 0x66, 0x72, 0x64, 0x72, 0x70, 0x63, 0x2e, 0x45, 0x78, 0x63, 0x68, 0x61, 0x6e, 0x67, 0x65, - 0x52, 0x61, 0x74, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x40, 0x0a, 0x09, - 0x4e, 0x6f, 0x64, 0x65, 0x41, 0x75, 0x64, 0x69, 0x74, 0x12, 0x18, 0x2e, 0x66, 0x72, 0x64, 0x72, - 0x70, 0x63, 0x2e, 0x4e, 0x6f, 0x64, 0x65, 0x41, 0x75, 0x64, 0x69, 0x74, 0x52, 0x65, 0x71, 0x75, - 0x65, 0x73, 0x74, 0x1a, 0x19, 0x2e, 0x66, 0x72, 0x64, 0x72, 0x70, 0x63, 0x2e, 0x4e, 0x6f, 0x64, - 0x65, 0x41, 0x75, 0x64, 0x69, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x46, - 0x0a, 0x0b, 0x43, 0x6c, 0x6f, 0x73, 0x65, 0x52, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x12, 0x1a, 0x2e, - 0x66, 0x72, 0x64, 0x72, 0x70, 0x63, 0x2e, 0x43, 0x6c, 0x6f, 0x73, 0x65, 0x52, 0x65, 0x70, 0x6f, - 0x72, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1b, 0x2e, 0x66, 0x72, 0x64, 0x72, + 0x70, 0x12, 0x1a, 0x0a, 0x08, 0x63, 0x75, 0x72, 0x72, 0x65, 0x6e, 0x63, 0x79, 0x18, 0x03, 0x20, + 0x01, 0x28, 0x09, 0x52, 0x08, 0x63, 0x75, 0x72, 0x72, 0x65, 0x6e, 0x63, 0x79, 0x22, 0x5f, 0x0a, + 0x0c, 0x45, 0x78, 0x63, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x52, 0x61, 0x74, 0x65, 0x12, 0x1c, 0x0a, + 0x09, 0x74, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x18, 0x01, 0x20, 0x01, 0x28, 0x04, + 0x52, 0x09, 0x74, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x12, 0x31, 0x0a, 0x09, 0x62, + 0x74, 0x63, 0x5f, 0x70, 0x72, 0x69, 0x63, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x14, + 0x2e, 0x66, 0x72, 0x64, 0x72, 0x70, 0x63, 0x2e, 0x42, 0x69, 0x74, 0x63, 0x6f, 0x69, 0x6e, 0x50, + 0x72, 0x69, 0x63, 0x65, 0x52, 0x08, 0x62, 0x74, 0x63, 0x50, 0x72, 0x69, 0x63, 0x65, 0x22, 0xa9, + 0x02, 0x0a, 0x10, 0x4e, 0x6f, 0x64, 0x65, 0x41, 0x75, 0x64, 0x69, 0x74, 0x52, 0x65, 0x71, 0x75, + 0x65, 0x73, 0x74, 0x12, 0x1d, 0x0a, 0x0a, 0x73, 0x74, 0x61, 0x72, 0x74, 0x5f, 0x74, 0x69, 0x6d, + 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x04, 0x52, 0x09, 0x73, 0x74, 0x61, 0x72, 0x74, 0x54, 0x69, + 0x6d, 0x65, 0x12, 0x19, 0x0a, 0x08, 0x65, 0x6e, 0x64, 0x5f, 0x74, 0x69, 0x6d, 0x65, 0x18, 0x02, + 0x20, 0x01, 0x28, 0x04, 0x52, 0x07, 0x65, 0x6e, 0x64, 0x54, 0x69, 0x6d, 0x65, 0x12, 0x21, 0x0a, + 0x0c, 0x64, 0x69, 0x73, 0x61, 0x62, 0x6c, 0x65, 0x5f, 0x66, 0x69, 0x61, 0x74, 0x18, 0x04, 0x20, + 0x01, 0x28, 0x08, 0x52, 0x0b, 0x64, 0x69, 0x73, 0x61, 0x62, 0x6c, 0x65, 0x46, 0x69, 0x61, 0x74, + 0x12, 0x35, 0x0a, 0x0b, 0x67, 0x72, 0x61, 0x6e, 0x75, 0x6c, 0x61, 0x72, 0x69, 0x74, 0x79, 0x18, + 0x05, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x13, 0x2e, 0x66, 0x72, 0x64, 0x72, 0x70, 0x63, 0x2e, 0x47, + 0x72, 0x61, 0x6e, 0x75, 0x6c, 0x61, 0x72, 0x69, 0x74, 0x79, 0x52, 0x0b, 0x67, 0x72, 0x61, 0x6e, + 0x75, 0x6c, 0x61, 0x72, 0x69, 0x74, 0x79, 0x12, 0x43, 0x0a, 0x11, 0x63, 0x75, 0x73, 0x74, 0x6f, + 0x6d, 0x5f, 0x63, 0x61, 0x74, 0x65, 0x67, 0x6f, 0x72, 0x69, 0x65, 0x73, 0x18, 0x06, 0x20, 0x03, + 0x28, 0x0b, 0x32, 0x16, 0x2e, 0x66, 0x72, 0x64, 0x72, 0x70, 0x63, 0x2e, 0x43, 0x75, 0x73, 0x74, + 0x6f, 0x6d, 0x43, 0x61, 0x74, 0x65, 0x67, 0x6f, 0x72, 0x79, 0x52, 0x10, 0x63, 0x75, 0x73, 0x74, + 0x6f, 0x6d, 0x43, 0x61, 0x74, 0x65, 0x67, 0x6f, 0x72, 0x69, 0x65, 0x73, 0x12, 0x36, 0x0a, 0x0c, + 0x66, 0x69, 0x61, 0x74, 0x5f, 0x62, 0x61, 0x63, 0x6b, 0x65, 0x6e, 0x64, 0x18, 0x07, 0x20, 0x01, + 0x28, 0x0e, 0x32, 0x13, 0x2e, 0x66, 0x72, 0x64, 0x72, 0x70, 0x63, 0x2e, 0x46, 0x69, 0x61, 0x74, + 0x42, 0x61, 0x63, 0x6b, 0x65, 0x6e, 0x64, 0x52, 0x0b, 0x66, 0x69, 0x61, 0x74, 0x42, 0x61, 0x63, + 0x6b, 0x65, 0x6e, 0x64, 0x4a, 0x04, 0x08, 0x03, 0x10, 0x04, 0x22, 0x83, 0x01, 0x0a, 0x0e, 0x43, + 0x75, 0x73, 0x74, 0x6f, 0x6d, 0x43, 0x61, 0x74, 0x65, 0x67, 0x6f, 0x72, 0x79, 0x12, 0x12, 0x0a, + 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, + 0x65, 0x12, 0x19, 0x0a, 0x08, 0x6f, 0x6e, 0x5f, 0x63, 0x68, 0x61, 0x69, 0x6e, 0x18, 0x02, 0x20, + 0x01, 0x28, 0x08, 0x52, 0x07, 0x6f, 0x6e, 0x43, 0x68, 0x61, 0x69, 0x6e, 0x12, 0x1b, 0x0a, 0x09, + 0x6f, 0x66, 0x66, 0x5f, 0x63, 0x68, 0x61, 0x69, 0x6e, 0x18, 0x03, 0x20, 0x01, 0x28, 0x08, 0x52, + 0x08, 0x6f, 0x66, 0x66, 0x43, 0x68, 0x61, 0x69, 0x6e, 0x12, 0x25, 0x0a, 0x0e, 0x6c, 0x61, 0x62, + 0x65, 0x6c, 0x5f, 0x70, 0x61, 0x74, 0x74, 0x65, 0x72, 0x6e, 0x73, 0x18, 0x05, 0x20, 0x03, 0x28, + 0x09, 0x52, 0x0d, 0x6c, 0x61, 0x62, 0x65, 0x6c, 0x50, 0x61, 0x74, 0x74, 0x65, 0x72, 0x6e, 0x73, + 0x22, 0xe9, 0x02, 0x0a, 0x0b, 0x52, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x45, 0x6e, 0x74, 0x72, 0x79, + 0x12, 0x1c, 0x0a, 0x09, 0x74, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x18, 0x01, 0x20, + 0x01, 0x28, 0x04, 0x52, 0x09, 0x74, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x12, 0x19, + 0x0a, 0x08, 0x6f, 0x6e, 0x5f, 0x63, 0x68, 0x61, 0x69, 0x6e, 0x18, 0x02, 0x20, 0x01, 0x28, 0x08, + 0x52, 0x07, 0x6f, 0x6e, 0x43, 0x68, 0x61, 0x69, 0x6e, 0x12, 0x16, 0x0a, 0x06, 0x61, 0x6d, 0x6f, + 0x75, 0x6e, 0x74, 0x18, 0x03, 0x20, 0x01, 0x28, 0x04, 0x52, 0x06, 0x61, 0x6d, 0x6f, 0x75, 0x6e, + 0x74, 0x12, 0x16, 0x0a, 0x06, 0x63, 0x72, 0x65, 0x64, 0x69, 0x74, 0x18, 0x04, 0x20, 0x01, 0x28, + 0x08, 0x52, 0x06, 0x63, 0x72, 0x65, 0x64, 0x69, 0x74, 0x12, 0x14, 0x0a, 0x05, 0x61, 0x73, 0x73, + 0x65, 0x74, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x61, 0x73, 0x73, 0x65, 0x74, 0x12, + 0x25, 0x0a, 0x04, 0x74, 0x79, 0x70, 0x65, 0x18, 0x06, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x11, 0x2e, + 0x66, 0x72, 0x64, 0x72, 0x70, 0x63, 0x2e, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x54, 0x79, 0x70, 0x65, + 0x52, 0x04, 0x74, 0x79, 0x70, 0x65, 0x12, 0x27, 0x0a, 0x0f, 0x63, 0x75, 0x73, 0x74, 0x6f, 0x6d, + 0x5f, 0x63, 0x61, 0x74, 0x65, 0x67, 0x6f, 0x72, 0x79, 0x18, 0x0c, 0x20, 0x01, 0x28, 0x09, 0x52, + 0x0e, 0x63, 0x75, 0x73, 0x74, 0x6f, 0x6d, 0x43, 0x61, 0x74, 0x65, 0x67, 0x6f, 0x72, 0x79, 0x12, + 0x12, 0x0a, 0x04, 0x74, 0x78, 0x69, 0x64, 0x18, 0x07, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x74, + 0x78, 0x69, 0x64, 0x12, 0x12, 0x0a, 0x04, 0x66, 0x69, 0x61, 0x74, 0x18, 0x08, 0x20, 0x01, 0x28, + 0x09, 0x52, 0x04, 0x66, 0x69, 0x61, 0x74, 0x12, 0x1c, 0x0a, 0x09, 0x72, 0x65, 0x66, 0x65, 0x72, + 0x65, 0x6e, 0x63, 0x65, 0x18, 0x09, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x72, 0x65, 0x66, 0x65, + 0x72, 0x65, 0x6e, 0x63, 0x65, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x6f, 0x74, 0x65, 0x18, 0x0a, 0x20, + 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, 0x6f, 0x74, 0x65, 0x12, 0x31, 0x0a, 0x09, 0x62, 0x74, 0x63, + 0x5f, 0x70, 0x72, 0x69, 0x63, 0x65, 0x18, 0x0b, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x14, 0x2e, 0x66, + 0x72, 0x64, 0x72, 0x70, 0x63, 0x2e, 0x42, 0x69, 0x74, 0x63, 0x6f, 0x69, 0x6e, 0x50, 0x72, 0x69, + 0x63, 0x65, 0x52, 0x08, 0x62, 0x74, 0x63, 0x50, 0x72, 0x69, 0x63, 0x65, 0x22, 0x42, 0x0a, 0x11, + 0x4e, 0x6f, 0x64, 0x65, 0x41, 0x75, 0x64, 0x69, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, + 0x65, 0x12, 0x2d, 0x0a, 0x07, 0x72, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x73, 0x18, 0x01, 0x20, 0x03, + 0x28, 0x0b, 0x32, 0x13, 0x2e, 0x66, 0x72, 0x64, 0x72, 0x70, 0x63, 0x2e, 0x52, 0x65, 0x70, 0x6f, + 0x72, 0x74, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x52, 0x07, 0x72, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x73, + 0x22, 0x39, 0x0a, 0x12, 0x43, 0x6c, 0x6f, 0x73, 0x65, 0x52, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x52, + 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x23, 0x0a, 0x0d, 0x63, 0x68, 0x61, 0x6e, 0x6e, 0x65, + 0x6c, 0x5f, 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0c, 0x63, + 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x50, 0x6f, 0x69, 0x6e, 0x74, 0x22, 0xdd, 0x01, 0x0a, 0x13, + 0x43, 0x6c, 0x6f, 0x73, 0x65, 0x52, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, + 0x6e, 0x73, 0x65, 0x12, 0x23, 0x0a, 0x0d, 0x63, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x5f, 0x70, + 0x6f, 0x69, 0x6e, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0c, 0x63, 0x68, 0x61, 0x6e, + 0x6e, 0x65, 0x6c, 0x50, 0x6f, 0x69, 0x6e, 0x74, 0x12, 0x2b, 0x0a, 0x11, 0x63, 0x68, 0x61, 0x6e, + 0x6e, 0x65, 0x6c, 0x5f, 0x69, 0x6e, 0x69, 0x74, 0x69, 0x61, 0x74, 0x6f, 0x72, 0x18, 0x02, 0x20, + 0x01, 0x28, 0x08, 0x52, 0x10, 0x63, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x49, 0x6e, 0x69, 0x74, + 0x69, 0x61, 0x74, 0x6f, 0x72, 0x12, 0x1d, 0x0a, 0x0a, 0x63, 0x6c, 0x6f, 0x73, 0x65, 0x5f, 0x74, + 0x79, 0x70, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x63, 0x6c, 0x6f, 0x73, 0x65, + 0x54, 0x79, 0x70, 0x65, 0x12, 0x1d, 0x0a, 0x0a, 0x63, 0x6c, 0x6f, 0x73, 0x65, 0x5f, 0x74, 0x78, + 0x69, 0x64, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x63, 0x6c, 0x6f, 0x73, 0x65, 0x54, + 0x78, 0x69, 0x64, 0x12, 0x19, 0x0a, 0x08, 0x6f, 0x70, 0x65, 0x6e, 0x5f, 0x66, 0x65, 0x65, 0x18, + 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x6f, 0x70, 0x65, 0x6e, 0x46, 0x65, 0x65, 0x12, 0x1b, + 0x0a, 0x09, 0x63, 0x6c, 0x6f, 0x73, 0x65, 0x5f, 0x66, 0x65, 0x65, 0x18, 0x06, 0x20, 0x01, 0x28, + 0x09, 0x52, 0x08, 0x63, 0x6c, 0x6f, 0x73, 0x65, 0x46, 0x65, 0x65, 0x2a, 0xa1, 0x01, 0x0a, 0x0b, + 0x47, 0x72, 0x61, 0x6e, 0x75, 0x6c, 0x61, 0x72, 0x69, 0x74, 0x79, 0x12, 0x17, 0x0a, 0x13, 0x55, + 0x4e, 0x4b, 0x4e, 0x4f, 0x57, 0x4e, 0x5f, 0x47, 0x52, 0x41, 0x4e, 0x55, 0x4c, 0x41, 0x52, 0x49, + 0x54, 0x59, 0x10, 0x00, 0x12, 0x0a, 0x0a, 0x06, 0x4d, 0x49, 0x4e, 0x55, 0x54, 0x45, 0x10, 0x01, + 0x12, 0x10, 0x0a, 0x0c, 0x46, 0x49, 0x56, 0x45, 0x5f, 0x4d, 0x49, 0x4e, 0x55, 0x54, 0x45, 0x53, + 0x10, 0x02, 0x12, 0x13, 0x0a, 0x0f, 0x46, 0x49, 0x46, 0x54, 0x45, 0x45, 0x4e, 0x5f, 0x4d, 0x49, + 0x4e, 0x55, 0x54, 0x45, 0x53, 0x10, 0x03, 0x12, 0x12, 0x0a, 0x0e, 0x54, 0x48, 0x49, 0x52, 0x54, + 0x59, 0x5f, 0x4d, 0x49, 0x4e, 0x55, 0x54, 0x45, 0x53, 0x10, 0x04, 0x12, 0x08, 0x0a, 0x04, 0x48, + 0x4f, 0x55, 0x52, 0x10, 0x05, 0x12, 0x0d, 0x0a, 0x09, 0x53, 0x49, 0x58, 0x5f, 0x48, 0x4f, 0x55, + 0x52, 0x53, 0x10, 0x06, 0x12, 0x10, 0x0a, 0x0c, 0x54, 0x57, 0x45, 0x4c, 0x56, 0x45, 0x5f, 0x48, + 0x4f, 0x55, 0x52, 0x53, 0x10, 0x07, 0x12, 0x07, 0x0a, 0x03, 0x44, 0x41, 0x59, 0x10, 0x08, 0x2a, + 0x41, 0x0a, 0x0b, 0x46, 0x69, 0x61, 0x74, 0x42, 0x61, 0x63, 0x6b, 0x65, 0x6e, 0x64, 0x12, 0x17, + 0x0a, 0x13, 0x55, 0x4e, 0x4b, 0x4e, 0x4f, 0x57, 0x4e, 0x5f, 0x46, 0x49, 0x41, 0x54, 0x42, 0x41, + 0x43, 0x4b, 0x45, 0x4e, 0x44, 0x10, 0x00, 0x12, 0x0b, 0x0a, 0x07, 0x43, 0x4f, 0x49, 0x4e, 0x43, + 0x41, 0x50, 0x10, 0x01, 0x12, 0x0c, 0x0a, 0x08, 0x43, 0x4f, 0x49, 0x4e, 0x44, 0x45, 0x53, 0x4b, + 0x10, 0x02, 0x2a, 0xa2, 0x02, 0x0a, 0x09, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x54, 0x79, 0x70, 0x65, + 0x12, 0x0b, 0x0a, 0x07, 0x55, 0x4e, 0x4b, 0x4e, 0x4f, 0x57, 0x4e, 0x10, 0x00, 0x12, 0x16, 0x0a, + 0x12, 0x4c, 0x4f, 0x43, 0x41, 0x4c, 0x5f, 0x43, 0x48, 0x41, 0x4e, 0x4e, 0x45, 0x4c, 0x5f, 0x4f, + 0x50, 0x45, 0x4e, 0x10, 0x01, 0x12, 0x17, 0x0a, 0x13, 0x52, 0x45, 0x4d, 0x4f, 0x54, 0x45, 0x5f, + 0x43, 0x48, 0x41, 0x4e, 0x4e, 0x45, 0x4c, 0x5f, 0x4f, 0x50, 0x45, 0x4e, 0x10, 0x02, 0x12, 0x14, + 0x0a, 0x10, 0x43, 0x48, 0x41, 0x4e, 0x4e, 0x45, 0x4c, 0x5f, 0x4f, 0x50, 0x45, 0x4e, 0x5f, 0x46, + 0x45, 0x45, 0x10, 0x03, 0x12, 0x11, 0x0a, 0x0d, 0x43, 0x48, 0x41, 0x4e, 0x4e, 0x45, 0x4c, 0x5f, + 0x43, 0x4c, 0x4f, 0x53, 0x45, 0x10, 0x04, 0x12, 0x0b, 0x0a, 0x07, 0x52, 0x45, 0x43, 0x45, 0x49, + 0x50, 0x54, 0x10, 0x05, 0x12, 0x0b, 0x0a, 0x07, 0x50, 0x41, 0x59, 0x4d, 0x45, 0x4e, 0x54, 0x10, + 0x06, 0x12, 0x07, 0x0a, 0x03, 0x46, 0x45, 0x45, 0x10, 0x07, 0x12, 0x14, 0x0a, 0x10, 0x43, 0x49, + 0x52, 0x43, 0x55, 0x4c, 0x41, 0x52, 0x5f, 0x52, 0x45, 0x43, 0x45, 0x49, 0x50, 0x54, 0x10, 0x08, + 0x12, 0x0b, 0x0a, 0x07, 0x46, 0x4f, 0x52, 0x57, 0x41, 0x52, 0x44, 0x10, 0x09, 0x12, 0x0f, 0x0a, + 0x0b, 0x46, 0x4f, 0x52, 0x57, 0x41, 0x52, 0x44, 0x5f, 0x46, 0x45, 0x45, 0x10, 0x0a, 0x12, 0x14, + 0x0a, 0x10, 0x43, 0x49, 0x52, 0x43, 0x55, 0x4c, 0x41, 0x52, 0x5f, 0x50, 0x41, 0x59, 0x4d, 0x45, + 0x4e, 0x54, 0x10, 0x0b, 0x12, 0x10, 0x0a, 0x0c, 0x43, 0x49, 0x52, 0x43, 0x55, 0x4c, 0x41, 0x52, + 0x5f, 0x46, 0x45, 0x45, 0x10, 0x0c, 0x12, 0x09, 0x0a, 0x05, 0x53, 0x57, 0x45, 0x45, 0x50, 0x10, + 0x0d, 0x12, 0x0d, 0x0a, 0x09, 0x53, 0x57, 0x45, 0x45, 0x50, 0x5f, 0x46, 0x45, 0x45, 0x10, 0x0e, + 0x12, 0x15, 0x0a, 0x11, 0x43, 0x48, 0x41, 0x4e, 0x4e, 0x45, 0x4c, 0x5f, 0x43, 0x4c, 0x4f, 0x53, + 0x45, 0x5f, 0x46, 0x45, 0x45, 0x10, 0x0f, 0x32, 0xd8, 0x04, 0x0a, 0x0d, 0x46, 0x61, 0x72, 0x61, + 0x64, 0x61, 0x79, 0x53, 0x65, 0x72, 0x76, 0x65, 0x72, 0x12, 0x65, 0x0a, 0x16, 0x4f, 0x75, 0x74, + 0x6c, 0x69, 0x65, 0x72, 0x52, 0x65, 0x63, 0x6f, 0x6d, 0x6d, 0x65, 0x6e, 0x64, 0x61, 0x74, 0x69, + 0x6f, 0x6e, 0x73, 0x12, 0x25, 0x2e, 0x66, 0x72, 0x64, 0x72, 0x70, 0x63, 0x2e, 0x4f, 0x75, 0x74, + 0x6c, 0x69, 0x65, 0x72, 0x52, 0x65, 0x63, 0x6f, 0x6d, 0x6d, 0x65, 0x6e, 0x64, 0x61, 0x74, 0x69, + 0x6f, 0x6e, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x24, 0x2e, 0x66, 0x72, 0x64, + 0x72, 0x70, 0x63, 0x2e, 0x43, 0x6c, 0x6f, 0x73, 0x65, 0x52, 0x65, 0x63, 0x6f, 0x6d, 0x6d, 0x65, + 0x6e, 0x64, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, + 0x12, 0x69, 0x0a, 0x18, 0x54, 0x68, 0x72, 0x65, 0x73, 0x68, 0x6f, 0x6c, 0x64, 0x52, 0x65, 0x63, + 0x6f, 0x6d, 0x6d, 0x65, 0x6e, 0x64, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x12, 0x27, 0x2e, 0x66, + 0x72, 0x64, 0x72, 0x70, 0x63, 0x2e, 0x54, 0x68, 0x72, 0x65, 0x73, 0x68, 0x6f, 0x6c, 0x64, 0x52, + 0x65, 0x63, 0x6f, 0x6d, 0x6d, 0x65, 0x6e, 0x64, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x52, 0x65, + 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x24, 0x2e, 0x66, 0x72, 0x64, 0x72, 0x70, 0x63, 0x2e, 0x43, + 0x6c, 0x6f, 0x73, 0x65, 0x52, 0x65, 0x63, 0x6f, 0x6d, 0x6d, 0x65, 0x6e, 0x64, 0x61, 0x74, 0x69, + 0x6f, 0x6e, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x4c, 0x0a, 0x0d, 0x52, + 0x65, 0x76, 0x65, 0x6e, 0x75, 0x65, 0x52, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x12, 0x1c, 0x2e, 0x66, + 0x72, 0x64, 0x72, 0x70, 0x63, 0x2e, 0x52, 0x65, 0x76, 0x65, 0x6e, 0x75, 0x65, 0x52, 0x65, 0x70, + 0x6f, 0x72, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1d, 0x2e, 0x66, 0x72, 0x64, + 0x72, 0x70, 0x63, 0x2e, 0x52, 0x65, 0x76, 0x65, 0x6e, 0x75, 0x65, 0x52, 0x65, 0x70, 0x6f, 0x72, + 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x52, 0x0a, 0x0f, 0x43, 0x68, 0x61, + 0x6e, 0x6e, 0x65, 0x6c, 0x49, 0x6e, 0x73, 0x69, 0x67, 0x68, 0x74, 0x73, 0x12, 0x1e, 0x2e, 0x66, + 0x72, 0x64, 0x72, 0x70, 0x63, 0x2e, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x49, 0x6e, 0x73, + 0x69, 0x67, 0x68, 0x74, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1f, 0x2e, 0x66, + 0x72, 0x64, 0x72, 0x70, 0x63, 0x2e, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x49, 0x6e, 0x73, + 0x69, 0x67, 0x68, 0x74, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x49, 0x0a, + 0x0c, 0x45, 0x78, 0x63, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x52, 0x61, 0x74, 0x65, 0x12, 0x1b, 0x2e, + 0x66, 0x72, 0x64, 0x72, 0x70, 0x63, 0x2e, 0x45, 0x78, 0x63, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x52, + 0x61, 0x74, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1c, 0x2e, 0x66, 0x72, 0x64, + 0x72, 0x70, 0x63, 0x2e, 0x45, 0x78, 0x63, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x52, 0x61, 0x74, 0x65, + 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x40, 0x0a, 0x09, 0x4e, 0x6f, 0x64, 0x65, + 0x41, 0x75, 0x64, 0x69, 0x74, 0x12, 0x18, 0x2e, 0x66, 0x72, 0x64, 0x72, 0x70, 0x63, 0x2e, 0x4e, + 0x6f, 0x64, 0x65, 0x41, 0x75, 0x64, 0x69, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, + 0x19, 0x2e, 0x66, 0x72, 0x64, 0x72, 0x70, 0x63, 0x2e, 0x4e, 0x6f, 0x64, 0x65, 0x41, 0x75, 0x64, + 0x69, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x46, 0x0a, 0x0b, 0x43, 0x6c, + 0x6f, 0x73, 0x65, 0x52, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x12, 0x1a, 0x2e, 0x66, 0x72, 0x64, 0x72, 0x70, 0x63, 0x2e, 0x43, 0x6c, 0x6f, 0x73, 0x65, 0x52, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x52, 0x65, - 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x42, 0x29, 0x5a, 0x27, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, - 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x6c, 0x69, 0x67, 0x68, 0x74, 0x6e, 0x69, 0x6e, 0x67, 0x6c, 0x61, - 0x62, 0x73, 0x2f, 0x66, 0x61, 0x72, 0x61, 0x64, 0x61, 0x79, 0x2f, 0x66, 0x72, 0x64, 0x72, 0x70, - 0x63, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, + 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1b, 0x2e, 0x66, 0x72, 0x64, 0x72, 0x70, 0x63, 0x2e, 0x43, + 0x6c, 0x6f, 0x73, 0x65, 0x52, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, + 0x73, 0x65, 0x42, 0x29, 0x5a, 0x27, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, + 0x2f, 0x6c, 0x69, 0x67, 0x68, 0x74, 0x6e, 0x69, 0x6e, 0x67, 0x6c, 0x61, 0x62, 0x73, 0x2f, 0x66, + 0x61, 0x72, 0x61, 0x64, 0x61, 0x79, 0x2f, 0x66, 0x72, 0x64, 0x72, 0x70, 0x63, 0x62, 0x06, 0x70, + 0x72, 0x6f, 0x74, 0x6f, 0x33, } var ( diff --git a/frdrpc/faraday.proto b/frdrpc/faraday.proto index bec204d..981ace4 100644 --- a/frdrpc/faraday.proto +++ b/frdrpc/faraday.proto @@ -365,6 +365,9 @@ message BitcoinPrice { // The timestamp for this price price provided. uint64 price_timestamp = 2; + + // The currency that the price is denoted in. + string currency = 3; } message ExchangeRate { @@ -522,7 +525,8 @@ message ReportEntry { // The transaction id of the entry. string txid = 7; - // The fiat amount of the entry's amount in USD. + // The fiat amount of the entry's amount in the currency specified in the + // btc_price field. string fiat = 8; // A unique identifier for the entry, if available. @@ -531,7 +535,7 @@ message ReportEntry { // An additional note for the entry, providing additional context. string note = 10; - // The bitcoin price and timestamp used to calcualte our fiat value. + // The bitcoin price and timestamp used to calculate our fiat value. BitcoinPrice btc_price = 11; } diff --git a/frdrpc/faraday.swagger.json b/frdrpc/faraday.swagger.json index d2fbd1c..90d149c 100644 --- a/frdrpc/faraday.swagger.json +++ b/frdrpc/faraday.swagger.json @@ -416,6 +416,10 @@ "type": "string", "format": "uint64", "description": "The timestamp for this price price provided." + }, + "currency": { + "type": "string", + "description": "The currency that the price is denoted in." } } }, @@ -732,7 +736,7 @@ }, "fiat": { "type": "string", - "description": "The fiat amount of the entry's amount in USD." + "description": "The fiat amount of the entry's amount in the currency specified in the\nbtc_price field." }, "reference": { "type": "string", @@ -744,7 +748,7 @@ }, "btc_price": { "$ref": "#/definitions/frdrpcBitcoinPrice", - "description": "The bitcoin price and timestamp used to calcualte our fiat value." + "description": "The bitcoin price and timestamp used to calculate our fiat value." } } }, diff --git a/frdrpc/node_audit.go b/frdrpc/node_audit.go index bae9ea6..0b80dde 100644 --- a/frdrpc/node_audit.go +++ b/frdrpc/node_audit.go @@ -165,7 +165,8 @@ func rpcReportResponse(report accounting.Report) (*NodeAuditResponse, Reference: entry.Reference, Note: entry.Note, BtcPrice: &BitcoinPrice{ - Price: entry.BTCPrice.Price.String(), + Price: entry.BTCPrice.Price.String(), + Currency: entry.BTCPrice.Currency, }, } From 66ad73ad878ee7e4fb9a148b245768af1899e5e5 Mon Sep 17 00:00:00 2001 From: Elle Mouton Date: Thu, 20 May 2021 14:36:13 +0200 Subject: [PATCH 06/12] fiat: add customprice impl of fiatBackend This commit adds a new implementation of the fiatBackend interface which uses user provided prices data. --- fiat/customprices.go | 18 ++++++++++++++++++ fiat/prices.go | 4 ++++ 2 files changed, 22 insertions(+) create mode 100644 fiat/customprices.go diff --git a/fiat/customprices.go b/fiat/customprices.go new file mode 100644 index 0000000..60a3d84 --- /dev/null +++ b/fiat/customprices.go @@ -0,0 +1,18 @@ +package fiat + +import ( + "context" + "time" +) + +// customPrices implements the fiatBackend interface. +type customPrices struct { + entries []*Price +} + +// rawPriceData returns the custom price point entries. +func (c *customPrices) rawPriceData(_ context.Context, _, + _ time.Time) ([]*Price, error) { + + return c.entries, nil +} diff --git a/fiat/prices.go b/fiat/prices.go index 88dc3e5..fb723cd 100644 --- a/fiat/prices.go +++ b/fiat/prices.go @@ -83,12 +83,16 @@ const ( // CoinDeskPriceBackend uses CoinDesk's API for fiat price data. CoinDeskPriceBackend + + // CustomPriceBackend uses user provided fiat price data. + CustomPriceBackend ) var priceBackendNames = map[PriceBackend]string{ UnknownPriceBackend: "unknown", CoinCapPriceBackend: "coincap", CoinDeskPriceBackend: "coindesk", + CustomPriceBackend: "custom", } // String returns the string representation of a price backend. From 71b47dc8eec27a359b8a504788cd16ae689fed0a Mon Sep 17 00:00:00 2001 From: Elle Mouton Date: Fri, 21 May 2021 10:01:11 +0200 Subject: [PATCH 07/12] multi: add PriceSourceConfig struct Add a PriceSourceConfig struct to consolidate the options required for initialising a new PriceSource. --- accounting/config.go | 36 ++++++++----------- accounting/conversions.go | 5 ++- accounting/off_chain.go | 2 +- accounting/on_chain.go | 2 +- fiat/prices.go | 61 ++++++++++++++++++++++++++----- fiat/prices_test.go | 76 +++++++++++++++++++++++++++++++++++++++ frdrpc/exchange_rate.go | 14 ++++---- frdrpc/node_audit.go | 10 ++++-- frdrpc/rpcserver.go | 8 ++--- 9 files changed, 165 insertions(+), 49 deletions(-) diff --git a/accounting/config.go b/accounting/config.go index 10277ee..81e9b20 100644 --- a/accounting/config.go +++ b/accounting/config.go @@ -85,13 +85,9 @@ type CommonConfig struct { // data api may be down. DisableFiat bool - // FiatBackend is the backend API to be used to for any fiat related - // queries. - FiatBackend fiat.PriceBackend - - // Granularity specifies the level of granularity with which we want to - // get fiat prices. - Granularity *fiat.Granularity + // PriceSourceCfg is the config to be used for initialising the + // PriceSource used for fiat related queries. + PriceSourceCfg *fiat.PriceSourceConfig // Categories is a set of custom categories which should be added to the // report. @@ -104,7 +100,7 @@ type CommonConfig struct { // that fee lookups are not possible in certain cases. func NewOnChainConfig(ctx context.Context, lnd lndclient.LndServices, startTime, endTime time.Time, disableFiat bool, txLookup fees.GetDetailsFunc, - fiatBackend fiat.PriceBackend, granularity *fiat.Granularity, + priceCfg *fiat.PriceSourceConfig, categories []CustomCategory) *OnChainConfig { var getFee func(chainhash.Hash) (btcutil.Amount, error) @@ -131,12 +127,11 @@ func NewOnChainConfig(ctx context.Context, lnd lndclient.LndServices, startTime, return lnd.WalletKit.ListSweeps(ctx) }, CommonConfig: CommonConfig{ - StartTime: startTime, - EndTime: endTime, - DisableFiat: disableFiat, - FiatBackend: fiatBackend, - Granularity: granularity, - Categories: categories, + StartTime: startTime, + EndTime: endTime, + DisableFiat: disableFiat, + Categories: categories, + PriceSourceCfg: priceCfg, }, GetFee: getFee, } @@ -148,7 +143,7 @@ func NewOnChainConfig(ctx context.Context, lnd lndclient.LndServices, startTime, func NewOffChainConfig(ctx context.Context, lnd lndclient.LndServices, maxInvoices, maxPayments, maxForwards uint64, ownPubkey route.Vertex, startTime, endTime time.Time, disableFiat bool, - fiatBackend fiat.PriceBackend, granularity *fiat.Granularity, + priceCfg *fiat.PriceSourceConfig, categories []CustomCategory) *OffChainConfig { return &OffChainConfig{ @@ -177,12 +172,11 @@ func NewOffChainConfig(ctx context.Context, lnd lndclient.LndServices, }, OwnPubKey: ownPubkey, CommonConfig: CommonConfig{ - StartTime: startTime, - EndTime: endTime, - DisableFiat: disableFiat, - FiatBackend: fiatBackend, - Granularity: granularity, - Categories: categories, + StartTime: startTime, + EndTime: endTime, + DisableFiat: disableFiat, + Categories: categories, + PriceSourceCfg: priceCfg, }, } } diff --git a/accounting/conversions.go b/accounting/conversions.go index 320547b..537e58b 100644 --- a/accounting/conversions.go +++ b/accounting/conversions.go @@ -33,8 +33,7 @@ func invertMsat(msat int64) int64 { // of price data and returns a convert function which can be used to get // individual price points from this data. func getConversion(ctx context.Context, startTime, endTime time.Time, - disableFiat bool, fiatBackend fiat.PriceBackend, - granularity *fiat.Granularity) (fiatPrice, error) { + disableFiat bool, priceCfg *fiat.PriceSourceConfig) (fiatPrice, error) { // If we don't want fiat values, just return a price which will yield // a zero price and timestamp. @@ -49,7 +48,7 @@ func getConversion(ctx context.Context, startTime, endTime time.Time, return nil, err } - fiatClient, err := fiat.NewPriceSource(fiatBackend, granularity) + fiatClient, err := fiat.NewPriceSource(priceCfg) if err != nil { return nil, err } diff --git a/accounting/off_chain.go b/accounting/off_chain.go index b498317..6e72012 100644 --- a/accounting/off_chain.go +++ b/accounting/off_chain.go @@ -45,7 +45,7 @@ func OffChainReport(ctx context.Context, cfg *OffChainConfig) (Report, error) { // or a no-op function if we do not want prices. getPrice, err := getConversion( ctx, cfg.StartTime, cfg.EndTime, cfg.DisableFiat, - cfg.FiatBackend, cfg.Granularity, + cfg.PriceSourceCfg, ) if err != nil { return nil, err diff --git a/accounting/on_chain.go b/accounting/on_chain.go index 54c3e4c..cd33e62 100644 --- a/accounting/on_chain.go +++ b/accounting/on_chain.go @@ -21,7 +21,7 @@ func OnChainReport(ctx context.Context, cfg *OnChainConfig) (Report, error) { // or a no-op function if we do not want prices. getPrice, err := getConversion( ctx, cfg.StartTime, cfg.EndTime, cfg.DisableFiat, - cfg.FiatBackend, cfg.Granularity, + cfg.PriceSourceCfg, ) if err != nil { return nil, err diff --git a/fiat/prices.go b/fiat/prices.go index fb723cd..8aba14a 100644 --- a/fiat/prices.go +++ b/fiat/prices.go @@ -22,6 +22,12 @@ var ( // required fiat prices but the granularity of those prices is not set. errGranularityRequired = errors.New("granularity required when " + "fiat prices are enabled") + + errPricePointsRequired = errors.New("at least one price point " + + "required for a custom price backend") + + errPriceSourceConfigExpected = errors.New("a non-nil " + + "PriceSourceConfig is expected") ) // fiatBackend is an interface that must be implemented by any backend that @@ -37,6 +43,40 @@ type PriceSource struct { impl fiatBackend } +// PriceSourceConfig is a struct holding various config options used for +// initialising a new PriceSource. +type PriceSourceConfig struct { + // Backend is the PriceBackend to be used for fetching price data. + Backend PriceBackend + + // Granularity specifies the level of granularity with which we want to + // get fiat prices. This option is only used for the CoinCap + // PriceBackend. + Granularity *Granularity + + // PricePoints is a set of price points that is used for fiat related + // queries if the PriceBackend being used is the CustomPriceBackend. + PricePoints []*Price +} + +// validatePriceSourceConfig checks that the PriceSourceConfig fields are valid +// given the chosen price backend. +func (cfg *PriceSourceConfig) validatePriceSourceConfig() error { + switch cfg.Backend { + case UnknownPriceBackend, CoinCapPriceBackend: + if cfg.Granularity == nil { + return errGranularityRequired + } + + case CustomPriceBackend: + if len(cfg.PricePoints) == 0 { + return errPricePointsRequired + } + } + + return nil +} + // GetPrices fetches price information using the given the PriceSource // fiatBackend implementation. GetPrices also validates the time parameters and // sorts the results. @@ -102,16 +142,19 @@ func (p PriceBackend) String() string { // NewPriceSource returns a PriceSource which can be used to query price // data. -func NewPriceSource(backend PriceBackend, granularity *Granularity) ( - *PriceSource, error) { +func NewPriceSource(cfg *PriceSourceConfig) (*PriceSource, error) { + if cfg == nil { + return nil, errPriceSourceConfigExpected + } - switch backend { + if err := cfg.validatePriceSourceConfig(); err != nil { + return nil, err + } + + switch cfg.Backend { case UnknownPriceBackend, CoinCapPriceBackend: - if granularity == nil { - return nil, errGranularityRequired - } return &PriceSource{ - impl: newCoinCapAPI(*granularity), + impl: newCoinCapAPI(*cfg.Granularity), }, nil case CoinDeskPriceBackend: @@ -137,7 +180,7 @@ type PriceRequest struct { // GetPrices gets a set of prices for a set of timestamps. func GetPrices(ctx context.Context, timestamps []time.Time, - backend PriceBackend, granularity Granularity) ( + priceCfg *PriceSourceConfig) ( map[time.Time]*Price, error) { if len(timestamps) == 0 { @@ -156,7 +199,7 @@ func GetPrices(ctx context.Context, timestamps []time.Time, // timestamp if we have 1 entry, but that's ok. start, end := timestamps[0], timestamps[len(timestamps)-1] - client, err := NewPriceSource(backend, &granularity) + client, err := NewPriceSource(priceCfg) if err != nil { return nil, err } diff --git a/fiat/prices_test.go b/fiat/prices_test.go index be3705b..ca94089 100644 --- a/fiat/prices_test.go +++ b/fiat/prices_test.go @@ -1,6 +1,7 @@ package fiat import ( + "errors" "testing" "time" @@ -134,3 +135,78 @@ func TestMSatToFiat(t *testing.T) { }) } } + +// TestValidatePriceSourceConfig tests that the validatePriceSourceConfig +// function correctly validates the fields of PriceSourceConfig given the +// chosen price backend. +func TestValidatePriceSourceConfig(t *testing.T) { + tests := []struct { + name string + cfg *PriceSourceConfig + expectedErr error + }{ + { + name: "valid Coin Cap config", + cfg: &PriceSourceConfig{ + Backend: CoinCapPriceBackend, + Granularity: &GranularityDay, + }, + }, + { + name: "invalid Coin Cap config", + cfg: &PriceSourceConfig{ + Backend: CoinCapPriceBackend, + }, + expectedErr: errGranularityRequired, + }, + { + name: "valid default config", + cfg: &PriceSourceConfig{ + Backend: UnknownPriceBackend, + Granularity: &GranularityDay, + }, + }, + { + name: "invalid default config", + cfg: &PriceSourceConfig{ + Backend: UnknownPriceBackend, + }, + expectedErr: errGranularityRequired, + }, + { + name: "valid custom prices config", + cfg: &PriceSourceConfig{ + Backend: CustomPriceBackend, + PricePoints: []*Price{ + { + Timestamp: time.Now(), + Price: decimal.NewFromInt(10), + Currency: "USD", + }, + }, + }, + }, + { + name: "invalid custom prices config", + cfg: &PriceSourceConfig{ + Backend: CustomPriceBackend, + }, + expectedErr: errPricePointsRequired, + }, + } + + for _, test := range tests { + test := test + + t.Run(test.name, func(t *testing.T) { + t.Parallel() + + err := test.cfg.validatePriceSourceConfig() + if !errors.Is(err, test.expectedErr) { + t.Fatalf("expected: %v, got %v", + test.expectedErr, err) + } + }) + + } +} diff --git a/frdrpc/exchange_rate.go b/frdrpc/exchange_rate.go index ff25e73..3991a26 100644 --- a/frdrpc/exchange_rate.go +++ b/frdrpc/exchange_rate.go @@ -77,11 +77,10 @@ func fiatBackendFromRPC(backend FiatBackend) (fiat.PriceBackend, error) { } func parseExchangeRateRequest(req *ExchangeRateRequest) ([]time.Time, - fiat.PriceBackend, *fiat.Granularity, error) { + *fiat.PriceSourceConfig, error) { if len(req.Timestamps) == 0 { - return nil, fiat.UnknownPriceBackend, nil, - errors.New("at least one timestamp required") + return nil, nil, errors.New("at least one timestamp required") } timestamps := make([]time.Time, len(req.Timestamps)) @@ -104,15 +103,18 @@ func parseExchangeRateRequest(req *ExchangeRateRequest) ([]time.Time, req.Granularity, false, end.Sub(start), ) if err != nil { - return nil, fiat.UnknownPriceBackend, nil, err + return nil, nil, err } fiatBackend, err := fiatBackendFromRPC(req.FiatBackend) if err != nil { - return nil, fiat.UnknownPriceBackend, nil, err + return nil, nil, err } - return timestamps, fiatBackend, granularity, nil + return timestamps, &fiat.PriceSourceConfig{ + Backend: fiatBackend, + Granularity: granularity, + }, nil } func exchangeRateResponse(prices map[time.Time]*fiat.Price) *ExchangeRateResponse { diff --git a/frdrpc/node_audit.go b/frdrpc/node_audit.go index 0b80dde..760dc37 100644 --- a/frdrpc/node_audit.go +++ b/frdrpc/node_audit.go @@ -10,6 +10,7 @@ import ( "github.com/lightninglabs/faraday/accounting" "github.com/lightninglabs/faraday/fees" + "github.com/lightninglabs/faraday/fiat" ) var ( @@ -52,6 +53,11 @@ func parseNodeAuditRequest(ctx context.Context, cfg *Config, return nil, nil, err } + priceSourceCfg := &fiat.PriceSourceConfig{ + Backend: fiatBackend, + Granularity: granularity, + } + pubkey, err := route.NewVertexFromBytes(info.IdentityPubkey[:]) if err != nil { return nil, nil, err @@ -71,7 +77,7 @@ func parseNodeAuditRequest(ctx context.Context, cfg *Config, offChain := accounting.NewOffChainConfig( ctx, cfg.Lnd, uint64(maxInvoiceQueries), uint64(maxPaymentQueries), uint64(maxForwardQueries), - pubkey, start, end, req.DisableFiat, fiatBackend, granularity, + pubkey, start, end, req.DisableFiat, priceSourceCfg, offChainCategories, ) @@ -87,7 +93,7 @@ func parseNodeAuditRequest(ctx context.Context, cfg *Config, onChain := accounting.NewOnChainConfig( ctx, cfg.Lnd, start, end, req.DisableFiat, - feeLookup, fiatBackend, granularity, onChainCategories, + feeLookup, priceSourceCfg, onChainCategories, ) return onChain, offChain, nil diff --git a/frdrpc/rpcserver.go b/frdrpc/rpcserver.go index 9a0eca9..9746a3b 100644 --- a/frdrpc/rpcserver.go +++ b/frdrpc/rpcserver.go @@ -443,16 +443,12 @@ func (s *RPCServer) ExchangeRate(ctx context.Context, log.Debugf("[FiatEstimate]: %v requests", len(req.Timestamps)) - timestamps, fiatBackend, granularity, err := parseExchangeRateRequest( - req, - ) + timestamps, priceCfg, err := parseExchangeRateRequest(req) if err != nil { return nil, err } - prices, err := fiat.GetPrices( - ctx, timestamps, fiatBackend, *granularity, - ) + prices, err := fiat.GetPrices(ctx, timestamps, priceCfg) if err != nil { return nil, err } From 504762194e0e6381e8a55adc44c501ef1ccb8d00 Mon Sep 17 00:00:00 2001 From: Elle Mouton Date: Fri, 21 May 2021 10:15:53 +0200 Subject: [PATCH 08/12] fiat: add new custom price case to NewPriceSource Add new CustomPriceBackend option to NewPriceSource function that initialises the customPrices implementation of the fiatBackend interface. --- fiat/prices.go | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/fiat/prices.go b/fiat/prices.go index 8aba14a..2103ba1 100644 --- a/fiat/prices.go +++ b/fiat/prices.go @@ -161,6 +161,13 @@ func NewPriceSource(cfg *PriceSourceConfig) (*PriceSource, error) { return &PriceSource{ impl: &coinDeskAPI{}, }, nil + + case CustomPriceBackend: + return &PriceSource{ + impl: &customPrices{ + entries: cfg.PricePoints, + }, + }, nil } return nil, errUnknownPriceBackend From bffe524896c16e009aa3ab132f7c8a0dbcd8bf01 Mon Sep 17 00:00:00 2001 From: Elle Mouton Date: Wed, 9 Jun 2021 14:12:10 +0200 Subject: [PATCH 09/12] cmd/frcli: rename csv function to writeToCSV Rename the csv function to writeToCSV so that the csv package can be imported in a future commit without needing to rename the import. --- cmd/frcli/csv.go | 4 ++-- cmd/frcli/node_audit.go | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/cmd/frcli/csv.go b/cmd/frcli/csv.go index 9b21a52..18f81ef 100644 --- a/cmd/frcli/csv.go +++ b/cmd/frcli/csv.go @@ -10,10 +10,10 @@ import ( // CSVHeaders returns the headers used for harmony csv records. var CSVHeaders = "Timestamp,OnChain,Type,Category,Amount(Msat),Amount(%v),TxID,Reference,BTCPrice,BTCTimestamp,Note" -// csv returns a csv string of the values contained in a rpc entry. For ease +// writeToCSV returns a csv string of the values contained in a rpc entry. For ease // of use, the credit field is used to set a negative sign (-) on the amount // of an entry when it decreases our balance (credit=false). -func csv(e *frdrpc.ReportEntry) string { +func writeToCSV(e *frdrpc.ReportEntry) string { amountPrefix := "" if !e.Credit { amountPrefix = "-" diff --git a/cmd/frcli/node_audit.go b/cmd/frcli/node_audit.go index cd7874d..4b7197b 100644 --- a/cmd/frcli/node_audit.go +++ b/cmd/frcli/node_audit.go @@ -204,7 +204,7 @@ func queryOnChainReport(ctx *cli.Context) error { csvStrs := []string{headers} for _, report := range report.Reports { - csvStrs = append(csvStrs, csv(report)) + csvStrs = append(csvStrs, writeToCSV(report)) } csvString := strings.Join(csvStrs, "\n") From f064bb228d034ffd02536240f4d051553cfcfeb9 Mon Sep 17 00:00:00 2001 From: Elle Mouton Date: Wed, 9 Jun 2021 14:19:59 +0200 Subject: [PATCH 10/12] multi: add custom price options to RPC methods This commit adds the options required for the custom fiat price data to the RPC methods. These methods include the NodeAudit and ExchangeRate methods. --- cmd/frcli/csv.go | 55 +++++ cmd/frcli/fiat_estimate.go | 29 ++- cmd/frcli/node_audit.go | 39 +++- cmd/frcli/utils.go | 3 + frdrpc/exchange_rate.go | 9 + frdrpc/faraday.pb.go | 418 +++++++++++++++++++----------------- frdrpc/faraday.proto | 9 + frdrpc/faraday.swagger.json | 15 +- frdrpc/node_audit.go | 34 +++ 9 files changed, 405 insertions(+), 206 deletions(-) diff --git a/cmd/frcli/csv.go b/cmd/frcli/csv.go index 18f81ef..4b6e3cd 100644 --- a/cmd/frcli/csv.go +++ b/cmd/frcli/csv.go @@ -1,7 +1,11 @@ package main import ( + "encoding/csv" + "errors" "fmt" + "os" + "strconv" "time" "github.com/lightninglabs/faraday/frdrpc" @@ -26,3 +30,54 @@ func writeToCSV(e *frdrpc.ReportEntry) string { amountPrefix, e.Fiat, e.Txid, e.Reference, e.BtcPrice.Price, e.BtcPrice.PriceTimestamp, e.Note) } + +// parsePricesFromCSV reads price point data from the csv at the specified path. +// This function expects the first csv line to be headers and expects the rest +// of the lines to be tuples of the following format: +// 'unix tx seconds, price of 1 BTC in chosen currency'. +func parsePricesFromCSV(path, currency string) ([]*frdrpc.BitcoinPrice, error) { + if path == "" || currency == "" { + return nil, errors.New("custom price csv path and " + + "currency must both be specified") + } + csvFile, err := os.Open(path) + if err != nil { + return nil, err + } + defer csvFile.Close() + + csvLines, err := csv.NewReader(csvFile).ReadAll() + if err != nil { + return nil, err + } + + if len(csvLines) < 2 { + return nil, errors.New("no price points found in CSV") + } + + // Skip the first line in the CSV file since we expect this line + // to contain column headers. + csvLines = csvLines[1:] + + prices := make([]*frdrpc.BitcoinPrice, len(csvLines)) + + for i, line := range csvLines { + if len(line) != 2 { + return nil, errors.New("incorrect csv format. " + + "Two columns items are expected per row") + } + + timestamp, err := strconv.ParseInt(line[0], 10, 64) + if err != nil { + return nil, err + } + + prices[i] = &frdrpc.BitcoinPrice{ + PriceTimestamp: uint64(timestamp), + Price: line[1], + Currency: currency, + } + } + + return prices, nil +} diff --git a/cmd/frcli/fiat_estimate.go b/cmd/frcli/fiat_estimate.go index 798c039..83c03c6 100644 --- a/cmd/frcli/fiat_estimate.go +++ b/cmd/frcli/fiat_estimate.go @@ -32,6 +32,18 @@ var fiatEstimateCommand = cli.Command{ Usage: "fiat backend to be used. Options include: " + "'coincap' (default) and 'coindesk'", }, + cli.StringFlag{ + Name: "prices_csv_path", + Usage: "Path to a CSV file containing custom fiat " + + "price data. This is only required if " + + "'fiat_backend' is set to 'custom'.", + }, + cli.StringFlag{ + Name: "custom_price_currency", + Usage: "The currency that the custom prices are " + + "quoted in. This is only required if " + + "'fiat_backend' is set to 'custom'.", + }, }, Action: queryFiatEstimate, } @@ -55,11 +67,24 @@ func queryFiatEstimate(ctx *cli.Context) error { return err } + var customPrices []*frdrpc.BitcoinPrice + + if fiatBackend == frdrpc.FiatBackend_CUSTOM { + customPrices, err = parsePricesFromCSV( + ctx.String("prices_csv_path"), + ctx.String("custom_price_currency"), + ) + if err != nil { + return err + } + } + // Set start and end times from user specified values, defaulting // to zero if they are not set. req := &frdrpc.ExchangeRateRequest{ - Timestamps: []uint64{uint64(ts)}, - FiatBackend: fiatBackend, + Timestamps: []uint64{uint64(ts)}, + FiatBackend: fiatBackend, + CustomPrices: customPrices, } rpcCtx := context.Background() diff --git a/cmd/frcli/node_audit.go b/cmd/frcli/node_audit.go index 4b7197b..81969f9 100644 --- a/cmd/frcli/node_audit.go +++ b/cmd/frcli/node_audit.go @@ -96,7 +96,23 @@ var onChainReportCommand = cli.Command{ cli.StringFlag{ Name: "fiat_backend", Usage: "fiat backend to be used. Options include: " + - "'coincap' (default) and 'coindesk'", + "'coincap' (default), 'coindesk' or " + + "'custom' which allows custom price data to " + + "be used. The 'custom' option requires the" + + "'prices_csv_path' and " + + "'custom_price_currency' options to be set.", + }, + cli.StringFlag{ + Name: "prices_csv_path", + Usage: "Path to a CSV file containing custom fiat " + + "price data. This is only required if " + + "'fiat_backend' is set to 'custom'.", + }, + cli.StringFlag{ + Name: "custom_price_currency", + Usage: "The currency that the custom prices are " + + "quoted in. This is only required if " + + "'fiat_backend' is set to 'custom'.", }, }, Action: queryOnChainReport, @@ -111,13 +127,26 @@ func queryOnChainReport(ctx *cli.Context) error { return err } + var customPrices []*frdrpc.BitcoinPrice + + if fiatBackend == frdrpc.FiatBackend_CUSTOM { + customPrices, err = parsePricesFromCSV( + ctx.String("prices_csv_path"), + ctx.String("custom_price_currency"), + ) + if err != nil { + return err + } + } + // Set start and end times from user specified values, defaulting // to zero if they are not set. req := &frdrpc.NodeAuditRequest{ - StartTime: uint64(ctx.Int64("start_time")), - EndTime: uint64(ctx.Int64("end_time")), - DisableFiat: !ctx.IsSet("enable_fiat"), - FiatBackend: fiatBackend, + StartTime: uint64(ctx.Int64("start_time")), + EndTime: uint64(ctx.Int64("end_time")), + DisableFiat: !ctx.IsSet("enable_fiat"), + FiatBackend: fiatBackend, + CustomPrices: customPrices, } // If start time is zero, default to a week ago. diff --git a/cmd/frcli/utils.go b/cmd/frcli/utils.go index 6bb9282..fc450f0 100644 --- a/cmd/frcli/utils.go +++ b/cmd/frcli/utils.go @@ -288,6 +288,9 @@ func parseFiatBackend(fiatBackend string) (frdrpc.FiatBackend, error) { case fiat.CoinDeskPriceBackend.String(): return frdrpc.FiatBackend_COINDESK, nil + case fiat.CustomPriceBackend.String(): + return frdrpc.FiatBackend_CUSTOM, nil + default: return frdrpc.FiatBackend_UNKNOWN_FIATBACKEND, fmt.Errorf( "unknown fiat backend", diff --git a/frdrpc/exchange_rate.go b/frdrpc/exchange_rate.go index 3991a26..7720538 100644 --- a/frdrpc/exchange_rate.go +++ b/frdrpc/exchange_rate.go @@ -70,6 +70,9 @@ func fiatBackendFromRPC(backend FiatBackend) (fiat.PriceBackend, error) { case FiatBackend_COINDESK: return fiat.CoinDeskPriceBackend, nil + case FiatBackend_CUSTOM: + return fiat.CustomPriceBackend, nil + default: return fiat.UnknownPriceBackend, fmt.Errorf("unknown fiat backend: %v", backend) @@ -111,9 +114,15 @@ func parseExchangeRateRequest(req *ExchangeRateRequest) ([]time.Time, return nil, nil, err } + pricePoints, err := pricePointsFromRPC(req.CustomPrices) + if err != nil { + return nil, nil, err + } + return timestamps, &fiat.PriceSourceConfig{ Backend: fiatBackend, Granularity: granularity, + PricePoints: pricePoints, }, nil } diff --git a/frdrpc/faraday.pb.go b/frdrpc/faraday.pb.go index e044750..0736ccc 100644 --- a/frdrpc/faraday.pb.go +++ b/frdrpc/faraday.pb.go @@ -105,6 +105,8 @@ const ( // This API is reached through the following URL: // https://api.coindesk.com/v1/bpi/historical/close.json FiatBackend_COINDESK FiatBackend = 2 + // Use custom price data provided in a CSV file for fiat price information. + FiatBackend_CUSTOM FiatBackend = 3 ) // Enum value maps for FiatBackend. @@ -113,11 +115,13 @@ var ( 0: "UNKNOWN_FIATBACKEND", 1: "COINCAP", 2: "COINDESK", + 3: "CUSTOM", } FiatBackend_value = map[string]int32{ "UNKNOWN_FIATBACKEND": 0, "COINCAP": 1, "COINDESK": 2, + "CUSTOM": 3, } ) @@ -1152,6 +1156,8 @@ type ExchangeRateRequest struct { Granularity Granularity `protobuf:"varint,4,opt,name=granularity,proto3,enum=frdrpc.Granularity" json:"granularity,omitempty"` // The api to be used for fiat related queries. FiatBackend FiatBackend `protobuf:"varint,5,opt,name=fiat_backend,json=fiatBackend,proto3,enum=frdrpc.FiatBackend" json:"fiat_backend,omitempty"` + // Custom price points to use if the CUSTOM FiatBackend option is set. + CustomPrices []*BitcoinPrice `protobuf:"bytes,8,rep,name=custom_prices,json=customPrices,proto3" json:"custom_prices,omitempty"` } func (x *ExchangeRateRequest) Reset() { @@ -1207,6 +1213,13 @@ func (x *ExchangeRateRequest) GetFiatBackend() FiatBackend { return FiatBackend_UNKNOWN_FIATBACKEND } +func (x *ExchangeRateRequest) GetCustomPrices() []*BitcoinPrice { + if x != nil { + return x.CustomPrices + } + return nil +} + type ExchangeRateResponse struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache @@ -1404,6 +1417,8 @@ type NodeAuditRequest struct { CustomCategories []*CustomCategory `protobuf:"bytes,6,rep,name=custom_categories,json=customCategories,proto3" json:"custom_categories,omitempty"` // The api to be used for fiat related queries. FiatBackend FiatBackend `protobuf:"varint,7,opt,name=fiat_backend,json=fiatBackend,proto3,enum=frdrpc.FiatBackend" json:"fiat_backend,omitempty"` + // Custom price points to use if the CUSTOM FiatBackend option is set. + CustomPrices []*BitcoinPrice `protobuf:"bytes,8,rep,name=custom_prices,json=customPrices,proto3" json:"custom_prices,omitempty"` } func (x *NodeAuditRequest) Reset() { @@ -1480,6 +1495,13 @@ func (x *NodeAuditRequest) GetFiatBackend() FiatBackend { return FiatBackend_UNKNOWN_FIATBACKEND } +func (x *NodeAuditRequest) GetCustomPrices() []*BitcoinPrice { + if x != nil { + return x.CustomPrices + } + return nil +} + type CustomCategory struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache @@ -2040,7 +2062,7 @@ var file_faraday_proto_rawDesc = []byte{ 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x18, 0x07, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x0d, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x72, 0x6d, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x12, 0x18, 0x0a, 0x07, 0x70, 0x72, 0x69, 0x76, 0x61, 0x74, 0x65, 0x18, 0x08, 0x20, 0x01, 0x28, 0x08, 0x52, 0x07, 0x70, 0x72, - 0x69, 0x76, 0x61, 0x74, 0x65, 0x22, 0xb0, 0x01, 0x0a, 0x13, 0x45, 0x78, 0x63, 0x68, 0x61, 0x6e, + 0x69, 0x76, 0x61, 0x74, 0x65, 0x22, 0xeb, 0x01, 0x0a, 0x13, 0x45, 0x78, 0x63, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x52, 0x61, 0x74, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x1e, 0x0a, 0x0a, 0x74, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x73, 0x18, 0x03, 0x20, 0x03, 0x28, 0x04, 0x52, 0x0a, 0x74, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x73, 0x12, 0x35, 0x0a, @@ -2050,171 +2072,179 @@ var file_faraday_proto_rawDesc = []byte{ 0x72, 0x69, 0x74, 0x79, 0x12, 0x36, 0x0a, 0x0c, 0x66, 0x69, 0x61, 0x74, 0x5f, 0x62, 0x61, 0x63, 0x6b, 0x65, 0x6e, 0x64, 0x18, 0x05, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x13, 0x2e, 0x66, 0x72, 0x64, 0x72, 0x70, 0x63, 0x2e, 0x46, 0x69, 0x61, 0x74, 0x42, 0x61, 0x63, 0x6b, 0x65, 0x6e, 0x64, 0x52, - 0x0b, 0x66, 0x69, 0x61, 0x74, 0x42, 0x61, 0x63, 0x6b, 0x65, 0x6e, 0x64, 0x4a, 0x04, 0x08, 0x01, - 0x10, 0x02, 0x4a, 0x04, 0x08, 0x02, 0x10, 0x03, 0x22, 0x48, 0x0a, 0x14, 0x45, 0x78, 0x63, 0x68, - 0x61, 0x6e, 0x67, 0x65, 0x52, 0x61, 0x74, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, - 0x12, 0x2a, 0x0a, 0x05, 0x72, 0x61, 0x74, 0x65, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, 0x0b, 0x32, - 0x14, 0x2e, 0x66, 0x72, 0x64, 0x72, 0x70, 0x63, 0x2e, 0x45, 0x78, 0x63, 0x68, 0x61, 0x6e, 0x67, - 0x65, 0x52, 0x61, 0x74, 0x65, 0x52, 0x05, 0x72, 0x61, 0x74, 0x65, 0x73, 0x4a, 0x04, 0x08, 0x01, - 0x10, 0x02, 0x22, 0x69, 0x0a, 0x0c, 0x42, 0x69, 0x74, 0x63, 0x6f, 0x69, 0x6e, 0x50, 0x72, 0x69, - 0x63, 0x65, 0x12, 0x14, 0x0a, 0x05, 0x70, 0x72, 0x69, 0x63, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, - 0x09, 0x52, 0x05, 0x70, 0x72, 0x69, 0x63, 0x65, 0x12, 0x27, 0x0a, 0x0f, 0x70, 0x72, 0x69, 0x63, - 0x65, 0x5f, 0x74, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x18, 0x02, 0x20, 0x01, 0x28, - 0x04, 0x52, 0x0e, 0x70, 0x72, 0x69, 0x63, 0x65, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, - 0x70, 0x12, 0x1a, 0x0a, 0x08, 0x63, 0x75, 0x72, 0x72, 0x65, 0x6e, 0x63, 0x79, 0x18, 0x03, 0x20, - 0x01, 0x28, 0x09, 0x52, 0x08, 0x63, 0x75, 0x72, 0x72, 0x65, 0x6e, 0x63, 0x79, 0x22, 0x5f, 0x0a, - 0x0c, 0x45, 0x78, 0x63, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x52, 0x61, 0x74, 0x65, 0x12, 0x1c, 0x0a, - 0x09, 0x74, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x18, 0x01, 0x20, 0x01, 0x28, 0x04, - 0x52, 0x09, 0x74, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x12, 0x31, 0x0a, 0x09, 0x62, - 0x74, 0x63, 0x5f, 0x70, 0x72, 0x69, 0x63, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x14, - 0x2e, 0x66, 0x72, 0x64, 0x72, 0x70, 0x63, 0x2e, 0x42, 0x69, 0x74, 0x63, 0x6f, 0x69, 0x6e, 0x50, - 0x72, 0x69, 0x63, 0x65, 0x52, 0x08, 0x62, 0x74, 0x63, 0x50, 0x72, 0x69, 0x63, 0x65, 0x22, 0xa9, - 0x02, 0x0a, 0x10, 0x4e, 0x6f, 0x64, 0x65, 0x41, 0x75, 0x64, 0x69, 0x74, 0x52, 0x65, 0x71, 0x75, - 0x65, 0x73, 0x74, 0x12, 0x1d, 0x0a, 0x0a, 0x73, 0x74, 0x61, 0x72, 0x74, 0x5f, 0x74, 0x69, 0x6d, - 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x04, 0x52, 0x09, 0x73, 0x74, 0x61, 0x72, 0x74, 0x54, 0x69, - 0x6d, 0x65, 0x12, 0x19, 0x0a, 0x08, 0x65, 0x6e, 0x64, 0x5f, 0x74, 0x69, 0x6d, 0x65, 0x18, 0x02, - 0x20, 0x01, 0x28, 0x04, 0x52, 0x07, 0x65, 0x6e, 0x64, 0x54, 0x69, 0x6d, 0x65, 0x12, 0x21, 0x0a, - 0x0c, 0x64, 0x69, 0x73, 0x61, 0x62, 0x6c, 0x65, 0x5f, 0x66, 0x69, 0x61, 0x74, 0x18, 0x04, 0x20, - 0x01, 0x28, 0x08, 0x52, 0x0b, 0x64, 0x69, 0x73, 0x61, 0x62, 0x6c, 0x65, 0x46, 0x69, 0x61, 0x74, - 0x12, 0x35, 0x0a, 0x0b, 0x67, 0x72, 0x61, 0x6e, 0x75, 0x6c, 0x61, 0x72, 0x69, 0x74, 0x79, 0x18, - 0x05, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x13, 0x2e, 0x66, 0x72, 0x64, 0x72, 0x70, 0x63, 0x2e, 0x47, - 0x72, 0x61, 0x6e, 0x75, 0x6c, 0x61, 0x72, 0x69, 0x74, 0x79, 0x52, 0x0b, 0x67, 0x72, 0x61, 0x6e, - 0x75, 0x6c, 0x61, 0x72, 0x69, 0x74, 0x79, 0x12, 0x43, 0x0a, 0x11, 0x63, 0x75, 0x73, 0x74, 0x6f, - 0x6d, 0x5f, 0x63, 0x61, 0x74, 0x65, 0x67, 0x6f, 0x72, 0x69, 0x65, 0x73, 0x18, 0x06, 0x20, 0x03, - 0x28, 0x0b, 0x32, 0x16, 0x2e, 0x66, 0x72, 0x64, 0x72, 0x70, 0x63, 0x2e, 0x43, 0x75, 0x73, 0x74, - 0x6f, 0x6d, 0x43, 0x61, 0x74, 0x65, 0x67, 0x6f, 0x72, 0x79, 0x52, 0x10, 0x63, 0x75, 0x73, 0x74, - 0x6f, 0x6d, 0x43, 0x61, 0x74, 0x65, 0x67, 0x6f, 0x72, 0x69, 0x65, 0x73, 0x12, 0x36, 0x0a, 0x0c, - 0x66, 0x69, 0x61, 0x74, 0x5f, 0x62, 0x61, 0x63, 0x6b, 0x65, 0x6e, 0x64, 0x18, 0x07, 0x20, 0x01, - 0x28, 0x0e, 0x32, 0x13, 0x2e, 0x66, 0x72, 0x64, 0x72, 0x70, 0x63, 0x2e, 0x46, 0x69, 0x61, 0x74, - 0x42, 0x61, 0x63, 0x6b, 0x65, 0x6e, 0x64, 0x52, 0x0b, 0x66, 0x69, 0x61, 0x74, 0x42, 0x61, 0x63, - 0x6b, 0x65, 0x6e, 0x64, 0x4a, 0x04, 0x08, 0x03, 0x10, 0x04, 0x22, 0x83, 0x01, 0x0a, 0x0e, 0x43, - 0x75, 0x73, 0x74, 0x6f, 0x6d, 0x43, 0x61, 0x74, 0x65, 0x67, 0x6f, 0x72, 0x79, 0x12, 0x12, 0x0a, - 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, - 0x65, 0x12, 0x19, 0x0a, 0x08, 0x6f, 0x6e, 0x5f, 0x63, 0x68, 0x61, 0x69, 0x6e, 0x18, 0x02, 0x20, - 0x01, 0x28, 0x08, 0x52, 0x07, 0x6f, 0x6e, 0x43, 0x68, 0x61, 0x69, 0x6e, 0x12, 0x1b, 0x0a, 0x09, - 0x6f, 0x66, 0x66, 0x5f, 0x63, 0x68, 0x61, 0x69, 0x6e, 0x18, 0x03, 0x20, 0x01, 0x28, 0x08, 0x52, - 0x08, 0x6f, 0x66, 0x66, 0x43, 0x68, 0x61, 0x69, 0x6e, 0x12, 0x25, 0x0a, 0x0e, 0x6c, 0x61, 0x62, - 0x65, 0x6c, 0x5f, 0x70, 0x61, 0x74, 0x74, 0x65, 0x72, 0x6e, 0x73, 0x18, 0x05, 0x20, 0x03, 0x28, - 0x09, 0x52, 0x0d, 0x6c, 0x61, 0x62, 0x65, 0x6c, 0x50, 0x61, 0x74, 0x74, 0x65, 0x72, 0x6e, 0x73, - 0x22, 0xe9, 0x02, 0x0a, 0x0b, 0x52, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x45, 0x6e, 0x74, 0x72, 0x79, - 0x12, 0x1c, 0x0a, 0x09, 0x74, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x18, 0x01, 0x20, - 0x01, 0x28, 0x04, 0x52, 0x09, 0x74, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x12, 0x19, - 0x0a, 0x08, 0x6f, 0x6e, 0x5f, 0x63, 0x68, 0x61, 0x69, 0x6e, 0x18, 0x02, 0x20, 0x01, 0x28, 0x08, - 0x52, 0x07, 0x6f, 0x6e, 0x43, 0x68, 0x61, 0x69, 0x6e, 0x12, 0x16, 0x0a, 0x06, 0x61, 0x6d, 0x6f, - 0x75, 0x6e, 0x74, 0x18, 0x03, 0x20, 0x01, 0x28, 0x04, 0x52, 0x06, 0x61, 0x6d, 0x6f, 0x75, 0x6e, - 0x74, 0x12, 0x16, 0x0a, 0x06, 0x63, 0x72, 0x65, 0x64, 0x69, 0x74, 0x18, 0x04, 0x20, 0x01, 0x28, - 0x08, 0x52, 0x06, 0x63, 0x72, 0x65, 0x64, 0x69, 0x74, 0x12, 0x14, 0x0a, 0x05, 0x61, 0x73, 0x73, - 0x65, 0x74, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x61, 0x73, 0x73, 0x65, 0x74, 0x12, - 0x25, 0x0a, 0x04, 0x74, 0x79, 0x70, 0x65, 0x18, 0x06, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x11, 0x2e, - 0x66, 0x72, 0x64, 0x72, 0x70, 0x63, 0x2e, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x54, 0x79, 0x70, 0x65, - 0x52, 0x04, 0x74, 0x79, 0x70, 0x65, 0x12, 0x27, 0x0a, 0x0f, 0x63, 0x75, 0x73, 0x74, 0x6f, 0x6d, - 0x5f, 0x63, 0x61, 0x74, 0x65, 0x67, 0x6f, 0x72, 0x79, 0x18, 0x0c, 0x20, 0x01, 0x28, 0x09, 0x52, - 0x0e, 0x63, 0x75, 0x73, 0x74, 0x6f, 0x6d, 0x43, 0x61, 0x74, 0x65, 0x67, 0x6f, 0x72, 0x79, 0x12, - 0x12, 0x0a, 0x04, 0x74, 0x78, 0x69, 0x64, 0x18, 0x07, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x74, - 0x78, 0x69, 0x64, 0x12, 0x12, 0x0a, 0x04, 0x66, 0x69, 0x61, 0x74, 0x18, 0x08, 0x20, 0x01, 0x28, - 0x09, 0x52, 0x04, 0x66, 0x69, 0x61, 0x74, 0x12, 0x1c, 0x0a, 0x09, 0x72, 0x65, 0x66, 0x65, 0x72, - 0x65, 0x6e, 0x63, 0x65, 0x18, 0x09, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x72, 0x65, 0x66, 0x65, - 0x72, 0x65, 0x6e, 0x63, 0x65, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x6f, 0x74, 0x65, 0x18, 0x0a, 0x20, - 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, 0x6f, 0x74, 0x65, 0x12, 0x31, 0x0a, 0x09, 0x62, 0x74, 0x63, - 0x5f, 0x70, 0x72, 0x69, 0x63, 0x65, 0x18, 0x0b, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x14, 0x2e, 0x66, - 0x72, 0x64, 0x72, 0x70, 0x63, 0x2e, 0x42, 0x69, 0x74, 0x63, 0x6f, 0x69, 0x6e, 0x50, 0x72, 0x69, - 0x63, 0x65, 0x52, 0x08, 0x62, 0x74, 0x63, 0x50, 0x72, 0x69, 0x63, 0x65, 0x22, 0x42, 0x0a, 0x11, - 0x4e, 0x6f, 0x64, 0x65, 0x41, 0x75, 0x64, 0x69, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, - 0x65, 0x12, 0x2d, 0x0a, 0x07, 0x72, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x73, 0x18, 0x01, 0x20, 0x03, - 0x28, 0x0b, 0x32, 0x13, 0x2e, 0x66, 0x72, 0x64, 0x72, 0x70, 0x63, 0x2e, 0x52, 0x65, 0x70, 0x6f, - 0x72, 0x74, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x52, 0x07, 0x72, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x73, - 0x22, 0x39, 0x0a, 0x12, 0x43, 0x6c, 0x6f, 0x73, 0x65, 0x52, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x52, - 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x23, 0x0a, 0x0d, 0x63, 0x68, 0x61, 0x6e, 0x6e, 0x65, - 0x6c, 0x5f, 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0c, 0x63, - 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x50, 0x6f, 0x69, 0x6e, 0x74, 0x22, 0xdd, 0x01, 0x0a, 0x13, - 0x43, 0x6c, 0x6f, 0x73, 0x65, 0x52, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, - 0x6e, 0x73, 0x65, 0x12, 0x23, 0x0a, 0x0d, 0x63, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x5f, 0x70, - 0x6f, 0x69, 0x6e, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0c, 0x63, 0x68, 0x61, 0x6e, - 0x6e, 0x65, 0x6c, 0x50, 0x6f, 0x69, 0x6e, 0x74, 0x12, 0x2b, 0x0a, 0x11, 0x63, 0x68, 0x61, 0x6e, - 0x6e, 0x65, 0x6c, 0x5f, 0x69, 0x6e, 0x69, 0x74, 0x69, 0x61, 0x74, 0x6f, 0x72, 0x18, 0x02, 0x20, - 0x01, 0x28, 0x08, 0x52, 0x10, 0x63, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x49, 0x6e, 0x69, 0x74, - 0x69, 0x61, 0x74, 0x6f, 0x72, 0x12, 0x1d, 0x0a, 0x0a, 0x63, 0x6c, 0x6f, 0x73, 0x65, 0x5f, 0x74, - 0x79, 0x70, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x63, 0x6c, 0x6f, 0x73, 0x65, - 0x54, 0x79, 0x70, 0x65, 0x12, 0x1d, 0x0a, 0x0a, 0x63, 0x6c, 0x6f, 0x73, 0x65, 0x5f, 0x74, 0x78, - 0x69, 0x64, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x63, 0x6c, 0x6f, 0x73, 0x65, 0x54, - 0x78, 0x69, 0x64, 0x12, 0x19, 0x0a, 0x08, 0x6f, 0x70, 0x65, 0x6e, 0x5f, 0x66, 0x65, 0x65, 0x18, - 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x6f, 0x70, 0x65, 0x6e, 0x46, 0x65, 0x65, 0x12, 0x1b, - 0x0a, 0x09, 0x63, 0x6c, 0x6f, 0x73, 0x65, 0x5f, 0x66, 0x65, 0x65, 0x18, 0x06, 0x20, 0x01, 0x28, - 0x09, 0x52, 0x08, 0x63, 0x6c, 0x6f, 0x73, 0x65, 0x46, 0x65, 0x65, 0x2a, 0xa1, 0x01, 0x0a, 0x0b, - 0x47, 0x72, 0x61, 0x6e, 0x75, 0x6c, 0x61, 0x72, 0x69, 0x74, 0x79, 0x12, 0x17, 0x0a, 0x13, 0x55, - 0x4e, 0x4b, 0x4e, 0x4f, 0x57, 0x4e, 0x5f, 0x47, 0x52, 0x41, 0x4e, 0x55, 0x4c, 0x41, 0x52, 0x49, - 0x54, 0x59, 0x10, 0x00, 0x12, 0x0a, 0x0a, 0x06, 0x4d, 0x49, 0x4e, 0x55, 0x54, 0x45, 0x10, 0x01, - 0x12, 0x10, 0x0a, 0x0c, 0x46, 0x49, 0x56, 0x45, 0x5f, 0x4d, 0x49, 0x4e, 0x55, 0x54, 0x45, 0x53, - 0x10, 0x02, 0x12, 0x13, 0x0a, 0x0f, 0x46, 0x49, 0x46, 0x54, 0x45, 0x45, 0x4e, 0x5f, 0x4d, 0x49, - 0x4e, 0x55, 0x54, 0x45, 0x53, 0x10, 0x03, 0x12, 0x12, 0x0a, 0x0e, 0x54, 0x48, 0x49, 0x52, 0x54, - 0x59, 0x5f, 0x4d, 0x49, 0x4e, 0x55, 0x54, 0x45, 0x53, 0x10, 0x04, 0x12, 0x08, 0x0a, 0x04, 0x48, - 0x4f, 0x55, 0x52, 0x10, 0x05, 0x12, 0x0d, 0x0a, 0x09, 0x53, 0x49, 0x58, 0x5f, 0x48, 0x4f, 0x55, - 0x52, 0x53, 0x10, 0x06, 0x12, 0x10, 0x0a, 0x0c, 0x54, 0x57, 0x45, 0x4c, 0x56, 0x45, 0x5f, 0x48, - 0x4f, 0x55, 0x52, 0x53, 0x10, 0x07, 0x12, 0x07, 0x0a, 0x03, 0x44, 0x41, 0x59, 0x10, 0x08, 0x2a, - 0x41, 0x0a, 0x0b, 0x46, 0x69, 0x61, 0x74, 0x42, 0x61, 0x63, 0x6b, 0x65, 0x6e, 0x64, 0x12, 0x17, - 0x0a, 0x13, 0x55, 0x4e, 0x4b, 0x4e, 0x4f, 0x57, 0x4e, 0x5f, 0x46, 0x49, 0x41, 0x54, 0x42, 0x41, - 0x43, 0x4b, 0x45, 0x4e, 0x44, 0x10, 0x00, 0x12, 0x0b, 0x0a, 0x07, 0x43, 0x4f, 0x49, 0x4e, 0x43, - 0x41, 0x50, 0x10, 0x01, 0x12, 0x0c, 0x0a, 0x08, 0x43, 0x4f, 0x49, 0x4e, 0x44, 0x45, 0x53, 0x4b, - 0x10, 0x02, 0x2a, 0xa2, 0x02, 0x0a, 0x09, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x54, 0x79, 0x70, 0x65, - 0x12, 0x0b, 0x0a, 0x07, 0x55, 0x4e, 0x4b, 0x4e, 0x4f, 0x57, 0x4e, 0x10, 0x00, 0x12, 0x16, 0x0a, - 0x12, 0x4c, 0x4f, 0x43, 0x41, 0x4c, 0x5f, 0x43, 0x48, 0x41, 0x4e, 0x4e, 0x45, 0x4c, 0x5f, 0x4f, - 0x50, 0x45, 0x4e, 0x10, 0x01, 0x12, 0x17, 0x0a, 0x13, 0x52, 0x45, 0x4d, 0x4f, 0x54, 0x45, 0x5f, - 0x43, 0x48, 0x41, 0x4e, 0x4e, 0x45, 0x4c, 0x5f, 0x4f, 0x50, 0x45, 0x4e, 0x10, 0x02, 0x12, 0x14, - 0x0a, 0x10, 0x43, 0x48, 0x41, 0x4e, 0x4e, 0x45, 0x4c, 0x5f, 0x4f, 0x50, 0x45, 0x4e, 0x5f, 0x46, - 0x45, 0x45, 0x10, 0x03, 0x12, 0x11, 0x0a, 0x0d, 0x43, 0x48, 0x41, 0x4e, 0x4e, 0x45, 0x4c, 0x5f, - 0x43, 0x4c, 0x4f, 0x53, 0x45, 0x10, 0x04, 0x12, 0x0b, 0x0a, 0x07, 0x52, 0x45, 0x43, 0x45, 0x49, - 0x50, 0x54, 0x10, 0x05, 0x12, 0x0b, 0x0a, 0x07, 0x50, 0x41, 0x59, 0x4d, 0x45, 0x4e, 0x54, 0x10, - 0x06, 0x12, 0x07, 0x0a, 0x03, 0x46, 0x45, 0x45, 0x10, 0x07, 0x12, 0x14, 0x0a, 0x10, 0x43, 0x49, - 0x52, 0x43, 0x55, 0x4c, 0x41, 0x52, 0x5f, 0x52, 0x45, 0x43, 0x45, 0x49, 0x50, 0x54, 0x10, 0x08, - 0x12, 0x0b, 0x0a, 0x07, 0x46, 0x4f, 0x52, 0x57, 0x41, 0x52, 0x44, 0x10, 0x09, 0x12, 0x0f, 0x0a, - 0x0b, 0x46, 0x4f, 0x52, 0x57, 0x41, 0x52, 0x44, 0x5f, 0x46, 0x45, 0x45, 0x10, 0x0a, 0x12, 0x14, - 0x0a, 0x10, 0x43, 0x49, 0x52, 0x43, 0x55, 0x4c, 0x41, 0x52, 0x5f, 0x50, 0x41, 0x59, 0x4d, 0x45, - 0x4e, 0x54, 0x10, 0x0b, 0x12, 0x10, 0x0a, 0x0c, 0x43, 0x49, 0x52, 0x43, 0x55, 0x4c, 0x41, 0x52, - 0x5f, 0x46, 0x45, 0x45, 0x10, 0x0c, 0x12, 0x09, 0x0a, 0x05, 0x53, 0x57, 0x45, 0x45, 0x50, 0x10, - 0x0d, 0x12, 0x0d, 0x0a, 0x09, 0x53, 0x57, 0x45, 0x45, 0x50, 0x5f, 0x46, 0x45, 0x45, 0x10, 0x0e, - 0x12, 0x15, 0x0a, 0x11, 0x43, 0x48, 0x41, 0x4e, 0x4e, 0x45, 0x4c, 0x5f, 0x43, 0x4c, 0x4f, 0x53, - 0x45, 0x5f, 0x46, 0x45, 0x45, 0x10, 0x0f, 0x32, 0xd8, 0x04, 0x0a, 0x0d, 0x46, 0x61, 0x72, 0x61, - 0x64, 0x61, 0x79, 0x53, 0x65, 0x72, 0x76, 0x65, 0x72, 0x12, 0x65, 0x0a, 0x16, 0x4f, 0x75, 0x74, - 0x6c, 0x69, 0x65, 0x72, 0x52, 0x65, 0x63, 0x6f, 0x6d, 0x6d, 0x65, 0x6e, 0x64, 0x61, 0x74, 0x69, - 0x6f, 0x6e, 0x73, 0x12, 0x25, 0x2e, 0x66, 0x72, 0x64, 0x72, 0x70, 0x63, 0x2e, 0x4f, 0x75, 0x74, - 0x6c, 0x69, 0x65, 0x72, 0x52, 0x65, 0x63, 0x6f, 0x6d, 0x6d, 0x65, 0x6e, 0x64, 0x61, 0x74, 0x69, - 0x6f, 0x6e, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x24, 0x2e, 0x66, 0x72, 0x64, - 0x72, 0x70, 0x63, 0x2e, 0x43, 0x6c, 0x6f, 0x73, 0x65, 0x52, 0x65, 0x63, 0x6f, 0x6d, 0x6d, 0x65, - 0x6e, 0x64, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, - 0x12, 0x69, 0x0a, 0x18, 0x54, 0x68, 0x72, 0x65, 0x73, 0x68, 0x6f, 0x6c, 0x64, 0x52, 0x65, 0x63, - 0x6f, 0x6d, 0x6d, 0x65, 0x6e, 0x64, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x12, 0x27, 0x2e, 0x66, - 0x72, 0x64, 0x72, 0x70, 0x63, 0x2e, 0x54, 0x68, 0x72, 0x65, 0x73, 0x68, 0x6f, 0x6c, 0x64, 0x52, - 0x65, 0x63, 0x6f, 0x6d, 0x6d, 0x65, 0x6e, 0x64, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x52, 0x65, - 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x24, 0x2e, 0x66, 0x72, 0x64, 0x72, 0x70, 0x63, 0x2e, 0x43, - 0x6c, 0x6f, 0x73, 0x65, 0x52, 0x65, 0x63, 0x6f, 0x6d, 0x6d, 0x65, 0x6e, 0x64, 0x61, 0x74, 0x69, - 0x6f, 0x6e, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x4c, 0x0a, 0x0d, 0x52, - 0x65, 0x76, 0x65, 0x6e, 0x75, 0x65, 0x52, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x12, 0x1c, 0x2e, 0x66, - 0x72, 0x64, 0x72, 0x70, 0x63, 0x2e, 0x52, 0x65, 0x76, 0x65, 0x6e, 0x75, 0x65, 0x52, 0x65, 0x70, - 0x6f, 0x72, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1d, 0x2e, 0x66, 0x72, 0x64, - 0x72, 0x70, 0x63, 0x2e, 0x52, 0x65, 0x76, 0x65, 0x6e, 0x75, 0x65, 0x52, 0x65, 0x70, 0x6f, 0x72, - 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x52, 0x0a, 0x0f, 0x43, 0x68, 0x61, - 0x6e, 0x6e, 0x65, 0x6c, 0x49, 0x6e, 0x73, 0x69, 0x67, 0x68, 0x74, 0x73, 0x12, 0x1e, 0x2e, 0x66, - 0x72, 0x64, 0x72, 0x70, 0x63, 0x2e, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x49, 0x6e, 0x73, - 0x69, 0x67, 0x68, 0x74, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1f, 0x2e, 0x66, - 0x72, 0x64, 0x72, 0x70, 0x63, 0x2e, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x49, 0x6e, 0x73, - 0x69, 0x67, 0x68, 0x74, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x49, 0x0a, - 0x0c, 0x45, 0x78, 0x63, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x52, 0x61, 0x74, 0x65, 0x12, 0x1b, 0x2e, - 0x66, 0x72, 0x64, 0x72, 0x70, 0x63, 0x2e, 0x45, 0x78, 0x63, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x52, - 0x61, 0x74, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1c, 0x2e, 0x66, 0x72, 0x64, + 0x0b, 0x66, 0x69, 0x61, 0x74, 0x42, 0x61, 0x63, 0x6b, 0x65, 0x6e, 0x64, 0x12, 0x39, 0x0a, 0x0d, + 0x63, 0x75, 0x73, 0x74, 0x6f, 0x6d, 0x5f, 0x70, 0x72, 0x69, 0x63, 0x65, 0x73, 0x18, 0x08, 0x20, + 0x03, 0x28, 0x0b, 0x32, 0x14, 0x2e, 0x66, 0x72, 0x64, 0x72, 0x70, 0x63, 0x2e, 0x42, 0x69, 0x74, + 0x63, 0x6f, 0x69, 0x6e, 0x50, 0x72, 0x69, 0x63, 0x65, 0x52, 0x0c, 0x63, 0x75, 0x73, 0x74, 0x6f, + 0x6d, 0x50, 0x72, 0x69, 0x63, 0x65, 0x73, 0x4a, 0x04, 0x08, 0x01, 0x10, 0x02, 0x4a, 0x04, 0x08, + 0x02, 0x10, 0x03, 0x22, 0x48, 0x0a, 0x14, 0x45, 0x78, 0x63, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x52, + 0x61, 0x74, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x2a, 0x0a, 0x05, 0x72, + 0x61, 0x74, 0x65, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x14, 0x2e, 0x66, 0x72, 0x64, 0x72, 0x70, 0x63, 0x2e, 0x45, 0x78, 0x63, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x52, 0x61, 0x74, 0x65, - 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x40, 0x0a, 0x09, 0x4e, 0x6f, 0x64, 0x65, - 0x41, 0x75, 0x64, 0x69, 0x74, 0x12, 0x18, 0x2e, 0x66, 0x72, 0x64, 0x72, 0x70, 0x63, 0x2e, 0x4e, - 0x6f, 0x64, 0x65, 0x41, 0x75, 0x64, 0x69, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, - 0x19, 0x2e, 0x66, 0x72, 0x64, 0x72, 0x70, 0x63, 0x2e, 0x4e, 0x6f, 0x64, 0x65, 0x41, 0x75, 0x64, - 0x69, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x46, 0x0a, 0x0b, 0x43, 0x6c, - 0x6f, 0x73, 0x65, 0x52, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x12, 0x1a, 0x2e, 0x66, 0x72, 0x64, 0x72, - 0x70, 0x63, 0x2e, 0x43, 0x6c, 0x6f, 0x73, 0x65, 0x52, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x52, 0x65, - 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1b, 0x2e, 0x66, 0x72, 0x64, 0x72, 0x70, 0x63, 0x2e, 0x43, - 0x6c, 0x6f, 0x73, 0x65, 0x52, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, - 0x73, 0x65, 0x42, 0x29, 0x5a, 0x27, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, - 0x2f, 0x6c, 0x69, 0x67, 0x68, 0x74, 0x6e, 0x69, 0x6e, 0x67, 0x6c, 0x61, 0x62, 0x73, 0x2f, 0x66, - 0x61, 0x72, 0x61, 0x64, 0x61, 0x79, 0x2f, 0x66, 0x72, 0x64, 0x72, 0x70, 0x63, 0x62, 0x06, 0x70, - 0x72, 0x6f, 0x74, 0x6f, 0x33, + 0x52, 0x05, 0x72, 0x61, 0x74, 0x65, 0x73, 0x4a, 0x04, 0x08, 0x01, 0x10, 0x02, 0x22, 0x69, 0x0a, + 0x0c, 0x42, 0x69, 0x74, 0x63, 0x6f, 0x69, 0x6e, 0x50, 0x72, 0x69, 0x63, 0x65, 0x12, 0x14, 0x0a, + 0x05, 0x70, 0x72, 0x69, 0x63, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x70, 0x72, + 0x69, 0x63, 0x65, 0x12, 0x27, 0x0a, 0x0f, 0x70, 0x72, 0x69, 0x63, 0x65, 0x5f, 0x74, 0x69, 0x6d, + 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x18, 0x02, 0x20, 0x01, 0x28, 0x04, 0x52, 0x0e, 0x70, 0x72, + 0x69, 0x63, 0x65, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x12, 0x1a, 0x0a, 0x08, + 0x63, 0x75, 0x72, 0x72, 0x65, 0x6e, 0x63, 0x79, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, + 0x63, 0x75, 0x72, 0x72, 0x65, 0x6e, 0x63, 0x79, 0x22, 0x5f, 0x0a, 0x0c, 0x45, 0x78, 0x63, 0x68, + 0x61, 0x6e, 0x67, 0x65, 0x52, 0x61, 0x74, 0x65, 0x12, 0x1c, 0x0a, 0x09, 0x74, 0x69, 0x6d, 0x65, + 0x73, 0x74, 0x61, 0x6d, 0x70, 0x18, 0x01, 0x20, 0x01, 0x28, 0x04, 0x52, 0x09, 0x74, 0x69, 0x6d, + 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x12, 0x31, 0x0a, 0x09, 0x62, 0x74, 0x63, 0x5f, 0x70, 0x72, + 0x69, 0x63, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x14, 0x2e, 0x66, 0x72, 0x64, 0x72, + 0x70, 0x63, 0x2e, 0x42, 0x69, 0x74, 0x63, 0x6f, 0x69, 0x6e, 0x50, 0x72, 0x69, 0x63, 0x65, 0x52, + 0x08, 0x62, 0x74, 0x63, 0x50, 0x72, 0x69, 0x63, 0x65, 0x22, 0xe4, 0x02, 0x0a, 0x10, 0x4e, 0x6f, + 0x64, 0x65, 0x41, 0x75, 0x64, 0x69, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x1d, + 0x0a, 0x0a, 0x73, 0x74, 0x61, 0x72, 0x74, 0x5f, 0x74, 0x69, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, + 0x28, 0x04, 0x52, 0x09, 0x73, 0x74, 0x61, 0x72, 0x74, 0x54, 0x69, 0x6d, 0x65, 0x12, 0x19, 0x0a, + 0x08, 0x65, 0x6e, 0x64, 0x5f, 0x74, 0x69, 0x6d, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x04, 0x52, + 0x07, 0x65, 0x6e, 0x64, 0x54, 0x69, 0x6d, 0x65, 0x12, 0x21, 0x0a, 0x0c, 0x64, 0x69, 0x73, 0x61, + 0x62, 0x6c, 0x65, 0x5f, 0x66, 0x69, 0x61, 0x74, 0x18, 0x04, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0b, + 0x64, 0x69, 0x73, 0x61, 0x62, 0x6c, 0x65, 0x46, 0x69, 0x61, 0x74, 0x12, 0x35, 0x0a, 0x0b, 0x67, + 0x72, 0x61, 0x6e, 0x75, 0x6c, 0x61, 0x72, 0x69, 0x74, 0x79, 0x18, 0x05, 0x20, 0x01, 0x28, 0x0e, + 0x32, 0x13, 0x2e, 0x66, 0x72, 0x64, 0x72, 0x70, 0x63, 0x2e, 0x47, 0x72, 0x61, 0x6e, 0x75, 0x6c, + 0x61, 0x72, 0x69, 0x74, 0x79, 0x52, 0x0b, 0x67, 0x72, 0x61, 0x6e, 0x75, 0x6c, 0x61, 0x72, 0x69, + 0x74, 0x79, 0x12, 0x43, 0x0a, 0x11, 0x63, 0x75, 0x73, 0x74, 0x6f, 0x6d, 0x5f, 0x63, 0x61, 0x74, + 0x65, 0x67, 0x6f, 0x72, 0x69, 0x65, 0x73, 0x18, 0x06, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x16, 0x2e, + 0x66, 0x72, 0x64, 0x72, 0x70, 0x63, 0x2e, 0x43, 0x75, 0x73, 0x74, 0x6f, 0x6d, 0x43, 0x61, 0x74, + 0x65, 0x67, 0x6f, 0x72, 0x79, 0x52, 0x10, 0x63, 0x75, 0x73, 0x74, 0x6f, 0x6d, 0x43, 0x61, 0x74, + 0x65, 0x67, 0x6f, 0x72, 0x69, 0x65, 0x73, 0x12, 0x36, 0x0a, 0x0c, 0x66, 0x69, 0x61, 0x74, 0x5f, + 0x62, 0x61, 0x63, 0x6b, 0x65, 0x6e, 0x64, 0x18, 0x07, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x13, 0x2e, + 0x66, 0x72, 0x64, 0x72, 0x70, 0x63, 0x2e, 0x46, 0x69, 0x61, 0x74, 0x42, 0x61, 0x63, 0x6b, 0x65, + 0x6e, 0x64, 0x52, 0x0b, 0x66, 0x69, 0x61, 0x74, 0x42, 0x61, 0x63, 0x6b, 0x65, 0x6e, 0x64, 0x12, + 0x39, 0x0a, 0x0d, 0x63, 0x75, 0x73, 0x74, 0x6f, 0x6d, 0x5f, 0x70, 0x72, 0x69, 0x63, 0x65, 0x73, + 0x18, 0x08, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x14, 0x2e, 0x66, 0x72, 0x64, 0x72, 0x70, 0x63, 0x2e, + 0x42, 0x69, 0x74, 0x63, 0x6f, 0x69, 0x6e, 0x50, 0x72, 0x69, 0x63, 0x65, 0x52, 0x0c, 0x63, 0x75, + 0x73, 0x74, 0x6f, 0x6d, 0x50, 0x72, 0x69, 0x63, 0x65, 0x73, 0x4a, 0x04, 0x08, 0x03, 0x10, 0x04, + 0x22, 0x83, 0x01, 0x0a, 0x0e, 0x43, 0x75, 0x73, 0x74, 0x6f, 0x6d, 0x43, 0x61, 0x74, 0x65, 0x67, + 0x6f, 0x72, 0x79, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, + 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x19, 0x0a, 0x08, 0x6f, 0x6e, 0x5f, 0x63, 0x68, + 0x61, 0x69, 0x6e, 0x18, 0x02, 0x20, 0x01, 0x28, 0x08, 0x52, 0x07, 0x6f, 0x6e, 0x43, 0x68, 0x61, + 0x69, 0x6e, 0x12, 0x1b, 0x0a, 0x09, 0x6f, 0x66, 0x66, 0x5f, 0x63, 0x68, 0x61, 0x69, 0x6e, 0x18, + 0x03, 0x20, 0x01, 0x28, 0x08, 0x52, 0x08, 0x6f, 0x66, 0x66, 0x43, 0x68, 0x61, 0x69, 0x6e, 0x12, + 0x25, 0x0a, 0x0e, 0x6c, 0x61, 0x62, 0x65, 0x6c, 0x5f, 0x70, 0x61, 0x74, 0x74, 0x65, 0x72, 0x6e, + 0x73, 0x18, 0x05, 0x20, 0x03, 0x28, 0x09, 0x52, 0x0d, 0x6c, 0x61, 0x62, 0x65, 0x6c, 0x50, 0x61, + 0x74, 0x74, 0x65, 0x72, 0x6e, 0x73, 0x22, 0xe9, 0x02, 0x0a, 0x0b, 0x52, 0x65, 0x70, 0x6f, 0x72, + 0x74, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x1c, 0x0a, 0x09, 0x74, 0x69, 0x6d, 0x65, 0x73, 0x74, + 0x61, 0x6d, 0x70, 0x18, 0x01, 0x20, 0x01, 0x28, 0x04, 0x52, 0x09, 0x74, 0x69, 0x6d, 0x65, 0x73, + 0x74, 0x61, 0x6d, 0x70, 0x12, 0x19, 0x0a, 0x08, 0x6f, 0x6e, 0x5f, 0x63, 0x68, 0x61, 0x69, 0x6e, + 0x18, 0x02, 0x20, 0x01, 0x28, 0x08, 0x52, 0x07, 0x6f, 0x6e, 0x43, 0x68, 0x61, 0x69, 0x6e, 0x12, + 0x16, 0x0a, 0x06, 0x61, 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x03, 0x20, 0x01, 0x28, 0x04, 0x52, + 0x06, 0x61, 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x16, 0x0a, 0x06, 0x63, 0x72, 0x65, 0x64, 0x69, + 0x74, 0x18, 0x04, 0x20, 0x01, 0x28, 0x08, 0x52, 0x06, 0x63, 0x72, 0x65, 0x64, 0x69, 0x74, 0x12, + 0x14, 0x0a, 0x05, 0x61, 0x73, 0x73, 0x65, 0x74, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, + 0x61, 0x73, 0x73, 0x65, 0x74, 0x12, 0x25, 0x0a, 0x04, 0x74, 0x79, 0x70, 0x65, 0x18, 0x06, 0x20, + 0x01, 0x28, 0x0e, 0x32, 0x11, 0x2e, 0x66, 0x72, 0x64, 0x72, 0x70, 0x63, 0x2e, 0x45, 0x6e, 0x74, + 0x72, 0x79, 0x54, 0x79, 0x70, 0x65, 0x52, 0x04, 0x74, 0x79, 0x70, 0x65, 0x12, 0x27, 0x0a, 0x0f, + 0x63, 0x75, 0x73, 0x74, 0x6f, 0x6d, 0x5f, 0x63, 0x61, 0x74, 0x65, 0x67, 0x6f, 0x72, 0x79, 0x18, + 0x0c, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0e, 0x63, 0x75, 0x73, 0x74, 0x6f, 0x6d, 0x43, 0x61, 0x74, + 0x65, 0x67, 0x6f, 0x72, 0x79, 0x12, 0x12, 0x0a, 0x04, 0x74, 0x78, 0x69, 0x64, 0x18, 0x07, 0x20, + 0x01, 0x28, 0x09, 0x52, 0x04, 0x74, 0x78, 0x69, 0x64, 0x12, 0x12, 0x0a, 0x04, 0x66, 0x69, 0x61, + 0x74, 0x18, 0x08, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x66, 0x69, 0x61, 0x74, 0x12, 0x1c, 0x0a, + 0x09, 0x72, 0x65, 0x66, 0x65, 0x72, 0x65, 0x6e, 0x63, 0x65, 0x18, 0x09, 0x20, 0x01, 0x28, 0x09, + 0x52, 0x09, 0x72, 0x65, 0x66, 0x65, 0x72, 0x65, 0x6e, 0x63, 0x65, 0x12, 0x12, 0x0a, 0x04, 0x6e, + 0x6f, 0x74, 0x65, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, 0x6f, 0x74, 0x65, 0x12, + 0x31, 0x0a, 0x09, 0x62, 0x74, 0x63, 0x5f, 0x70, 0x72, 0x69, 0x63, 0x65, 0x18, 0x0b, 0x20, 0x01, + 0x28, 0x0b, 0x32, 0x14, 0x2e, 0x66, 0x72, 0x64, 0x72, 0x70, 0x63, 0x2e, 0x42, 0x69, 0x74, 0x63, + 0x6f, 0x69, 0x6e, 0x50, 0x72, 0x69, 0x63, 0x65, 0x52, 0x08, 0x62, 0x74, 0x63, 0x50, 0x72, 0x69, + 0x63, 0x65, 0x22, 0x42, 0x0a, 0x11, 0x4e, 0x6f, 0x64, 0x65, 0x41, 0x75, 0x64, 0x69, 0x74, 0x52, + 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x2d, 0x0a, 0x07, 0x72, 0x65, 0x70, 0x6f, 0x72, + 0x74, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x13, 0x2e, 0x66, 0x72, 0x64, 0x72, 0x70, + 0x63, 0x2e, 0x52, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x52, 0x07, 0x72, + 0x65, 0x70, 0x6f, 0x72, 0x74, 0x73, 0x22, 0x39, 0x0a, 0x12, 0x43, 0x6c, 0x6f, 0x73, 0x65, 0x52, + 0x65, 0x70, 0x6f, 0x72, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x23, 0x0a, 0x0d, + 0x63, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x5f, 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x18, 0x01, 0x20, + 0x01, 0x28, 0x09, 0x52, 0x0c, 0x63, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x50, 0x6f, 0x69, 0x6e, + 0x74, 0x22, 0xdd, 0x01, 0x0a, 0x13, 0x43, 0x6c, 0x6f, 0x73, 0x65, 0x52, 0x65, 0x70, 0x6f, 0x72, + 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x23, 0x0a, 0x0d, 0x63, 0x68, 0x61, + 0x6e, 0x6e, 0x65, 0x6c, 0x5f, 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, + 0x52, 0x0c, 0x63, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x50, 0x6f, 0x69, 0x6e, 0x74, 0x12, 0x2b, + 0x0a, 0x11, 0x63, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x5f, 0x69, 0x6e, 0x69, 0x74, 0x69, 0x61, + 0x74, 0x6f, 0x72, 0x18, 0x02, 0x20, 0x01, 0x28, 0x08, 0x52, 0x10, 0x63, 0x68, 0x61, 0x6e, 0x6e, + 0x65, 0x6c, 0x49, 0x6e, 0x69, 0x74, 0x69, 0x61, 0x74, 0x6f, 0x72, 0x12, 0x1d, 0x0a, 0x0a, 0x63, + 0x6c, 0x6f, 0x73, 0x65, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, + 0x09, 0x63, 0x6c, 0x6f, 0x73, 0x65, 0x54, 0x79, 0x70, 0x65, 0x12, 0x1d, 0x0a, 0x0a, 0x63, 0x6c, + 0x6f, 0x73, 0x65, 0x5f, 0x74, 0x78, 0x69, 0x64, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, + 0x63, 0x6c, 0x6f, 0x73, 0x65, 0x54, 0x78, 0x69, 0x64, 0x12, 0x19, 0x0a, 0x08, 0x6f, 0x70, 0x65, + 0x6e, 0x5f, 0x66, 0x65, 0x65, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x6f, 0x70, 0x65, + 0x6e, 0x46, 0x65, 0x65, 0x12, 0x1b, 0x0a, 0x09, 0x63, 0x6c, 0x6f, 0x73, 0x65, 0x5f, 0x66, 0x65, + 0x65, 0x18, 0x06, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x63, 0x6c, 0x6f, 0x73, 0x65, 0x46, 0x65, + 0x65, 0x2a, 0xa1, 0x01, 0x0a, 0x0b, 0x47, 0x72, 0x61, 0x6e, 0x75, 0x6c, 0x61, 0x72, 0x69, 0x74, + 0x79, 0x12, 0x17, 0x0a, 0x13, 0x55, 0x4e, 0x4b, 0x4e, 0x4f, 0x57, 0x4e, 0x5f, 0x47, 0x52, 0x41, + 0x4e, 0x55, 0x4c, 0x41, 0x52, 0x49, 0x54, 0x59, 0x10, 0x00, 0x12, 0x0a, 0x0a, 0x06, 0x4d, 0x49, + 0x4e, 0x55, 0x54, 0x45, 0x10, 0x01, 0x12, 0x10, 0x0a, 0x0c, 0x46, 0x49, 0x56, 0x45, 0x5f, 0x4d, + 0x49, 0x4e, 0x55, 0x54, 0x45, 0x53, 0x10, 0x02, 0x12, 0x13, 0x0a, 0x0f, 0x46, 0x49, 0x46, 0x54, + 0x45, 0x45, 0x4e, 0x5f, 0x4d, 0x49, 0x4e, 0x55, 0x54, 0x45, 0x53, 0x10, 0x03, 0x12, 0x12, 0x0a, + 0x0e, 0x54, 0x48, 0x49, 0x52, 0x54, 0x59, 0x5f, 0x4d, 0x49, 0x4e, 0x55, 0x54, 0x45, 0x53, 0x10, + 0x04, 0x12, 0x08, 0x0a, 0x04, 0x48, 0x4f, 0x55, 0x52, 0x10, 0x05, 0x12, 0x0d, 0x0a, 0x09, 0x53, + 0x49, 0x58, 0x5f, 0x48, 0x4f, 0x55, 0x52, 0x53, 0x10, 0x06, 0x12, 0x10, 0x0a, 0x0c, 0x54, 0x57, + 0x45, 0x4c, 0x56, 0x45, 0x5f, 0x48, 0x4f, 0x55, 0x52, 0x53, 0x10, 0x07, 0x12, 0x07, 0x0a, 0x03, + 0x44, 0x41, 0x59, 0x10, 0x08, 0x2a, 0x4d, 0x0a, 0x0b, 0x46, 0x69, 0x61, 0x74, 0x42, 0x61, 0x63, + 0x6b, 0x65, 0x6e, 0x64, 0x12, 0x17, 0x0a, 0x13, 0x55, 0x4e, 0x4b, 0x4e, 0x4f, 0x57, 0x4e, 0x5f, + 0x46, 0x49, 0x41, 0x54, 0x42, 0x41, 0x43, 0x4b, 0x45, 0x4e, 0x44, 0x10, 0x00, 0x12, 0x0b, 0x0a, + 0x07, 0x43, 0x4f, 0x49, 0x4e, 0x43, 0x41, 0x50, 0x10, 0x01, 0x12, 0x0c, 0x0a, 0x08, 0x43, 0x4f, + 0x49, 0x4e, 0x44, 0x45, 0x53, 0x4b, 0x10, 0x02, 0x12, 0x0a, 0x0a, 0x06, 0x43, 0x55, 0x53, 0x54, + 0x4f, 0x4d, 0x10, 0x03, 0x2a, 0xa2, 0x02, 0x0a, 0x09, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x54, 0x79, + 0x70, 0x65, 0x12, 0x0b, 0x0a, 0x07, 0x55, 0x4e, 0x4b, 0x4e, 0x4f, 0x57, 0x4e, 0x10, 0x00, 0x12, + 0x16, 0x0a, 0x12, 0x4c, 0x4f, 0x43, 0x41, 0x4c, 0x5f, 0x43, 0x48, 0x41, 0x4e, 0x4e, 0x45, 0x4c, + 0x5f, 0x4f, 0x50, 0x45, 0x4e, 0x10, 0x01, 0x12, 0x17, 0x0a, 0x13, 0x52, 0x45, 0x4d, 0x4f, 0x54, + 0x45, 0x5f, 0x43, 0x48, 0x41, 0x4e, 0x4e, 0x45, 0x4c, 0x5f, 0x4f, 0x50, 0x45, 0x4e, 0x10, 0x02, + 0x12, 0x14, 0x0a, 0x10, 0x43, 0x48, 0x41, 0x4e, 0x4e, 0x45, 0x4c, 0x5f, 0x4f, 0x50, 0x45, 0x4e, + 0x5f, 0x46, 0x45, 0x45, 0x10, 0x03, 0x12, 0x11, 0x0a, 0x0d, 0x43, 0x48, 0x41, 0x4e, 0x4e, 0x45, + 0x4c, 0x5f, 0x43, 0x4c, 0x4f, 0x53, 0x45, 0x10, 0x04, 0x12, 0x0b, 0x0a, 0x07, 0x52, 0x45, 0x43, + 0x45, 0x49, 0x50, 0x54, 0x10, 0x05, 0x12, 0x0b, 0x0a, 0x07, 0x50, 0x41, 0x59, 0x4d, 0x45, 0x4e, + 0x54, 0x10, 0x06, 0x12, 0x07, 0x0a, 0x03, 0x46, 0x45, 0x45, 0x10, 0x07, 0x12, 0x14, 0x0a, 0x10, + 0x43, 0x49, 0x52, 0x43, 0x55, 0x4c, 0x41, 0x52, 0x5f, 0x52, 0x45, 0x43, 0x45, 0x49, 0x50, 0x54, + 0x10, 0x08, 0x12, 0x0b, 0x0a, 0x07, 0x46, 0x4f, 0x52, 0x57, 0x41, 0x52, 0x44, 0x10, 0x09, 0x12, + 0x0f, 0x0a, 0x0b, 0x46, 0x4f, 0x52, 0x57, 0x41, 0x52, 0x44, 0x5f, 0x46, 0x45, 0x45, 0x10, 0x0a, + 0x12, 0x14, 0x0a, 0x10, 0x43, 0x49, 0x52, 0x43, 0x55, 0x4c, 0x41, 0x52, 0x5f, 0x50, 0x41, 0x59, + 0x4d, 0x45, 0x4e, 0x54, 0x10, 0x0b, 0x12, 0x10, 0x0a, 0x0c, 0x43, 0x49, 0x52, 0x43, 0x55, 0x4c, + 0x41, 0x52, 0x5f, 0x46, 0x45, 0x45, 0x10, 0x0c, 0x12, 0x09, 0x0a, 0x05, 0x53, 0x57, 0x45, 0x45, + 0x50, 0x10, 0x0d, 0x12, 0x0d, 0x0a, 0x09, 0x53, 0x57, 0x45, 0x45, 0x50, 0x5f, 0x46, 0x45, 0x45, + 0x10, 0x0e, 0x12, 0x15, 0x0a, 0x11, 0x43, 0x48, 0x41, 0x4e, 0x4e, 0x45, 0x4c, 0x5f, 0x43, 0x4c, + 0x4f, 0x53, 0x45, 0x5f, 0x46, 0x45, 0x45, 0x10, 0x0f, 0x32, 0xd8, 0x04, 0x0a, 0x0d, 0x46, 0x61, + 0x72, 0x61, 0x64, 0x61, 0x79, 0x53, 0x65, 0x72, 0x76, 0x65, 0x72, 0x12, 0x65, 0x0a, 0x16, 0x4f, + 0x75, 0x74, 0x6c, 0x69, 0x65, 0x72, 0x52, 0x65, 0x63, 0x6f, 0x6d, 0x6d, 0x65, 0x6e, 0x64, 0x61, + 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x12, 0x25, 0x2e, 0x66, 0x72, 0x64, 0x72, 0x70, 0x63, 0x2e, 0x4f, + 0x75, 0x74, 0x6c, 0x69, 0x65, 0x72, 0x52, 0x65, 0x63, 0x6f, 0x6d, 0x6d, 0x65, 0x6e, 0x64, 0x61, + 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x24, 0x2e, 0x66, + 0x72, 0x64, 0x72, 0x70, 0x63, 0x2e, 0x43, 0x6c, 0x6f, 0x73, 0x65, 0x52, 0x65, 0x63, 0x6f, 0x6d, + 0x6d, 0x65, 0x6e, 0x64, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, + 0x73, 0x65, 0x12, 0x69, 0x0a, 0x18, 0x54, 0x68, 0x72, 0x65, 0x73, 0x68, 0x6f, 0x6c, 0x64, 0x52, + 0x65, 0x63, 0x6f, 0x6d, 0x6d, 0x65, 0x6e, 0x64, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x12, 0x27, + 0x2e, 0x66, 0x72, 0x64, 0x72, 0x70, 0x63, 0x2e, 0x54, 0x68, 0x72, 0x65, 0x73, 0x68, 0x6f, 0x6c, + 0x64, 0x52, 0x65, 0x63, 0x6f, 0x6d, 0x6d, 0x65, 0x6e, 0x64, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, + 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x24, 0x2e, 0x66, 0x72, 0x64, 0x72, 0x70, 0x63, + 0x2e, 0x43, 0x6c, 0x6f, 0x73, 0x65, 0x52, 0x65, 0x63, 0x6f, 0x6d, 0x6d, 0x65, 0x6e, 0x64, 0x61, + 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x4c, 0x0a, + 0x0d, 0x52, 0x65, 0x76, 0x65, 0x6e, 0x75, 0x65, 0x52, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x12, 0x1c, + 0x2e, 0x66, 0x72, 0x64, 0x72, 0x70, 0x63, 0x2e, 0x52, 0x65, 0x76, 0x65, 0x6e, 0x75, 0x65, 0x52, + 0x65, 0x70, 0x6f, 0x72, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1d, 0x2e, 0x66, + 0x72, 0x64, 0x72, 0x70, 0x63, 0x2e, 0x52, 0x65, 0x76, 0x65, 0x6e, 0x75, 0x65, 0x52, 0x65, 0x70, + 0x6f, 0x72, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x52, 0x0a, 0x0f, 0x43, + 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x49, 0x6e, 0x73, 0x69, 0x67, 0x68, 0x74, 0x73, 0x12, 0x1e, + 0x2e, 0x66, 0x72, 0x64, 0x72, 0x70, 0x63, 0x2e, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x49, + 0x6e, 0x73, 0x69, 0x67, 0x68, 0x74, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1f, + 0x2e, 0x66, 0x72, 0x64, 0x72, 0x70, 0x63, 0x2e, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x49, + 0x6e, 0x73, 0x69, 0x67, 0x68, 0x74, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, + 0x49, 0x0a, 0x0c, 0x45, 0x78, 0x63, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x52, 0x61, 0x74, 0x65, 0x12, + 0x1b, 0x2e, 0x66, 0x72, 0x64, 0x72, 0x70, 0x63, 0x2e, 0x45, 0x78, 0x63, 0x68, 0x61, 0x6e, 0x67, + 0x65, 0x52, 0x61, 0x74, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1c, 0x2e, 0x66, + 0x72, 0x64, 0x72, 0x70, 0x63, 0x2e, 0x45, 0x78, 0x63, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x52, 0x61, + 0x74, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x40, 0x0a, 0x09, 0x4e, 0x6f, + 0x64, 0x65, 0x41, 0x75, 0x64, 0x69, 0x74, 0x12, 0x18, 0x2e, 0x66, 0x72, 0x64, 0x72, 0x70, 0x63, + 0x2e, 0x4e, 0x6f, 0x64, 0x65, 0x41, 0x75, 0x64, 0x69, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, + 0x74, 0x1a, 0x19, 0x2e, 0x66, 0x72, 0x64, 0x72, 0x70, 0x63, 0x2e, 0x4e, 0x6f, 0x64, 0x65, 0x41, + 0x75, 0x64, 0x69, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x46, 0x0a, 0x0b, + 0x43, 0x6c, 0x6f, 0x73, 0x65, 0x52, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x12, 0x1a, 0x2e, 0x66, 0x72, + 0x64, 0x72, 0x70, 0x63, 0x2e, 0x43, 0x6c, 0x6f, 0x73, 0x65, 0x52, 0x65, 0x70, 0x6f, 0x72, 0x74, + 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1b, 0x2e, 0x66, 0x72, 0x64, 0x72, 0x70, 0x63, + 0x2e, 0x43, 0x6c, 0x6f, 0x73, 0x65, 0x52, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x52, 0x65, 0x73, 0x70, + 0x6f, 0x6e, 0x73, 0x65, 0x42, 0x29, 0x5a, 0x27, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, + 0x6f, 0x6d, 0x2f, 0x6c, 0x69, 0x67, 0x68, 0x74, 0x6e, 0x69, 0x6e, 0x67, 0x6c, 0x61, 0x62, 0x73, + 0x2f, 0x66, 0x61, 0x72, 0x61, 0x64, 0x61, 0x79, 0x2f, 0x66, 0x72, 0x64, 0x72, 0x70, 0x63, 0x62, + 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, } var ( @@ -2270,34 +2300,36 @@ var file_faraday_proto_depIdxs = []int32{ 15, // 6: frdrpc.ChannelInsightsResponse.channel_insights:type_name -> frdrpc.ChannelInsight 0, // 7: frdrpc.ExchangeRateRequest.granularity:type_name -> frdrpc.Granularity 1, // 8: frdrpc.ExchangeRateRequest.fiat_backend:type_name -> frdrpc.FiatBackend - 19, // 9: frdrpc.ExchangeRateResponse.rates:type_name -> frdrpc.ExchangeRate - 18, // 10: frdrpc.ExchangeRate.btc_price:type_name -> frdrpc.BitcoinPrice - 0, // 11: frdrpc.NodeAuditRequest.granularity:type_name -> frdrpc.Granularity - 21, // 12: frdrpc.NodeAuditRequest.custom_categories:type_name -> frdrpc.CustomCategory - 1, // 13: frdrpc.NodeAuditRequest.fiat_backend:type_name -> frdrpc.FiatBackend - 2, // 14: frdrpc.ReportEntry.type:type_name -> frdrpc.EntryType - 18, // 15: frdrpc.ReportEntry.btc_price:type_name -> frdrpc.BitcoinPrice - 22, // 16: frdrpc.NodeAuditResponse.reports:type_name -> frdrpc.ReportEntry - 12, // 17: frdrpc.RevenueReport.PairReportsEntry.value:type_name -> frdrpc.PairReport - 5, // 18: frdrpc.FaradayServer.OutlierRecommendations:input_type -> frdrpc.OutlierRecommendationsRequest - 6, // 19: frdrpc.FaradayServer.ThresholdRecommendations:input_type -> frdrpc.ThresholdRecommendationsRequest - 9, // 20: frdrpc.FaradayServer.RevenueReport:input_type -> frdrpc.RevenueReportRequest - 13, // 21: frdrpc.FaradayServer.ChannelInsights:input_type -> frdrpc.ChannelInsightsRequest - 16, // 22: frdrpc.FaradayServer.ExchangeRate:input_type -> frdrpc.ExchangeRateRequest - 20, // 23: frdrpc.FaradayServer.NodeAudit:input_type -> frdrpc.NodeAuditRequest - 24, // 24: frdrpc.FaradayServer.CloseReport:input_type -> frdrpc.CloseReportRequest - 7, // 25: frdrpc.FaradayServer.OutlierRecommendations:output_type -> frdrpc.CloseRecommendationsResponse - 7, // 26: frdrpc.FaradayServer.ThresholdRecommendations:output_type -> frdrpc.CloseRecommendationsResponse - 10, // 27: frdrpc.FaradayServer.RevenueReport:output_type -> frdrpc.RevenueReportResponse - 14, // 28: frdrpc.FaradayServer.ChannelInsights:output_type -> frdrpc.ChannelInsightsResponse - 17, // 29: frdrpc.FaradayServer.ExchangeRate:output_type -> frdrpc.ExchangeRateResponse - 23, // 30: frdrpc.FaradayServer.NodeAudit:output_type -> frdrpc.NodeAuditResponse - 25, // 31: frdrpc.FaradayServer.CloseReport:output_type -> frdrpc.CloseReportResponse - 25, // [25:32] is the sub-list for method output_type - 18, // [18:25] is the sub-list for method input_type - 18, // [18:18] is the sub-list for extension type_name - 18, // [18:18] is the sub-list for extension extendee - 0, // [0:18] is the sub-list for field type_name + 18, // 9: frdrpc.ExchangeRateRequest.custom_prices:type_name -> frdrpc.BitcoinPrice + 19, // 10: frdrpc.ExchangeRateResponse.rates:type_name -> frdrpc.ExchangeRate + 18, // 11: frdrpc.ExchangeRate.btc_price:type_name -> frdrpc.BitcoinPrice + 0, // 12: frdrpc.NodeAuditRequest.granularity:type_name -> frdrpc.Granularity + 21, // 13: frdrpc.NodeAuditRequest.custom_categories:type_name -> frdrpc.CustomCategory + 1, // 14: frdrpc.NodeAuditRequest.fiat_backend:type_name -> frdrpc.FiatBackend + 18, // 15: frdrpc.NodeAuditRequest.custom_prices:type_name -> frdrpc.BitcoinPrice + 2, // 16: frdrpc.ReportEntry.type:type_name -> frdrpc.EntryType + 18, // 17: frdrpc.ReportEntry.btc_price:type_name -> frdrpc.BitcoinPrice + 22, // 18: frdrpc.NodeAuditResponse.reports:type_name -> frdrpc.ReportEntry + 12, // 19: frdrpc.RevenueReport.PairReportsEntry.value:type_name -> frdrpc.PairReport + 5, // 20: frdrpc.FaradayServer.OutlierRecommendations:input_type -> frdrpc.OutlierRecommendationsRequest + 6, // 21: frdrpc.FaradayServer.ThresholdRecommendations:input_type -> frdrpc.ThresholdRecommendationsRequest + 9, // 22: frdrpc.FaradayServer.RevenueReport:input_type -> frdrpc.RevenueReportRequest + 13, // 23: frdrpc.FaradayServer.ChannelInsights:input_type -> frdrpc.ChannelInsightsRequest + 16, // 24: frdrpc.FaradayServer.ExchangeRate:input_type -> frdrpc.ExchangeRateRequest + 20, // 25: frdrpc.FaradayServer.NodeAudit:input_type -> frdrpc.NodeAuditRequest + 24, // 26: frdrpc.FaradayServer.CloseReport:input_type -> frdrpc.CloseReportRequest + 7, // 27: frdrpc.FaradayServer.OutlierRecommendations:output_type -> frdrpc.CloseRecommendationsResponse + 7, // 28: frdrpc.FaradayServer.ThresholdRecommendations:output_type -> frdrpc.CloseRecommendationsResponse + 10, // 29: frdrpc.FaradayServer.RevenueReport:output_type -> frdrpc.RevenueReportResponse + 14, // 30: frdrpc.FaradayServer.ChannelInsights:output_type -> frdrpc.ChannelInsightsResponse + 17, // 31: frdrpc.FaradayServer.ExchangeRate:output_type -> frdrpc.ExchangeRateResponse + 23, // 32: frdrpc.FaradayServer.NodeAudit:output_type -> frdrpc.NodeAuditResponse + 25, // 33: frdrpc.FaradayServer.CloseReport:output_type -> frdrpc.CloseReportResponse + 27, // [27:34] is the sub-list for method output_type + 20, // [20:27] is the sub-list for method input_type + 20, // [20:20] is the sub-list for extension type_name + 20, // [20:20] is the sub-list for extension extendee + 0, // [0:20] is the sub-list for field type_name } func init() { file_faraday_proto_init() } diff --git a/frdrpc/faraday.proto b/frdrpc/faraday.proto index 981ace4..378a90d 100644 --- a/frdrpc/faraday.proto +++ b/frdrpc/faraday.proto @@ -335,6 +335,9 @@ enum FiatBackend { // This API is reached through the following URL: // https://api.coindesk.com/v1/bpi/historical/close.json COINDESK = 2; + + // Use custom price data provided in a CSV file for fiat price information. + CUSTOM = 3; } message ExchangeRateRequest { @@ -350,6 +353,9 @@ message ExchangeRateRequest { // The api to be used for fiat related queries. FiatBackend fiat_backend = 5; + + // Custom price points to use if the CUSTOM FiatBackend option is set. + repeated BitcoinPrice custom_prices = 8; } message ExchangeRateResponse { @@ -410,6 +416,9 @@ message NodeAuditRequest { // The api to be used for fiat related queries. FiatBackend fiat_backend = 7; + + // Custom price points to use if the CUSTOM FiatBackend option is set. + repeated BitcoinPrice custom_prices = 8; } message CustomCategory { diff --git a/frdrpc/faraday.swagger.json b/frdrpc/faraday.swagger.json index 90d149c..41d93a2 100644 --- a/frdrpc/faraday.swagger.json +++ b/frdrpc/faraday.swagger.json @@ -102,14 +102,15 @@ }, { "name": "fiat_backend", - "description": "The api to be used for fiat related queries.\n\n - COINCAP: Use the CoinCap API for fiat price information.\nThis API is reached through the following URL:\nhttps://api.coincap.io/v2/assets/bitcoin/history\n - COINDESK: Use the CoinDesk API for fiat price information.\nThis API is reached through the following URL:\nhttps://api.coindesk.com/v1/bpi/historical/close.json", + "description": "The api to be used for fiat related queries.\n\n - COINCAP: Use the CoinCap API for fiat price information.\nThis API is reached through the following URL:\nhttps://api.coincap.io/v2/assets/bitcoin/history\n - COINDESK: Use the CoinDesk API for fiat price information.\nThis API is reached through the following URL:\nhttps://api.coindesk.com/v1/bpi/historical/close.json\n - CUSTOM: Use custom price data provided in a CSV file for fiat price information.", "in": "query", "required": false, "type": "string", "enum": [ "UNKNOWN_FIATBACKEND", "COINCAP", - "COINDESK" + "COINDESK", + "CUSTOM" ], "default": "UNKNOWN_FIATBACKEND" } @@ -207,14 +208,15 @@ }, { "name": "fiat_backend", - "description": "The api to be used for fiat related queries.\n\n - COINCAP: Use the CoinCap API for fiat price information.\nThis API is reached through the following URL:\nhttps://api.coincap.io/v2/assets/bitcoin/history\n - COINDESK: Use the CoinDesk API for fiat price information.\nThis API is reached through the following URL:\nhttps://api.coindesk.com/v1/bpi/historical/close.json", + "description": "The api to be used for fiat related queries.\n\n - COINCAP: Use the CoinCap API for fiat price information.\nThis API is reached through the following URL:\nhttps://api.coincap.io/v2/assets/bitcoin/history\n - COINDESK: Use the CoinDesk API for fiat price information.\nThis API is reached through the following URL:\nhttps://api.coindesk.com/v1/bpi/historical/close.json\n - CUSTOM: Use custom price data provided in a CSV file for fiat price information.", "in": "query", "required": false, "type": "string", "enum": [ "UNKNOWN_FIATBACKEND", "COINCAP", - "COINDESK" + "COINDESK", + "CUSTOM" ], "default": "UNKNOWN_FIATBACKEND" } @@ -621,10 +623,11 @@ "enum": [ "UNKNOWN_FIATBACKEND", "COINCAP", - "COINDESK" + "COINDESK", + "CUSTOM" ], "default": "UNKNOWN_FIATBACKEND", - "description": "FiatBackend is the API endpoint to be used for any fiat related queries.\n\n - COINCAP: Use the CoinCap API for fiat price information.\nThis API is reached through the following URL:\nhttps://api.coincap.io/v2/assets/bitcoin/history\n - COINDESK: Use the CoinDesk API for fiat price information.\nThis API is reached through the following URL:\nhttps://api.coindesk.com/v1/bpi/historical/close.json" + "description": "FiatBackend is the API endpoint to be used for any fiat related queries.\n\n - COINCAP: Use the CoinCap API for fiat price information.\nThis API is reached through the following URL:\nhttps://api.coincap.io/v2/assets/bitcoin/history\n - COINDESK: Use the CoinDesk API for fiat price information.\nThis API is reached through the following URL:\nhttps://api.coindesk.com/v1/bpi/historical/close.json\n - CUSTOM: Use custom price data provided in a CSV file for fiat price information." }, "frdrpcGranularity": { "type": "string", diff --git a/frdrpc/node_audit.go b/frdrpc/node_audit.go index 760dc37..6b3d443 100644 --- a/frdrpc/node_audit.go +++ b/frdrpc/node_audit.go @@ -5,8 +5,10 @@ import ( "errors" "fmt" "sort" + "time" "github.com/lightningnetwork/lnd/routing/route" + "github.com/shopspring/decimal" "github.com/lightninglabs/faraday/accounting" "github.com/lightninglabs/faraday/fees" @@ -53,9 +55,22 @@ func parseNodeAuditRequest(ctx context.Context, cfg *Config, return nil, nil, err } + if len(req.CustomPrices) > 0 && req.FiatBackend != FiatBackend_CUSTOM { + return nil, nil, errors.New( + "custom price points provided but custom fiat " + + "backend not set", + ) + } + + pricePoints, err := pricePointsFromRPC(req.CustomPrices) + if err != nil { + return nil, nil, err + } + priceSourceCfg := &fiat.PriceSourceConfig{ Backend: fiatBackend, Granularity: granularity, + PricePoints: pricePoints, } pubkey, err := route.NewVertexFromBytes(info.IdentityPubkey[:]) @@ -128,6 +143,25 @@ func validateCustomCategories(categories []*CustomCategory) error { return nil } +func pricePointsFromRPC(prices []*BitcoinPrice) ([]*fiat.Price, error) { + res := make([]*fiat.Price, len(prices)) + + for i, p := range prices { + price, err := decimal.NewFromString(p.Price) + if err != nil { + return nil, err + } + + res[i] = &fiat.Price{ + Timestamp: time.Unix(int64(p.PriceTimestamp), 0), + Price: price, + Currency: p.Currency, + } + } + + return res, nil +} + func getCategories(categories []*CustomCategory) ([]accounting.CustomCategory, []accounting.CustomCategory, error) { From e478853361efcab3625fd781f2fd8105a546efeb Mon Sep 17 00:00:00 2001 From: Elle Mouton Date: Thu, 10 Jun 2021 10:25:03 +0200 Subject: [PATCH 11/12] frcli: filter custom prices by timestamp This commit filters the custom price points provided using the given start and end timestamps. This is done so that only the necessary time stamps are sent to the server. --- cmd/frcli/fiat_estimate.go | 12 +++- cmd/frcli/node_audit.go | 21 +++++-- cmd/frcli/utils.go | 59 ++++++++++++++++++ cmd/frcli/utils_test.go | 120 +++++++++++++++++++++++++++++++++++++ 4 files changed, 204 insertions(+), 8 deletions(-) create mode 100644 cmd/frcli/utils_test.go diff --git a/cmd/frcli/fiat_estimate.go b/cmd/frcli/fiat_estimate.go index 83c03c6..5256cf5 100644 --- a/cmd/frcli/fiat_estimate.go +++ b/cmd/frcli/fiat_estimate.go @@ -67,16 +67,22 @@ func queryFiatEstimate(ctx *cli.Context) error { return err } - var customPrices []*frdrpc.BitcoinPrice + // nolint: prealloc + var filteredPrices []*frdrpc.BitcoinPrice if fiatBackend == frdrpc.FiatBackend_CUSTOM { - customPrices, err = parsePricesFromCSV( + customPrices, err := parsePricesFromCSV( ctx.String("prices_csv_path"), ctx.String("custom_price_currency"), ) if err != nil { return err } + + filteredPrices, err = filterPrices(customPrices, ts, ts) + if err != nil { + return err + } } // Set start and end times from user specified values, defaulting @@ -84,7 +90,7 @@ func queryFiatEstimate(ctx *cli.Context) error { req := &frdrpc.ExchangeRateRequest{ Timestamps: []uint64{uint64(ts)}, FiatBackend: fiatBackend, - CustomPrices: customPrices, + CustomPrices: filteredPrices, } rpcCtx := context.Background() diff --git a/cmd/frcli/node_audit.go b/cmd/frcli/node_audit.go index 81969f9..344db9d 100644 --- a/cmd/frcli/node_audit.go +++ b/cmd/frcli/node_audit.go @@ -127,26 +127,37 @@ func queryOnChainReport(ctx *cli.Context) error { return err } - var customPrices []*frdrpc.BitcoinPrice + startTime := ctx.Int64("start_time") + endTime := ctx.Int64("end_time") + + // nolint: prealloc + var filteredPrices []*frdrpc.BitcoinPrice if fiatBackend == frdrpc.FiatBackend_CUSTOM { - customPrices, err = parsePricesFromCSV( + customPrices, err := parsePricesFromCSV( ctx.String("prices_csv_path"), ctx.String("custom_price_currency"), ) if err != nil { return err } + + filteredPrices, err = filterPrices( + customPrices, startTime, endTime, + ) + if err != nil { + return err + } } // Set start and end times from user specified values, defaulting // to zero if they are not set. req := &frdrpc.NodeAuditRequest{ - StartTime: uint64(ctx.Int64("start_time")), - EndTime: uint64(ctx.Int64("end_time")), + StartTime: uint64(startTime), + EndTime: uint64(endTime), DisableFiat: !ctx.IsSet("enable_fiat"), FiatBackend: fiatBackend, - CustomPrices: customPrices, + CustomPrices: filteredPrices, } // If start time is zero, default to a week ago. diff --git a/cmd/frcli/utils.go b/cmd/frcli/utils.go index fc450f0..41d6dba 100644 --- a/cmd/frcli/utils.go +++ b/cmd/frcli/utils.go @@ -4,13 +4,18 @@ import ( "bytes" "context" "encoding/json" + "errors" "fmt" "io/ioutil" "net" "os" "path/filepath" + "sort" "strconv" "strings" + "time" + + "github.com/lightninglabs/faraday/utils" "github.com/btcsuite/btcd/chaincfg/chainhash" "github.com/btcsuite/btcd/wire" @@ -297,3 +302,57 @@ func parseFiatBackend(fiatBackend string) (frdrpc.FiatBackend, error) { ) } } + +// filterPrices filters a slice of prices based on given start and end +// timestamps. +func filterPrices(prices []*frdrpc.BitcoinPrice, startTime, endTime int64) ( + []*frdrpc.BitcoinPrice, error) { + + // Ensure that startTime is before endTime. + if err := utils.ValidateTimeRange( + time.Unix(startTime, 0), time.Unix(endTime, 0), + utils.DisallowFutureRange, + ); err != nil { + return nil, err + } + + // Sort the prices by timestamp. + sort.SliceStable(prices, func(i, j int) bool { + return prices[i].PriceTimestamp < prices[j].PriceTimestamp + }) + + // Filter out timestamps that are not within the start time to + // end time range but ensure that the timestamp right before + // or equal to the start timestamp is kept. + // + // nolint: prealloc + var ( + filteredPrices []*frdrpc.BitcoinPrice + earliestTimeStamp *frdrpc.BitcoinPrice + ) + for _, p := range prices { + if p.PriceTimestamp <= uint64(startTime) { + if earliestTimeStamp == nil || + earliestTimeStamp.PriceTimestamp < + p.PriceTimestamp { + + earliestTimeStamp = p + } + continue + } + + if p.PriceTimestamp >= uint64(endTime) { + continue + } + + filteredPrices = append(filteredPrices, p) + } + + if earliestTimeStamp == nil { + return nil, errors.New("a price point with a timestamp " + + "earlier than the given start timestamp is required") + } + + return append([]*frdrpc.BitcoinPrice{earliestTimeStamp}, + filteredPrices...), nil +} diff --git a/cmd/frcli/utils_test.go b/cmd/frcli/utils_test.go new file mode 100644 index 0000000..6bf3309 --- /dev/null +++ b/cmd/frcli/utils_test.go @@ -0,0 +1,120 @@ +package main + +import ( + "testing" + + "github.com/lightninglabs/faraday/frdrpc" +) + +// TestFilterPrices checks that the filterPrices function correctly filters +// prices based on given start and end timestamps. +func TestFilterPrices(t *testing.T) { + tests := []struct { + name string + prices []*frdrpc.BitcoinPrice + startTime int64 + endTime int64 + expectedPrices []*frdrpc.BitcoinPrice + expectErr bool + }{ + { + name: "test that prices are sorted correctly", + prices: []*frdrpc.BitcoinPrice{ + {PriceTimestamp: 200}, + {PriceTimestamp: 300}, + {PriceTimestamp: 100}, + }, + startTime: 100, + endTime: 400, + expectedPrices: []*frdrpc.BitcoinPrice{ + {PriceTimestamp: 100}, + {PriceTimestamp: 200}, + {PriceTimestamp: 300}, + }, + }, + { + name: "error if end time is before start time", + prices: []*frdrpc.BitcoinPrice{ + {PriceTimestamp: 100}, + {PriceTimestamp: 200}, + {PriceTimestamp: 300}, + }, + startTime: 200, + endTime: 100, + expectErr: true, + }, + { + name: "error if no timestamp before or equal to start " + + "time is provided", + prices: []*frdrpc.BitcoinPrice{ + {PriceTimestamp: 100}, + {PriceTimestamp: 200}, + }, + startTime: 50, + endTime: 100, + expectErr: true, + }, + { + name: "check correct filtering of prices", + prices: []*frdrpc.BitcoinPrice{ + {PriceTimestamp: 100}, + {PriceTimestamp: 200}, + {PriceTimestamp: 300}, + {PriceTimestamp: 400}, + {PriceTimestamp: 500}, + {PriceTimestamp: 600}, + }, + startTime: 250, + endTime: 400, + expectedPrices: []*frdrpc.BitcoinPrice{ + {PriceTimestamp: 200}, + {PriceTimestamp: 300}, + }, + }, + { + name: "equal start and end timestamps", + prices: []*frdrpc.BitcoinPrice{ + {PriceTimestamp: 100}, + {PriceTimestamp: 200}, + {PriceTimestamp: 300}, + }, + startTime: 200, + endTime: 200, + expectedPrices: []*frdrpc.BitcoinPrice{ + {PriceTimestamp: 200}, + }, + }, + } + + for _, test := range tests { + test := test + + t.Run(test.name, func(t *testing.T) { + t.Parallel() + + prices, err := filterPrices( + test.prices, test.startTime, test.endTime, + ) + if err != nil { + if test.expectErr { + return + } + t.Fatalf("expected no error, got: %v", err) + } + + if len(prices) != len(test.expectedPrices) { + t.Fatalf("expected %d prices, got %d", + len(test.expectedPrices), len(prices)) + } + + for i, p := range prices { + if p.PriceTimestamp != test.expectedPrices[i].PriceTimestamp { + t.Fatalf("expected timestamp "+ + "%d at index %d, got timestamp %d", + test.expectedPrices[i].PriceTimestamp, + i, p.PriceTimestamp) + } + } + }) + } +} From 07ec73e23f253be8f505a023465ee0c4ae9406cb Mon Sep 17 00:00:00 2001 From: Elle Mouton Date: Fri, 27 Aug 2021 17:00:01 +0200 Subject: [PATCH 12/12] frdrpc: validate custom pricepoints This commit adds a server side check that ensures that at least one price point is provided that has a timestamp preceding the start time. --- frdrpc/node_audit.go | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/frdrpc/node_audit.go b/frdrpc/node_audit.go index 6b3d443..62cd1c4 100644 --- a/frdrpc/node_audit.go +++ b/frdrpc/node_audit.go @@ -67,6 +67,14 @@ func parseNodeAuditRequest(ctx context.Context, cfg *Config, return nil, nil, err } + if req.FiatBackend == FiatBackend_CUSTOM { + if err := validateCustomPricePoints( + pricePoints, time.Unix(int64(req.StartTime), 0), + ); err != nil { + return nil, nil, err + } + } + priceSourceCfg := &fiat.PriceSourceConfig{ Backend: fiatBackend, Granularity: granularity, @@ -162,6 +170,21 @@ func pricePointsFromRPC(prices []*BitcoinPrice) ([]*fiat.Price, error) { return res, nil } +// validateCustomPricePoints checks that there is at lease one price point +// in the set before the given start time. +func validateCustomPricePoints(prices []*fiat.Price, + startTime time.Time) error { + + for _, price := range prices { + if price.Timestamp.Before(startTime) { + return nil + } + } + + return errors.New("expected at least one price point with a " + + "timestamp preceding the given start time") +} + func getCategories(categories []*CustomCategory) ([]accounting.CustomCategory, []accounting.CustomCategory, error) {