diff --git a/api/worker.go b/api/worker.go index 3a57f359..1dd8d8d7 100644 --- a/api/worker.go +++ b/api/worker.go @@ -19,6 +19,7 @@ import ( "github.com/trezor/blockbook/bchain/coins/eth" "github.com/trezor/blockbook/common" "github.com/trezor/blockbook/db" + "github.com/trezor/blockbook/fiat" ) // Worker is handle to api worker @@ -31,11 +32,12 @@ type Worker struct { useAddressAliases bool mempool bchain.Mempool is *common.InternalState + fiatRates *fiat.FiatRates metrics *common.Metrics } // NewWorker creates new api worker -func NewWorker(db *db.RocksDB, chain bchain.BlockChain, mempool bchain.Mempool, txCache *db.TxCache, metrics *common.Metrics, is *common.InternalState) (*Worker, error) { +func NewWorker(db *db.RocksDB, chain bchain.BlockChain, mempool bchain.Mempool, txCache *db.TxCache, metrics *common.Metrics, is *common.InternalState, fiatRates *fiat.FiatRates) (*Worker, error) { w := &Worker{ db: db, txCache: txCache, @@ -45,6 +47,7 @@ func NewWorker(db *db.RocksDB, chain bchain.BlockChain, mempool bchain.Mempool, useAddressAliases: chain.GetChainParser().UseAddressAliases(), mempool: mempool, is: is, + fiatRates: fiatRates, metrics: metrics, } if w.chainType == bchain.ChainBitcoinType { @@ -1072,7 +1075,7 @@ func (w *Worker) getEthereumTypeAddressBalances(addrDesc bchain.AddressDescripto if err != nil { return nil, nil, errors.Annotatef(err, "EthereumTypeGetNonce %v", addrDesc) } - ticker := w.is.GetCurrentTicker("", "") + ticker := w.fiatRates.GetCurrentTicker("", "") if details > AccountDetailsBasic { d.tokens = make([]Token, len(ca.Contracts)) var j int @@ -1346,7 +1349,7 @@ func (w *Worker) GetAddress(address string, page int, txsOnPage int, option Acco } var secondaryRate, totalSecondaryValue, totalBaseValue, secondaryValue float64 if secondaryCoin != "" { - ticker := w.is.GetCurrentTicker("", "") + ticker := w.fiatRates.GetCurrentTicker("", "") balance, err := strconv.ParseFloat((*Amount)(&ba.BalanceSat).DecimalString(w.chainParser.AmountDecimals()), 64) if ticker != nil && err == nil { r, found := ticker.Rates[secondaryCoin] @@ -1887,7 +1890,7 @@ func (w *Worker) GetCurrentFiatRates(currencies []string, token string) (*FiatTi if len(currencies) == 1 { vsCurrency = currencies[0] } - ticker := w.is.GetCurrentTicker(vsCurrency, token) + ticker := w.fiatRates.GetCurrentTicker(vsCurrency, token) var err error if ticker == nil { ticker, err = w.db.FiatRatesFindLastTicker(vsCurrency, token) @@ -2289,8 +2292,9 @@ func (w *Worker) GetSystemInfo(internal bool) (*SystemInfo, error) { internalDBSize = w.is.DBSizeTotal() } var currentFiatRatesTime time.Time - if w.is.CurrentTicker != nil { - currentFiatRatesTime = w.is.CurrentTicker.Timestamp + ct := w.fiatRates.GetCurrentTicker("", "") + if ct != nil { + currentFiatRatesTime = ct.Timestamp } blockbookInfo := &BlockbookInfo{ Coin: w.is.Coin, diff --git a/api/xpub.go b/api/xpub.go index da245258..57714533 100644 --- a/api/xpub.go +++ b/api/xpub.go @@ -571,7 +571,7 @@ func (w *Worker) GetXpubAddress(xpub string, page int, txsOnPage int, option Acc var secondaryValue float64 if secondaryCoin != "" { - ticker := w.is.GetCurrentTicker("", "") + ticker := w.fiatRates.GetCurrentTicker("", "") balance, err := strconv.ParseFloat((*Amount)(&data.balanceSat).DecimalString(w.chainParser.AmountDecimals()), 64) if ticker != nil && err == nil { r, found := ticker.Rates[secondaryCoin] diff --git a/blockbook.go b/blockbook.go index 288adbfa..54b2dc1f 100644 --- a/blockbook.go +++ b/blockbook.go @@ -102,6 +102,7 @@ var ( metrics *common.Metrics syncWorker *db.SyncWorker internalState *common.InternalState + fiatRates *fiat.FiatRates callbacksOnNewBlock []bchain.OnNewBlockFunc callbacksOnNewTxAddr []bchain.OnNewTxAddrFunc callbacksOnNewTx []bchain.OnNewTxFunc @@ -273,6 +274,11 @@ func mainWithExitCode() int { return exitCodeFatal } + if fiatRates, err = fiat.NewFiatRates(index, *configFile, onNewFiatRatesTicker); err != nil { + glog.Error("fiatRates ", err) + return exitCodeFatal + } + // report BlockbookAppInfo metric, only log possible error if err = blockbookAppInfoMetric(index, chain, txCache, internalState, metrics); err != nil { glog.Error("blockbookAppInfoMetric ", err) @@ -397,7 +403,7 @@ func getBlockChainWithRetry(coin string, configFile string, pushHandler func(bch } func startInternalServer() (*server.InternalServer, error) { - internalServer, err := server.NewInternalServer(*internalBinding, *certFiles, index, chain, mempool, txCache, metrics, internalState) + internalServer, err := server.NewInternalServer(*internalBinding, *certFiles, index, chain, mempool, txCache, metrics, internalState, fiatRates) if err != nil { return nil, err } @@ -417,7 +423,7 @@ func startInternalServer() (*server.InternalServer, error) { func startPublicServer() (*server.PublicServer, error) { // start public server in limited functionality, extend it after sync is finished by calling ConnectFullPublicInterface - publicServer, err := server.NewPublicServer(*publicBinding, *certFiles, index, chain, mempool, txCache, *explorerURL, metrics, internalState, *debugMode) + publicServer, err := server.NewPublicServer(*publicBinding, *certFiles, index, chain, mempool, txCache, *explorerURL, metrics, internalState, fiatRates, *debugMode) if err != nil { return nil, err } @@ -463,7 +469,7 @@ func performRollback() error { } func blockbookAppInfoMetric(db *db.RocksDB, chain bchain.BlockChain, txCache *db.TxCache, is *common.InternalState, metrics *common.Metrics) error { - api, err := api.NewWorker(db, chain, mempool, txCache, metrics, is) + api, err := api.NewWorker(db, chain, mempool, txCache, metrics, is, fiatRates) if err != nil { return err } @@ -682,7 +688,7 @@ func waitForSignalAndShutdown(internal *server.InternalServer, public *server.Pu func computeFeeStats(stopCompute chan os.Signal, blockFrom, blockTo int, db *db.RocksDB, chain bchain.BlockChain, txCache *db.TxCache, is *common.InternalState, metrics *common.Metrics) error { start := time.Now() glog.Info("computeFeeStats start") - api, err := api.NewWorker(db, chain, mempool, txCache, metrics, is) + api, err := api.NewWorker(db, chain, mempool, txCache, metrics, is, fiatRates) if err != nil { return err } @@ -692,6 +698,10 @@ func computeFeeStats(stopCompute chan os.Signal, blockFrom, blockTo int, db *db. } func initDownloaders(db *db.RocksDB, chain bchain.BlockChain, configFile string) { + if fiatRates.Enabled { + go fiatRates.RunDownloader() + } + data, err := ioutil.ReadFile(configFile) if err != nil { glog.Errorf("Error reading file %v, %v", configFile, err) @@ -699,10 +709,7 @@ func initDownloaders(db *db.RocksDB, chain bchain.BlockChain, configFile string) } var config struct { - FiatRates string `json:"fiat_rates"` - FiatRatesParams string `json:"fiat_rates_params"` - FiatRatesVsCurrencies string `json:"fiat_rates_vs_currencies"` - FourByteSignatures string `json:"fourByteSignatures"` + FourByteSignatures string `json:"fourByteSignatures"` } err = json.Unmarshal(data, &config) @@ -711,18 +718,6 @@ func initDownloaders(db *db.RocksDB, chain bchain.BlockChain, configFile string) return } - if config.FiatRates == "" || config.FiatRatesParams == "" { - glog.Infof("FiatRates config (%v) is empty, not downloading fiat rates", configFile) - } else { - fiatRates, err := fiat.NewFiatRatesDownloader(db, config.FiatRates, config.FiatRatesParams, config.FiatRatesVsCurrencies, onNewFiatRatesTicker) - if err != nil { - glog.Errorf("NewFiatRatesDownloader Init error: %v", err) - } else { - glog.Infof("Starting %v FiatRates downloader...", config.FiatRates) - go fiatRates.Run() - } - } - if config.FourByteSignatures != "" && chain.GetChainParser().GetChainType() == bchain.ChainEthereumType { fbsd, err := fourbyte.NewFourByteSignaturesDownloader(db, config.FourByteSignatures) if err != nil { diff --git a/common/currencyrateticker.go b/common/currencyrateticker.go index f69fc25e..7043f6eb 100644 --- a/common/currencyrateticker.go +++ b/common/currencyrateticker.go @@ -7,9 +7,9 @@ import ( // CurrencyRatesTicker contains coin ticker data fetched from API type CurrencyRatesTicker struct { - Timestamp time.Time `json:"timestamp"` // return as unix timestamp in API - Rates map[string]float32 `json:"rates"` // rates of the base currency against a list of vs currencies - TokenRates map[string]float32 `json:"tokenRates"` // rates of the tokens (identified by the address of the contract) against the base currency + Timestamp time.Time `json:"timestamp"` // return as unix timestamp in API + Rates map[string]float32 `json:"rates"` // rates of the base currency against a list of vs currencies + TokenRates map[string]float32 `json:"tokenRates,omitempty"` // rates of the tokens (identified by the address of the contract) against the base currency } var ( diff --git a/common/internalstate.go b/common/internalstate.go index 8878bf77..f064386b 100644 --- a/common/internalstate.go +++ b/common/internalstate.go @@ -80,11 +80,10 @@ type InternalState struct { DbColumns []InternalStateColumn `json:"dbColumns"` - HasFiatRates bool `json:"-"` - HasTokenFiatRates bool `json:"-"` - HistoricalFiatRatesTime time.Time `json:"historicalFiatRatesTime"` - HistoricalTokenFiatRatesTime time.Time `json:"historicalTokenFiatRatesTime"` - CurrentTicker *CurrencyRatesTicker `json:"currentTicker"` + HasFiatRates bool `json:"-"` + HasTokenFiatRates bool `json:"-"` + HistoricalFiatRatesTime time.Time `json:"historicalFiatRatesTime"` + HistoricalTokenFiatRatesTime time.Time `json:"historicalTokenFiatRatesTime"` EnableSubNewTx bool `json:"-"` @@ -319,24 +318,6 @@ func (is *InternalState) Pack() ([]byte, error) { return json.Marshal(is) } -// GetCurrentTicker returns current ticker -func (is *InternalState) GetCurrentTicker(vsCurrency string, token string) *CurrencyRatesTicker { - is.mux.Lock() - currentTicker := is.CurrentTicker - is.mux.Unlock() - if currentTicker != nil && IsSuitableTicker(currentTicker, vsCurrency, token) { - return currentTicker - } - return nil -} - -// SetCurrentTicker sets current ticker -func (is *InternalState) SetCurrentTicker(t *CurrencyRatesTicker) { - is.mux.Lock() - defer is.mux.Unlock() - is.CurrentTicker = t -} - // UnpackInternalState unmarshals internal state from json func UnpackInternalState(buf []byte) (*InternalState, error) { var is InternalState diff --git a/db/fiat.go b/db/fiat.go index f4392f2d..040494b3 100644 --- a/db/fiat.go +++ b/db/fiat.go @@ -2,8 +2,8 @@ package db import ( "encoding/binary" + "encoding/json" "math" - "sync" "time" vlq "github.com/bsm/go-vlq" @@ -16,9 +16,6 @@ import ( // FiatRatesTimeFormat is a format string for storing FiatRates timestamps in rocksdb const FiatRatesTimeFormat = "20060102150405" // YYYYMMDDhhmmss -var lastTickerInDB *common.CurrencyRatesTicker -var lastTickerInDBMux sync.Mutex - func packTimestamp(t *time.Time) []byte { return []byte(t.UTC().Format(FiatRatesTimeFormat)) } @@ -148,21 +145,21 @@ func (d *RocksDB) FiatRatesGetTicker(tickerTime *time.Time) (*common.CurrencyRat // FiatRatesFindTicker gets FiatRates data closest to the specified timestamp, of the base currency, vsCurrency or the token if specified func (d *RocksDB) FiatRatesFindTicker(tickerTime *time.Time, vsCurrency string, token string) (*common.CurrencyRatesTicker, error) { - currentTicker := d.is.GetCurrentTicker("", "") - lastTickerInDBMux.Lock() - dbTicker := lastTickerInDB - lastTickerInDBMux.Unlock() - if currentTicker != nil { - if !tickerTime.Before(currentTicker.Timestamp) || (dbTicker != nil && tickerTime.After(dbTicker.Timestamp)) { - f := true - if token != "" && currentTicker.TokenRates != nil { - _, f = currentTicker.TokenRates[token] - } - if f { - return currentTicker, nil - } - } - } + // currentTicker := d.is.GetCurrentTicker("", "") + // lastTickerInDBMux.Lock() + // dbTicker := lastTickerInDB + // lastTickerInDBMux.Unlock() + // if currentTicker != nil { + // if !tickerTime.Before(currentTicker.Timestamp) || (dbTicker != nil && tickerTime.After(dbTicker.Timestamp)) { + // f := true + // if token != "" && currentTicker.TokenRates != nil { + // _, f = currentTicker.TokenRates[token] + // } + // if f { + // return currentTicker, nil + // } + // } + // } tickerTimeFormatted := tickerTime.UTC().Format(FiatRatesTimeFormat) it := d.db.NewIteratorCF(d.ro, d.cfh[cfFiatRates]) @@ -193,14 +190,33 @@ func (d *RocksDB) FiatRatesFindLastTicker(vsCurrency string, token string) (*com return nil, err } if ticker != nil { - // if without filter, store the ticker for later use - if vsCurrency == "" && token == "" { - lastTickerInDBMux.Lock() - lastTickerInDB = ticker - lastTickerInDBMux.Unlock() - } return ticker, nil } } return nil, nil } + +func (d *RocksDB) FiatRatesGetSpecialTickers(key string) (*[]common.CurrencyRatesTicker, error) { + val, err := d.db.GetCF(d.ro, d.cfh[cfDefault], []byte(key)) + if err != nil { + return nil, err + } + defer val.Free() + data := val.Data() + if data == nil { + return nil, nil + } + var tickers []common.CurrencyRatesTicker + if err := json.Unmarshal(data, &tickers); err != nil { + return nil, err + } + return &tickers, nil +} + +func (d *RocksDB) FiatRatesStoreSpecialTickers(key string, tickers *[]common.CurrencyRatesTicker) error { + data, err := json.Marshal(tickers) + if err != nil { + return err + } + return d.db.PutCF(d.wo, d.cfh[cfDefault], []byte(key), data) +} diff --git a/db/fiat_test.go b/db/fiat_test.go index 1e5f55fb..05f3ee54 100644 --- a/db/fiat_test.go +++ b/db/fiat_test.go @@ -158,22 +158,6 @@ func TestRocksTickers(t *testing.T) { t.Errorf("Ticker %v found unexpectedly for aud vsCurrency", ticker) } - ticker = d.is.GetCurrentTicker("", "") - if ticker != nil { - t.Errorf("FiatRatesGetCurrentTicker %v found unexpectedly", ticker) - } - - d.is.SetCurrentTicker(ticker1) - ticker = d.is.GetCurrentTicker("", "") - if err != nil { - t.Errorf("TestRocksTickers err: %+v", err) - } else if ticker == nil { - t.Errorf("Ticker not found") - } else if ticker.Timestamp.Format(FiatRatesTimeFormat) != ticker1.Timestamp.Format(FiatRatesTimeFormat) { - t.Errorf("Incorrect ticker found. Expected: %v, found: %+v", ticker1.Timestamp, ticker.Timestamp) - } - - d.is.SetCurrentTicker(nil) } func Test_packUnpackCurrencyRatesTicker(t *testing.T) { diff --git a/fiat/coingecko.go b/fiat/coingecko.go index 1f5087c3..418a2db2 100644 --- a/fiat/coingecko.go +++ b/fiat/coingecko.go @@ -190,13 +190,15 @@ func (cg *Coingecko) coinsList() (coinList, error) { } // coinMarketChart /coins/{id}/market_chart?vs_currency={usd, eur, jpy, etc.}&days={1,14,30,max} -func (cg *Coingecko) coinMarketChart(id string, vs_currency string, days string) (*marketChartPrices, error) { +func (cg *Coingecko) coinMarketChart(id string, vs_currency string, days string, daily bool) (*marketChartPrices, error) { if len(id) == 0 || len(vs_currency) == 0 || len(days) == 0 { return nil, fmt.Errorf("id, vs_currency, and days is required") } params := url.Values{} - params.Add("interval", "daily") + if daily { + params.Add("interval", "daily") + } params.Add("vs_currency", vs_currency) params.Add("days", days) @@ -241,6 +243,7 @@ func (cg *Coingecko) platformIds() error { return nil } +// CurrentTickers returns the latest exchange rates func (cg *Coingecko) CurrentTickers() (*common.CurrencyRatesTicker, error) { cg.updatingCurrent = true defer func() { cg.updatingCurrent = false }() @@ -296,6 +299,16 @@ func (cg *Coingecko) CurrentTickers() (*common.CurrencyRatesTicker, error) { return &newTickers, nil } +// HourlyTickers returns the array of the exchange rates in hourly granularity +func (cg *Coingecko) HourlyTickers() (*[]common.CurrencyRatesTicker, error) { + return nil, nil +} + +// HourlyTickers returns the array of the exchange rates in five minutes granularity +func (cg *Coingecko) FiveMinutesTickers() (*[]common.CurrencyRatesTicker, error) { + return nil, nil +} + func (cg *Coingecko) getHistoricalTicker(tickersToUpdate map[uint]*common.CurrencyRatesTicker, coinId string, vsCurrency string, token string) (bool, error) { lastTicker, err := cg.db.FiatRatesFindLastTicker(vsCurrency, token) if err != nil { @@ -312,7 +325,7 @@ func (cg *Coingecko) getHistoricalTicker(tickersToUpdate map[uint]*common.Curren } days = strconv.Itoa(d) } - mc, err := cg.coinMarketChart(coinId, vsCurrency, days) + mc, err := cg.coinMarketChart(coinId, vsCurrency, days, true) if err != nil { return false, err } diff --git a/fiat/fiat_rates.go b/fiat/fiat_rates.go index de0e8abc..b21699ec 100644 --- a/fiat/fiat_rates.go +++ b/fiat/fiat_rates.go @@ -4,8 +4,10 @@ import ( "encoding/json" "errors" "fmt" + "io/ioutil" "math/rand" "strings" + "sync" "time" "github.com/golang/glog" @@ -13,29 +15,84 @@ import ( "github.com/trezor/blockbook/db" ) +const CurrentTickersKey = "CurrentTickers" +const HourlyTickersKey = "HourlyTickers" +const FiveMinutesTickersKey = "FiveMinutesTickers" + // OnNewFiatRatesTicker is used to send notification about a new FiatRates ticker type OnNewFiatRatesTicker func(ticker *common.CurrencyRatesTicker) // RatesDownloaderInterface provides method signatures for specific fiat rates downloaders type RatesDownloaderInterface interface { CurrentTickers() (*common.CurrencyRatesTicker, error) + HourlyTickers() (*[]common.CurrencyRatesTicker, error) + FiveMinutesTickers() (*[]common.CurrencyRatesTicker, error) UpdateHistoricalTickers() error UpdateHistoricalTokenTickers() error } -// RatesDownloader stores FiatRates API parameters -type RatesDownloader struct { - periodSeconds int64 - db *db.RocksDB - timeFormat string - callbackOnNewTicker OnNewFiatRatesTicker - downloader RatesDownloaderInterface - downloadTokens bool +// FiatRates stores FiatRates API parameters +type FiatRates struct { + Enabled bool + periodSeconds int64 + db *db.RocksDB + timeFormat string + callbackOnNewTicker OnNewFiatRatesTicker + downloader RatesDownloaderInterface + downloadTokens bool + provider string + allowedVsCurrencies string + mux sync.RWMutex + currentTicker *common.CurrencyRatesTicker + hourlyTickers map[int64]*common.CurrencyRatesTicker + hourlyTickersFrom int64 + hourlyTickersTo int64 + fiveMinutesTickers map[int64]*common.CurrencyRatesTicker + fiveMinutesTickersFrom int64 + fiveMinutesTickersTo int64 + dailyTickers map[int64]*common.CurrencyRatesTicker + dailyTickersFrom int64 + dailyTickersTo int64 } -// NewFiatRatesDownloader initializes the downloader for FiatRates API. -func NewFiatRatesDownloader(db *db.RocksDB, apiType string, params string, allowedVsCurrencies string, callback OnNewFiatRatesTicker) (*RatesDownloader, error) { - var rd = &RatesDownloader{} +func tickersToMap(tickers *[]common.CurrencyRatesTicker, granularitySeconds int64) (map[int64]*common.CurrencyRatesTicker, int64, int64) { + if tickers == nil || len(*tickers) == 0 { + return nil, 0, 0 + } + halfGranularity := granularitySeconds / 2 + m := make(map[int64]*common.CurrencyRatesTicker, len(*tickers)) + from := ((*tickers)[0].Timestamp.UTC().Unix() + halfGranularity) % granularitySeconds + to := ((*tickers)[len(*tickers)-1].Timestamp.UTC().Unix() + halfGranularity) % granularitySeconds + return m, from, to +} + +// NewFiatRates initializes the FiatRates handler +func NewFiatRates(db *db.RocksDB, configFile string, 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 := ioutil.ReadFile(configFile) + 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) + } + + var fr = &FiatRates{ + provider: config.FiatRates, + allowedVsCurrencies: config.FiatRatesVsCurrencies, + } + + if config.FiatRates == "" || config.FiatRatesParams == "" { + glog.Infof("FiatRates config (%v) is empty, not downloading fiat rates", configFile) + fr.Enabled = false + return fr, nil + } + type fiatRatesParams struct { URL string `json:"url"` Coin string `json:"coin"` @@ -44,80 +101,151 @@ func NewFiatRatesDownloader(db *db.RocksDB, apiType string, params string, allow PeriodSeconds int64 `json:"periodSeconds"` } rdParams := &fiatRatesParams{} - err := json.Unmarshal([]byte(params), &rdParams) + err = json.Unmarshal([]byte(config.FiatRatesParams), &rdParams) if err != nil { return nil, err } if rdParams.URL == "" || rdParams.PeriodSeconds == 0 { - return nil, errors.New("Missing parameters") + return nil, errors.New("missing parameters") } - rd.timeFormat = "02-01-2006" // Layout string for FiatRates date formatting (DD-MM-YYYY) - rd.periodSeconds = rdParams.PeriodSeconds // Time period for syncing the latest market data - if rd.periodSeconds < 60 { // minimum is one minute - rd.periodSeconds = 60 + fr.timeFormat = "02-01-2006" // Layout string for FiatRates date formatting (DD-MM-YYYY) + fr.periodSeconds = rdParams.PeriodSeconds // Time period for syncing the latest market data + if fr.periodSeconds < 60 { // minimum is one minute + fr.periodSeconds = 60 } - rd.db = db - rd.callbackOnNewTicker = callback - rd.downloadTokens = rdParams.PlatformIdentifier != "" && rdParams.PlatformVsCurrency != "" - if rd.downloadTokens { + fr.db = db + fr.callbackOnNewTicker = callback + fr.downloadTokens = rdParams.PlatformIdentifier != "" && rdParams.PlatformVsCurrency != "" + if fr.downloadTokens { common.TickerRecalculateTokenRate = strings.ToLower(db.GetInternalState().CoinShortcut) != rdParams.PlatformVsCurrency common.TickerTokenVsCurrency = rdParams.PlatformVsCurrency } - is := rd.db.GetInternalState() - if apiType == "coingecko" { + is := fr.db.GetInternalState() + if fr.provider == "coingecko" { throttle := true if callback == nil { // a small hack - in tests the callback is not used, therefore there is no delay slowing down the test throttle = false } - rd.downloader = NewCoinGeckoDownloader(db, rdParams.URL, rdParams.Coin, rdParams.PlatformIdentifier, rdParams.PlatformVsCurrency, allowedVsCurrencies, rd.timeFormat, throttle) + fr.downloader = NewCoinGeckoDownloader(db, rdParams.URL, rdParams.Coin, rdParams.PlatformIdentifier, rdParams.PlatformVsCurrency, fr.allowedVsCurrencies, fr.timeFormat, throttle) if is != nil { is.HasFiatRates = true - is.HasTokenFiatRates = rd.downloadTokens - } + is.HasTokenFiatRates = fr.downloadTokens + fr.Enabled = true + currentTickers, err := db.FiatRatesGetSpecialTickers(CurrentTickersKey) + if err != nil { + glog.Error("FiatRatesDownloader: get CurrentTickers from DB error ", err) + } + if currentTickers != nil && len(*currentTickers) > 0 { + fr.currentTicker = &(*currentTickers)[0] + } + + hourlyTickers, err := db.FiatRatesGetSpecialTickers(HourlyTickersKey) + if err != nil { + glog.Error("FiatRatesDownloader: get HourlyTickers from DB error ", err) + } + fr.hourlyTickers, fr.hourlyTickersFrom, fr.hourlyTickersTo = tickersToMap(hourlyTickers, 3600) + + fiveMinutesTickers, err := db.FiatRatesGetSpecialTickers(FiveMinutesTickersKey) + if err != nil { + glog.Error("FiatRatesDownloader: get FiveMinutesTickers from DB error ", err) + } + fr.fiveMinutesTickers, fr.fiveMinutesTickersFrom, fr.fiveMinutesTickersTo = tickersToMap(fiveMinutesTickers, 5*60) + + } } else { - return nil, fmt.Errorf("NewFiatRatesDownloader: incorrect API type %q", apiType) + return nil, fmt.Errorf("unknown provider %q", fr.provider) } - return rd, nil + return fr, nil } -// Run periodically downloads current (every 15 minutes) and historical (once a day) tickers -func (rd *RatesDownloader) Run() error { +// GetCurrentTicker returns current ticker +func (fr *FiatRates) GetCurrentTicker(vsCurrency string, token string) *common.CurrencyRatesTicker { + fr.mux.RLock() + currentTicker := fr.currentTicker + fr.mux.RUnlock() + if currentTicker != nil && common.IsSuitableTicker(currentTicker, vsCurrency, token) { + return currentTicker + } + return nil +} + +// setCurrentTicker sets current ticker +func (fr *FiatRates) setCurrentTicker(t *common.CurrencyRatesTicker) { + fr.mux.Lock() + defer fr.mux.Unlock() + fr.currentTicker = t + fr.db.FiatRatesStoreSpecialTickers(CurrentTickersKey, &[]common.CurrencyRatesTicker{*t}) +} + +// setCurrentTicker sets hourly tickers +func (fr *FiatRates) setHourlyTickers(t *[]common.CurrencyRatesTicker) { + fr.mux.Lock() + defer fr.mux.Unlock() + fr.hourlyTickers, fr.hourlyTickersFrom, fr.hourlyTickersTo = tickersToMap(t, 3600) + fr.db.FiatRatesStoreSpecialTickers(HourlyTickersKey, t) +} + +// setCurrentTicker sets hourly tickers +func (fr *FiatRates) setFiveMinutesTickers(t *[]common.CurrencyRatesTicker) { + fr.mux.Lock() + defer fr.mux.Unlock() + fr.fiveMinutesTickers, fr.fiveMinutesTickersFrom, fr.fiveMinutesTickersTo = tickersToMap(t, 5*60) + fr.db.FiatRatesStoreSpecialTickers(FiveMinutesTickersKey, t) +} + +// RunDownloader periodically downloads current (every 15 minutes) and historical (once a day) tickers +func (fr *FiatRates) RunDownloader() error { + glog.Infof("Starting %v FiatRates downloader...", fr.provider) var lastHistoricalTickers time.Time - is := rd.db.GetInternalState() - tickerFromIs := is.GetCurrentTicker("", "") + is := fr.db.GetInternalState() + tickerFromIs := fr.GetCurrentTicker("", "") firstRun := true for { unix := time.Now().Unix() - next := unix + rd.periodSeconds - next -= next % rd.periodSeconds + next := unix + fr.periodSeconds + next -= next % fr.periodSeconds // skip waiting for the period for the first run if there are no tickerFromIs or they are too old - if !firstRun || (tickerFromIs != nil && next-tickerFromIs.Timestamp.Unix() < rd.periodSeconds) { + if !firstRun || (tickerFromIs != nil && next-tickerFromIs.Timestamp.Unix() < fr.periodSeconds) { // wait for the next run with a slight random value to avoid too many request at the same time next += int64(rand.Intn(12)) time.Sleep(time.Duration(next-unix) * time.Second) } firstRun = false - tickers, err := rd.downloader.CurrentTickers() - if err != nil || tickers == nil { + currentTicker, err := fr.downloader.CurrentTickers() + if err != nil || currentTicker == nil { glog.Error("FiatRatesDownloader: CurrentTickers error ", err) } else { - is.SetCurrentTicker(tickers) + fr.setCurrentTicker(currentTicker) glog.Info("FiatRatesDownloader: CurrentTickers updated") - if rd.callbackOnNewTicker != nil { - rd.callbackOnNewTicker(tickers) + if fr.callbackOnNewTicker != nil { + fr.callbackOnNewTicker(currentTicker) } } + hourlyTickers, err := fr.downloader.HourlyTickers() + if err != nil || hourlyTickers == nil { + glog.Error("FiatRatesDownloader: HourlyTickers error ", err) + } else { + fr.setHourlyTickers(hourlyTickers) + glog.Info("FiatRatesDownloader: HourlyTickers updated") + } + fiveMinutesTickers, err := fr.downloader.FiveMinutesTickers() + if err != nil || fiveMinutesTickers == nil { + glog.Error("FiatRatesDownloader: FiveMinutesTickers error ", err) + } else { + fr.setFiveMinutesTickers(fiveMinutesTickers) + glog.Info("FiatRatesDownloader: FiveMinutesTickers updated") + } now := time.Now().UTC() // once a day, 1 hour after UTC midnight (to let the provider prepare historical rates) update historical tickers if (now.YearDay() != lastHistoricalTickers.YearDay() || now.Year() != lastHistoricalTickers.Year()) && now.Hour() > 0 { - err = rd.downloader.UpdateHistoricalTickers() + err = fr.downloader.UpdateHistoricalTickers() if err != nil { glog.Error("FiatRatesDownloader: UpdateHistoricalTickers error ", err) } else { lastHistoricalTickers = time.Now().UTC() - ticker, err := rd.db.FiatRatesFindLastTicker("", "") + ticker, err := fr.db.FiatRatesFindLastTicker("", "") if err != nil || ticker == nil { glog.Error("FiatRatesDownloader: FiatRatesFindLastTicker error ", err) } else { @@ -126,10 +254,10 @@ func (rd *RatesDownloader) Run() error { is.HistoricalFiatRatesTime = ticker.Timestamp } } - if rd.downloadTokens { + if fr.downloadTokens { // UpdateHistoricalTokenTickers in a goroutine, it can take quite some time as there are many tokens go func() { - err := rd.downloader.UpdateHistoricalTokenTickers() + err := fr.downloader.UpdateHistoricalTokenTickers() if err != nil { glog.Error("FiatRatesDownloader: UpdateHistoricalTokenTickers error ", err) } else { diff --git a/fiat/fiat_rates_test.go b/fiat/fiat_rates_test.go index f80d9efa..868a25a2 100644 --- a/fiat/fiat_rates_test.go +++ b/fiat/fiat_rates_test.go @@ -3,9 +3,8 @@ package fiat import ( - "encoding/json" "fmt" - "io/ioutil" + "io" "net/http" "net/http/httptest" "os" @@ -32,7 +31,7 @@ func TestMain(m *testing.M) { } func setupRocksDB(t *testing.T, parser bchain.BlockChainParser) (*db.RocksDB, *common.InternalState, string) { - tmp, err := ioutil.TempDir("", "testdb") + tmp, err := os.MkdirTemp("", "testdb") if err != nil { t.Fatal(err) } @@ -75,7 +74,7 @@ func getFiatRatesMockData(name string) (string, error) { glog.Errorf("Cannot open file %v", filename) return "", err } - b, err := ioutil.ReadAll(mockFile) + b, err := io.ReadAll(mockFile) if err != nil { glog.Errorf("Cannot read file %v", filename) return "", err @@ -133,165 +132,159 @@ 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}"}` - type fiatRatesConfig struct { - FiatRates string `json:"fiat_rates"` - FiatRatesParams string `json:"fiat_rates_params"` - } - - var config fiatRatesConfig - err := json.Unmarshal([]byte(configJSON), &config) + configFile, err := os.CreateTemp("", "config*.json") if err != nil { - t.Fatalf("Error parsing config: %v", err) + 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) } - if config.FiatRates == "" || config.FiatRatesParams == "" { - t.Fatalf("Error parsing FiatRates config - empty parameter") - return - } - fiatRates, err := NewFiatRatesDownloader(d, config.FiatRates, config.FiatRatesParams, "", nil) + fiatRates, err := NewFiatRates(d, configFile.Name(), nil) if err != nil { t.Fatalf("FiatRates init error: %v", err) } - if config.FiatRates == "coingecko" { - // get current tickers - currentTickers, err := fiatRates.downloader.CurrentTickers() - if err != nil { - t.Fatalf("Error in CurrentTickers: %v", err) - return - } - if currentTickers == nil { - t.Fatalf("CurrentTickers returned nil value") - return - } + // get current tickers + currentTickers, err := fiatRates.downloader.CurrentTickers() + if err != nil { + t.Fatalf("Error in CurrentTickers: %v", err) + return + } + if currentTickers == nil { + t.Fatalf("CurrentTickers returned nil value") + return + } - wantCurrentTickers := common.CurrencyRatesTicker{ - Rates: map[string]float32{ - "aed": 8447.1, - "ars": 268901, - "aud": 3314.36, - "btc": 0.07531005, - "eth": 1, - "eur": 2182.99, - "ltc": 29.097696, - "usd": 2299.72, - }, - TokenRates: map[string]float32{ - "0x5e9997684d061269564f94e5d11ba6ce6fa9528c": 5.58195e-07, - "0x906710835d1ae85275eb770f06873340ca54274b": 1.39852e-10, - }, - Timestamp: currentTickers.Timestamp, - } - if !reflect.DeepEqual(currentTickers, &wantCurrentTickers) { - t.Fatalf("CurrentTickers() = %v, want %v", *currentTickers, wantCurrentTickers) - } + wantCurrentTickers := common.CurrencyRatesTicker{ + Rates: map[string]float32{ + "aed": 8447.1, + "ars": 268901, + "aud": 3314.36, + "btc": 0.07531005, + "eth": 1, + "eur": 2182.99, + "ltc": 29.097696, + "usd": 2299.72, + }, + TokenRates: map[string]float32{ + "0x5e9997684d061269564f94e5d11ba6ce6fa9528c": 5.58195e-07, + "0x906710835d1ae85275eb770f06873340ca54274b": 1.39852e-10, + }, + Timestamp: currentTickers.Timestamp, + } + if !reflect.DeepEqual(currentTickers, &wantCurrentTickers) { + t.Fatalf("CurrentTickers() = %v, want %v", *currentTickers, wantCurrentTickers) + } - ticker, err := fiatRates.db.FiatRatesFindLastTicker("usd", "") - if err != nil { - t.Fatalf("FiatRatesFindLastTicker failed with error: %v", err) - } - if ticker != nil { - t.Fatalf("FiatRatesFindLastTicker found unexpected data") - } + ticker, err := fiatRates.db.FiatRatesFindLastTicker("usd", "") + if err != nil { + t.Fatalf("FiatRatesFindLastTicker failed with error: %v", err) + } + if ticker != nil { + t.Fatalf("FiatRatesFindLastTicker found unexpected data") + } - // update historical tickers for the first time - err = fiatRates.downloader.UpdateHistoricalTickers() - if err != nil { - t.Fatalf("UpdateHistoricalTickers 1st pass failed with error: %v", err) - } - err = fiatRates.downloader.UpdateHistoricalTokenTickers() - if err != nil { - t.Fatalf("UpdateHistoricalTokenTickers 1st pass failed with error: %v", err) - } + // update historical tickers for the first time + err = fiatRates.downloader.UpdateHistoricalTickers() + if err != nil { + t.Fatalf("UpdateHistoricalTickers 1st pass failed with error: %v", err) + } + err = fiatRates.downloader.UpdateHistoricalTokenTickers() + if err != nil { + t.Fatalf("UpdateHistoricalTokenTickers 1st pass failed with error: %v", err) + } - ticker, err = fiatRates.db.FiatRatesFindLastTicker("usd", "") - if err != nil || ticker == nil { - t.Fatalf("FiatRatesFindLastTicker failed with error: %v", err) - } - wantTicker := common.CurrencyRatesTicker{ - Rates: map[string]float32{ - "aed": 241272.48, - "ars": 241272.48, - "aud": 241272.48, - "btc": 241272.48, - "eth": 241272.48, - "eur": 241272.48, - "ltc": 241272.48, - "usd": 1794.5397, - }, - TokenRates: map[string]float32{ - "0x5e9997684d061269564f94e5d11ba6ce6fa9528c": 4.161734e+07, - "0x906710835d1ae85275eb770f06873340ca54274b": 4.161734e+07, - }, - Timestamp: time.Unix(1654732800, 0).UTC(), - } - if !reflect.DeepEqual(ticker, &wantTicker) { - t.Fatalf("UpdateHistoricalTickers(usd) 1st pass = %v, want %v", *ticker, wantTicker) - } + ticker, err = fiatRates.db.FiatRatesFindLastTicker("usd", "") + if err != nil || ticker == nil { + t.Fatalf("FiatRatesFindLastTicker failed with error: %v", err) + } + wantTicker := common.CurrencyRatesTicker{ + Rates: map[string]float32{ + "aed": 241272.48, + "ars": 241272.48, + "aud": 241272.48, + "btc": 241272.48, + "eth": 241272.48, + "eur": 241272.48, + "ltc": 241272.48, + "usd": 1794.5397, + }, + TokenRates: map[string]float32{ + "0x5e9997684d061269564f94e5d11ba6ce6fa9528c": 4.161734e+07, + "0x906710835d1ae85275eb770f06873340ca54274b": 4.161734e+07, + }, + Timestamp: time.Unix(1654732800, 0).UTC(), + } + if !reflect.DeepEqual(ticker, &wantTicker) { + t.Fatalf("UpdateHistoricalTickers(usd) 1st pass = %v, want %v", *ticker, wantTicker) + } - ticker, err = fiatRates.db.FiatRatesFindLastTicker("eur", "") - if err != nil || ticker == nil { - t.Fatalf("FiatRatesFindLastTicker failed with error: %v", err) - } - wantTicker = common.CurrencyRatesTicker{ - Rates: map[string]float32{ - "aed": 240402.97, - "ars": 240402.97, - "aud": 240402.97, - "btc": 240402.97, - "eth": 240402.97, - "eur": 240402.97, - "ltc": 240402.97, - }, - TokenRates: map[string]float32{ - "0x5e9997684d061269564f94e5d11ba6ce6fa9528c": 4.1464476e+07, - "0x906710835d1ae85275eb770f06873340ca54274b": 4.1464476e+07, - }, - Timestamp: time.Unix(1654819200, 0).UTC(), - } - if !reflect.DeepEqual(ticker, &wantTicker) { - t.Fatalf("UpdateHistoricalTickers(eur) 1st pass = %v, want %v", *ticker, wantTicker) - } + ticker, err = fiatRates.db.FiatRatesFindLastTicker("eur", "") + if err != nil || ticker == nil { + t.Fatalf("FiatRatesFindLastTicker failed with error: %v", err) + } + wantTicker = common.CurrencyRatesTicker{ + Rates: map[string]float32{ + "aed": 240402.97, + "ars": 240402.97, + "aud": 240402.97, + "btc": 240402.97, + "eth": 240402.97, + "eur": 240402.97, + "ltc": 240402.97, + }, + TokenRates: map[string]float32{ + "0x5e9997684d061269564f94e5d11ba6ce6fa9528c": 4.1464476e+07, + "0x906710835d1ae85275eb770f06873340ca54274b": 4.1464476e+07, + }, + Timestamp: time.Unix(1654819200, 0).UTC(), + } + if !reflect.DeepEqual(ticker, &wantTicker) { + t.Fatalf("UpdateHistoricalTickers(eur) 1st pass = %v, want %v", *ticker, wantTicker) + } - // update historical tickers for the second time - err = fiatRates.downloader.UpdateHistoricalTickers() - if err != nil { - t.Fatalf("UpdateHistoricalTickers 2nd pass failed with error: %v", err) - } - err = fiatRates.downloader.UpdateHistoricalTokenTickers() - if err != nil { - t.Fatalf("UpdateHistoricalTokenTickers 2nd pass failed with error: %v", err) - } - ticker, err = fiatRates.db.FiatRatesFindLastTicker("usd", "") - if err != nil || ticker == nil { - t.Fatalf("FiatRatesFindLastTicker failed with error: %v", err) - } - wantTicker = common.CurrencyRatesTicker{ - Rates: map[string]float32{ - "aed": 240402.97, - "ars": 240402.97, - "aud": 240402.97, - "btc": 240402.97, - "eth": 240402.97, - "eur": 240402.97, - "ltc": 240402.97, - "usd": 1788.4183, - }, - TokenRates: map[string]float32{ - "0x5e9997684d061269564f94e5d11ba6ce6fa9528c": 4.1464476e+07, - "0x906710835d1ae85275eb770f06873340ca54274b": 4.1464476e+07, - }, - Timestamp: time.Unix(1654819200, 0).UTC(), - } - if !reflect.DeepEqual(ticker, &wantTicker) { - t.Fatalf("UpdateHistoricalTickers(usd) 2nd pass = %v, want %v", *ticker, wantTicker) - } - ticker, err = fiatRates.db.FiatRatesFindLastTicker("eur", "") - if err != nil || ticker == nil { - t.Fatalf("FiatRatesFindLastTicker failed with error: %v", err) - } - if !reflect.DeepEqual(ticker, &wantTicker) { - t.Fatalf("UpdateHistoricalTickers(eur) 2nd pass = %v, want %v", *ticker, wantTicker) - } + // update historical tickers for the second time + err = fiatRates.downloader.UpdateHistoricalTickers() + if err != nil { + t.Fatalf("UpdateHistoricalTickers 2nd pass failed with error: %v", err) + } + err = fiatRates.downloader.UpdateHistoricalTokenTickers() + if err != nil { + t.Fatalf("UpdateHistoricalTokenTickers 2nd pass failed with error: %v", err) + } + ticker, err = fiatRates.db.FiatRatesFindLastTicker("usd", "") + if err != nil || ticker == nil { + t.Fatalf("FiatRatesFindLastTicker failed with error: %v", err) + } + wantTicker = common.CurrencyRatesTicker{ + Rates: map[string]float32{ + "aed": 240402.97, + "ars": 240402.97, + "aud": 240402.97, + "btc": 240402.97, + "eth": 240402.97, + "eur": 240402.97, + "ltc": 240402.97, + "usd": 1788.4183, + }, + TokenRates: map[string]float32{ + "0x5e9997684d061269564f94e5d11ba6ce6fa9528c": 4.1464476e+07, + "0x906710835d1ae85275eb770f06873340ca54274b": 4.1464476e+07, + }, + Timestamp: time.Unix(1654819200, 0).UTC(), + } + if !reflect.DeepEqual(ticker, &wantTicker) { + t.Fatalf("UpdateHistoricalTickers(usd) 2nd pass = %v, want %v", *ticker, wantTicker) + } + ticker, err = fiatRates.db.FiatRatesFindLastTicker("eur", "") + if err != nil || ticker == nil { + t.Fatalf("FiatRatesFindLastTicker failed with error: %v", err) + } + if !reflect.DeepEqual(ticker, &wantTicker) { + t.Fatalf("UpdateHistoricalTickers(eur) 2nd pass = %v, want %v", *ticker, wantTicker) } } diff --git a/server/internal.go b/server/internal.go index df04bc64..17d07a45 100644 --- a/server/internal.go +++ b/server/internal.go @@ -14,6 +14,7 @@ import ( "github.com/trezor/blockbook/bchain" "github.com/trezor/blockbook/common" "github.com/trezor/blockbook/db" + "github.com/trezor/blockbook/fiat" ) // InternalServer is handle to internal http server @@ -31,8 +32,8 @@ type InternalServer struct { } // NewInternalServer creates new internal http interface to blockbook and returns its handle -func NewInternalServer(binding, certFiles string, db *db.RocksDB, chain bchain.BlockChain, mempool bchain.Mempool, txCache *db.TxCache, metrics *common.Metrics, is *common.InternalState) (*InternalServer, error) { - api, err := api.NewWorker(db, chain, mempool, txCache, metrics, is) +func NewInternalServer(binding, certFiles string, db *db.RocksDB, chain bchain.BlockChain, mempool bchain.Mempool, txCache *db.TxCache, metrics *common.Metrics, is *common.InternalState, fiatRates *fiat.FiatRates) (*InternalServer, error) { + api, err := api.NewWorker(db, chain, mempool, txCache, metrics, is, fiatRates) if err != nil { return nil, err } diff --git a/server/public.go b/server/public.go index 86d90251..45026053 100644 --- a/server/public.go +++ b/server/public.go @@ -25,6 +25,7 @@ import ( "github.com/trezor/blockbook/bchain" "github.com/trezor/blockbook/common" "github.com/trezor/blockbook/db" + "github.com/trezor/blockbook/fiat" ) const txsOnPage = 25 @@ -57,23 +58,24 @@ type PublicServer struct { explorerURL string internalExplorer bool is *common.InternalState + fiatRates *fiat.FiatRates } // NewPublicServer creates new public server http interface to blockbook and returns its handle // only basic functionality is mapped, to map all functions, call -func NewPublicServer(binding string, certFiles string, db *db.RocksDB, chain bchain.BlockChain, mempool bchain.Mempool, txCache *db.TxCache, explorerURL string, metrics *common.Metrics, is *common.InternalState, debugMode bool) (*PublicServer, error) { +func NewPublicServer(binding string, certFiles string, db *db.RocksDB, chain bchain.BlockChain, mempool bchain.Mempool, txCache *db.TxCache, explorerURL string, metrics *common.Metrics, is *common.InternalState, fiatRates *fiat.FiatRates, debugMode bool) (*PublicServer, error) { - api, err := api.NewWorker(db, chain, mempool, txCache, metrics, is) + api, err := api.NewWorker(db, chain, mempool, txCache, metrics, is, fiatRates) if err != nil { return nil, err } - socketio, err := NewSocketIoServer(db, chain, mempool, txCache, metrics, is) + socketio, err := NewSocketIoServer(db, chain, mempool, txCache, metrics, is, fiatRates) if err != nil { return nil, err } - websocket, err := NewWebsocketServer(db, chain, mempool, txCache, metrics, is) + websocket, err := NewWebsocketServer(db, chain, mempool, txCache, metrics, is, fiatRates) if err != nil { return nil, err } @@ -104,6 +106,7 @@ func NewPublicServer(binding string, certFiles string, db *db.RocksDB, chain bch explorerURL: explorerURL, internalExplorer: explorerURL == "", is: is, + fiatRates: fiatRates, } s.htmlTemplates.newTemplateData = s.newTemplateData s.htmlTemplates.newTemplateDataWithError = s.newTemplateDataWithError @@ -377,10 +380,10 @@ func (s *PublicServer) newTemplateData(r *http.Request) *TemplateData { secondary = "usd" } } - ticker := s.is.GetCurrentTicker(secondary, "") + ticker := s.fiatRates.GetCurrentTicker(secondary, "") if ticker == nil && secondary != "usd" { secondary = "usd" - ticker = s.is.GetCurrentTicker(secondary, "") + ticker = s.fiatRates.GetCurrentTicker(secondary, "") } if ticker != nil { t.SecondaryCoin = strings.ToUpper(secondary) diff --git a/server/public_test.go b/server/public_test.go index 3f862e34..2d04512b 100644 --- a/server/public_test.go +++ b/server/public_test.go @@ -24,6 +24,7 @@ import ( "github.com/trezor/blockbook/bchain/coins/btc" "github.com/trezor/blockbook/common" "github.com/trezor/blockbook/db" + "github.com/trezor/blockbook/fiat" "github.com/trezor/blockbook/tests/dbtestdata" ) @@ -121,7 +122,7 @@ func setupPublicHTTPServer(parser bchain.BlockChainParser, chain bchain.BlockCha } // s.Run is never called, binding can be to any port - s, err := NewPublicServer("localhost:12345", "", d, chain, mempool, txCache, "", metrics, is, false) + s, err := NewPublicServer("localhost:12345", "", d, chain, mempool, txCache, "", metrics, is, &fiat.FiatRates{}, false) if err != nil { t.Fatal(err) } diff --git a/server/socketio.go b/server/socketio.go index ff8f19df..cd1e6a77 100644 --- a/server/socketio.go +++ b/server/socketio.go @@ -17,6 +17,7 @@ import ( "github.com/trezor/blockbook/bchain" "github.com/trezor/blockbook/common" "github.com/trezor/blockbook/db" + "github.com/trezor/blockbook/fiat" ) // SocketIoServer is handle to SocketIoServer @@ -33,8 +34,8 @@ type SocketIoServer struct { } // NewSocketIoServer creates new SocketIo interface to blockbook and returns its handle -func NewSocketIoServer(db *db.RocksDB, chain bchain.BlockChain, mempool bchain.Mempool, txCache *db.TxCache, metrics *common.Metrics, is *common.InternalState) (*SocketIoServer, error) { - api, err := api.NewWorker(db, chain, mempool, txCache, metrics, is) +func NewSocketIoServer(db *db.RocksDB, chain bchain.BlockChain, mempool bchain.Mempool, txCache *db.TxCache, metrics *common.Metrics, is *common.InternalState, fiatRates *fiat.FiatRates) (*SocketIoServer, error) { + api, err := api.NewWorker(db, chain, mempool, txCache, metrics, is, fiatRates) if err != nil { return nil, err } diff --git a/server/websocket.go b/server/websocket.go index 38f34297..781b519c 100644 --- a/server/websocket.go +++ b/server/websocket.go @@ -18,6 +18,7 @@ import ( "github.com/trezor/blockbook/bchain" "github.com/trezor/blockbook/common" "github.com/trezor/blockbook/db" + "github.com/trezor/blockbook/fiat" ) const upgradeFailed = "Upgrade failed: " @@ -70,8 +71,8 @@ type WebsocketServer struct { } // NewWebsocketServer creates new websocket interface to blockbook and returns its handle -func NewWebsocketServer(db *db.RocksDB, chain bchain.BlockChain, mempool bchain.Mempool, txCache *db.TxCache, metrics *common.Metrics, is *common.InternalState) (*WebsocketServer, error) { - api, err := api.NewWorker(db, chain, mempool, txCache, metrics, is) +func NewWebsocketServer(db *db.RocksDB, chain bchain.BlockChain, mempool bchain.Mempool, txCache *db.TxCache, metrics *common.Metrics, is *common.InternalState, fiatRates *fiat.FiatRates) (*WebsocketServer, error) { + api, err := api.NewWorker(db, chain, mempool, txCache, metrics, is, fiatRates) if err != nil { return nil, err }