From 210ec75a3da138a155eab31e43fa181e9ce384e4 Mon Sep 17 00:00:00 2001 From: Martin Boehm Date: Wed, 26 Apr 2023 21:48:31 +0200 Subject: [PATCH] Refactor fiat rates usage --- api/worker.go | 152 +++++++++++++++-------------- bchain/coins/blockchain.go | 10 +- blockbook.go | 25 +++-- fiat/fiat_rates.go | 70 ++++++++----- fiat/fiat_rates_test.go | 13 +-- server/public.go | 6 +- server/public_ethereumtype_test.go | 49 +++++++--- server/public_test.go | 85 ++++++++-------- static/templates/base.html | 2 +- 9 files changed, 224 insertions(+), 188 deletions(-) diff --git a/api/worker.go b/api/worker.go index 1dd8d8d7..230d7176 100644 --- a/api/worker.go +++ b/api/worker.go @@ -1010,24 +1010,27 @@ func (w *Worker) getEthereumContractBalanceFromBlockchain(addrDesc, contract bch }, nil } -// GetContractBaseRate returns contract rate in base coin from the ticker or DB at the blocktime. Zero blocktime means now. -func (w *Worker) GetContractBaseRate(ticker *common.CurrencyRatesTicker, contract string, blocktime int64) (float64, bool) { +// 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, token string, timestamp int64) (float64, bool) { if ticker == nil { return 0, false } - rate, found := ticker.GetTokenRate(contract) + rate, found := ticker.GetTokenRate(token) if !found { - var date time.Time - if blocktime == 0 { - date = time.Now().UTC() + if timestamp == 0 { + ticker = w.fiatRates.GetCurrentTicker("", token) } 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 { return 0, false } - rate, found = ticker.GetTokenRate(contract) + rate, found = ticker.GetTokenRate(token) } 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 { 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) + tickers, err := w.fiatRates.GetTickersForTimestamps([]int64{int64(bh.Time)}, "", "") + if err != nil || tickers == nil || len(*tickers) == 0 { + glog.Errorf("Error finding ticker by date %v. Error: %v", bh.Time, err) continue - } else if ticker == nil { + } + ticker := (*tickers)[0] + if ticker == nil { continue } 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 func (w *Worker) getFiatRatesResult(currencies []string, ticker *common.CurrencyRatesTicker, token string) (*FiatTicker, error) { if token != "" { - if len(currencies) != 1 { - return nil, NewAPIError("Rates for token only for a single currency", true) - } - rate := ticker.TokenRateInCurrency(token, currencies[0]) - if rate <= 0 { - rate = -1 + rates := make(map[string]float32) + if len(currencies) == 0 { + for currency := range ticker.Rates { + currency = strings.ToLower(currency) + rate := ticker.TokenRateInCurrency(token, currency) + 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{ Timestamp: ticker.Timestamp.UTC().Unix(), - Rates: map[string]float32{currencies[0]: rate}, + Rates: rates, }, nil } if len(currencies) == 0 { @@ -1853,36 +1870,6 @@ func (w *Worker) getFiatRatesResult(currencies []string, ticker *common.Currency }, 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 func (w *Worker) GetCurrentFiatRates(currencies []string, token string) (*FiatTicker, error) { vsCurrency := "" @@ -1927,47 +1914,64 @@ func (w *Worker) GetFiatRatesForTimestamps(timestamps []int64, currencies []stri if len(currencies) == 1 { vsCurrency = currencies[0] } - - ret := &FiatTickers{} - for _, timestamp := range timestamps { - date := time.Unix(timestamp, 0) - date = date.UTC() - ticker, err := w.db.FiatRatesFindTicker(&date, vsCurrency, token) - if err != nil { - glog.Errorf("Error finding ticker for date %v. Error: %v", date, err) - ret.Tickers = append(ret.Tickers, FiatTicker{Timestamp: date.Unix(), Rates: makeErrorRates(currencies)}) - continue - } else if ticker == nil { - ret.Tickers = append(ret.Tickers, FiatTicker{Timestamp: date.Unix(), Rates: makeErrorRates(currencies)}) + tickers, err := w.fiatRates.GetTickersForTimestamps(timestamps, vsCurrency, token) + if err != nil { + return nil, err + } + if tickers == nil { + return nil, NewAPIError("No tickers found", true) + } + if len(*tickers) != len(timestamps) { + glog.Error("GetFiatRatesForTimestamps: number of tickers does not match timestamps ", len(*tickers), ", ", len(timestamps)) + return nil, NewAPIError("No tickers found", false) + } + fiatTickers := make([]FiatTicker, len(*tickers)) + for i, t := range *tickers { + if t == nil { + fiatTickers[i] = FiatTicker{Timestamp: timestamps[i], Rates: makeErrorRates(currencies)} continue } - result, err := w.getFiatRatesResult(currencies, ticker, token) + result, err := w.getFiatRatesResult(currencies, t, token) if err != nil { if apiErr, ok := err.(*APIError); ok { if apiErr.Public { 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 } - 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 func (w *Worker) GetAvailableVsCurrencies(timestamp int64, token string) (*AvailableVsCurrencies, error) { - date := time.Unix(timestamp, 0) - date = date.UTC() - - ticker, err := w.db.FiatRatesFindTicker(&date, "", strings.ToLower(token)) + tickers, err := w.fiatRates.GetTickersForTimestamps([]int64{timestamp}, "", 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 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)) for k := range ticker.Rates { keys = append(keys, k) diff --git a/bchain/coins/blockchain.go b/bchain/coins/blockchain.go index 32eba742..0181cd25 100644 --- a/bchain/coins/blockchain.go +++ b/bchain/coins/blockchain.go @@ -140,19 +140,15 @@ func init() { } // GetCoinNameFromConfig gets coin name and coin shortcut from config file -func GetCoinNameFromConfig(configfile string) (string, string, string, error) { - data, err := ioutil.ReadFile(configfile) - if err != nil { - return "", "", "", errors.Annotatef(err, "Error reading file %v", configfile) - } +func GetCoinNameFromConfig(configFileContent []byte) (string, string, string, error) { var cn struct { CoinName string `json:"coin_name"` CoinShortcut string `json:"coin_shortcut"` CoinLabel string `json:"coin_label"` } - err = json.Unmarshal(data, &cn) + err := json.Unmarshal(configFileContent, &cn) 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 } diff --git a/blockbook.go b/blockbook.go index 54b2dc1f..e1f48e1a 100644 --- a/blockbook.go +++ b/blockbook.go @@ -4,7 +4,6 @@ import ( "context" "encoding/json" "flag" - "io/ioutil" "log" "math/rand" "net/http" @@ -158,7 +157,13 @@ func mainWithExitCode() int { 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 { glog.Error("config: ", err) return exitCodeFatal @@ -274,7 +279,7 @@ func mainWithExitCode() int { 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) return exitCodeFatal } @@ -363,7 +368,7 @@ func mainWithExitCode() int { if internalServer != nil || publicServer != nil || chain != nil { // 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) } @@ -697,24 +702,18 @@ func computeFeeStats(stopCompute chan os.Signal, blockFrom, blockTo int, db *db. 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 { go fiatRates.RunDownloader() } - data, err := ioutil.ReadFile(configFile) - if err != nil { - glog.Errorf("Error reading file %v, %v", configFile, err) - return - } - var config struct { FourByteSignatures string `json:"fourByteSignatures"` } - err = json.Unmarshal(data, &config) + err := json.Unmarshal(configFileContent, &config) if err != nil { - glog.Errorf("Error parsing config file %v, %v", configFile, err) + glog.Errorf("Error parsing config file %v, %v", *configFile, err) return } diff --git a/fiat/fiat_rates.go b/fiat/fiat_rates.go index b1f59ba8..0721197a 100644 --- a/fiat/fiat_rates.go +++ b/fiat/fiat_rates.go @@ -5,7 +5,6 @@ import ( "errors" "fmt" "math/rand" - "os" "strings" "sync" "time" @@ -37,7 +36,7 @@ type RatesDownloaderInterface interface { UpdateHistoricalTokenTickers() error } -// FiatRates stores FiatRates API parameters +// FiatRates is used to fetch and refresh fiat rates type FiatRates struct { Enabled bool periodSeconds int64 @@ -62,19 +61,15 @@ type FiatRates struct { } // 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 { FiatRates string `json:"fiat_rates"` FiatRatesParams string `json:"fiat_rates_params"` FiatRatesVsCurrencies string `json:"fiat_rates_vs_currencies"` } - data, err := os.ReadFile(configFile) + err := json.Unmarshal(configFileContent, &config) if err != nil { - return nil, fmt.Errorf("error reading file %v, %v", configFile, err) - } - err = json.Unmarshal(data, &config) - if err != nil { - return nil, fmt.Errorf("error parsing config file %v, %v", configFile, err) + return nil, fmt.Errorf("error parsing config file, %v", err) } var fr = &FiatRates{ @@ -83,7 +78,7 @@ func NewFiatRates(db *db.RocksDB, configFile string, callback OnNewFiatRatesTick } 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 return fr, nil } @@ -176,22 +171,30 @@ func (fr *FiatRates) getTokenTickersForTimestamps(timestamps []int64, vsCurrency currentTicker := fr.GetCurrentTicker("", token) tickers := make([]*common.CurrencyRatesTicker, len(timestamps)) var prevTicker *common.CurrencyRatesTicker + var prevTs int64 + var err error 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 if currentTicker != nil { var ticker *common.CurrencyRatesTicker 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 prevTicker != nil && !date.After(prevTicker.Timestamp) { + if prevTicker != nil && t >= prevTs && !date.After(prevTicker.Timestamp) { ticker = prevTicker + prevTs = t } else { - ticker, _ := fr.db.FiatRatesFindTicker(&date, vsCurrency, token) + ticker, err = fr.db.FiatRatesFindTicker(&date, vsCurrency, token) + if err != nil { + return nil, err + } prevTicker = ticker + prevTs = t } // if ticker not found in DB, use current ticker if ticker == nil { tickers[i] = currentTicker prevTicker = currentTicker + prevTs = t } else { 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 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 if token != "" { return fr.getTokenTickersForTimestamps(timestamps, vsCurrency, token) @@ -210,12 +216,13 @@ func (fr *FiatRates) GetTickersForTimestamps(timestamps []int64, vsCurrency stri defer fr.mux.RUnlock() tickers := make([]*common.CurrencyRatesTicker, len(timestamps)) var prevTicker *common.CurrencyRatesTicker + var prevTs int64 for i, t := range timestamps { - dailyTs := normalizedUnix(t, secondsInDay) + dailyTs := ceilUnix(t, secondsInDay) // use higher granularity only for non daily timestamps if t != dailyTs { 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) { tickers[i] = ticker continue @@ -223,7 +230,7 @@ func (fr *FiatRates) GetTickersForTimestamps(timestamps []int64, vsCurrency stri } } 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) { tickers[i] = ticker 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 continue } else { @@ -245,15 +252,17 @@ func (fr *FiatRates) GetTickersForTimestamps(timestamps []int64, vsCurrency stri if common.IsSuitableTicker(ticker, vsCurrency, token) { tickers[i] = ticker prevTicker = ticker + prevTs = t break } else { found = false } } - if !found { - tickers[i] = fr.currentTicker - prevTicker = fr.currentTicker - } + } + if !found { + 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")) } -func normalizedTimeUnix(t time.Time, granularity int64) int64 { - return normalizedUnix(t.UTC().Unix(), granularity) +func roundTimeUnix(t time.Time, granularity int64) int64 { + return roundUnix(t.UTC().Unix(), granularity) } -func normalizedUnix(t int64, granularity int64) int64 { +func roundUnix(t int64, granularity int64) int64 { unix := t + (granularity >> 1) 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 func (fr *FiatRates) loadDailyTickers() error { fr.mux.Lock() defer fr.mux.Unlock() fr.dailyTickers = make(map[int64]*common.CurrencyRatesTicker) 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) ticker.TokenRates = nil if len(fr.dailyTickers) > 0 { @@ -321,8 +339,8 @@ func (fr *FiatRates) tickersToMap(tickers *[]common.CurrencyRatesTicker, granula to := int64(0) for i := range *tickers { ticker := (*tickers)[i] - normalizedTime := normalizedTimeUnix(ticker.Timestamp, granularitySeconds) - dailyTime := normalizedTimeUnix(ticker.Timestamp, secondsInDay) + normalizedTime := roundTimeUnix(ticker.Timestamp, granularitySeconds) + dailyTime := roundTimeUnix(ticker.Timestamp, secondsInDay) dailyTicker, found := fr.dailyTickers[dailyTime] if !found { // if not found in historical tickers, use current ticker diff --git a/fiat/fiat_rates_test.go b/fiat/fiat_rates_test.go index 868a25a2..13146114 100644 --- a/fiat/fiat_rates_test.go +++ b/fiat/fiat_rates_test.go @@ -132,18 +132,7 @@ func TestFiatRates(t *testing.T) { // mocked CoinGecko API configJSON := `{"fiat_rates": "coingecko", "fiat_rates_params": "{\"url\": \"` + mockServer.URL + `\", \"coin\": \"ethereum\",\"platformIdentifier\":\"ethereum\",\"platformVsCurrency\": \"eth\",\"periodSeconds\": 60}"}` - configFile, err := os.CreateTemp("", "config*.json") - 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) + fiatRates, err := NewFiatRates(d, []byte(configJSON), nil) if err != nil { t.Fatalf("FiatRates init error: %v", err) } diff --git a/server/public.go b/server/public.go index 45026053..5c199d7b 100644 --- a/server/public.go +++ b/server/public.go @@ -580,7 +580,11 @@ func (s *PublicServer) amountSpan(a *api.Amount, td *TemplateData, classes strin if td.TxTicker == nil { date := time.Unix(td.Tx.Blocktime, 0).UTC() 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 { td.TxSecondaryCoinRate = float64(ticker.Rates[secondary]) // the ticker is from the midnight, valid for the whole day before diff --git a/server/public_ethereumtype_test.go b/server/public_ethereumtype_test.go index 26b6b49c..345be68f 100644 --- a/server/public_ethereumtype_test.go +++ b/server/public_ethereumtype_test.go @@ -7,11 +7,13 @@ import ( "net/http" "net/http/httptest" "testing" + "time" "github.com/golang/glog" "github.com/linxGnu/grocksdb" "github.com/trezor/blockbook/bchain" "github.com/trezor/blockbook/bchain/coins/eth" + "github.com/trezor/blockbook/common" "github.com/trezor/blockbook/db" "github.com/trezor/blockbook/tests/dbtestdata" ) @@ -24,7 +26,7 @@ func httpTestsEthereumType(t *testing.T, ts *httptest.Server) { status: http.StatusOK, contentType: "text/html; charset=utf-8", body: []string{ - `Trezor Fake Coin Explorer

