Refactor fiat rates handler

This commit is contained in:
Martin Boehm 2023-04-18 01:32:37 +02:00
parent ad50758984
commit d856618607
15 changed files with 426 additions and 305 deletions

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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