Refactor fiat rates handler
This commit is contained in:
parent
ad50758984
commit
d856618607
@ -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,
|
||||
|
||||
@ -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]
|
||||
|
||||
35
blockbook.go
35
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 {
|
||||
|
||||
@ -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 (
|
||||
|
||||
@ -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
|
||||
|
||||
66
db/fiat.go
66
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)
|
||||
}
|
||||
|
||||
@ -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) {
|
||||
|
||||
@ -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
|
||||
}
|
||||
|
||||
@ -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 {
|
||||
|
||||
@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
@ -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
|
||||
}
|
||||
|
||||
@ -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)
|
||||
|
||||
@ -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)
|
||||
}
|
||||
|
||||
@ -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
|
||||
}
|
||||
|
||||
@ -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
|
||||
}
|
||||
|
||||
Loading…
Reference in New Issue
Block a user