Address address7b.eth

0x7B62EB7fe80350DC7EC945C0B73242cb9877FB1b

0.000000000123450123 FAKE

Confirmed
Balance0.000000000123450123 FAKE
Transactions2
Non-contract Transactions0
Internal Transactions0
Nonce123
ContractQuantityValueTransfers#
Contract 130.000000001000123013 S131
Contract 740.001000123074 S741
ContractTokensTransfers#
Contract 20511

Transactions

ERC721 Token Transfers
`, + `Trezor Fake Coin Explorer

Address address7b.eth

0x7B62EB7fe80350DC7EC945C0B73242cb9877FB1b

0.000000000123450123 FAKE
0.00 USD

Confirmed
Balance0.000000000123450123 FAKE0.00 USD
Transactions2
Non-contract Transactions0
Internal Transactions0
Nonce123
ContractQuantityValueTransfers#
Contract 130.000000001000123013 S13-1
Contract 740.001000123074 S74-1
ContractTokensTransfers#
Contract 20511

Transactions

ERC721 Token Transfers
ERC20 Token Transfers
address7b.eth
 
871.180000950184 S74-
 
address7b.eth
7.674999999999991915 S13-
`, }, }, { @@ -33,7 +35,7 @@ func httpTestsEthereumType(t *testing.T, ts *httptest.Server) { status: http.StatusOK, contentType: "text/html; charset=utf-8", body: []string{ - `Trezor Fake Coin Explorer

