Add optional fiat rate to balance history

balanceHistory
Martin Boehm 2019-12-18 00:02:24 +01:00
parent 0340cef13c
commit 225ac85a2a
7 changed files with 115 additions and 52 deletions

View File

@ -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"`
}

View File

@ -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
}

View File

@ -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
}

View File

@ -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()
}
}

View File

@ -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"}]}`,
},
}

View File

@ -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

View File

@ -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 = {