Minor refactor

This commit is contained in:
Martin Boehm 2022-02-06 21:57:54 +01:00 committed by Martin
parent ec510811cd
commit d5e871818a
7 changed files with 285 additions and 266 deletions

View File

@ -13,7 +13,6 @@ import (
"os/signal"
"runtime/debug"
"strings"
"sync/atomic"
"syscall"
"time"
@ -42,7 +41,7 @@ const exitCodeOK = 0
const exitCodeFatal = 255
var (
blockchain = flag.String("blockchaincfg", "", "path to blockchain RPC service configuration json file")
configFile = flag.String("blockchaincfg", "", "path to blockchain RPC service configuration json file")
dbPath = flag.String("datadir", "./data", "path to database directory")
dbCache = flag.Int("dbcache", 1<<29, "size of the rocksdb cache")
@ -105,7 +104,6 @@ var (
callbacksOnNewTx []bchain.OnNewTxFunc
callbacksOnNewFiatRatesTicker []fiat.OnNewFiatRatesTicker
chanOsSignal chan os.Signal
inShutdown int32
)
func init() {
@ -151,26 +149,24 @@ func mainWithExitCode() int {
return exitCodeOK
}
if *blockchain == "" {
if *configFile == "" {
glog.Error("Missing blockchaincfg configuration parameter")
return exitCodeFatal
}
coin, coinShortcut, coinLabel, err := coins.GetCoinNameFromConfig(*blockchain)
coin, coinShortcut, coinLabel, err := coins.GetCoinNameFromConfig(*configFile)
if err != nil {
glog.Error("config: ", err)
return exitCodeFatal
}
// gspt.SetProcTitle("blockbook-" + normalizeName(coin))
metrics, err = common.GetMetrics(coin)
if err != nil {
glog.Error("metrics: ", err)
return exitCodeFatal
}
if chain, mempool, err = getBlockChainWithRetry(coin, *blockchain, pushSynchronizationHandler, metrics, 120); err != nil {
if chain, mempool, err = getBlockChainWithRetry(coin, *configFile, pushSynchronizationHandler, metrics, 120); err != nil {
glog.Error("rpc: ", err)
return exitCodeFatal
}
@ -347,7 +343,7 @@ func mainWithExitCode() int {
if internalServer != nil || publicServer != nil || chain != nil {
// start fiat rates downloader only if not shutting down immediately
initFiatRatesDownloader(index, *blockchain)
initFiatRatesDownloader(index, *configFile)
waitForSignalAndShutdown(internalServer, publicServer, chain, 10*time.Second)
}
@ -362,13 +358,13 @@ func mainWithExitCode() int {
return exitCodeOK
}
func getBlockChainWithRetry(coin string, configfile string, pushHandler func(bchain.NotificationType), metrics *common.Metrics, seconds int) (bchain.BlockChain, bchain.Mempool, error) {
func getBlockChainWithRetry(coin string, configFile string, pushHandler func(bchain.NotificationType), metrics *common.Metrics, seconds int) (bchain.BlockChain, bchain.Mempool, error) {
var chain bchain.BlockChain
var mempool bchain.Mempool
var err error
timer := time.NewTimer(time.Second)
for i := 0; ; i++ {
if chain, mempool, err = coins.NewBlockChain(coin, configfile, pushHandler, metrics); err != nil {
if chain, mempool, err = coins.NewBlockChain(coin, configFile, pushHandler, metrics); err != nil {
if i < seconds {
glog.Error("rpc: ", err, " Retrying...")
select {
@ -496,46 +492,11 @@ func newInternalState(coin, coinShortcut, coinLabel string, d *db.RocksDB) (*com
return is, nil
}
func tickAndDebounce(tickTime time.Duration, debounceTime time.Duration, input chan struct{}, f func()) {
timer := time.NewTimer(tickTime)
var firstDebounce time.Time
Loop:
for {
select {
case _, ok := <-input:
if !timer.Stop() {
<-timer.C
}
// exit loop on closed input channel
if !ok {
break Loop
}
if firstDebounce.IsZero() {
firstDebounce = time.Now()
}
// debounce for up to debounceTime period
// afterwards execute immediately
if firstDebounce.Add(debounceTime).After(time.Now()) {
timer.Reset(debounceTime)
} else {
timer.Reset(0)
}
case <-timer.C:
// do the action, if not in shutdown, then start the loop again
if atomic.LoadInt32(&inShutdown) == 0 {
f()
}
timer.Reset(tickTime)
firstDebounce = time.Time{}
}
}
}
func syncIndexLoop() {
defer close(chanSyncIndexDone)
glog.Info("syncIndexLoop starting")
// resync index about every 15 minutes if there are no chanSyncIndex requests, with debounce 1 second
tickAndDebounce(time.Duration(*resyncIndexPeriodMs)*time.Millisecond, debounceResyncIndexMs*time.Millisecond, chanSyncIndex, func() {
common.TickAndDebounce(time.Duration(*resyncIndexPeriodMs)*time.Millisecond, debounceResyncIndexMs*time.Millisecond, chanSyncIndex, func() {
if err := syncWorker.ResyncIndex(onNewBlockHash, false); err != nil {
glog.Error("syncIndexLoop ", errors.ErrorStack(err), ", will retry...")
// retry once in case of random network error, after a slight delay
@ -574,7 +535,7 @@ func syncMempoolLoop() {
defer close(chanSyncMempoolDone)
glog.Info("syncMempoolLoop starting")
// resync mempool about every minute if there are no chanSyncMempool requests, with debounce 1 second
tickAndDebounce(time.Duration(*resyncMempoolPeriodMs)*time.Millisecond, debounceResyncMempoolMs*time.Millisecond, chanSyncMempool, func() {
common.TickAndDebounce(time.Duration(*resyncMempoolPeriodMs)*time.Millisecond, debounceResyncMempoolMs*time.Millisecond, chanSyncMempool, func() {
internalState.StartedMempoolSync()
if count, err := mempool.Resync(); err != nil {
glog.Error("syncMempoolLoop ", errors.ErrorStack(err))
@ -604,7 +565,7 @@ func storeInternalStateLoop() {
} else {
glog.Info("storeInternalStateLoop starting with db stats compute disabled")
}
tickAndDebounce(storeInternalStatePeriodMs*time.Millisecond, (storeInternalStatePeriodMs-1)*time.Millisecond, chanStoreInternalState, func() {
common.TickAndDebounce(storeInternalStatePeriodMs*time.Millisecond, (storeInternalStatePeriodMs-1)*time.Millisecond, chanStoreInternalState, func() {
if (*dbStatsPeriodHours) > 0 && !computeRunning && lastCompute.Add(computePeriod).Before(time.Now()) {
computeRunning = true
go func() {
@ -654,7 +615,7 @@ func onNewTx(tx *bchain.MempoolTx) {
func pushSynchronizationHandler(nt bchain.NotificationType) {
glog.V(1).Info("MQ: notification ", nt)
if atomic.LoadInt32(&inShutdown) != 0 {
if common.IsInShutdown() {
return
}
if nt == bchain.NotificationNewBlock {
@ -668,7 +629,7 @@ func pushSynchronizationHandler(nt bchain.NotificationType) {
func waitForSignalAndShutdown(internal *server.InternalServer, public *server.PublicServer, chain bchain.BlockChain, timeout time.Duration) {
sig := <-chanOsSignal
atomic.StoreInt32(&inShutdown, 1)
common.SetInShutdown()
glog.Infof("shutdown: %v", sig)
ctx, cancel := context.WithTimeout(context.Background(), timeout)
@ -693,17 +654,6 @@ func waitForSignalAndShutdown(internal *server.InternalServer, public *server.Pu
}
}
func printResult(txid string, vout int32, isOutput bool) error {
glog.Info(txid, vout, isOutput)
return nil
}
func normalizeName(s string) string {
s = strings.ToLower(s)
s = strings.Replace(s, " ", "-", -1)
return s
}
// computeFeeStats computes fee distribution in defined blocks
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()

View File

@ -4,6 +4,7 @@ import (
"encoding/json"
"sort"
"sync"
"sync/atomic"
"time"
)
@ -16,6 +17,8 @@ const (
DbStateInconsistent
)
var inShutdown int32
// InternalStateColumn contains the data of a db column
type InternalStateColumn struct {
Name string `json:"name"`
@ -265,3 +268,13 @@ func UnpackInternalState(buf []byte) (*InternalState, error) {
}
return &is, nil
}
// SetInShutdown sets the internal state to in shutdown state
func SetInShutdown() {
atomic.StoreInt32(&inShutdown, 1)
}
// IsInShutdown returns true if in application shutdown state
func IsInShutdown() bool {
return atomic.LoadInt32(&inShutdown) != 0
}

41
common/utils.go Normal file
View File

@ -0,0 +1,41 @@
package common
import (
"time"
)
// TickAndDebounce calls function f on trigger channel or with tickTime period (whatever is sooner) with debounce
func TickAndDebounce(tickTime time.Duration, debounceTime time.Duration, trigger chan struct{}, f func()) {
timer := time.NewTimer(tickTime)
var firstDebounce time.Time
Loop:
for {
select {
case _, ok := <-trigger:
if !timer.Stop() {
<-timer.C
}
// exit loop on closed input channel
if !ok {
break Loop
}
if firstDebounce.IsZero() {
firstDebounce = time.Now()
}
// debounce for up to debounceTime period
// afterwards execute immediately
if firstDebounce.Add(debounceTime).After(time.Now()) {
timer.Reset(debounceTime)
} else {
timer.Reset(0)
}
case <-timer.C:
// do the action, if not in shutdown, then start the loop again
if !IsInShutdown() {
f()
}
timer.Reset(tickTime)
firstDebounce = time.Time{}
}
}
}

135
db/fiat.go Normal file
View File

@ -0,0 +1,135 @@
package db
import (
"encoding/json"
"time"
"github.com/golang/glog"
"github.com/juju/errors"
)
// FiatRatesTimeFormat is a format string for storing FiatRates timestamps in rocksdb
const FiatRatesTimeFormat = "20060102150405" // YYYYMMDDhhmmss
// CurrencyRatesTicker contains coin ticker data fetched from API
type CurrencyRatesTicker struct {
Timestamp *time.Time // return as unix timestamp in API
Rates map[string]float64
}
// ResultTickerAsString contains formatted CurrencyRatesTicker data
type ResultTickerAsString struct {
Timestamp int64 `json:"ts,omitempty"`
Rates map[string]float64 `json:"rates"`
Error string `json:"error,omitempty"`
}
// ResultTickersAsString contains a formatted CurrencyRatesTicker list
type ResultTickersAsString struct {
Tickers []ResultTickerAsString `json:"tickers"`
}
// ResultTickerListAsString contains formatted data about available currency tickers
type ResultTickerListAsString struct {
Timestamp int64 `json:"ts,omitempty"`
Tickers []string `json:"available_currencies"`
Error string `json:"error,omitempty"`
}
// FiatRatesConvertDate checks if the date is in correct format and returns the Time object.
// Possible formats are: YYYYMMDDhhmmss, YYYYMMDDhhmm, YYYYMMDDhh, YYYYMMDD
func FiatRatesConvertDate(date string) (*time.Time, error) {
for format := FiatRatesTimeFormat; len(format) >= 8; format = format[:len(format)-2] {
convertedDate, err := time.Parse(format, date)
if err == nil {
return &convertedDate, nil
}
}
msg := "Date \"" + date + "\" does not match any of available formats. "
msg += "Possible formats are: YYYYMMDDhhmmss, YYYYMMDDhhmm, YYYYMMDDhh, YYYYMMDD"
return nil, errors.New(msg)
}
// FiatRatesStoreTicker stores ticker data at the specified time
func (d *RocksDB) FiatRatesStoreTicker(ticker *CurrencyRatesTicker) error {
if len(ticker.Rates) == 0 {
return errors.New("Error storing ticker: empty rates")
} else if ticker.Timestamp == nil {
return errors.New("Error storing ticker: empty timestamp")
}
ratesMarshalled, err := json.Marshal(ticker.Rates)
if err != nil {
glog.Error("Error marshalling ticker rates: ", err)
return err
}
timeFormatted := ticker.Timestamp.UTC().Format(FiatRatesTimeFormat)
err = d.db.PutCF(d.wo, d.cfh[cfFiatRates], []byte(timeFormatted), ratesMarshalled)
if err != nil {
glog.Error("Error storing ticker: ", err)
return err
}
return nil
}
// FiatRatesFindTicker gets FiatRates data closest to the specified timestamp
func (d *RocksDB) FiatRatesFindTicker(tickerTime *time.Time) (*CurrencyRatesTicker, error) {
ticker := &CurrencyRatesTicker{}
tickerTimeFormatted := tickerTime.UTC().Format(FiatRatesTimeFormat)
it := d.db.NewIteratorCF(d.ro, d.cfh[cfFiatRates])
defer it.Close()
for it.Seek([]byte(tickerTimeFormatted)); it.Valid(); it.Next() {
timeObj, err := time.Parse(FiatRatesTimeFormat, string(it.Key().Data()))
if err != nil {
glog.Error("FiatRatesFindTicker time parse error: ", err)
return nil, err
}
timeObj = timeObj.UTC()
ticker.Timestamp = &timeObj
err = json.Unmarshal(it.Value().Data(), &ticker.Rates)
if err != nil {
glog.Error("FiatRatesFindTicker error unpacking rates: ", err)
return nil, err
}
break
}
if err := it.Err(); err != nil {
glog.Error("FiatRatesFindTicker Iterator error: ", err)
return nil, err
}
if !it.Valid() {
return nil, nil // ticker not found
}
return ticker, nil
}
// FiatRatesFindLastTicker gets the last FiatRates record
func (d *RocksDB) FiatRatesFindLastTicker() (*CurrencyRatesTicker, error) {
ticker := &CurrencyRatesTicker{}
it := d.db.NewIteratorCF(d.ro, d.cfh[cfFiatRates])
defer it.Close()
for it.SeekToLast(); it.Valid(); it.Next() {
timeObj, err := time.Parse(FiatRatesTimeFormat, string(it.Key().Data()))
if err != nil {
glog.Error("FiatRatesFindTicker time parse error: ", err)
return nil, err
}
timeObj = timeObj.UTC()
ticker.Timestamp = &timeObj
err = json.Unmarshal(it.Value().Data(), &ticker.Rates)
if err != nil {
glog.Error("FiatRatesFindTicker error unpacking rates: ", err)
return nil, err
}
break
}
if err := it.Err(); err != nil {
glog.Error("FiatRatesFindLastTicker Iterator error: ", err)
return ticker, err
}
if !it.Valid() {
return nil, nil // ticker not found
}
return ticker, nil
}

84
db/fiat_test.go Normal file
View File

@ -0,0 +1,84 @@
//go:build unittest
package db
import (
"testing"
"time"
)
func TestRocksTickers(t *testing.T) {
d := setupRocksDB(t, &testBitcoinParser{
BitcoinParser: bitcoinTestnetParser(),
})
defer closeAndDestroyRocksDB(t, d)
// Test valid formats
for _, date := range []string{"20190130", "2019013012", "201901301250", "20190130125030"} {
_, err := FiatRatesConvertDate(date)
if err != nil {
t.Errorf("%v", err)
}
}
// Test invalid formats
for _, date := range []string{"01102019", "10201901", "", "abc", "20190130xxx"} {
_, err := FiatRatesConvertDate(date)
if err == nil {
t.Errorf("Wrongly-formatted date \"%v\" marked as valid!", date)
}
}
// Test storing & finding tickers
key, _ := time.Parse(FiatRatesTimeFormat, "20190627000000")
futureKey, _ := time.Parse(FiatRatesTimeFormat, "20190630000000")
ts1, _ := time.Parse(FiatRatesTimeFormat, "20190628000000")
ticker1 := &CurrencyRatesTicker{
Timestamp: &ts1,
Rates: map[string]float64{
"usd": 20000,
},
}
ts2, _ := time.Parse(FiatRatesTimeFormat, "20190629000000")
ticker2 := &CurrencyRatesTicker{
Timestamp: &ts2,
Rates: map[string]float64{
"usd": 30000,
},
}
err := d.FiatRatesStoreTicker(ticker1)
if err != nil {
t.Errorf("Error storing ticker! %v", err)
}
d.FiatRatesStoreTicker(ticker2)
if err != nil {
t.Errorf("Error storing ticker! %v", err)
}
ticker, err := d.FiatRatesFindTicker(&key) // should find the closest key (ticker1)
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)
}
ticker, err = d.FiatRatesFindLastTicker() // should find the last key (ticker2)
if err != nil {
t.Errorf("TestRocksTickers err: %+v", err)
} else if ticker == nil {
t.Errorf("Ticker not found")
} else if ticker.Timestamp.Format(FiatRatesTimeFormat) != ticker2.Timestamp.Format(FiatRatesTimeFormat) {
t.Errorf("Incorrect ticker found. Expected: %v, found: %+v", ticker1.Timestamp, ticker.Timestamp)
}
ticker, err = d.FiatRatesFindTicker(&futureKey) // should not find anything
if err != nil {
t.Errorf("TestRocksTickers err: %+v", err)
} else if ticker != nil {
t.Errorf("Ticker found, but the timestamp is older than the last ticker entry.")
}
}

View File

@ -4,7 +4,6 @@ import (
"bytes"
"encoding/binary"
"encoding/hex"
"encoding/json"
"fmt"
"math/big"
"os"
@ -31,34 +30,6 @@ const maxAddrDescLen = 1024
// when doing huge scan, it is better to close it and reopen from time to time to free the resources
const refreshIterator = 5000000
// FiatRatesTimeFormat is a format string for storing FiatRates timestamps in rocksdb
const FiatRatesTimeFormat = "20060102150405" // YYYYMMDDhhmmss
// CurrencyRatesTicker contains coin ticker data fetched from API
type CurrencyRatesTicker struct {
Timestamp *time.Time // return as unix timestamp in API
Rates map[string]float64
}
// ResultTickerAsString contains formatted CurrencyRatesTicker data
type ResultTickerAsString struct {
Timestamp int64 `json:"ts,omitempty"`
Rates map[string]float64 `json:"rates"`
Error string `json:"error,omitempty"`
}
// ResultTickersAsString contains a formatted CurrencyRatesTicker list
type ResultTickersAsString struct {
Tickers []ResultTickerAsString `json:"tickers"`
}
// ResultTickerListAsString contains formatted data about available currency tickers
type ResultTickerListAsString struct {
Timestamp int64 `json:"ts,omitempty"`
Tickers []string `json:"available_currencies"`
Error string `json:"error,omitempty"`
}
// RepairRocksDB calls RocksDb db repair function
func RepairRocksDB(name string) error {
glog.Infof("rocksdb: repair")
@ -183,104 +154,6 @@ func (d *RocksDB) closeDB() error {
return nil
}
// FiatRatesConvertDate checks if the date is in correct format and returns the Time object.
// Possible formats are: YYYYMMDDhhmmss, YYYYMMDDhhmm, YYYYMMDDhh, YYYYMMDD
func FiatRatesConvertDate(date string) (*time.Time, error) {
for format := FiatRatesTimeFormat; len(format) >= 8; format = format[:len(format)-2] {
convertedDate, err := time.Parse(format, date)
if err == nil {
return &convertedDate, nil
}
}
msg := "Date \"" + date + "\" does not match any of available formats. "
msg += "Possible formats are: YYYYMMDDhhmmss, YYYYMMDDhhmm, YYYYMMDDhh, YYYYMMDD"
return nil, errors.New(msg)
}
// FiatRatesStoreTicker stores ticker data at the specified time
func (d *RocksDB) FiatRatesStoreTicker(ticker *CurrencyRatesTicker) error {
if len(ticker.Rates) == 0 {
return errors.New("Error storing ticker: empty rates")
} else if ticker.Timestamp == nil {
return errors.New("Error storing ticker: empty timestamp")
}
ratesMarshalled, err := json.Marshal(ticker.Rates)
if err != nil {
glog.Error("Error marshalling ticker rates: ", err)
return err
}
timeFormatted := ticker.Timestamp.UTC().Format(FiatRatesTimeFormat)
err = d.db.PutCF(d.wo, d.cfh[cfFiatRates], []byte(timeFormatted), ratesMarshalled)
if err != nil {
glog.Error("Error storing ticker: ", err)
return err
}
return nil
}
// FiatRatesFindTicker gets FiatRates data closest to the specified timestamp
func (d *RocksDB) FiatRatesFindTicker(tickerTime *time.Time) (*CurrencyRatesTicker, error) {
ticker := &CurrencyRatesTicker{}
tickerTimeFormatted := tickerTime.UTC().Format(FiatRatesTimeFormat)
it := d.db.NewIteratorCF(d.ro, d.cfh[cfFiatRates])
defer it.Close()
for it.Seek([]byte(tickerTimeFormatted)); it.Valid(); it.Next() {
timeObj, err := time.Parse(FiatRatesTimeFormat, string(it.Key().Data()))
if err != nil {
glog.Error("FiatRatesFindTicker time parse error: ", err)
return nil, err
}
timeObj = timeObj.UTC()
ticker.Timestamp = &timeObj
err = json.Unmarshal(it.Value().Data(), &ticker.Rates)
if err != nil {
glog.Error("FiatRatesFindTicker error unpacking rates: ", err)
return nil, err
}
break
}
if err := it.Err(); err != nil {
glog.Error("FiatRatesFindTicker Iterator error: ", err)
return nil, err
}
if !it.Valid() {
return nil, nil // ticker not found
}
return ticker, nil
}
// FiatRatesFindLastTicker gets the last FiatRates record
func (d *RocksDB) FiatRatesFindLastTicker() (*CurrencyRatesTicker, error) {
ticker := &CurrencyRatesTicker{}
it := d.db.NewIteratorCF(d.ro, d.cfh[cfFiatRates])
defer it.Close()
for it.SeekToLast(); it.Valid(); it.Next() {
timeObj, err := time.Parse(FiatRatesTimeFormat, string(it.Key().Data()))
if err != nil {
glog.Error("FiatRatesFindTicker time parse error: ", err)
return nil, err
}
timeObj = timeObj.UTC()
ticker.Timestamp = &timeObj
err = json.Unmarshal(it.Value().Data(), &ticker.Rates)
if err != nil {
glog.Error("FiatRatesFindTicker error unpacking rates: ", err)
return nil, err
}
break
}
if err := it.Err(); err != nil {
glog.Error("FiatRatesFindLastTicker Iterator error: ", err)
return ticker, err
}
if !it.Valid() {
return nil, nil // ticker not found
}
return ticker, nil
}
// Close releases the RocksDB environment opened in NewRocksDB.
func (d *RocksDB) Close() error {
if d.db != nil {

View File

@ -12,7 +12,6 @@ import (
"sort"
"strings"
"testing"
"time"
vlq "github.com/bsm/go-vlq"
"github.com/juju/errors"
@ -1482,79 +1481,3 @@ func Test_reorderUtxo(t *testing.T) {
})
}
}
func TestRocksTickers(t *testing.T) {
d := setupRocksDB(t, &testBitcoinParser{
BitcoinParser: bitcoinTestnetParser(),
})
defer closeAndDestroyRocksDB(t, d)
// Test valid formats
for _, date := range []string{"20190130", "2019013012", "201901301250", "20190130125030"} {
_, err := FiatRatesConvertDate(date)
if err != nil {
t.Errorf("%v", err)
}
}
// Test invalid formats
for _, date := range []string{"01102019", "10201901", "", "abc", "20190130xxx"} {
_, err := FiatRatesConvertDate(date)
if err == nil {
t.Errorf("Wrongly-formatted date \"%v\" marked as valid!", date)
}
}
// Test storing & finding tickers
key, _ := time.Parse(FiatRatesTimeFormat, "20190627000000")
futureKey, _ := time.Parse(FiatRatesTimeFormat, "20190630000000")
ts1, _ := time.Parse(FiatRatesTimeFormat, "20190628000000")
ticker1 := &CurrencyRatesTicker{
Timestamp: &ts1,
Rates: map[string]float64{
"usd": 20000,
},
}
ts2, _ := time.Parse(FiatRatesTimeFormat, "20190629000000")
ticker2 := &CurrencyRatesTicker{
Timestamp: &ts2,
Rates: map[string]float64{
"usd": 30000,
},
}
err := d.FiatRatesStoreTicker(ticker1)
if err != nil {
t.Errorf("Error storing ticker! %v", err)
}
d.FiatRatesStoreTicker(ticker2)
if err != nil {
t.Errorf("Error storing ticker! %v", err)
}
ticker, err := d.FiatRatesFindTicker(&key) // should find the closest key (ticker1)
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)
}
ticker, err = d.FiatRatesFindLastTicker() // should find the last key (ticker2)
if err != nil {
t.Errorf("TestRocksTickers err: %+v", err)
} else if ticker == nil {
t.Errorf("Ticker not found")
} else if ticker.Timestamp.Format(FiatRatesTimeFormat) != ticker2.Timestamp.Format(FiatRatesTimeFormat) {
t.Errorf("Incorrect ticker found. Expected: %v, found: %+v", ticker1.Timestamp, ticker.Timestamp)
}
ticker, err = d.FiatRatesFindTicker(&futureKey) // should not find anything
if err != nil {
t.Errorf("TestRocksTickers err: %+v", err)
} else if ticker != nil {
t.Errorf("Ticker found, but the timestamp is older than the last ticker entry.")
}
}