Address

0x5Dc6288b35E0807A3d6fEB89b3a2Ff4aB773168e

0.000000000123450093 FAKE

Confirmed
Balance0.000000000123450093 FAKE
Transactions1
Non-contract Transactions1
Internal Transactions0
Nonce93
ContractTokensTransfers#
Contract 1111 S111 of ID 1776, 10 S111 of ID 18981

Transactions

0x5Dc6288b35E0807A3d6fEB89b3a2Ff4aB773168e
 
0 FAKE
ERC1155 Token Transfers
 
0x5Dc6288b35E0807A3d6fEB89b3a2Ff4aB773168e
1 S111 of ID 1776, 10 S111 of ID 1898
`, + `Trezor Fake Coin Explorer

Address

0x5Dc6288b35E0807A3d6fEB89b3a2Ff4aB773168e

0.000000000123450093 FAKE
0.00 USD

Confirmed
Balance0.000000000123450093 FAKE0.00 USD
Transactions1
Non-contract Transactions1
Internal Transactions0
Nonce93
ContractTokensTransfers#
Contract 1111 S111 of ID 1776, 10 S111 of ID 18981

Transactions

0x5Dc6288b35E0807A3d6fEB89b3a2Ff4aB773168e
 
0 FAKE0.00 USD0.00 USD
ERC1155 Token Transfers
 
