Refactor fiat rates usage

This commit is contained in:
Martin Boehm 2023-04-26 21:48:31 +02:00
parent a4f7f5b965
commit 210ec75a3d
9 changed files with 224 additions and 188 deletions

View File

@ -1010,24 +1010,27 @@ func (w *Worker) getEthereumContractBalanceFromBlockchain(addrDesc, contract bch
}, nil }, nil
} }
// GetContractBaseRate returns contract rate in base coin from the ticker or DB at the blocktime. Zero blocktime means now. // GetContractBaseRate returns contract rate in base coin from the ticker or DB at the timestamp. Zero timestamp means now.
func (w *Worker) GetContractBaseRate(ticker *common.CurrencyRatesTicker, contract string, blocktime int64) (float64, bool) { func (w *Worker) GetContractBaseRate(ticker *common.CurrencyRatesTicker, token string, timestamp int64) (float64, bool) {
if ticker == nil { if ticker == nil {
return 0, false return 0, false
} }
rate, found := ticker.GetTokenRate(contract) rate, found := ticker.GetTokenRate(token)
if !found { if !found {
var date time.Time if timestamp == 0 {
if blocktime == 0 { ticker = w.fiatRates.GetCurrentTicker("", token)
date = time.Now().UTC()
} else { } else {
date = time.Unix(blocktime, 0).UTC() tickers, err := w.fiatRates.GetTickersForTimestamps([]int64{timestamp}, "", token)
if err != nil || tickers == nil || len(*tickers) == 0 {
ticker = nil
} else {
ticker = (*tickers)[0]
}
} }
ticker, _ = w.db.FiatRatesFindTicker(&date, "", contract)
if ticker == nil { if ticker == nil {
return 0, false return 0, false
} }
rate, found = ticker.GetTokenRate(contract) rate, found = ticker.GetTokenRate(token)
} }
return float64(rate), found return float64(rate), found
@ -1564,12 +1567,13 @@ func (w *Worker) balanceHistoryForTxid(addrDesc bchain.AddressDescriptor, txid s
func (w *Worker) setFiatRateToBalanceHistories(histories BalanceHistories, currencies []string) error { func (w *Worker) setFiatRateToBalanceHistories(histories BalanceHistories, currencies []string) error {
for i := range histories { for i := range histories {
bh := &histories[i] bh := &histories[i]
t := time.Unix(int64(bh.Time), 0) tickers, err := w.fiatRates.GetTickersForTimestamps([]int64{int64(bh.Time)}, "", "")
ticker, err := w.db.FiatRatesFindTicker(&t, "", "") if err != nil || tickers == nil || len(*tickers) == 0 {
if err != nil { glog.Errorf("Error finding ticker by date %v. Error: %v", bh.Time, err)
glog.Errorf("Error finding ticker by date %v. Error: %v", t, err)
continue continue
} else if ticker == nil { }
ticker := (*tickers)[0]
if ticker == nil {
continue continue
} }
if len(currencies) == 0 { if len(currencies) == 0 {
@ -1818,16 +1822,29 @@ func removeEmpty(stringSlice []string) []string {
// getFiatRatesResult checks if CurrencyRatesTicker contains all necessary data and returns formatted result // getFiatRatesResult checks if CurrencyRatesTicker contains all necessary data and returns formatted result
func (w *Worker) getFiatRatesResult(currencies []string, ticker *common.CurrencyRatesTicker, token string) (*FiatTicker, error) { func (w *Worker) getFiatRatesResult(currencies []string, ticker *common.CurrencyRatesTicker, token string) (*FiatTicker, error) {
if token != "" { if token != "" {
if len(currencies) != 1 { rates := make(map[string]float32)
return nil, NewAPIError("Rates for token only for a single currency", true) if len(currencies) == 0 {
} for currency := range ticker.Rates {
rate := ticker.TokenRateInCurrency(token, currencies[0]) currency = strings.ToLower(currency)
if rate <= 0 { rate := ticker.TokenRateInCurrency(token, currency)
rate = -1 if rate <= 0 {
rate = -1
}
rates[currency] = rate
}
} else {
for _, currency := range currencies {
currency = strings.ToLower(currency)
rate := ticker.TokenRateInCurrency(token, currency)
if rate <= 0 {
rate = -1
}
rates[currency] = rate
}
} }
return &FiatTicker{ return &FiatTicker{
Timestamp: ticker.Timestamp.UTC().Unix(), Timestamp: ticker.Timestamp.UTC().Unix(),
Rates: map[string]float32{currencies[0]: rate}, Rates: rates,
}, nil }, nil
} }
if len(currencies) == 0 { if len(currencies) == 0 {
@ -1853,36 +1870,6 @@ func (w *Worker) getFiatRatesResult(currencies []string, ticker *common.Currency
}, nil }, nil
} }
// GetFiatRatesForBlockID returns fiat rates for block height or block hash
func (w *Worker) GetFiatRatesForBlockID(blockID string, currencies []string, token string) (*FiatTicker, error) {
var ticker *common.CurrencyRatesTicker
bi, err := w.getBlockInfoFromBlockID(blockID)
if err != nil {
if err == bchain.ErrBlockNotFound {
return nil, NewAPIError(fmt.Sprintf("Block %v not found", blockID), true)
}
return nil, NewAPIError(fmt.Sprintf("Block %v not found, error: %v", blockID, err), false)
}
dbi := &db.BlockInfo{Time: bi.Time} // get Unix timestamp from block
tm := time.Unix(dbi.Time, 0) // convert it to Time object
vsCurrency := ""
currencies = removeEmpty(currencies)
if len(currencies) == 1 {
vsCurrency = currencies[0]
}
ticker, err = w.db.FiatRatesFindTicker(&tm, vsCurrency, token)
if err != nil {
return nil, NewAPIError(fmt.Sprintf("Error finding ticker: %v", err), false)
} else if ticker == nil {
return nil, NewAPIError(fmt.Sprintf("No tickers available for %s", tm), true)
}
result, err := w.getFiatRatesResult(currencies, ticker, token)
if err != nil {
return nil, err
}
return result, nil
}
// GetCurrentFiatRates returns last available fiat rates // GetCurrentFiatRates returns last available fiat rates
func (w *Worker) GetCurrentFiatRates(currencies []string, token string) (*FiatTicker, error) { func (w *Worker) GetCurrentFiatRates(currencies []string, token string) (*FiatTicker, error) {
vsCurrency := "" vsCurrency := ""
@ -1927,47 +1914,64 @@ func (w *Worker) GetFiatRatesForTimestamps(timestamps []int64, currencies []stri
if len(currencies) == 1 { if len(currencies) == 1 {
vsCurrency = currencies[0] vsCurrency = currencies[0]
} }
tickers, err := w.fiatRates.GetTickersForTimestamps(timestamps, vsCurrency, token)
ret := &FiatTickers{} if err != nil {
for _, timestamp := range timestamps { return nil, err
date := time.Unix(timestamp, 0) }
date = date.UTC() if tickers == nil {
ticker, err := w.db.FiatRatesFindTicker(&date, vsCurrency, token) return nil, NewAPIError("No tickers found", true)
if err != nil { }
glog.Errorf("Error finding ticker for date %v. Error: %v", date, err) if len(*tickers) != len(timestamps) {
ret.Tickers = append(ret.Tickers, FiatTicker{Timestamp: date.Unix(), Rates: makeErrorRates(currencies)}) glog.Error("GetFiatRatesForTimestamps: number of tickers does not match timestamps ", len(*tickers), ", ", len(timestamps))
continue return nil, NewAPIError("No tickers found", false)
} else if ticker == nil { }
ret.Tickers = append(ret.Tickers, FiatTicker{Timestamp: date.Unix(), Rates: makeErrorRates(currencies)}) fiatTickers := make([]FiatTicker, len(*tickers))
for i, t := range *tickers {
if t == nil {
fiatTickers[i] = FiatTicker{Timestamp: timestamps[i], Rates: makeErrorRates(currencies)}
continue continue
} }
result, err := w.getFiatRatesResult(currencies, ticker, token) result, err := w.getFiatRatesResult(currencies, t, token)
if err != nil { if err != nil {
if apiErr, ok := err.(*APIError); ok { if apiErr, ok := err.(*APIError); ok {
if apiErr.Public { if apiErr.Public {
return nil, err return nil, err
} }
} }
ret.Tickers = append(ret.Tickers, FiatTicker{Timestamp: date.Unix(), Rates: makeErrorRates(currencies)}) fiatTickers[i] = FiatTicker{Timestamp: timestamps[i], Rates: makeErrorRates(currencies)}
continue continue
} }
ret.Tickers = append(ret.Tickers, *result) fiatTickers[i] = *result
} }
return ret, nil return &FiatTickers{Tickers: fiatTickers}, nil
}
// GetFiatRatesForBlockID returns fiat rates for block height or block hash
func (w *Worker) GetFiatRatesForBlockID(blockID string, currencies []string, token string) (*FiatTicker, error) {
bi, err := w.getBlockInfoFromBlockID(blockID)
if err != nil {
if err == bchain.ErrBlockNotFound {
return nil, NewAPIError(fmt.Sprintf("Block %v not found", blockID), true)
}
return nil, NewAPIError(fmt.Sprintf("Block %v not found, error: %v", blockID, err), false)
}
tickers, err := w.GetFiatRatesForTimestamps([]int64{bi.Time}, currencies, token)
if err != nil || tickers == nil || len(tickers.Tickers) == 0 {
return nil, err
}
return &tickers.Tickers[0], nil
} }
// GetAvailableVsCurrencies returns the list of available versus currencies for exchange rates // GetAvailableVsCurrencies returns the list of available versus currencies for exchange rates
func (w *Worker) GetAvailableVsCurrencies(timestamp int64, token string) (*AvailableVsCurrencies, error) { func (w *Worker) GetAvailableVsCurrencies(timestamp int64, token string) (*AvailableVsCurrencies, error) {
date := time.Unix(timestamp, 0) tickers, err := w.fiatRates.GetTickersForTimestamps([]int64{timestamp}, "", token)
date = date.UTC()
ticker, err := w.db.FiatRatesFindTicker(&date, "", strings.ToLower(token))
if err != nil { if err != nil {
return nil, NewAPIError(fmt.Sprintf("Error finding ticker: %v", err), false) return nil, NewAPIError(fmt.Sprintf("Error finding ticker: %v", err), false)
} else if ticker == nil {
return nil, NewAPIError(fmt.Sprintf("No tickers found for date %v.", date), true)
} }
if tickers == nil || len(*tickers) == 0 {
return nil, NewAPIError("No tickers found", true)
}
ticker := (*tickers)[0]
keys := make([]string, 0, len(ticker.Rates)) keys := make([]string, 0, len(ticker.Rates))
for k := range ticker.Rates { for k := range ticker.Rates {
keys = append(keys, k) keys = append(keys, k)

View File

@ -140,19 +140,15 @@ func init() {
} }
// GetCoinNameFromConfig gets coin name and coin shortcut from config file // GetCoinNameFromConfig gets coin name and coin shortcut from config file
func GetCoinNameFromConfig(configfile string) (string, string, string, error) { func GetCoinNameFromConfig(configFileContent []byte) (string, string, string, error) {
data, err := ioutil.ReadFile(configfile)
if err != nil {
return "", "", "", errors.Annotatef(err, "Error reading file %v", configfile)
}
var cn struct { var cn struct {
CoinName string `json:"coin_name"` CoinName string `json:"coin_name"`
CoinShortcut string `json:"coin_shortcut"` CoinShortcut string `json:"coin_shortcut"`
CoinLabel string `json:"coin_label"` CoinLabel string `json:"coin_label"`
} }
err = json.Unmarshal(data, &cn) err := json.Unmarshal(configFileContent, &cn)
if err != nil { if err != nil {
return "", "", "", errors.Annotatef(err, "Error parsing file %v", configfile) return "", "", "", errors.Annotatef(err, "Error parsing config file ")
} }
return cn.CoinName, cn.CoinShortcut, cn.CoinLabel, nil return cn.CoinName, cn.CoinShortcut, cn.CoinLabel, nil
} }

View File

@ -4,7 +4,6 @@ import (
"context" "context"
"encoding/json" "encoding/json"
"flag" "flag"
"io/ioutil"
"log" "log"
"math/rand" "math/rand"
"net/http" "net/http"
@ -158,7 +157,13 @@ func mainWithExitCode() int {
return exitCodeFatal return exitCodeFatal
} }
coin, coinShortcut, coinLabel, err := coins.GetCoinNameFromConfig(*configFile) configFileContent, err := os.ReadFile(*configFile)
if err != nil {
glog.Errorf("Error reading file %v, %v", configFile, err)
return exitCodeFatal
}
coin, coinShortcut, coinLabel, err := coins.GetCoinNameFromConfig(configFileContent)
if err != nil { if err != nil {
glog.Error("config: ", err) glog.Error("config: ", err)
return exitCodeFatal return exitCodeFatal
@ -274,7 +279,7 @@ func mainWithExitCode() int {
return exitCodeFatal return exitCodeFatal
} }
if fiatRates, err = fiat.NewFiatRates(index, *configFile, onNewFiatRatesTicker); err != nil { if fiatRates, err = fiat.NewFiatRates(index, configFileContent, onNewFiatRatesTicker); err != nil {
glog.Error("fiatRates ", err) glog.Error("fiatRates ", err)
return exitCodeFatal return exitCodeFatal
} }
@ -363,7 +368,7 @@ func mainWithExitCode() int {
if internalServer != nil || publicServer != nil || chain != nil { if internalServer != nil || publicServer != nil || chain != nil {
// start fiat rates downloader only if not shutting down immediately // start fiat rates downloader only if not shutting down immediately
initDownloaders(index, chain, *configFile) initDownloaders(index, chain, configFileContent)
waitForSignalAndShutdown(internalServer, publicServer, chain, 10*time.Second) waitForSignalAndShutdown(internalServer, publicServer, chain, 10*time.Second)
} }
@ -697,24 +702,18 @@ func computeFeeStats(stopCompute chan os.Signal, blockFrom, blockTo int, db *db.
return err return err
} }
func initDownloaders(db *db.RocksDB, chain bchain.BlockChain, configFile string) { func initDownloaders(db *db.RocksDB, chain bchain.BlockChain, configFileContent []byte) {
if fiatRates.Enabled { if fiatRates.Enabled {
go fiatRates.RunDownloader() go fiatRates.RunDownloader()
} }
data, err := ioutil.ReadFile(configFile)
if err != nil {
glog.Errorf("Error reading file %v, %v", configFile, err)
return
}
var config struct { var config struct {
FourByteSignatures string `json:"fourByteSignatures"` FourByteSignatures string `json:"fourByteSignatures"`
} }
err = json.Unmarshal(data, &config) err := json.Unmarshal(configFileContent, &config)
if err != nil { if err != nil {
glog.Errorf("Error parsing config file %v, %v", configFile, err) glog.Errorf("Error parsing config file %v, %v", *configFile, err)
return return
} }

View File

@ -5,7 +5,6 @@ import (
"errors" "errors"
"fmt" "fmt"
"math/rand" "math/rand"
"os"
"strings" "strings"
"sync" "sync"
"time" "time"
@ -37,7 +36,7 @@ type RatesDownloaderInterface interface {
UpdateHistoricalTokenTickers() error UpdateHistoricalTokenTickers() error
} }
// FiatRates stores FiatRates API parameters // FiatRates is used to fetch and refresh fiat rates
type FiatRates struct { type FiatRates struct {
Enabled bool Enabled bool
periodSeconds int64 periodSeconds int64
@ -62,19 +61,15 @@ type FiatRates struct {
} }
// NewFiatRates initializes the FiatRates handler // NewFiatRates initializes the FiatRates handler
func NewFiatRates(db *db.RocksDB, configFile string, callback OnNewFiatRatesTicker) (*FiatRates, error) { func NewFiatRates(db *db.RocksDB, configFileContent []byte, callback OnNewFiatRatesTicker) (*FiatRates, error) {
var config struct { var config struct {
FiatRates string `json:"fiat_rates"` FiatRates string `json:"fiat_rates"`
FiatRatesParams string `json:"fiat_rates_params"` FiatRatesParams string `json:"fiat_rates_params"`
FiatRatesVsCurrencies string `json:"fiat_rates_vs_currencies"` FiatRatesVsCurrencies string `json:"fiat_rates_vs_currencies"`
} }
data, err := os.ReadFile(configFile) err := json.Unmarshal(configFileContent, &config)
if err != nil { if err != nil {
return nil, fmt.Errorf("error reading file %v, %v", configFile, err) return nil, fmt.Errorf("error parsing config file, %v", err)
}
err = json.Unmarshal(data, &config)
if err != nil {
return nil, fmt.Errorf("error parsing config file %v, %v", configFile, err)
} }
var fr = &FiatRates{ var fr = &FiatRates{
@ -83,7 +78,7 @@ func NewFiatRates(db *db.RocksDB, configFile string, callback OnNewFiatRatesTick
} }
if config.FiatRates == "" || config.FiatRatesParams == "" { if config.FiatRates == "" || config.FiatRatesParams == "" {
glog.Infof("FiatRates config (%v) is empty, not downloading fiat rates", configFile) glog.Infof("FiatRates config is empty, not downloading fiat rates")
fr.Enabled = false fr.Enabled = false
return fr, nil return fr, nil
} }
@ -176,22 +171,30 @@ func (fr *FiatRates) getTokenTickersForTimestamps(timestamps []int64, vsCurrency
currentTicker := fr.GetCurrentTicker("", token) currentTicker := fr.GetCurrentTicker("", token)
tickers := make([]*common.CurrencyRatesTicker, len(timestamps)) tickers := make([]*common.CurrencyRatesTicker, len(timestamps))
var prevTicker *common.CurrencyRatesTicker var prevTicker *common.CurrencyRatesTicker
var prevTs int64
var err error
for i, t := range timestamps { for i, t := range timestamps {
// check if the token is available in the current ticker - if not, return nil ticker instead of wasting time in costly DB searches // check if the token is available in the current ticker - if not, return nil ticker instead of wasting time in costly DB searches
if currentTicker != nil { if currentTicker != nil {
var ticker *common.CurrencyRatesTicker var ticker *common.CurrencyRatesTicker
date := time.Unix(t, 0) date := time.Unix(t, 0)
// if previously found ticker is newer than this one (token tickers may not be in DB for every day), skip search in DB // if previously found ticker is newer than this one (token tickers may not be in DB for every day), skip search in DB
if prevTicker != nil && !date.After(prevTicker.Timestamp) { if prevTicker != nil && t >= prevTs && !date.After(prevTicker.Timestamp) {
ticker = prevTicker ticker = prevTicker
prevTs = t
} else { } else {
ticker, _ := fr.db.FiatRatesFindTicker(&date, vsCurrency, token) ticker, err = fr.db.FiatRatesFindTicker(&date, vsCurrency, token)
if err != nil {
return nil, err
}
prevTicker = ticker prevTicker = ticker
prevTs = t
} }
// if ticker not found in DB, use current ticker // if ticker not found in DB, use current ticker
if ticker == nil { if ticker == nil {
tickers[i] = currentTicker tickers[i] = currentTicker
prevTicker = currentTicker prevTicker = currentTicker
prevTs = t
} else { } else {
tickers[i] = ticker tickers[i] = ticker
} }
@ -202,6 +205,9 @@ func (fr *FiatRates) getTokenTickersForTimestamps(timestamps []int64, vsCurrency
// GetTickersForTimestamps returns tickers for slice of timestamps, that contain requested vsCurrency and token // GetTickersForTimestamps returns tickers for slice of timestamps, that contain requested vsCurrency and token
func (fr *FiatRates) GetTickersForTimestamps(timestamps []int64, vsCurrency string, token string) (*[]*common.CurrencyRatesTicker, error) { func (fr *FiatRates) GetTickersForTimestamps(timestamps []int64, vsCurrency string, token string) (*[]*common.CurrencyRatesTicker, error) {
if !fr.Enabled {
return nil, nil
}
// token rates are not in memory, them load from DB // token rates are not in memory, them load from DB
if token != "" { if token != "" {
return fr.getTokenTickersForTimestamps(timestamps, vsCurrency, token) return fr.getTokenTickersForTimestamps(timestamps, vsCurrency, token)
@ -210,12 +216,13 @@ func (fr *FiatRates) GetTickersForTimestamps(timestamps []int64, vsCurrency stri
defer fr.mux.RUnlock() defer fr.mux.RUnlock()
tickers := make([]*common.CurrencyRatesTicker, len(timestamps)) tickers := make([]*common.CurrencyRatesTicker, len(timestamps))
var prevTicker *common.CurrencyRatesTicker var prevTicker *common.CurrencyRatesTicker
var prevTs int64
for i, t := range timestamps { for i, t := range timestamps {
dailyTs := normalizedUnix(t, secondsInDay) dailyTs := ceilUnix(t, secondsInDay)
// use higher granularity only for non daily timestamps // use higher granularity only for non daily timestamps
if t != dailyTs { if t != dailyTs {
if t >= fr.fiveMinutesTickersFrom && t <= fr.fiveMinutesTickersTo { if t >= fr.fiveMinutesTickersFrom && t <= fr.fiveMinutesTickersTo {
if ticker, found := fr.fiveMinutesTickers[normalizedUnix(t, secondsInFiveMinutes)]; found && ticker != nil { if ticker, found := fr.fiveMinutesTickers[ceilUnix(t, secondsInFiveMinutes)]; found && ticker != nil {
if common.IsSuitableTicker(ticker, vsCurrency, token) { if common.IsSuitableTicker(ticker, vsCurrency, token) {
tickers[i] = ticker tickers[i] = ticker
continue continue
@ -223,7 +230,7 @@ func (fr *FiatRates) GetTickersForTimestamps(timestamps []int64, vsCurrency stri
} }
} }
if t >= fr.hourlyTickersFrom && t <= fr.hourlyTickersTo { if t >= fr.hourlyTickersFrom && t <= fr.hourlyTickersTo {
if ticker, found := fr.hourlyTickers[normalizedUnix(t, secondsInHour)]; found && ticker != nil { if ticker, found := fr.hourlyTickers[ceilUnix(t, secondsInHour)]; found && ticker != nil {
if common.IsSuitableTicker(ticker, vsCurrency, token) { if common.IsSuitableTicker(ticker, vsCurrency, token) {
tickers[i] = ticker tickers[i] = ticker
continue continue
@ -231,7 +238,7 @@ func (fr *FiatRates) GetTickersForTimestamps(timestamps []int64, vsCurrency stri
} }
} }
} }
if prevTicker != nil && dailyTs >= prevTicker.Timestamp.Unix() { if prevTicker != nil && t >= prevTs && t <= prevTicker.Timestamp.Unix() {
tickers[i] = prevTicker tickers[i] = prevTicker
continue continue
} else { } else {
@ -245,15 +252,17 @@ func (fr *FiatRates) GetTickersForTimestamps(timestamps []int64, vsCurrency stri
if common.IsSuitableTicker(ticker, vsCurrency, token) { if common.IsSuitableTicker(ticker, vsCurrency, token) {
tickers[i] = ticker tickers[i] = ticker
prevTicker = ticker prevTicker = ticker
prevTs = t
break break
} else { } else {
found = false found = false
} }
} }
if !found { }
tickers[i] = fr.currentTicker if !found {
prevTicker = fr.currentTicker tickers[i] = fr.currentTicker
} prevTicker = fr.currentTicker
prevTs = t
} }
} }
} }
@ -266,22 +275,31 @@ func (fr *FiatRates) logTickersInfo() {
len(fr.fiveMinutesTickers), time.Unix(fr.fiveMinutesTickersFrom, 0).Format("2006-01-02 15:04"), time.Unix(fr.fiveMinutesTickersTo, 0).Format("2006-01-02 15:04")) len(fr.fiveMinutesTickers), time.Unix(fr.fiveMinutesTickersFrom, 0).Format("2006-01-02 15:04"), time.Unix(fr.fiveMinutesTickersTo, 0).Format("2006-01-02 15:04"))
} }
func normalizedTimeUnix(t time.Time, granularity int64) int64 { func roundTimeUnix(t time.Time, granularity int64) int64 {
return normalizedUnix(t.UTC().Unix(), granularity) return roundUnix(t.UTC().Unix(), granularity)
} }
func normalizedUnix(t int64, granularity int64) int64 { func roundUnix(t int64, granularity int64) int64 {
unix := t + (granularity >> 1) unix := t + (granularity >> 1)
return unix - unix%granularity return unix - unix%granularity
} }
func ceilUnix(t int64, granularity int64) int64 {
unix := t + (granularity - 1)
return unix - unix%granularity
}
// loadDailyTickers loads daily tickers to cache // loadDailyTickers loads daily tickers to cache
func (fr *FiatRates) loadDailyTickers() error { func (fr *FiatRates) loadDailyTickers() error {
fr.mux.Lock() fr.mux.Lock()
defer fr.mux.Unlock() defer fr.mux.Unlock()
fr.dailyTickers = make(map[int64]*common.CurrencyRatesTicker) fr.dailyTickers = make(map[int64]*common.CurrencyRatesTicker)
err := fr.db.FiatRatesGetAllTickers(func(ticker *common.CurrencyRatesTicker) error { err := fr.db.FiatRatesGetAllTickers(func(ticker *common.CurrencyRatesTicker) error {
normalizedTime := normalizedTimeUnix(ticker.Timestamp, secondsInDay) normalizedTime := roundTimeUnix(ticker.Timestamp, secondsInDay)
if normalizedTime == fr.dailyTickersFrom {
// there are multiple tickers on the first day, use only the first one
return nil
}
// remove token rates from cache to save memory (tickers with token rates are hundreds of kb big) // remove token rates from cache to save memory (tickers with token rates are hundreds of kb big)
ticker.TokenRates = nil ticker.TokenRates = nil
if len(fr.dailyTickers) > 0 { if len(fr.dailyTickers) > 0 {
@ -321,8 +339,8 @@ func (fr *FiatRates) tickersToMap(tickers *[]common.CurrencyRatesTicker, granula
to := int64(0) to := int64(0)
for i := range *tickers { for i := range *tickers {
ticker := (*tickers)[i] ticker := (*tickers)[i]
normalizedTime := normalizedTimeUnix(ticker.Timestamp, granularitySeconds) normalizedTime := roundTimeUnix(ticker.Timestamp, granularitySeconds)
dailyTime := normalizedTimeUnix(ticker.Timestamp, secondsInDay) dailyTime := roundTimeUnix(ticker.Timestamp, secondsInDay)
dailyTicker, found := fr.dailyTickers[dailyTime] dailyTicker, found := fr.dailyTickers[dailyTime]
if !found { if !found {
// if not found in historical tickers, use current ticker // if not found in historical tickers, use current ticker

View File

@ -132,18 +132,7 @@ func TestFiatRates(t *testing.T) {
// mocked CoinGecko API // mocked CoinGecko API
configJSON := `{"fiat_rates": "coingecko", "fiat_rates_params": "{\"url\": \"` + mockServer.URL + `\", \"coin\": \"ethereum\",\"platformIdentifier\":\"ethereum\",\"platformVsCurrency\": \"eth\",\"periodSeconds\": 60}"}` configJSON := `{"fiat_rates": "coingecko", "fiat_rates_params": "{\"url\": \"` + mockServer.URL + `\", \"coin\": \"ethereum\",\"platformIdentifier\":\"ethereum\",\"platformVsCurrency\": \"eth\",\"periodSeconds\": 60}"}`
configFile, err := os.CreateTemp("", "config*.json") fiatRates, err := NewFiatRates(d, []byte(configJSON), nil)
if err != nil {
t.Fatalf("FiatRates configFile error: %v", err)
}
if _, err := configFile.WriteString(configJSON); err != nil {
t.Fatalf("FiatRates configFile WriteString error: %v", err)
}
if err := configFile.Close(); err != nil {
t.Fatalf("FiatRates configFile Close error: %v", err)
}
fiatRates, err := NewFiatRates(d, configFile.Name(), nil)
if err != nil { if err != nil {
t.Fatalf("FiatRates init error: %v", err) t.Fatalf("FiatRates init error: %v", err)
} }

View File

@ -580,7 +580,11 @@ func (s *PublicServer) amountSpan(a *api.Amount, td *TemplateData, classes strin
if td.TxTicker == nil { if td.TxTicker == nil {
date := time.Unix(td.Tx.Blocktime, 0).UTC() date := time.Unix(td.Tx.Blocktime, 0).UTC()
secondary := strings.ToLower(td.SecondaryCoin) secondary := strings.ToLower(td.SecondaryCoin)
ticker, _ := s.db.FiatRatesFindTicker(&date, secondary, "") var ticker *common.CurrencyRatesTicker
tickers, err := s.fiatRates.GetTickersForTimestamps([]int64{int64(td.Tx.Blocktime)}, "", "")
if err == nil && tickers != nil && len(*tickers) > 0 {
ticker = (*tickers)[0]
}
if ticker != nil { if ticker != nil {
td.TxSecondaryCoinRate = float64(ticker.Rates[secondary]) td.TxSecondaryCoinRate = float64(ticker.Rates[secondary])
// the ticker is from the midnight, valid for the whole day before // the ticker is from the midnight, valid for the whole day before

File diff suppressed because one or more lines are too long

View File

@ -121,8 +121,15 @@ func setupPublicHTTPServer(parser bchain.BlockChainParser, chain bchain.BlockCha
glog.Fatal("txCache: ", err) glog.Fatal("txCache: ", err)
} }
// mocked CoinGecko API
configJSON := `{"fiat_rates": "coingecko", "fiat_rates_params": "{\"url\": \"none\", \"coin\": \"ethereum\",\"platformIdentifier\":\"ethereum\",\"platformVsCurrency\": \"usd\",\"periodSeconds\": 60}"}`
fiatRates, err := fiat.NewFiatRates(d, []byte(configJSON), nil)
if err != nil {
glog.Fatal("fiatRates ", err)
}
// s.Run is never called, binding can be to any port // s.Run is never called, binding can be to any port
s, err := NewPublicServer("localhost:12345", "", d, chain, mempool, txCache, "", metrics, is, &fiat.FiatRates{}, false) s, err := NewPublicServer("localhost:12345", "", d, chain, mempool, txCache, "", metrics, is, fiatRates, false)
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
@ -187,37 +194,37 @@ func insertFiatRate(date string, rates map[string]float32, tokenRates map[string
// initTestFiatRates initializes test data for /api/v2/tickers endpoint // initTestFiatRates initializes test data for /api/v2/tickers endpoint
func initTestFiatRates(d *db.RocksDB) error { func initTestFiatRates(d *db.RocksDB) error {
if err := insertFiatRate("20180320020000", map[string]float32{ if err := insertFiatRate("20180320000000", map[string]float32{
"usd": 2000.0, "usd": 2000.0,
"eur": 1300.0, "eur": 1300.0,
}, nil, d); err != nil { }, nil, d); err != nil {
return err return err
} }
if err := insertFiatRate("20180320030000", map[string]float32{ if err := insertFiatRate("20180321000000", map[string]float32{
"usd": 2001.0, "usd": 2001.0,
"eur": 1301.0, "eur": 1301.0,
}, nil, d); err != nil { }, nil, d); err != nil {
return err return err
} }
if err := insertFiatRate("20180320040000", map[string]float32{ if err := insertFiatRate("20180322000000", map[string]float32{
"usd": 2002.0, "usd": 2002.0,
"eur": 1302.0, "eur": 1302.0,
}, nil, d); err != nil { }, nil, d); err != nil {
return err return err
} }
if err := insertFiatRate("20180321055521", map[string]float32{ if err := insertFiatRate("20180324000000", map[string]float32{
"usd": 2003.0, "usd": 2003.0,
"eur": 1303.0, "eur": 1303.0,
}, nil, d); err != nil { }, nil, d); err != nil {
return err return err
} }
if err := insertFiatRate("20191121140000", map[string]float32{ if err := insertFiatRate("20191121000000", map[string]float32{
"usd": 7814.5, "usd": 7814.5,
"eur": 7100.0, "eur": 7100.0,
}, nil, d); err != nil { }, nil, d); err != nil {
return err return err
} }
return insertFiatRate("20191121143015", map[string]float32{ return insertFiatRate("20191122000000", map[string]float32{
"usd": 7914.5, "usd": 7914.5,
"eur": 7134.1, "eur": 7134.1,
}, nil, d) }, nil, d)
@ -323,7 +330,7 @@ func httpTestsBitcoinType(t *testing.T, ts *httptest.Server) {
contentType: "text/html; charset=utf-8", contentType: "text/html; charset=utf-8",
body: []string{ body: []string{
`<!doctype html><html lang="en"><head><meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1.0,shrink-to-fit=no"><link href="https://cdn.jsdelivr.net/npm/bootstrap@5.2.2/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-Zenh87qX5JnK2Jl0vWa8Ck2rdkQ2Bzep5IDxbcnCeuOxjzrPF/et3URy9Bv1WTRi" crossorigin="anonymous"><link rel="stylesheet" href="/static/css/main.min.2.css"><script src="https://cdn.jsdelivr.net/npm/bootstrap@5.2.2/dist/js/bootstrap.bundle.min.js" integrity="sha384-OERcA2EqjJCMA+/3y+gxIOqMEjwtxJY7qPCqsdltbNJuaOe923+mo//f6V8Qbsw3" crossorigin="anonymous"></script><script src="/static/js/main.min.2.js"></script><meta http-equiv="X-UA-Compatible" content="IE=edge"><meta name="description" content="Trezor Fake Coin Explorer"><title>Trezor Fake Coin Explorer</title></head><body><header id="header"><nav class="navbar navbar-expand-lg"><div class="container"><a class="navbar-brand" href="/" title="Home"><span class="trezor-logo"></span><span style="padding-left: 140px;">Fake Coin Explorer</span></a><button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarSupportedContent" aria-controls="navbarSupportedContent" aria-expanded="false" aria-label="Toggle navigation"><span class="navbar-toggler-icon"></span></button><div class="collapse navbar-collapse" id="navbarSupportedContent"><ul class="navbar-nav m-md-auto"><li class="nav-item pe-xl-4"><a href="/blocks" class="nav-link">Blocks</a></li><li class="nav-item"><a href="/" class="nav-link">Status</a></li></ul><span class="navbar-form"><form class="d-flex" id="search" action="/search" method="get"><input name="q" type="text" class="form-control form-control-lg" placeholder="Search for block, transaction, address or xpub" focus="true"><button class="btn" type="submit"><span class="search-icon"></span></button></form></span></div></div></nav></header><main id="wrap"><div class="container"><h1>Application status</h1><h3><span class="badge bg-warning text-white p-3 w-100" style="white-space: break-spaces;">Synchronization with backend is disabled, the state of index is not up to date.</span></h3><div class="row"><div class="col-lg-6"><table class="table data-table info-table"><tbody><tr><td style="white-space: nowrap;"><h3>Blockbook</h3></td><td></td></tr><tr><td>Coin</td><td>Fakecoin</td></tr><tr><td>Host</td><td></td></tr><tr><td>Version / Commit / Build</td><td>unknown / <a href="https://github.com/trezor/blockbook/commit/unknown" target="_blank" rel="noopener noreferrer">unknown</a> / unknown</td></tr><tr><td>Synchronized</td><td><h6 class="badge bg-success">true</h6></td></tr><tr><td>Last Block</td><td><a href="/block/225494">225<span class="ns">494</span></a></td></tr><tr><td>Last Block Update</td><td>`, `<!doctype html><html lang="en"><head><meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1.0,shrink-to-fit=no"><link href="https://cdn.jsdelivr.net/npm/bootstrap@5.2.2/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-Zenh87qX5JnK2Jl0vWa8Ck2rdkQ2Bzep5IDxbcnCeuOxjzrPF/et3URy9Bv1WTRi" crossorigin="anonymous"><link rel="stylesheet" href="/static/css/main.min.2.css"><script src="https://cdn.jsdelivr.net/npm/bootstrap@5.2.2/dist/js/bootstrap.bundle.min.js" integrity="sha384-OERcA2EqjJCMA+/3y+gxIOqMEjwtxJY7qPCqsdltbNJuaOe923+mo//f6V8Qbsw3" crossorigin="anonymous"></script><script src="/static/js/main.min.2.js"></script><meta http-equiv="X-UA-Compatible" content="IE=edge"><meta name="description" content="Trezor Fake Coin Explorer"><title>Trezor Fake Coin Explorer</title></head><body><header id="header"><nav class="navbar navbar-expand-lg"><div class="container"><a class="navbar-brand" href="/" title="Home"><span class="trezor-logo"></span><span style="padding-left: 140px;">Fake Coin Explorer</span></a><button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarSupportedContent" aria-controls="navbarSupportedContent" aria-expanded="false" aria-label="Toggle navigation"><span class="navbar-toggler-icon"></span></button><div class="collapse navbar-collapse" id="navbarSupportedContent"><ul class="navbar-nav m-md-auto"><li class="nav-item pe-xl-4"><a href="/blocks" class="nav-link">Blocks</a></li><li class="nav-item"><a href="/" class="nav-link">Status</a></li></ul><span class="navbar-form"><form class="d-flex" id="search" action="/search" method="get"><input name="q" type="text" class="form-control form-control-lg" placeholder="Search for block, transaction, address or xpub" focus="true"><button class="btn" type="submit"><span class="search-icon"></span></button></form></span></div></div></nav></header><main id="wrap"><div class="container"><h1>Application status</h1><h3><span class="badge bg-warning text-white p-3 w-100" style="white-space: break-spaces;">Synchronization with backend is disabled, the state of index is not up to date.</span></h3><div class="row"><div class="col-lg-6"><table class="table data-table info-table"><tbody><tr><td style="white-space: nowrap;"><h3>Blockbook</h3></td><td></td></tr><tr><td>Coin</td><td>Fakecoin</td></tr><tr><td>Host</td><td></td></tr><tr><td>Version / Commit / Build</td><td>unknown / <a href="https://github.com/trezor/blockbook/commit/unknown" target="_blank" rel="noopener noreferrer">unknown</a> / unknown</td></tr><tr><td>Synchronized</td><td><h6 class="badge bg-success">true</h6></td></tr><tr><td>Last Block</td><td><a href="/block/225494">225<span class="ns">494</span></a></td></tr><tr><td>Last Block Update</td><td>`,
`</td></tr><tr><td>Mempool in Sync</td><td><h6 class="badge bg-danger">false</h6></td></tr><tr><td>Last Mempool Update</td><td></td></tr><tr><td>Transactions in Mempool</td><td><a href="/mempool">0</a></td></tr><tr><td>Size On Disk</td>`, `</td></tr><tr><td>Mempool in Sync</td><td><h6 class="badge bg-danger">false</h6></td></tr><tr><td>Last Mempool Update</td><td></td></tr><tr><td>Transactions in Mempool</td><td><a href="/mempool">0</a></td></tr><tr><td>Current Fiat rates</td>`,
`</td></tr></tbody></table></div><div class="col-lg-6"><table class="table data-table info-table"><tbody><tr><td style="white-space: nowrap;"><h3>Backend</h3></td><td></td></tr><tr><td>Chain</td><td>fakecoin</td></tr><tr><td>Version</td><td>001001</td></tr><tr><td>Subversion</td><td>/Fakecoin:0.0.1/</td></tr><tr><td>Last Block</td><td>2</td></tr><tr><td>Difficulty</td><td></td></tr></tbody></table></div></div><span class="text-muted">Blockbook - blockchain indexer for Trezor Suite https://trezor.io/trezor-suite. Do not use for any other purpose.</span></div></main><footer id="footer"><div class="container"><nav class="navbar navbar-dark"><span class="navbar-nav"><a class="nav-link" href="https://satoshilabs.com/" target="_blank" rel="noopener noreferrer">Created by SatoshiLabs</a></span><span class="navbar-nav ml-md-auto"><a class="nav-link" href="https://trezor.io/terms-of-use" target="_blank" rel="noopener noreferrer">Terms of Use</a></span><span class="navbar-nav ml-md-auto d-md-flex d-none"><a class="nav-link" href="https://trezor.io/" target="_blank" rel="noopener noreferrer">Trezor</a></span><span class="navbar-nav ml-md-auto d-md-flex d-none"><a class="nav-link" href="https://trezor.io/trezor-suite" target="_blank" rel="noopener noreferrer">Suite</a></span><span class="navbar-nav ml-md-auto d-md-flex d-none"><a class="nav-link" href="https://trezor.io/support" target="_blank" rel="noopener noreferrer">Support</a></span><span class="navbar-nav ml-md-auto"><a class="nav-link" href="/sendtx">Send Transaction</a></span><span class="navbar-nav ml-md-auto d-lg-flex d-none"><a class="nav-link" href="https://trezor.io/compare" target="_blank" rel="noopener noreferrer">Don't have a Trezor? Get one!</a></span></nav></div></footer></body></html>`, `</td></tr></tbody></table></div><div class="col-lg-6"><table class="table data-table info-table"><tbody><tr><td style="white-space: nowrap;"><h3>Backend</h3></td><td></td></tr><tr><td>Chain</td><td>fakecoin</td></tr><tr><td>Version</td><td>001001</td></tr><tr><td>Subversion</td><td>/Fakecoin:0.0.1/</td></tr><tr><td>Last Block</td><td>2</td></tr><tr><td>Difficulty</td><td></td></tr></tbody></table></div></div><span class="text-muted">Blockbook - blockchain indexer for Trezor Suite https://trezor.io/trezor-suite. Do not use for any other purpose.</span></div></main><footer id="footer"><div class="container"><nav class="navbar navbar-dark"><span class="navbar-nav"><a class="nav-link" href="https://satoshilabs.com/" target="_blank" rel="noopener noreferrer">Created by SatoshiLabs</a></span><span class="navbar-nav ml-md-auto"><a class="nav-link" href="https://trezor.io/terms-of-use" target="_blank" rel="noopener noreferrer">Terms of Use</a></span><span class="navbar-nav ml-md-auto d-md-flex d-none"><a class="nav-link" href="https://trezor.io/" target="_blank" rel="noopener noreferrer">Trezor</a></span><span class="navbar-nav ml-md-auto d-md-flex d-none"><a class="nav-link" href="https://trezor.io/trezor-suite" target="_blank" rel="noopener noreferrer">Suite</a></span><span class="navbar-nav ml-md-auto d-md-flex d-none"><a class="nav-link" href="https://trezor.io/support" target="_blank" rel="noopener noreferrer">Support</a></span><span class="navbar-nav ml-md-auto"><a class="nav-link" href="/sendtx">Send Transaction</a></span><span class="navbar-nav ml-md-auto d-lg-flex d-none"><a class="nav-link" href="https://trezor.io/compare" target="_blank" rel="noopener noreferrer">Don't have a Trezor? Get one!</a></span></nav></div></footer></body></html>`,
}, },
}, },
@ -485,12 +492,12 @@ func httpTestsBitcoinType(t *testing.T, ts *httptest.Server) {
}, },
}, },
{ {
name: "apiFiatRates missing currency", name: "apiFiatRates all currencies",
r: newGetRequest(ts.URL + "/api/v2/tickers"), r: newGetRequest(ts.URL + "/api/v2/tickers"),
status: http.StatusOK, status: http.StatusOK,
contentType: "application/json; charset=utf-8", contentType: "application/json; charset=utf-8",
body: []string{ body: []string{
`{"ts":1574346615,"rates":{"eur":7134.1,"usd":7914.5}}`, `{"ts":1574380800,"rates":{"eur":7134.1,"usd":7914.5}}`,
}, },
}, },
{ {
@ -499,16 +506,16 @@ func httpTestsBitcoinType(t *testing.T, ts *httptest.Server) {
status: http.StatusOK, status: http.StatusOK,
contentType: "application/json; charset=utf-8", contentType: "application/json; charset=utf-8",
body: []string{ body: []string{
`{"ts":1574346615,"rates":{"usd":7914.5}}`, `{"ts":1574380800,"rates":{"usd":7914.5}}`,
}, },
}, },
{ {
name: "apiFiatRates get rate by exact timestamp", name: "apiFiatRates get rate by exact timestamp",
r: newGetRequest(ts.URL + "/api/v2/tickers?currency=usd&timestamp=1574344800"), r: newGetRequest(ts.URL + "/api/v2/tickers?currency=usd&timestamp=1521545531"),
status: http.StatusOK, status: http.StatusOK,
contentType: "application/json; charset=utf-8", contentType: "application/json; charset=utf-8",
body: []string{ body: []string{
`{"ts":1574344800,"rates":{"usd":7814.5}}`, `{"ts":1521590400,"rates":{"usd":2001}}`,
}, },
}, },
{ {
@ -544,25 +551,25 @@ func httpTestsBitcoinType(t *testing.T, ts *httptest.Server) {
status: http.StatusOK, status: http.StatusOK,
contentType: "application/json; charset=utf-8", contentType: "application/json; charset=utf-8",
body: []string{ body: []string{
`{"ts":1574344800,"rates":{"eur":7100}}`, `{"ts":1574380800,"rates":{"eur":7134.1}`,
}, },
}, },
{ {
name: "apiMultiFiatRates all currencies", name: "apiMultiFiatRates all currencies",
r: newGetRequest(ts.URL + "/api/v2/multi-tickers?timestamp=1574344800,1574346615"), r: newGetRequest(ts.URL + "/api/v2/multi-tickers?timestamp=1574344800,1521677000"),
status: http.StatusOK, status: http.StatusOK,
contentType: "application/json; charset=utf-8", contentType: "application/json; charset=utf-8",
body: []string{ body: []string{
`[{"ts":1574344800,"rates":{"eur":7100,"usd":7814.5}},{"ts":1574346615,"rates":{"eur":7134.1,"usd":7914.5}}]`, `[{"ts":1574380800,"rates":{"eur":7134.1,"usd":7914.5}},{"ts":1521849600,"rates":{"eur":1303,"usd":2003}}]`,
}, },
}, },
{ {
name: "apiMultiFiatRates get EUR rate", name: "apiMultiFiatRates get EUR rate",
r: newGetRequest(ts.URL + "/api/v2/multi-tickers?timestamp=1574344800,1574346615&currency=eur"), r: newGetRequest(ts.URL + "/api/v2/multi-tickers?timestamp=1521545531,1574346615&currency=eur"),
status: http.StatusOK, status: http.StatusOK,
contentType: "application/json; charset=utf-8", contentType: "application/json; charset=utf-8",
body: []string{ body: []string{
`[{"ts":1574344800,"rates":{"eur":7100}},{"ts":1574346615,"rates":{"eur":7134.1}}]`, `[{"ts":1521590400,"rates":{"eur":1301}},{"ts":1574380800,"rates":{"eur":7134.1}}]`,
}, },
}, },
{ {
@ -571,7 +578,7 @@ func httpTestsBitcoinType(t *testing.T, ts *httptest.Server) {
status: http.StatusOK, status: http.StatusOK,
contentType: "application/json; charset=utf-8", contentType: "application/json; charset=utf-8",
body: []string{ body: []string{
`{"ts":1521511200,"rates":{"usd":2000}}`, `{"ts":1521504000,"rates":{"usd":2000}}`,
}, },
}, },
{ {
@ -580,7 +587,7 @@ func httpTestsBitcoinType(t *testing.T, ts *httptest.Server) {
status: http.StatusOK, status: http.StatusOK,
contentType: "application/json; charset=utf-8", contentType: "application/json; charset=utf-8",
body: []string{ body: []string{
`{"ts":1521611721,"rates":{"usd":2003}}`, `{"ts":1521676800,"rates":{"usd":2002}}`,
}, },
}, },
{ {
@ -589,7 +596,7 @@ func httpTestsBitcoinType(t *testing.T, ts *httptest.Server) {
status: http.StatusOK, status: http.StatusOK,
contentType: "application/json; charset=utf-8", contentType: "application/json; charset=utf-8",
body: []string{ body: []string{
`{"ts":1574346615,"rates":{"eur":7134.1}}`, `{"ts":1574380800,"rates":{"eur":7134.1}}`,
}, },
}, },
{ {
@ -607,7 +614,7 @@ func httpTestsBitcoinType(t *testing.T, ts *httptest.Server) {
status: http.StatusOK, status: http.StatusOK,
contentType: "application/json; charset=utf-8", contentType: "application/json; charset=utf-8",
body: []string{ body: []string{
`{"ts":1574346615,"available_currencies":["eur","usd"]}`, `{"ts":1574380800,"available_currencies":["eur","usd"]}`,
}, },
}, },
{ {
@ -778,7 +785,7 @@ func httpTestsBitcoinType(t *testing.T, ts *httptest.Server) {
status: http.StatusOK, status: http.StatusOK,
contentType: "application/json; charset=utf-8", contentType: "application/json; charset=utf-8",
body: []string{ body: []string{
`[{"time":1521514800,"txs":1,"received":"24690","sent":"0","sentToSelf":"0","rates":{"eur":1301,"usd":2001}},{"time":1521594000,"txs":1,"received":"0","sent":"12345","sentToSelf":"0","rates":{"eur":1303,"usd":2003}}]`, `[{"time":1521514800,"txs":1,"received":"24690","sent":"0","sentToSelf":"0","rates":{"eur":1301,"usd":2001}},{"time":1521594000,"txs":1,"received":"0","sent":"12345","sentToSelf":"0","rates":{"eur":1302,"usd":2002}}]`,
}, },
}, },
{ {
@ -787,7 +794,7 @@ func httpTestsBitcoinType(t *testing.T, ts *httptest.Server) {
status: http.StatusOK, status: http.StatusOK,
contentType: "application/json; charset=utf-8", contentType: "application/json; charset=utf-8",
body: []string{ body: []string{
`[{"time":1521514800,"txs":1,"received":"9876","sent":"0","sentToSelf":"0","rates":{"eur":1301,"usd":2001}},{"time":1521594000,"txs":1,"received":"9000","sent":"9876","sentToSelf":"9000","rates":{"eur":1303,"usd":2003}}]`, `[{"time":1521514800,"txs":1,"received":"9876","sent":"0","sentToSelf":"0","rates":{"eur":1301,"usd":2001}},{"time":1521594000,"txs":1,"received":"9000","sent":"9876","sentToSelf":"9000","rates":{"eur":1302,"usd":2002}}]`,
}, },
}, },
{ {
@ -796,7 +803,7 @@ func httpTestsBitcoinType(t *testing.T, ts *httptest.Server) {
status: http.StatusOK, status: http.StatusOK,
contentType: "application/json; charset=utf-8", contentType: "application/json; charset=utf-8",
body: []string{ body: []string{
`[{"time":1521514800,"txs":1,"received":"9876","sent":"0","sentToSelf":"0","rates":{"eur":1301}},{"time":1521594000,"txs":1,"received":"9000","sent":"9876","sentToSelf":"9000","rates":{"eur":1303}}]`, `[{"time":1521514800,"txs":1,"received":"9876","sent":"0","sentToSelf":"0","rates":{"eur":1301}},{"time":1521594000,"txs":1,"received":"9000","sent":"9876","sentToSelf":"9000","rates":{"eur":1302}}]`,
}, },
}, },
{ {
@ -814,7 +821,7 @@ func httpTestsBitcoinType(t *testing.T, ts *httptest.Server) {
status: http.StatusOK, status: http.StatusOK,
contentType: "application/json; charset=utf-8", contentType: "application/json; charset=utf-8",
body: []string{ body: []string{
`[{"time":1521514800,"txs":1,"received":"1","sent":"0","sentToSelf":"0","rates":{"eur":1301,"usd":2001}},{"time":1521594000,"txs":1,"received":"118641975500","sent":"1","sentToSelf":"118641975500","rates":{"eur":1303,"usd":2003}}]`, `[{"time":1521514800,"txs":1,"received":"1","sent":"0","sentToSelf":"0","rates":{"eur":1301,"usd":2001}},{"time":1521594000,"txs":1,"received":"118641975500","sent":"1","sentToSelf":"118641975500","rates":{"eur":1302,"usd":2002}}]`,
}, },
}, },
{ {
@ -841,7 +848,7 @@ func httpTestsBitcoinType(t *testing.T, ts *httptest.Server) {
status: http.StatusOK, status: http.StatusOK,
contentType: "application/json; charset=utf-8", contentType: "application/json; charset=utf-8",
body: []string{ body: []string{
`[{"time":1521594000,"txs":1,"received":"118641975500","sent":"1","sentToSelf":"118641975500","rates":{"eur":1303,"usd":2003}}]`, `[{"time":1521594000,"txs":1,"received":"118641975500","sent":"1","sentToSelf":"118641975500","rates":{"eur":1302,"usd":2002}}]`,
}, },
}, },
{ {
@ -1210,7 +1217,7 @@ func websocketTestsBitcoinType(t *testing.T, ts *httptest.Server) {
"currencies": []string{}, "currencies": []string{},
}, },
}, },
want: `{"id":"18","data":{"ts":1574346615,"rates":{"eur":7134.1,"usd":7914.5}}}`, want: `{"id":"18","data":{"ts":1574380800,"rates":{"eur":7134.1,"usd":7914.5}}}`,
}, },
{ {
name: "websocket getCurrentFiatRates usd", name: "websocket getCurrentFiatRates usd",
@ -1220,7 +1227,7 @@ func websocketTestsBitcoinType(t *testing.T, ts *httptest.Server) {
"currencies": []string{"usd"}, "currencies": []string{"usd"},
}, },
}, },
want: `{"id":"19","data":{"ts":1574346615,"rates":{"usd":7914.5}}}`, want: `{"id":"19","data":{"ts":1574380800,"rates":{"usd":7914.5}}}`,
}, },
{ {
name: "websocket getCurrentFiatRates eur", name: "websocket getCurrentFiatRates eur",
@ -1230,7 +1237,7 @@ func websocketTestsBitcoinType(t *testing.T, ts *httptest.Server) {
"currencies": []string{"eur"}, "currencies": []string{"eur"},
}, },
}, },
want: `{"id":"20","data":{"ts":1574346615,"rates":{"eur":7134.1}}}`, want: `{"id":"20","data":{"ts":1574380800,"rates":{"eur":7134.1}}}`,
}, },
{ {
name: "websocket getCurrentFiatRates incorrect currency", name: "websocket getCurrentFiatRates incorrect currency",
@ -1291,10 +1298,10 @@ func websocketTestsBitcoinType(t *testing.T, ts *httptest.Server) {
Method: "getFiatRatesForTimestamps", Method: "getFiatRatesForTimestamps",
Params: map[string]interface{}{ Params: map[string]interface{}{
"currencies": []string{"usd"}, "currencies": []string{"usd"},
"timestamps": []int64{1574346615}, "timestamps": []int64{1574380800},
}, },
}, },
want: `{"id":"26","data":{"tickers":[{"ts":1574346615,"rates":{"usd":7914.5}}]}}`, want: `{"id":"26","data":{"tickers":[{"ts":1574380800,"rates":{"usd":7914.5}}]}}`,
}, },
{ {
name: "websocket getFiatRatesForTimestamps closest date, eur", name: "websocket getFiatRatesForTimestamps closest date, eur",
@ -1305,7 +1312,7 @@ func websocketTestsBitcoinType(t *testing.T, ts *httptest.Server) {
"timestamps": []int64{1521507600}, "timestamps": []int64{1521507600},
}, },
}, },
want: `{"id":"27","data":{"tickers":[{"ts":1521511200,"rates":{"eur":1300}}]}}`, want: `{"id":"27","data":{"tickers":[{"ts":1521590400,"rates":{"eur":1301}}]}}`,
}, },
{ {
name: "websocket getFiatRatesForTimestamps multiple timestamps usd", name: "websocket getFiatRatesForTimestamps multiple timestamps usd",
@ -1316,7 +1323,7 @@ func websocketTestsBitcoinType(t *testing.T, ts *httptest.Server) {
"timestamps": []int64{1570346615, 1574346615}, "timestamps": []int64{1570346615, 1574346615},
}, },
}, },
want: `{"id":"28","data":{"tickers":[{"ts":1574344800,"rates":{"usd":7814.5}},{"ts":1574346615,"rates":{"usd":7914.5}}]}}`, want: `{"id":"28","data":{"tickers":[{"ts":1574294400,"rates":{"usd":7814.5}},{"ts":1574380800,"rates":{"usd":7914.5}}]}}`,
}, },
{ {
name: "websocket getFiatRatesForTimestamps multiple timestamps eur", name: "websocket getFiatRatesForTimestamps multiple timestamps eur",
@ -1327,7 +1334,7 @@ func websocketTestsBitcoinType(t *testing.T, ts *httptest.Server) {
"timestamps": []int64{1570346615, 1574346615}, "timestamps": []int64{1570346615, 1574346615},
}, },
}, },
want: `{"id":"29","data":{"tickers":[{"ts":1574344800,"rates":{"eur":7100}},{"ts":1574346615,"rates":{"eur":7134.1}}]}}`, want: `{"id":"29","data":{"tickers":[{"ts":1574294400,"rates":{"eur":7100}},{"ts":1574380800,"rates":{"eur":7134.1}}]}}`,
}, },
{ {
name: "websocket getFiatRatesForTimestamps multiple timestamps with an error", name: "websocket getFiatRatesForTimestamps multiple timestamps with an error",
@ -1338,7 +1345,7 @@ func websocketTestsBitcoinType(t *testing.T, ts *httptest.Server) {
"timestamps": []int64{1570346615, 1574346615, 2000000000}, "timestamps": []int64{1570346615, 1574346615, 2000000000},
}, },
}, },
want: `{"id":"30","data":{"tickers":[{"ts":1574344800,"rates":{"usd":7814.5}},{"ts":1574346615,"rates":{"usd":7914.5}},{"ts":2000000000,"rates":{"usd":-1}}]}}`, want: `{"id":"30","data":{"tickers":[{"ts":1574294400,"rates":{"usd":7814.5}},{"ts":1574380800,"rates":{"usd":7914.5}},{"ts":2000000000,"rates":{"usd":-1}}]}}`,
}, },
{ {
name: "websocket getFiatRatesForTimestamps multiple errors", name: "websocket getFiatRatesForTimestamps multiple errors",
@ -1359,7 +1366,7 @@ func websocketTestsBitcoinType(t *testing.T, ts *httptest.Server) {
"timestamp": 1570346615, "timestamp": 1570346615,
}, },
}, },
want: `{"id":"32","data":{"ts":1574344800,"available_currencies":["eur","usd"]}}`, want: `{"id":"32","data":{"ts":1574294400,"available_currencies":["eur","usd"]}}`,
}, },
{ {
name: "websocket getBalanceHistory Addr2", name: "websocket getBalanceHistory Addr2",
@ -1369,7 +1376,7 @@ func websocketTestsBitcoinType(t *testing.T, ts *httptest.Server) {
"descriptor": "mtGXQvBowMkBpnhLckhxhbwYK44Gs9eEtz", "descriptor": "mtGXQvBowMkBpnhLckhxhbwYK44Gs9eEtz",
}, },
}, },
want: `{"id":"33","data":[{"time":1521514800,"txs":1,"received":"24690","sent":"0","sentToSelf":"0","rates":{"eur":1301,"usd":2001}},{"time":1521594000,"txs":1,"received":"0","sent":"12345","sentToSelf":"0","rates":{"eur":1303,"usd":2003}}]}`, want: `{"id":"33","data":[{"time":1521514800,"txs":1,"received":"24690","sent":"0","sentToSelf":"0","rates":{"eur":1301,"usd":2001}},{"time":1521594000,"txs":1,"received":"0","sent":"12345","sentToSelf":"0","rates":{"eur":1302,"usd":2002}}]}`,
}, },
{ {
name: "websocket getBalanceHistory xpub", name: "websocket getBalanceHistory xpub",
@ -1379,7 +1386,7 @@ func websocketTestsBitcoinType(t *testing.T, ts *httptest.Server) {
"descriptor": dbtestdata.Xpub, "descriptor": dbtestdata.Xpub,
}, },
}, },
want: `{"id":"34","data":[{"time":1521514800,"txs":1,"received":"1","sent":"0","sentToSelf":"0","rates":{"eur":1301,"usd":2001}},{"time":1521594000,"txs":1,"received":"118641975500","sent":"1","sentToSelf":"118641975500","rates":{"eur":1303,"usd":2003}}]}`, want: `{"id":"34","data":[{"time":1521514800,"txs":1,"received":"1","sent":"0","sentToSelf":"0","rates":{"eur":1301,"usd":2001}},{"time":1521594000,"txs":1,"received":"118641975500","sent":"1","sentToSelf":"118641975500","rates":{"eur":1302,"usd":2002}}]}`,
}, },
{ {
name: "websocket getBalanceHistory xpub from=1521504000&to=1521590400 currencies=[usd]", name: "websocket getBalanceHistory xpub from=1521504000&to=1521590400 currencies=[usd]",