diff --git a/api/types.go b/api/types.go index 66acb903..d3ed51e5 100644 --- a/api/types.go +++ b/api/types.go @@ -303,7 +303,7 @@ type BalanceHistory struct { Txs uint32 `json:"txs"` ReceivedSat *Amount `json:"received"` SentSat *Amount `json:"sent"` - FiatRate string `json:"fiatrate,omitempty"` + FiatRate string `json:"fiatRate,omitempty"` Txid string `json:"txid,omitempty"` } diff --git a/api/worker.go b/api/worker.go index 83a6ecf4..4fa32b6e 100644 --- a/api/worker.go +++ b/api/worker.go @@ -914,8 +914,26 @@ func (w *Worker) balanceHistoryForTxid(addrDesc bchain.AddressDescriptor, txid s return &bh, nil } +func (w *Worker) setFiatRateToBalanceHistories(histories BalanceHistories, fiat string) error { + for i := range histories { + bh := &histories[i] + t := time.Unix(int64(bh.Time), 0) + ticker, err := w.db.FiatRatesFindTicker(&t) + if err != nil { + glog.Errorf("Error finding ticker by date %v. Error: %v", t, err) + continue + } else if ticker == nil { + continue + } + if rate, found := ticker.Rates[fiat]; found { + bh.FiatRate = string(rate) + } + } + return nil +} + // GetBalanceHistory returns history of balance for given address -func (w *Worker) GetBalanceHistory(address string, fromTime, toTime time.Time) (BalanceHistories, error) { +func (w *Worker) GetBalanceHistory(address string, fromTime, toTime time.Time, fiat string) (BalanceHistories, error) { bhs := make(BalanceHistories, 0) start := time.Now() addrDesc, _, err := w.getAddrDescAndNormalizeAddress(address) @@ -940,6 +958,12 @@ func (w *Worker) GetBalanceHistory(address string, fromTime, toTime time.Time) ( } } bha := bhs.SortAndAggregate(3600) + if fiat != "" { + err = w.setFiatRateToBalanceHistories(bha, fiat) + if err != nil { + return nil, err + } + } glog.Info("GetBalanceHistory ", address, ", blocks ", fromHeight, "-", toHeight, ", count ", len(bha), " finished in ", time.Since(start)) return bha, nil } @@ -1117,29 +1141,27 @@ func (w *Worker) GetBlocks(page int, blocksOnPage int) (*Blocks, error) { // getFiatRatesResult checks if CurrencyRatesTicker contains all necessary data and returns formatted result func (w *Worker) getFiatRatesResult(currency string, ticker *db.CurrencyRatesTicker) (*db.ResultTickerAsString, error) { - resultRates := make(map[string]json.Number) + rates := make(map[string]json.Number, 2) timeFormatted := ticker.Timestamp.Format(db.FiatRatesTimeFormat) - - // Check if both USD rate and the desired currency rate exist in the result - for _, currencySymbol := range []string{"usd", currency} { - if _, found := ticker.Rates[currencySymbol]; !found { - availableCurrencies := make([]string, 0, len(ticker.Rates)) - for availableCurrency := range ticker.Rates { - availableCurrencies = append(availableCurrencies, string(availableCurrency)) - } - sort.Strings(availableCurrencies) // sort to get deterministic results - availableCurrenciesString := strings.Join(availableCurrencies, ", ") - return nil, NewAPIError(fmt.Sprintf("Currency %q is not available for timestamp %s. Available currencies are: %s.", currency, timeFormatted, availableCurrenciesString), true) + if rate, found := ticker.Rates[currency]; !found { + availableCurrencies := make([]string, 0, len(ticker.Rates)) + for availableCurrency := range ticker.Rates { + availableCurrencies = append(availableCurrencies, availableCurrency) } - resultRates[currencySymbol] = ticker.Rates[currencySymbol] - if currencySymbol == "usd" && currency == "usd" { - break + sort.Strings(availableCurrencies) // sort to get deterministic results + return nil, NewAPIError(fmt.Sprintf("Currency %q is not available for timestamp %s. Available currencies are: %s", currency, timeFormatted, strings.Join(availableCurrencies, ",")), true) + } else { + rates[currency] = rate + } + // add default usd currency + if currency != "usd" { + if rate, found := ticker.Rates["usd"]; found { + rates["usd"] = rate } } - result := &db.ResultTickerAsString{ Timestamp: timeFormatted, - Rates: resultRates, + Rates: rates, } return result, nil } diff --git a/api/xpub.go b/api/xpub.go index f33af72f..b14ba292 100644 --- a/api/xpub.go +++ b/api/xpub.go @@ -591,7 +591,7 @@ func (w *Worker) GetXpubUtxo(xpub string, onlyConfirmed bool, gap int) (Utxos, e } // GetXpubBalanceHistory returns history of balance for given xpub -func (w *Worker) GetXpubBalanceHistory(xpub string, fromTime, toTime time.Time, gap int) (BalanceHistories, error) { +func (w *Worker) GetXpubBalanceHistory(xpub string, fromTime, toTime time.Time, fiat string, gap int) (BalanceHistories, error) { bhs := make(BalanceHistories, 0) start := time.Now() fromUnix, fromHeight, toUnix, toHeight := w.balanceHistoryHeightsFromTo(fromTime, toTime) @@ -623,6 +623,12 @@ func (w *Worker) GetXpubBalanceHistory(xpub string, fromTime, toTime time.Time, } } bha := bhs.SortAndAggregate(3600) + if fiat != "" { + err = w.setFiatRateToBalanceHistories(bha, fiat) + if err != nil { + return nil, err + } + } glog.Info("GetUtxoBalanceHistory ", xpub[:16], ", blocks ", fromHeight, "-", toHeight, ", count ", len(bha), ", finished in ", time.Since(start)) return bha, nil } diff --git a/server/public.go b/server/public.go index e128cdb3..e817dc99 100644 --- a/server/public.go +++ b/server/public.go @@ -1058,11 +1058,12 @@ func (s *PublicServer) apiBalanceHistory(r *http.Request, apiVersion int) (inter // time.RFC3339 toTime, _ = time.Parse("2006-01-02", t) } - history, err = s.api.GetXpubBalanceHistory(r.URL.Path[i+1:], fromTime, toTime, gap) + fiat := r.URL.Query().Get("fiatcurrency") + history, err = s.api.GetXpubBalanceHistory(r.URL.Path[i+1:], fromTime, toTime, fiat, gap) if err == nil { s.metrics.ExplorerViews.With(common.Labels{"action": "api-xpub-balancehistory"}).Inc() } else { - history, err = s.api.GetBalanceHistory(r.URL.Path[i+1:], fromTime, toTime) + history, err = s.api.GetBalanceHistory(r.URL.Path[i+1:], fromTime, toTime, fiat) s.metrics.ExplorerViews.With(common.Labels{"action": "api-address-balancehistory"}).Inc() } } diff --git a/server/public_test.go b/server/public_test.go index f609bce1..b92be2c8 100644 --- a/server/public_test.go +++ b/server/public_test.go @@ -154,39 +154,54 @@ func newPostRequest(u string, body string) *http.Request { return r } -// InitTestFiatRates initializes test data for /api/v2/tickers endpoint -func InitTestFiatRates(d *db.RocksDB) error { - convertedDate, err := db.FiatRatesConvertDate("20191121140000") +func insertFiatRate(date string, rates map[string]json.Number, d *db.RocksDB) error { + convertedDate, err := db.FiatRatesConvertDate(date) if err != nil { return err } ticker := &db.CurrencyRatesTicker{ Timestamp: convertedDate, - Rates: map[string]json.Number{ - "usd": "7814.5", - "eur": "7100.0", - }, + Rates: rates, } - err = d.FiatRatesStoreTicker(ticker) - if err != nil { + return d.FiatRatesStoreTicker(ticker) +} + +// InitTestFiatRates initializes test data for /api/v2/tickers endpoint +func InitTestFiatRates(d *db.RocksDB) error { + if err := insertFiatRate("20180320020000", map[string]json.Number{ + "usd": "2000.0", + "eur": "1300.0", + }, d); err != nil { return err } - convertedDate, err = db.FiatRatesConvertDate("20191121143015") - if err != nil { + if err := insertFiatRate("20180320030000", map[string]json.Number{ + "usd": "2001.0", + "eur": "1301.0", + }, d); err != nil { return err } - ticker = &db.CurrencyRatesTicker{ - Timestamp: convertedDate, - Rates: map[string]json.Number{ - "usd": "7914.5", - "eur": "7134.1", - }, - } - err = d.FiatRatesStoreTicker(ticker) - if err != nil { + if err := insertFiatRate("20180320040000", map[string]json.Number{ + "usd": "2002.0", + "eur": "1302.0", + }, d); err != nil { return err } - return nil + if err := insertFiatRate("20180321055521", map[string]json.Number{ + "usd": "2003.0", + "eur": "1303.0", + }, d); err != nil { + return err + } + if err := insertFiatRate("20191121140000", map[string]json.Number{ + "usd": "7814.5", + "eur": "7100.0", + }, d); err != nil { + return err + } + return insertFiatRate("20191121143015", map[string]json.Number{ + "usd": "7914.5", + "eur": "7134.1", + }, d) } func httpTestsBitcoinType(t *testing.T, ts *httptest.Server) { @@ -572,7 +587,7 @@ func httpTestsBitcoinType(t *testing.T, ts *httptest.Server) { status: http.StatusOK, contentType: "application/json; charset=utf-8", body: []string{ - `{"data_timestamp":"20191121140000","rates":{"usd":7814.5}}`, + `{"data_timestamp":"20180321055521","rates":{"usd":2003.0}}`, }, }, { @@ -590,7 +605,7 @@ func httpTestsBitcoinType(t *testing.T, ts *httptest.Server) { status: http.StatusOK, contentType: "application/json; charset=utf-8", body: []string{ - `{"error":"Currency \"does_not_exist\" is not available for timestamp 20191121140000. Available currencies are: eur, usd."}`, + `{"error":"Currency \"does_not_exist\" is not available for timestamp 20191121140000. Available currencies are: eur,usd"}`, }, }, { @@ -764,6 +779,15 @@ func httpTestsBitcoinType(t *testing.T, ts *httptest.Server) { `[{"time":1521514800,"txs":1,"received":"9876","sent":"0"},{"time":1521594000,"txs":1,"received":"9000","sent":"9876"}]`, }, }, + { + name: "apiBalanceHistory Addr5 v2 fiatcurrency=eur", + r: newGetRequest(ts.URL + "/api/v2/balancehistory/2NEVv9LJmAnY99W1pFoc5UJjVdypBqdnvu1?fiatcurrency=eur"), + status: http.StatusOK, + contentType: "application/json; charset=utf-8", + body: []string{ + `[{"time":1521514800,"txs":1,"received":"9876","sent":"0","fiatRate":"1301.0"},{"time":1521594000,"txs":1,"received":"9000","sent":"9876","fiatRate":"1303.0"}]`, + }, + }, { name: "apiBalanceHistory Addr2 v2 from=2018-03-20&to=2018-03-21", r: newGetRequest(ts.URL + "/api/v2/balancehistory/mtGXQvBowMkBpnhLckhxhbwYK44Gs9eEtz?from=2018-03-20&to=2018-03-21"), @@ -791,6 +815,15 @@ func httpTestsBitcoinType(t *testing.T, ts *httptest.Server) { `[{"time":1521514800,"txs":1,"received":"1","sent":"0"}]`, }, }, + { + name: "apiBalanceHistory xpub v2 from=2018-03-20&to=2018-03-21&fiatcurrency=usd", + r: newGetRequest(ts.URL + "/api/v2/balancehistory/" + dbtestdata.Xpub + "?from=2018-03-20&to=2018-03-21&fiatcurrency=usd"), + status: http.StatusOK, + contentType: "application/json; charset=utf-8", + body: []string{ + `[{"time":1521514800,"txs":1,"received":"1","sent":"0","fiatRate":"2001.0"}]`, + }, + }, { name: "apiBalanceHistory xpub v2 from=2018-03-21", r: newGetRequest(ts.URL + "/api/v2/balancehistory/" + dbtestdata.Xpub + "?from=2018-03-21"), @@ -1199,7 +1232,7 @@ func websocketTestsBitcoinType(t *testing.T, ts *httptest.Server) { "currency": "does-not-exist", }, }, - want: `{"id":"20","data":{"error":{"message":"Currency \"does-not-exist\" is not available for timestamp 20191121143015. Available currencies are: eur, usd."}}}`, + want: `{"id":"20","data":{"error":{"message":"Currency \"does-not-exist\" is not available for timestamp 20191121143015. Available currencies are: eur,usd"}}}`, }, { name: "websocket getFiatRatesForDates missing date", @@ -1330,16 +1363,17 @@ func websocketTestsBitcoinType(t *testing.T, ts *httptest.Server) { want: `{"id":"32","data":[{"time":1521514800,"txs":1,"received":"1","sent":"0"},{"time":1521594000,"txs":1,"received":"118641975500","sent":"1"}]}`, }, { - name: "websocket getBalanceHistory xpub from=2018-03-20&to=2018-03-21", + name: "websocket getBalanceHistory xpub from=2018-03-20&to=2018-03-21&fiat=usd", req: websocketReq{ Method: "getBalanceHistory", Params: map[string]interface{}{ "descriptor": dbtestdata.Xpub, "from": "2018-03-20", "to": "2018-03-21", + "fiat": "usd", }, }, - want: `{"id":"33","data":[{"time":1521514800,"txs":1,"received":"1","sent":"0"}]}`, + want: `{"id":"33","data":[{"time":1521514800,"txs":1,"received":"1","sent":"0","fiatRate":"2001.0"}]}`, }, } diff --git a/server/websocket.go b/server/websocket.go index 112fde63..a576926c 100644 --- a/server/websocket.go +++ b/server/websocket.go @@ -276,9 +276,9 @@ var requestHandlers = map[string]func(*WebsocketServer, *websocketChannel, *webs return } } - rv, err = s.api.GetXpubBalanceHistory(r.Descriptor, fromTime, toTime, r.Gap) + rv, err = s.api.GetXpubBalanceHistory(r.Descriptor, fromTime, toTime, r.Fiat, r.Gap) if err != nil { - rv, err = s.api.GetBalanceHistory(r.Descriptor, fromTime, toTime) + rv, err = s.api.GetBalanceHistory(r.Descriptor, fromTime, toTime, r.Fiat) } } return diff --git a/static/test-websocket.html b/static/test-websocket.html index efe27efd..a8f121a8 100644 --- a/static/test-websocket.html +++ b/static/test-websocket.html @@ -163,8 +163,8 @@ function getBalanceHistory() { const descriptor = document.getElementById('getBalanceHistoryDescriptor').value.trim(); - const from = parseInt(document.getElementById("getBalanceHistoryFrom").value); - const to = parseInt(document.getElementById("getBalanceHistoryTo").value); + const from = document.getElementById("getBalanceHistoryFrom").value.trim(); + const to = document.getElementById("getBalanceHistoryTo").value.trim(); const fiat = document.getElementById("getBalanceHistoryFiat").value.trim(); const method = 'getBalanceHistory'; const params = {