0x5Dc6288b35E0807A3d6fEB89b3a2Ff4aB773168e
1 S111 of ID 1776, 10 S111 of ID 1898
`, }, }, { @@ -42,14 +44,14 @@ func httpTestsEthereumType(t *testing.T, ts *httptest.Server) { status: http.StatusOK, contentType: "text/html; charset=utf-8", body: []string{ - `Trezor Fake Coin Explorer

Transaction

0xa9cd088aba2131000da6f38a33c20169baee476218deea6b78720700b895b101
In BlockUnconfirmed
StatusSuccess
Value0 FAKE
Gas Used / Limit52025 / 78037
Gas Price0.00000004 FAKE (40 Gwei)
Fees0.002081 FAKE
RBFON
ERC20 Token Transfers
Input Data

0xa9059cbb000000000000000000000000555ee11fbddc0e49a9bab358a8941ad95ffdb48f00000000000000000000000000000000000000000000021e19e0c9bab2400000
transfer(address, uint256)
#TypeData
0address0x555Ee11FBDDc0E49A9bAB358A8941AD95fFDB48f
1uint25610000000000000000000000
Raw Transaction
`, + `Trezor Fake Coin Explorer

Transaction

0xa9cd088aba2131000da6f38a33c20169baee476218deea6b78720700b895b101
In BlockUnconfirmed
StatusSuccess
Value0 FAKE0.00 USD0.00 USD
Gas Used / Limit52025 / 78037
Gas Price0.00000004 FAKE0.00 USD0.00 USD (40 Gwei)
Fees0.002081 FAKE4.16 USD18.55 USD
RBFON
 
