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
}
// 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)

View File

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

View File

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

View File

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

View File

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

View File

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

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)
}
// 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{
`<!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>`,
},
},
@ -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&timestamp=1574344800"),
r: newGetRequest(ts.URL + "/api/v2/tickers?currency=usd&timestamp=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&currency=eur"),
r: newGetRequest(ts.URL + "/api/v2/multi-tickers?timestamp=1521545531,1574346615&currency=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]",

View File

@ -53,7 +53,7 @@
{{range $c := .SecondaryCurrencies}}
<div class="col-3"><a href="?secondary={{$c}}&use_secondary=true">{{$c}}</a></div>
{{end}}
</div>
</div>
</div>
{{end}}
</div>