From 715567d7f3b2473d781dd15955b4b7faa1ecc8b3 Mon Sep 17 00:00:00 2001 From: Martin Boehm Date: Fri, 18 Nov 2022 00:19:43 +0100 Subject: [PATCH] Store current ticker in the internal state --- api/worker.go | 17 ++-- blockbook.go | 2 +- common/currencyrateticker.go | 74 ++++++++++++++++++ common/currencyrateticker_test.go | 62 +++++++++++++++ common/internalstate.go | 28 +++++-- db/fiat.go | 124 +++++------------------------- db/fiat_test.go | 86 +++------------------ fiat/coingecko.go | 19 ++--- fiat/fiat_rates.go | 29 +++---- fiat/fiat_rates_test.go | 8 +- server/public.go | 2 +- server/public_test.go | 2 +- server/websocket.go | 4 +- 13 files changed, 236 insertions(+), 221 deletions(-) create mode 100644 common/currencyrateticker.go create mode 100644 common/currencyrateticker_test.go diff --git a/api/worker.go b/api/worker.go index 864c6314..4eb0435a 100644 --- a/api/worker.go +++ b/api/worker.go @@ -1633,7 +1633,7 @@ func removeEmpty(stringSlice []string) []string { } // getFiatRatesResult checks if CurrencyRatesTicker contains all necessary data and returns formatted result -func (w *Worker) getFiatRatesResult(currencies []string, ticker *db.CurrencyRatesTicker, token string) (*FiatTicker, error) { +func (w *Worker) getFiatRatesResult(currencies []string, ticker *common.CurrencyRatesTicker, token string) (*FiatTicker, error) { if token != "" { if len(currencies) != 1 { return nil, NewAPIError("Rates for token only for a single currency", true) @@ -1672,7 +1672,7 @@ func (w *Worker) getFiatRatesResult(currencies []string, ticker *db.CurrencyRate // GetFiatRatesForBlockID returns fiat rates for block height or block hash func (w *Worker) GetFiatRatesForBlockID(blockID string, currencies []string, token string) (*FiatTicker, error) { - var ticker *db.CurrencyRatesTicker + var ticker *common.CurrencyRatesTicker bi, err := w.getBlockInfoFromBlockID(blockID) if err != nil { if err == bchain.ErrBlockNotFound { @@ -1707,13 +1707,14 @@ func (w *Worker) GetCurrentFiatRates(currencies []string, token string) (*FiatTi if len(currencies) == 1 { vsCurrency = currencies[0] } - ticker, err := w.db.FiatRatesGetCurrentTicker(vsCurrency, token) - if ticker == nil || err != nil { + ticker := w.is.GetCurrentTicker(vsCurrency, token) + var err error + if ticker == nil { ticker, err = w.db.FiatRatesFindLastTicker(vsCurrency, token) if err != nil { return nil, NewAPIError(fmt.Sprintf("Error finding ticker: %v", err), false) } else if ticker == nil { - return nil, NewAPIError(fmt.Sprintf("No tickers found!"), true) + return nil, NewAPIError("No tickers found!", true) } } result, err := w.getFiatRatesResult(currencies, ticker, token) @@ -2101,6 +2102,10 @@ func (w *Worker) GetSystemInfo(internal bool) (*SystemInfo, error) { columnStats = w.is.GetAllDBColumnStats() internalDBSize = w.is.DBSizeTotal() } + var currentFiatRatesTime time.Time + if w.is.CurrentTicker != nil { + currentFiatRatesTime = w.is.CurrentTicker.Timestamp + } blockbookInfo := &BlockbookInfo{ Coin: w.is.Coin, Host: w.is.Host, @@ -2118,7 +2123,7 @@ func (w *Worker) GetSystemInfo(internal bool) (*SystemInfo, error) { Decimals: w.chainParser.AmountDecimals(), HasFiatRates: w.is.HasFiatRates, HasTokenFiatRates: w.is.HasTokenFiatRates, - CurrentFiatRatesTime: nonZeroTime(w.is.CurrentFiatRatesTime), + CurrentFiatRatesTime: nonZeroTime(currentFiatRatesTime), HistoricalFiatRatesTime: nonZeroTime(w.is.HistoricalFiatRatesTime), HistoricalTokenFiatRatesTime: nonZeroTime(w.is.HistoricalTokenFiatRatesTime), DbSize: w.db.DatabaseSizeOnDisk(), diff --git a/blockbook.go b/blockbook.go index 663fa509..b95c26da 100644 --- a/blockbook.go +++ b/blockbook.go @@ -527,7 +527,7 @@ func onNewBlockHash(hash string, height uint32) { } } -func onNewFiatRatesTicker(ticker *db.CurrencyRatesTicker) { +func onNewFiatRatesTicker(ticker *common.CurrencyRatesTicker) { defer func() { if r := recover(); r != nil { glog.Error("onNewFiatRatesTicker recovered from panic: ", r) diff --git a/common/currencyrateticker.go b/common/currencyrateticker.go new file mode 100644 index 00000000..d6bcce5f --- /dev/null +++ b/common/currencyrateticker.go @@ -0,0 +1,74 @@ +package common + +import "time" + +// 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 +} + +// Convert converts value in base currency to toCurrency +func (t *CurrencyRatesTicker) Convert(baseValue float64, toCurrency string) float64 { + rate, found := t.Rates[toCurrency] + if !found { + return 0 + } + return baseValue * float64(rate) +} + +// ConvertTokenToBase converts token value to base currency +func (t *CurrencyRatesTicker) ConvertTokenToBase(value float64, token string) float64 { + if t.TokenRates != nil { + rate, found := t.TokenRates[token] + if found { + return value * float64(rate) + } + } + return 0 +} + +// ConvertTokenToBase converts token value to toCurrency currency +func (t *CurrencyRatesTicker) ConvertToken(value float64, token string, toCurrency string) float64 { + baseValue := t.ConvertTokenToBase(value, token) + if baseValue > 0 { + return t.Convert(baseValue, toCurrency) + } + return 0 +} + +// TokenRateInCurrency return token rate in toCurrency currency +func (t *CurrencyRatesTicker) TokenRateInCurrency(token string, toCurrency string) float32 { + if t.TokenRates != nil { + rate, found := t.TokenRates[token] + if found { + baseRate, found := t.Rates[toCurrency] + if found { + return baseRate * rate + } + } + } + return 0 +} + +// IsSuitableTicker checks if the ticker can provide data for given vsCurrency or token +func IsSuitableTicker(ticker *CurrencyRatesTicker, vsCurrency string, token string) bool { + if vsCurrency != "" { + if ticker.Rates == nil { + return false + } + if _, found := ticker.Rates[vsCurrency]; !found { + return false + } + } + if token != "" { + if ticker.TokenRates == nil { + return false + } + if _, found := ticker.TokenRates[token]; !found { + return false + } + } + return true +} diff --git a/common/currencyrateticker_test.go b/common/currencyrateticker_test.go new file mode 100644 index 00000000..70ddf141 --- /dev/null +++ b/common/currencyrateticker_test.go @@ -0,0 +1,62 @@ +package common + +import ( + "testing" +) + +func TestCurrencyRatesTicker_ConvertToken(t *testing.T) { + ticker := &CurrencyRatesTicker{ + Rates: map[string]float32{ + "usd": 2129.987654321, + "eur": 1332.12345678, + }, + TokenRates: map[string]float32{ + "0x82df128257a7d7556262e1ab7f1f639d9775b85e": 0.4092341123, + "0x6b175474e89094c44da98b954eedeac495271d0f": 12.32323232323232, + "0xdac17f958d2ee523a2206206994597c13d831ec7": 1332421341235.51234, + }, + } + tests := []struct { + name string + value float64 + token string + toCurrency string + want float64 + }{ + { + name: "usd 0x82df128257a7d7556262e1ab7f1f639d9775b85e", + value: 10, + token: "0x82df128257a7d7556262e1ab7f1f639d9775b85e", + toCurrency: "usd", + want: 8716.635514874506, + }, + { + name: "eur 0xdac17f958d2ee523a2206206994597c13d831ec7", + value: 23.123, + token: "0xdac17f958d2ee523a2206206994597c13d831ec7", + toCurrency: "eur", + want: 4.104216071804417e+16, + }, + { + name: "eur 0xdac17f958d2ee523a2206206994597c13d831ec8", + value: 23.123, + token: "0xdac17f958d2ee523a2206206994597c13d831ec8", + toCurrency: "eur", + want: 0, + }, + { + name: "eur 0xdac17f958d2ee523a2206206994597c13d831ec7", + value: 23.123, + token: "0xdac17f958d2ee523a2206206994597c13d831ec7", + toCurrency: "czk", + want: 0, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if got := ticker.ConvertToken(tt.value, tt.token, tt.toCurrency); got != tt.want { + t.Errorf("CurrencyRatesTicker.ConvertToken() = %v, want %v", got, tt.want) + } + }) + } +} diff --git a/common/internalstate.go b/common/internalstate.go index 6be529ec..701506d6 100644 --- a/common/internalstate.go +++ b/common/internalstate.go @@ -78,11 +78,11 @@ type InternalState struct { UtxoChecked bool `json:"utxoChecked"` // store only the historical state, not the current state of the fiat rates in DB - HasFiatRates bool `json:"-"` - HasTokenFiatRates bool `json:"-"` - CurrentFiatRatesTime time.Time `json:"-"` - HistoricalFiatRatesTime time.Time `json:"historicalFiatRatesTime"` - HistoricalTokenFiatRatesTime time.Time `json:"historicalTokenFiatRatesTime"` + HasFiatRates bool `json:"-"` + HasTokenFiatRates bool `json:"-"` + HistoricalFiatRatesTime time.Time `json:"historicalFiatRatesTime"` + HistoricalTokenFiatRatesTime time.Time `json:"historicalTokenFiatRatesTime"` + CurrentTicker *CurrencyRatesTicker `json:"currentTicker"` BackendInfo BackendInfo `json:"-"` } @@ -268,6 +268,24 @@ func (is *InternalState) Pack() ([]byte, error) { return json.Marshal(is) } +// GetCurrentTicker returns current ticker +func (is *InternalState) GetCurrentTicker(vsCurrency string, token string) *CurrencyRatesTicker { + is.mux.Lock() + currentTicker := is.CurrentTicker + is.mux.Unlock() + if currentTicker != nil && IsSuitableTicker(currentTicker, vsCurrency, token) { + return currentTicker + } + return nil +} + +// SetCurrentTicker sets current ticker +func (is *InternalState) SetCurrentTicker(t *CurrencyRatesTicker) { + is.mux.Lock() + defer is.mux.Unlock() + is.CurrentTicker = t +} + // UnpackInternalState unmarshals internal state from json func UnpackInternalState(buf []byte) (*InternalState, error) { var is InternalState diff --git a/db/fiat.go b/db/fiat.go index 5f9a5284..f4392f2d 100644 --- a/db/fiat.go +++ b/db/fiat.go @@ -10,64 +10,14 @@ import ( "github.com/golang/glog" "github.com/juju/errors" "github.com/linxGnu/grocksdb" + "github.com/trezor/blockbook/common" ) // FiatRatesTimeFormat is a format string for storing FiatRates timestamps in rocksdb const FiatRatesTimeFormat = "20060102150405" // YYYYMMDDhhmmss -var tickersMux sync.Mutex -var lastTickerInDB *CurrencyRatesTicker -var currentTicker *CurrencyRatesTicker - -// CurrencyRatesTicker contains coin ticker data fetched from API -type CurrencyRatesTicker struct { - Timestamp time.Time // return as unix timestamp in API - Rates map[string]float32 // rates of the base currency against a list of vs currencies - TokenRates map[string]float32 // rates of the tokens (identified by the address of the contract) against the base currency -} - -// Convert converts value in base currency to toCurrency -func (t *CurrencyRatesTicker) Convert(baseValue float64, toCurrency string) float64 { - rate, found := t.Rates[toCurrency] - if !found { - return 0 - } - return baseValue * float64(rate) -} - -// ConvertTokenToBase converts token value to base currency -func (t *CurrencyRatesTicker) ConvertTokenToBase(value float64, token string) float64 { - if t.TokenRates != nil { - rate, found := t.TokenRates[token] - if found { - return value * float64(rate) - } - } - return 0 -} - -// ConvertTokenToBase converts token value to toCurrency currency -func (t *CurrencyRatesTicker) ConvertToken(value float64, token string, toCurrency string) float64 { - baseValue := t.ConvertTokenToBase(value, token) - if baseValue > 0 { - return t.Convert(baseValue, toCurrency) - } - return 0 -} - -// TokenRateInCurrency return token rate in toCurrency currency -func (t *CurrencyRatesTicker) TokenRateInCurrency(token string, toCurrency string) float32 { - if t.TokenRates != nil { - rate, found := t.TokenRates[token] - if found { - baseRate, found := t.Rates[toCurrency] - if found { - return baseRate * rate - } - } - } - return 0 -} +var lastTickerInDB *common.CurrencyRatesTicker +var lastTickerInDBMux sync.Mutex func packTimestamp(t *time.Time) []byte { return []byte(t.UTC().Format(FiatRatesTimeFormat)) @@ -82,7 +32,7 @@ func unpackFloat32(buf []byte) (float32, int) { return math.Float32frombits(binary.BigEndian.Uint32(buf)), 4 } -func packCurrencyRatesTicker(ticker *CurrencyRatesTicker) []byte { +func packCurrencyRatesTicker(ticker *common.CurrencyRatesTicker) []byte { buf := make([]byte, 0, 32) varBuf := make([]byte, vlq.MaxLen64) l := packVaruint(uint(len(ticker.Rates)), varBuf) @@ -102,9 +52,9 @@ func packCurrencyRatesTicker(ticker *CurrencyRatesTicker) []byte { return buf } -func unpackCurrencyRatesTicker(buf []byte) (*CurrencyRatesTicker, error) { +func unpackCurrencyRatesTicker(buf []byte) (*common.CurrencyRatesTicker, error) { var ( - ticker CurrencyRatesTicker + ticker common.CurrencyRatesTicker s string l int len uint @@ -152,7 +102,7 @@ func FiatRatesConvertDate(date string) (*time.Time, error) { } // FiatRatesStoreTicker stores ticker data at the specified time -func (d *RocksDB) FiatRatesStoreTicker(wb *grocksdb.WriteBatch, ticker *CurrencyRatesTicker) error { +func (d *RocksDB) FiatRatesStoreTicker(wb *grocksdb.WriteBatch, ticker *common.CurrencyRatesTicker) error { if len(ticker.Rates) == 0 { return errors.New("Error storing ticker: empty rates") } @@ -160,27 +110,7 @@ func (d *RocksDB) FiatRatesStoreTicker(wb *grocksdb.WriteBatch, ticker *Currency return nil } -func isSuitableTicker(ticker *CurrencyRatesTicker, vsCurrency string, token string) bool { - if vsCurrency != "" { - if ticker.Rates == nil { - return false - } - if _, found := ticker.Rates[vsCurrency]; !found { - return false - } - } - if token != "" { - if ticker.TokenRates == nil { - return false - } - if _, found := ticker.TokenRates[token]; !found { - return false - } - } - return true -} - -func getTickerFromIterator(it *grocksdb.Iterator, vsCurrency string, token string) (*CurrencyRatesTicker, error) { +func getTickerFromIterator(it *grocksdb.Iterator, vsCurrency string, token string) (*common.CurrencyRatesTicker, error) { timeObj, err := time.Parse(FiatRatesTimeFormat, string(it.Key().Data())) if err != nil { return nil, err @@ -189,7 +119,7 @@ func getTickerFromIterator(it *grocksdb.Iterator, vsCurrency string, token strin if err != nil { return nil, err } - if !isSuitableTicker(ticker, vsCurrency, token) { + if !common.IsSuitableTicker(ticker, vsCurrency, token) { return nil, nil } ticker.Timestamp = timeObj.UTC() @@ -197,7 +127,7 @@ func getTickerFromIterator(it *grocksdb.Iterator, vsCurrency string, token strin } // FiatRatesGetTicker gets FiatRates ticker at the specified timestamp if it exist -func (d *RocksDB) FiatRatesGetTicker(tickerTime *time.Time) (*CurrencyRatesTicker, error) { +func (d *RocksDB) FiatRatesGetTicker(tickerTime *time.Time) (*common.CurrencyRatesTicker, error) { tickerTimeFormatted := tickerTime.UTC().Format(FiatRatesTimeFormat) val, err := d.db.GetCF(d.ro, d.cfh[cfFiatRates], []byte(tickerTimeFormatted)) if err != nil { @@ -217,21 +147,22 @@ func (d *RocksDB) FiatRatesGetTicker(tickerTime *time.Time) (*CurrencyRatesTicke } // 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) (*CurrencyRatesTicker, error) { - tickersMux.Lock() +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) || (lastTickerInDB != nil && tickerTime.After(lastTickerInDB.Timestamp)) { + if !tickerTime.Before(currentTicker.Timestamp) || (dbTicker != nil && tickerTime.After(dbTicker.Timestamp)) { f := true if token != "" && currentTicker.TokenRates != nil { _, f = currentTicker.TokenRates[token] } if f { - tickersMux.Unlock() return currentTicker, nil } } } - tickersMux.Unlock() tickerTimeFormatted := tickerTime.UTC().Format(FiatRatesTimeFormat) it := d.db.NewIteratorCF(d.ro, d.cfh[cfFiatRates]) @@ -251,7 +182,7 @@ func (d *RocksDB) FiatRatesFindTicker(tickerTime *time.Time, vsCurrency string, } // FiatRatesFindLastTicker gets the last FiatRates record, of the base currency, vsCurrency or the token if specified -func (d *RocksDB) FiatRatesFindLastTicker(vsCurrency string, token string) (*CurrencyRatesTicker, error) { +func (d *RocksDB) FiatRatesFindLastTicker(vsCurrency string, token string) (*common.CurrencyRatesTicker, error) { it := d.db.NewIteratorCF(d.ro, d.cfh[cfFiatRates]) defer it.Close() @@ -264,29 +195,12 @@ func (d *RocksDB) FiatRatesFindLastTicker(vsCurrency string, token string) (*Cur if ticker != nil { // if without filter, store the ticker for later use if vsCurrency == "" && token == "" { - tickersMux.Lock() + lastTickerInDBMux.Lock() lastTickerInDB = ticker - tickersMux.Unlock() + lastTickerInDBMux.Unlock() } return ticker, nil } } return nil, nil } - -// FiatRatesGetCurrentTicker returns current ticker -func (d *RocksDB) FiatRatesGetCurrentTicker(vsCurrency string, token string) (*CurrencyRatesTicker, error) { - tickersMux.Lock() - defer tickersMux.Unlock() - if currentTicker != nil && isSuitableTicker(currentTicker, vsCurrency, token) { - return currentTicker, nil - } - return nil, nil -} - -// FiatRatesCurrentTicker sets current ticker -func (d *RocksDB) FiatRatesSetCurrentTicker(t *CurrencyRatesTicker) { - tickersMux.Lock() - defer tickersMux.Unlock() - currentTicker = t -} diff --git a/db/fiat_test.go b/db/fiat_test.go index adb98563..1e5f55fb 100644 --- a/db/fiat_test.go +++ b/db/fiat_test.go @@ -8,6 +8,7 @@ import ( "time" "github.com/linxGnu/grocksdb" + "github.com/trezor/blockbook/common" ) func TestRocksTickers(t *testing.T) { @@ -37,7 +38,7 @@ func TestRocksTickers(t *testing.T) { futureKey, _ := time.Parse(FiatRatesTimeFormat, "20190630000000") ts1, _ := time.Parse(FiatRatesTimeFormat, "20190628000000") - ticker1 := &CurrencyRatesTicker{ + ticker1 := &common.CurrencyRatesTicker{ Timestamp: ts1, Rates: map[string]float32{ "usd": 20000, @@ -49,7 +50,7 @@ func TestRocksTickers(t *testing.T) { } ts2, _ := time.Parse(FiatRatesTimeFormat, "20190629000000") - ticker2 := &CurrencyRatesTicker{ + ticker2 := &common.CurrencyRatesTicker{ Timestamp: ts2, Rates: map[string]float32{ "usd": 30000, @@ -157,15 +158,13 @@ func TestRocksTickers(t *testing.T) { t.Errorf("Ticker %v found unexpectedly for aud vsCurrency", ticker) } - ticker, err = d.FiatRatesGetCurrentTicker("", "") - if err != nil { - t.Errorf("TestRocksTickers err: %+v", err) - } else if ticker != nil { + ticker = d.is.GetCurrentTicker("", "") + if ticker != nil { t.Errorf("FiatRatesGetCurrentTicker %v found unexpectedly", ticker) } - d.FiatRatesSetCurrentTicker(ticker1) - ticker, err = d.FiatRatesGetCurrentTicker("", "") + d.is.SetCurrentTicker(ticker1) + ticker = d.is.GetCurrentTicker("", "") if err != nil { t.Errorf("TestRocksTickers err: %+v", err) } else if ticker == nil { @@ -174,7 +173,7 @@ func TestRocksTickers(t *testing.T) { t.Errorf("Incorrect ticker found. Expected: %v, found: %+v", ticker1.Timestamp, ticker.Timestamp) } - d.FiatRatesSetCurrentTicker(nil) + d.is.SetCurrentTicker(nil) } func Test_packUnpackCurrencyRatesTicker(t *testing.T) { @@ -182,15 +181,15 @@ func Test_packUnpackCurrencyRatesTicker(t *testing.T) { } tests := []struct { name string - data CurrencyRatesTicker + data common.CurrencyRatesTicker }{ { name: "empty", - data: CurrencyRatesTicker{}, + data: common.CurrencyRatesTicker{}, }, { name: "rates", - data: CurrencyRatesTicker{ + data: common.CurrencyRatesTicker{ Rates: map[string]float32{ "usd": 2129.2341123, "eur": 1332.51234, @@ -199,7 +198,7 @@ func Test_packUnpackCurrencyRatesTicker(t *testing.T) { }, { name: "rates&tokenrates", - data: CurrencyRatesTicker{ + data: common.CurrencyRatesTicker{ Rates: map[string]float32{ "usd": 322129.987654321, "eur": 291332.12345678, @@ -226,64 +225,3 @@ func Test_packUnpackCurrencyRatesTicker(t *testing.T) { }) } } - -func TestCurrencyRatesTicker_ConvertToken(t *testing.T) { - ticker := &CurrencyRatesTicker{ - Rates: map[string]float32{ - "usd": 2129.987654321, - "eur": 1332.12345678, - }, - TokenRates: map[string]float32{ - "0x82df128257a7d7556262e1ab7f1f639d9775b85e": 0.4092341123, - "0x6b175474e89094c44da98b954eedeac495271d0f": 12.32323232323232, - "0xdac17f958d2ee523a2206206994597c13d831ec7": 1332421341235.51234, - }, - } - type args struct { - baseValue float64 - toCurrency string - } - tests := []struct { - name string - value float64 - token string - toCurrency string - want float64 - }{ - { - name: "usd 0x82df128257a7d7556262e1ab7f1f639d9775b85e", - value: 10, - token: "0x82df128257a7d7556262e1ab7f1f639d9775b85e", - toCurrency: "usd", - want: 8716.635514874506, - }, - { - name: "eur 0xdac17f958d2ee523a2206206994597c13d831ec7", - value: 23.123, - token: "0xdac17f958d2ee523a2206206994597c13d831ec7", - toCurrency: "eur", - want: 4.104216071804417e+16, - }, - { - name: "eur 0xdac17f958d2ee523a2206206994597c13d831ec8", - value: 23.123, - token: "0xdac17f958d2ee523a2206206994597c13d831ec8", - toCurrency: "eur", - want: 0, - }, - { - name: "eur 0xdac17f958d2ee523a2206206994597c13d831ec7", - value: 23.123, - token: "0xdac17f958d2ee523a2206206994597c13d831ec7", - toCurrency: "czk", - want: 0, - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - if got := ticker.ConvertToken(tt.value, tt.token, tt.toCurrency); got != tt.want { - t.Errorf("CurrencyRatesTicker.ConvertToken() = %v, want %v", got, tt.want) - } - }) - } -} diff --git a/fiat/coingecko.go b/fiat/coingecko.go index 5b5789a4..a1f4c47b 100644 --- a/fiat/coingecko.go +++ b/fiat/coingecko.go @@ -12,6 +12,7 @@ import ( "github.com/golang/glog" "github.com/linxGnu/grocksdb" + "github.com/trezor/blockbook/common" "github.com/trezor/blockbook/db" ) @@ -223,11 +224,11 @@ func (cg *Coingecko) platformIds() error { return nil } -func (cg *Coingecko) CurrentTickers() (*db.CurrencyRatesTicker, error) { +func (cg *Coingecko) CurrentTickers() (*common.CurrencyRatesTicker, error) { cg.updatingCurrent = true defer func() { cg.updatingCurrent = false }() - var newTickers = db.CurrencyRatesTicker{} + var newTickers = common.CurrencyRatesTicker{} if vsCurrencies == nil { vs, err := cg.simpleSupportedVSCurrencies() @@ -275,7 +276,7 @@ func (cg *Coingecko) CurrentTickers() (*db.CurrencyRatesTicker, error) { return &newTickers, nil } -func (cg *Coingecko) getHistoricalTicker(tickersToUpdate map[uint]*db.CurrencyRatesTicker, coinId string, vsCurrency string, token string) (bool, error) { +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 { return false, err @@ -306,7 +307,7 @@ func (cg *Coingecko) getHistoricalTicker(tickersToUpdate map[uint]*db.CurrencyRa rate := float32(p[1]) if timestamp%(24*3600) == 0 && timestamp != 0 && rate != 0 { // process only tickers for the whole day with non 0 value var found bool - var ticker *db.CurrencyRatesTicker + var ticker *common.CurrencyRatesTicker if ticker, found = tickersToUpdate[timestamp]; !found { u := time.Unix(int64(timestamp), 0).UTC() ticker, err = cg.db.FiatRatesGetTicker(&u) @@ -321,7 +322,7 @@ func (cg *Coingecko) getHistoricalTicker(tickersToUpdate map[uint]*db.CurrencyRa } continue } - ticker = &db.CurrencyRatesTicker{ + ticker = &common.CurrencyRatesTicker{ Timestamp: u, Rates: make(map[string]float32), } @@ -341,7 +342,7 @@ func (cg *Coingecko) getHistoricalTicker(tickersToUpdate map[uint]*db.CurrencyRa return true, nil } -func (cg *Coingecko) storeTickers(tickersToUpdate map[uint]*db.CurrencyRatesTicker) error { +func (cg *Coingecko) storeTickers(tickersToUpdate map[uint]*common.CurrencyRatesTicker) error { if len(tickersToUpdate) > 0 { wb := grocksdb.NewWriteBatch() defer wb.Destroy() @@ -368,7 +369,7 @@ func (cg *Coingecko) throttleHistoricalDownload() { // UpdateHistoricalTickers gets historical tickers for the main crypto currency func (cg *Coingecko) UpdateHistoricalTickers() error { - tickersToUpdate := make(map[uint]*db.CurrencyRatesTicker) + tickersToUpdate := make(map[uint]*common.CurrencyRatesTicker) // reload vs_currencies vs, err := cg.simpleSupportedVSCurrencies() @@ -401,7 +402,7 @@ func (cg *Coingecko) UpdateHistoricalTokenTickers() error { } cg.updatingTokens = true defer func() { cg.updatingTokens = false }() - tickersToUpdate := make(map[uint]*db.CurrencyRatesTicker) + tickersToUpdate := make(map[uint]*common.CurrencyRatesTicker) if cg.platformIdentifier != "" && cg.platformVsCurrency != "" { // reload platform ids @@ -425,7 +426,7 @@ func (cg *Coingecko) UpdateHistoricalTokenTickers() error { if err != nil { return err } - tickersToUpdate = make(map[uint]*db.CurrencyRatesTicker) + tickersToUpdate = make(map[uint]*common.CurrencyRatesTicker) glog.Infof("Coingecko updated %d of %d token tickers", count, len(platformIds)) } if req { diff --git a/fiat/fiat_rates.go b/fiat/fiat_rates.go index a920cb19..d78db9b8 100644 --- a/fiat/fiat_rates.go +++ b/fiat/fiat_rates.go @@ -8,15 +8,16 @@ import ( "time" "github.com/golang/glog" + "github.com/trezor/blockbook/common" "github.com/trezor/blockbook/db" ) // OnNewFiatRatesTicker is used to send notification about a new FiatRates ticker -type OnNewFiatRatesTicker func(ticker *db.CurrencyRatesTicker) +type OnNewFiatRatesTicker func(ticker *common.CurrencyRatesTicker) // RatesDownloaderInterface provides method signatures for specific fiat rates downloaders type RatesDownloaderInterface interface { - CurrentTickers() (*db.CurrencyRatesTicker, error) + CurrentTickers() (*common.CurrencyRatesTicker, error) UpdateHistoricalTickers() error UpdateHistoricalTokenTickers() error } @@ -80,17 +81,25 @@ func NewFiatRatesDownloader(db *db.RocksDB, apiType string, params string, callb func (rd *RatesDownloader) Run() error { var lastHistoricalTickers time.Time is := rd.db.GetInternalState() - + tickerFromIs := is.GetCurrentTicker("", "") + firstRun := true for { + unix := time.Now().Unix() + next := unix + rd.periodSeconds + next -= next % rd.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) { + // 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 { glog.Error("FiatRatesDownloader: CurrentTickers error ", err) } else { - rd.db.FiatRatesSetCurrentTicker(tickers) + is.SetCurrentTicker(tickers) glog.Info("FiatRatesDownloader: CurrentTickers updated") - if is != nil { - is.CurrentFiatRatesTime = time.Now() - } if rd.callbackOnNewTicker != nil { rd.callbackOnNewTicker(tickers) } @@ -128,11 +137,5 @@ func (rd *RatesDownloader) Run() error { } } } - // wait for the next run with a slight random value to avoid too many request at the same time - unix := time.Now().Unix() - next := unix + rd.periodSeconds - next -= next % rd.periodSeconds - next += int64(rand.Intn(12)) - time.Sleep(time.Duration(next-unix) * time.Second) } } diff --git a/fiat/fiat_rates_test.go b/fiat/fiat_rates_test.go index 4d7a503f..d1fdd983 100644 --- a/fiat/fiat_rates_test.go +++ b/fiat/fiat_rates_test.go @@ -165,7 +165,7 @@ func TestFiatRates(t *testing.T) { return } - wantCurrentTickers := db.CurrencyRatesTicker{ + wantCurrentTickers := common.CurrencyRatesTicker{ Rates: map[string]float32{ "aed": 8447.1, "ars": 268901, @@ -208,7 +208,7 @@ func TestFiatRates(t *testing.T) { if err != nil || ticker == nil { t.Fatalf("FiatRatesFindLastTicker failed with error: %v", err) } - wantTicker := db.CurrencyRatesTicker{ + wantTicker := common.CurrencyRatesTicker{ Rates: map[string]float32{ "aed": 241272.48, "ars": 241272.48, @@ -233,7 +233,7 @@ func TestFiatRates(t *testing.T) { if err != nil || ticker == nil { t.Fatalf("FiatRatesFindLastTicker failed with error: %v", err) } - wantTicker = db.CurrencyRatesTicker{ + wantTicker = common.CurrencyRatesTicker{ Rates: map[string]float32{ "aed": 240402.97, "ars": 240402.97, @@ -266,7 +266,7 @@ func TestFiatRates(t *testing.T) { if err != nil || ticker == nil { t.Fatalf("FiatRatesFindLastTicker failed with error: %v", err) } - wantTicker = db.CurrencyRatesTicker{ + wantTicker = common.CurrencyRatesTicker{ Rates: map[string]float32{ "aed": 240402.97, "ars": 240402.97, diff --git a/server/public.go b/server/public.go index 416ddae4..5c3f09ac 100644 --- a/server/public.go +++ b/server/public.go @@ -222,7 +222,7 @@ func (s *PublicServer) OnNewBlock(hash string, height uint32) { } // OnNewFiatRatesTicker notifies users subscribed to bitcoind/fiatrates about new ticker -func (s *PublicServer) OnNewFiatRatesTicker(ticker *db.CurrencyRatesTicker) { +func (s *PublicServer) OnNewFiatRatesTicker(ticker *common.CurrencyRatesTicker) { s.websocket.OnNewFiatRatesTicker(ticker) } diff --git a/server/public_test.go b/server/public_test.go index c21622b0..33039bc8 100644 --- a/server/public_test.go +++ b/server/public_test.go @@ -173,7 +173,7 @@ func insertFiatRate(date string, rates map[string]float32, tokenRates map[string if err != nil { return err } - ticker := &db.CurrencyRatesTicker{ + ticker := &common.CurrencyRatesTicker{ Timestamp: *convertedDate, Rates: rates, TokenRates: tokenRates, diff --git a/server/websocket.go b/server/websocket.go index e02793a0..15633af2 100644 --- a/server/websocket.go +++ b/server/websocket.go @@ -979,7 +979,7 @@ func (s *WebsocketServer) OnNewTx(tx *bchain.MempoolTx) { } } -func (s *WebsocketServer) broadcastTicker(currency string, rates map[string]float32, ticker *db.CurrencyRatesTicker) { +func (s *WebsocketServer) broadcastTicker(currency string, rates map[string]float32, ticker *common.CurrencyRatesTicker) { as, ok := s.fiatRatesSubscriptions[currency] if ok && len(as) > 0 { data := struct { @@ -1022,7 +1022,7 @@ func (s *WebsocketServer) broadcastTicker(currency string, rates map[string]floa } // OnNewFiatRatesTicker is a callback that broadcasts info about fiat rates affecting subscribed currency -func (s *WebsocketServer) OnNewFiatRatesTicker(ticker *db.CurrencyRatesTicker) { +func (s *WebsocketServer) OnNewFiatRatesTicker(ticker *common.CurrencyRatesTicker) { s.fiatRatesSubscriptionsLock.Lock() defer s.fiatRatesSubscriptionsLock.Unlock() for currency, rate := range ticker.Rates {