0 FAKE0.00 USD0.00 USD
ERC20 Token Transfers
Input Data

0xa9059cbb000000000000000000000000555ee11fbddc0e49a9bab358a8941ad95ffdb48f00000000000000000000000000000000000000000000021e19e0c9bab2400000
transfer(address, uint256)
#TypeData
0address0x555Ee11FBDDc0E49A9bAB358A8941AD95fFDB48f
1uint25610000000000000000000000
Raw Transaction
`, }, }, { name: "explorerTokenDetail " + dbtestdata.EthAddr7b, r: newGetRequest(ts.URL + "/nft/" + dbtestdata.EthAddrContractCd + "/" + "1"), status: http.StatusOK, contentType: "text/html; charset=utf-8", - body: []string{`Trezor Fake Coin Explorer

NFT Token Detail

Token ID1
Contract0xcdA9FC258358EcaA88845f19Af595e908bb7EfE9
Contract 205
Contract typeERC20
`}, + body: []string{`Trezor Fake Coin Explorer

NFT Token Detail

Token ID1
Contract0xcdA9FC258358EcaA88845f19Af595e908bb7EfE9
Contract 205
Contract typeERC20
`}, }, { name: "apiIndex", @@ -97,7 +99,7 @@ func httpTestsEthereumType(t *testing.T, ts *httptest.Server) { status: http.StatusOK, contentType: "application/json; charset=utf-8", body: []string{ - `{"ts":1574344800,"rates":{"usd":7814.5}}`, + `{"ts":1574380800,"rates":{"usd":7914.5}}`, }, }, { @@ -106,16 +108,16 @@ func httpTestsEthereumType(t *testing.T, ts *httptest.Server) { status: http.StatusOK, contentType: "application/json; charset=utf-8", body: []string{ - `{"ts":1574344800,"rates":{"usd":6251.6}}`, + `{"ts":1574380800,"rates":{"usd":1.2}}`, }, }, { name: "apiFiatRates get token rate by timestamp for all currencies", r: newGetRequest(ts.URL + "/api/v2/tickers?timestamp=1574340000&token=0xA4DD6Bc15Be95Af55f0447555c8b6aA3088562f3"), - status: http.StatusBadRequest, + status: http.StatusOK, contentType: "application/json; charset=utf-8", body: []string{ - `{"error":"Rates for token only for a single currency"}`, + `{"ts":1574380800,"rates":{"eur":1.0816754,"usd":1.2}}`, }, }, { @@ -147,7 +149,7 @@ func initEthereumTypeDB(d *db.RocksDB) error { // initTestFiatRatesEthereumType initializes test data for /api/v2/tickers endpoint func initTestFiatRatesEthereumType(d *db.RocksDB) error { - if err := insertFiatRate("20180320020000", map[string]float32{ + if err := insertFiatRate("20180320000000", map[string]float32{ "usd": 2000.0, "eur": 1300.0, }, map[string]float32{ @@ -156,7 +158,7 @@ func initTestFiatRatesEthereumType(d *db.RocksDB) error { }, d); err != nil { return err } - if err := insertFiatRate("20180320030000", map[string]float32{ + if err := insertFiatRate("20180321000000", map[string]float32{ "usd": 2001.0, "eur": 1301.0, }, map[string]float32{ @@ -165,7 +167,7 @@ func initTestFiatRatesEthereumType(d *db.RocksDB) error { }, d); err != nil { return err } - if err := insertFiatRate("20180320040000", map[string]float32{ + if err := insertFiatRate("20180322000000", map[string]float32{ "usd": 2002.0, "eur": 1302.0, }, map[string]float32{ @@ -174,7 +176,7 @@ func initTestFiatRatesEthereumType(d *db.RocksDB) error { }, d); err != nil { return err } - if err := insertFiatRate("20180321055521", map[string]float32{ + if err := insertFiatRate("20180323000000", map[string]float32{ "usd": 2003.0, "eur": 1303.0, }, map[string]float32{ @@ -183,7 +185,7 @@ func initTestFiatRatesEthereumType(d *db.RocksDB) error { }, d); err != nil { return err } - if err := insertFiatRate("20191121140000", map[string]float32{ + if err := insertFiatRate("20190321000000", map[string]float32{ "usd": 7814.5, "eur": 7100.0, }, map[string]float32{ @@ -193,14 +195,31 @@ func initTestFiatRatesEthereumType(d *db.RocksDB) error { }, d); err != nil { return err } - return insertFiatRate("20191121143015", map[string]float32{ + if err := insertFiatRate("20191122000000", map[string]float32{ "usd": 7914.5, "eur": 7134.1, }, map[string]float32{ "0xdac17f958d2ee523a2206206994597c13d831ec7": 7914.1, "0x2260fac5e5542a773aa44fbcfedf7c193bc2c599": 599.0, "0xa4dd6bc15be95af55f0447555c8b6aa3088562f3": 1.2, - }, d) + }, d); err != nil { + return err + } + + return d.FiatRatesStoreSpecialTickers("CurrentTickers", &[]common.CurrencyRatesTicker{ + { + Timestamp: time.Unix(1592821931, 0), + Rates: map[string]float32{ + "usd": 8914.5, + "eur": 8134.1, + }, + TokenRates: map[string]float32{ + "0xdac17f958d2ee523a2206206994597c13d831ec7": 8914.1, + "0x2260fac5e5542a773aa44fbcfedf7c193bc2c599": 899.0, + "0xa4dd6bc15be95af55f0447555c8b6aa3088562f3": 8.2, + }, + }, + }) } func Test_PublicServer_EthereumType(t *testing.T) { diff --git a/server/public_test.go b/server/public_test.go index 8f01c468..49eb3630 100644 --- a/server/public_test.go +++ b/server/public_test.go @@ -121,8 +121,15 @@ func setupPublicHTTPServer(parser bchain.BlockChainParser, chain bchain.BlockCha 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, 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 { 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 func initTestFiatRates(d *db.RocksDB) error { - if err := insertFiatRate("20180320020000", map[string]float32{ + if err := insertFiatRate("20180320000000", map[string]float32{ "usd": 2000.0, "eur": 1300.0, }, nil, d); err != nil { return err } - if err := insertFiatRate("20180320030000", map[string]float32{ + if err := insertFiatRate("20180321000000", map[string]float32{ "usd": 2001.0, "eur": 1301.0, }, nil, d); err != nil { return err } - if err := insertFiatRate("20180320040000", map[string]float32{ + if err := insertFiatRate("20180322000000", map[string]float32{ "usd": 2002.0, "eur": 1302.0, }, nil, d); err != nil { return err } - if err := insertFiatRate("20180321055521", map[string]float32{ + if err := insertFiatRate("20180324000000", map[string]float32{ "usd": 2003.0, "eur": 1303.0, }, nil, d); err != nil { return err } - if err := insertFiatRate("20191121140000", map[string]float32{ + if err := insertFiatRate("20191121000000", map[string]float32{ "usd": 7814.5, "eur": 7100.0, }, nil, d); err != nil { return err } - return insertFiatRate("20191121143015", map[string]float32{ + return insertFiatRate("20191122000000", map[string]float32{ "usd": 7914.5, "eur": 7134.1, }, nil, d) @@ -323,7 +330,7 @@ func httpTestsBitcoinType(t *testing.T, ts *httptest.Server) { contentType: "text/html; charset=utf-8", body: []string{ `Trezor Fake Coin Explorer

Application status

Synchronization with backend is disabled, the state of index is not up to date.

`, + ``, `

Blockbook

CoinFakecoin
Host
Version / Commit / Buildunknown / unknown / unknown
Synchronized
true
Last Block225494
Last Block Update`, - `
Mempool in Sync
false
Last Mempool Update
Transactions in Mempool0
Size On Disk
Mempool in Sync
false
Last Mempool Update
Transactions in Mempool0
Current Fiat rates

Backend

Chainfakecoin
Version001001
Subversion/Fakecoin:0.0.1/
Last Block2
Difficulty
Blockbook - blockchain indexer for Trezor Suite https://trezor.io/trezor-suite. Do not use for any other purpose.
`, }, }, @@ -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"), status: http.StatusOK, contentType: "application/json; charset=utf-8", 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, contentType: "application/json; charset=utf-8", body: []string{ - `{"ts":1574346615,"rates":{"usd":7914.5}}`, + `{"ts":1574380800,"rates":{"usd":7914.5}}`, }, }, { name: "apiFiatRates get rate by exact timestamp", - r: newGetRequest(ts.URL + "/api/v2/tickers?currency=usd×tamp=1574344800"), + r: newGetRequest(ts.URL + "/api/v2/tickers?currency=usd×tamp=1521545531"), status: http.StatusOK, contentType: "application/json; charset=utf-8", 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, contentType: "application/json; charset=utf-8", body: []string{ - `{"ts":1574344800,"rates":{"eur":7100}}`, + `{"ts":1574380800,"rates":{"eur":7134.1}`, }, }, { 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, contentType: "application/json; charset=utf-8", 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", - r: newGetRequest(ts.URL + "/api/v2/multi-tickers?timestamp=1574344800,1574346615¤cy=eur"), + r: newGetRequest(ts.URL + "/api/v2/multi-tickers?timestamp=1521545531,1574346615¤cy=eur"), status: http.StatusOK, contentType: "application/json; charset=utf-8", 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, contentType: "application/json; charset=utf-8", 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, contentType: "application/json; charset=utf-8", 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, contentType: "application/json; charset=utf-8", 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, contentType: "application/json; charset=utf-8", 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, contentType: "application/json; charset=utf-8", 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, contentType: "application/json; charset=utf-8", 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, contentType: "application/json; charset=utf-8", 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, contentType: "application/json; charset=utf-8", 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, contentType: "application/json; charset=utf-8", 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{}, }, }, - 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", @@ -1220,7 +1227,7 @@ func websocketTestsBitcoinType(t *testing.T, ts *httptest.Server) { "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", @@ -1230,7 +1237,7 @@ func websocketTestsBitcoinType(t *testing.T, ts *httptest.Server) { "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", @@ -1291,10 +1298,10 @@ func websocketTestsBitcoinType(t *testing.T, ts *httptest.Server) { Method: "getFiatRatesForTimestamps", Params: map[string]interface{}{ "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", @@ -1305,7 +1312,7 @@ func websocketTestsBitcoinType(t *testing.T, ts *httptest.Server) { "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", @@ -1316,7 +1323,7 @@ func websocketTestsBitcoinType(t *testing.T, ts *httptest.Server) { "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", @@ -1327,7 +1334,7 @@ func websocketTestsBitcoinType(t *testing.T, ts *httptest.Server) { "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", @@ -1338,7 +1345,7 @@ func websocketTestsBitcoinType(t *testing.T, ts *httptest.Server) { "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", @@ -1359,7 +1366,7 @@ func websocketTestsBitcoinType(t *testing.T, ts *httptest.Server) { "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", @@ -1369,7 +1376,7 @@ func websocketTestsBitcoinType(t *testing.T, ts *httptest.Server) { "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", @@ -1379,7 +1386,7 @@ func websocketTestsBitcoinType(t *testing.T, ts *httptest.Server) { "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]", diff --git a/static/templates/base.html b/static/templates/base.html index 86f2631e..1323c2be 100644 --- a/static/templates/base.html +++ b/static/templates/base.html @@ -53,7 +53,7 @@ {{range $c := .SecondaryCurrencies}}
{{$c}}
{{end}} - + {{end}}