diff --git a/SETUP_NODE.md b/SETUP_NODE.md index 174a44d33..2d1c14b71 100644 --- a/SETUP_NODE.md +++ b/SETUP_NODE.md @@ -213,9 +213,6 @@ folder. Is so that the API can be accessed by users outside the host. - - [ ] Add APIKey to `PriceUpdater.Fiat` section. - Get a key [here](https://exchangeratesapi.io/) - - [ ] Change PostgreSQL section with the values of user created before. - HostWrite: Use the IP from server instead of localhost diff --git a/api/fiat_test.go b/api/fiat_test.go index fcfb0ffe6..48b148e52 100644 --- a/api/fiat_test.go +++ b/api/fiat_test.go @@ -13,7 +13,10 @@ import ( ) func genFiatPrices(db *historydb.HistoryDB) error { - err := db.CreateFiatPrice("EUR", "USD", 0.82) + _, err := db.DB().Exec( + "INSERT INTO fiat(currency, base_currency, price) VALUES ($1, $2, $3);", + "EUR", "USD", 0.82, + ) if err != nil { return err } diff --git a/cmd/heznode/cfg.builder.toml b/cmd/heznode/cfg.builder.toml index 7ccd153cc..87a8c6de3 100644 --- a/cmd/heznode/cfg.builder.toml +++ b/cmd/heznode/cfg.builder.toml @@ -12,56 +12,6 @@ ### Maximum amount of time that an API request can wait to establish a SQL connection #SQLConnectionTimeout = "2s" -#[PriceUpdater] -### Interval between price updater calls -#Interval = "60s" -### Priority order to use the different providers -#Priority = "bitfinexV2,CoinGeckoV3" -### Static price for tokens -#Statictokens="" # =,= - -#[PriceUpdater.Fiat] -### This parameter allow access to the external service -#APIKey="" -### Url of the external service -#URL="https://api.exchangeratesapi.io/v1/" -### Base currency used to get prices -#BaseCurrency="USD" -### Currencies that the hermez node is going to store -#Currencies="CNY,EUR,JPY,GBP" - -#[[PriceUpdater.Provider]] -### Provider name used in "Priority" field -#Provider = "bitfinexV2" -### Provider api base url -#BASEURL = "https://api-pub.bitfinex.com/v2/" -### Provider url -#URL = "ticker/t" -### Second part of the url -#URLExtraParams = "USD" -### Available update methods: -### - ignore method don't update the price leave it as it is on the DB. Type ignore or leave the field empty -### - static method is performed writing the price in Statictokens. -### Example: Symbols = "0=ETH,1=HEZ,2=ignore,4=DAI,5=WBT,6=static,7=XAUT:,8=UNI,9=SUSHI:,10=COMP:,11=,12=AAVE:,13=YFI,14=LINK:" -### Symbols that the price updater should use in order to get the prices successfully -#Symbols = "0=ETH,2=UST,8=ignore,9=SUSHI:,5=WBT,14=LINK:,12=AAVE:,7=XAUT:,10=COMP:,6=ETH" - -#[[PriceUpdater.Provider]] -### Provider name used in "Priority" field -#Provider = "CoinGeckoV3" -### Provider api base url -#BASEURL = "https://api.coingecko.com/api/v3/" -### Provider url -#URL = "simple/token_price/ethereum?contract_addresses=" -### Second part of the url -#URLExtraParams = "&vs_currencies=usd" -### Available update methods: -### - ignore method don't update the price leave it as it is on the DB. Type ignore or leave the field empty -### - static method is performed writing the price in Statictokens. -### Example: Addresses="0=0x00,1=0x12,2=0x12,4=0x12,5=0x12,6=,7=0x12,8=,9=0x12,10=0x12,11=ignore,12=0x12,13=0x12,14=0x12" -### Addresses that the price updater should use in order to get the prices successfully -#Addresses="0=0x0000000000000000000000000000000000000000,2=0xdac17f958d2ee523a2206206994597c13d831ec7,8=ignore" - #[Debug] ### If it is set, the debug api will listen in this address and port #APIAddress = "0.0.0.0:12345" diff --git a/config/README.md b/config/README.md index 439233053..99be667c5 100644 --- a/config/README.md +++ b/config/README.md @@ -11,25 +11,6 @@ UpdateRecommendedFeeInterval = HEZNODE_API_UPDATERECOMMENDEDFEEINTERVAL MaxSQLConnections = HEZNODE_API_MAXSQLCONNECTIONS SQLConnectionTimeout = HEZNODE_API_SQLCONNECTIONTIMEOUT -[PriceUpdater] -Interval = HEZNODE_PRICEUPDATER_INTERVAL -Priority = HEZNODE_PRICEUPDATER_PRIORITY -Statictokens = HEZNODE_PRICEUPDATER_STATICTOKENS - -[PriceUpdater.Fiat] -APIKey = HEZNODE_PRICEUPDATER_FIAT_APIKEY -URL = HEZNODE_PRICEUPDATER_FIAT_URL -BaseCurrency = HEZNODE_PRICEUPDATER_FIAT_BASECURRENCY -Currencies = HEZNODE_PRICEUPDATER_FIAT_CURRENCIES - -[[PriceUpdater.Provider]] -Provider = HEZNODE_PRICEUPDATER_PROVIDER_PROVIDER -BASEURL = HEZNODE_PRICEUPDATER_PROVIDER_BASEURL -URL = HEZNODE_PRICEUPDATER_PROVIDER_URL -URLExtraParams = HEZNODE_PRICEUPDATER_PROVIDER_URLEXTRAPARAMS -Symbols = HEZNODE_PRICEUPDATER_PROVIDER_SYMBOLS -Addresses = HEZNODE_PRICEUPDATER_PROVIDER_ADDRESSES - [Debug] APIAddress = HEZNODE_DEBUG_APIADDRESS MeddlerLogs = HEZNODE_DEBUG_MEDDLERLOGS @@ -154,19 +135,6 @@ StaticValue = HEZNODE_RECOMMENDEDFEEPOLICY_STATICVALUE |API|UpdateRecommendedFeeInterval|HEZNODE_API_UPDATERECOMMENDEDFEEINTERVAL|Optional|"10s"|Interval between updates of the recommended fees |API|MaxSQLConnections|HEZNODE_API_MAXSQLCONNECTIONS|Optional|100|Maximum concurrent connections allowed between API and SQL |API|SQLConnectionTimeout|HEZNODE_API_SQLCONNECTIONTIMEOUT|Optional|"2s"|Maximum amount of time that an API request can wait to establish a SQL connection -|PriceUpdater|Interval|HEZNODE_PRICEUPDATER_INTERVAL|Optional|"60s"|Interval between price updater calls -|PriceUpdater|Priority|HEZNODE_PRICEUPDATER_PRIORITY|Optional|"bitfinexV2,CoinGeckoV3"|Priority order to use the different providers -|PriceUpdater|Statictokens|HEZNODE_PRICEUPDATER_STATICTOKENS|Optional|""|Static price for tokens -|PriceUpdater.Fiat|APIKey|HEZNODE_PRICEUPDATER_FIAT_APIKEY|Optional|""|This parameter allow access to the external service -|PriceUpdater.Fiat|URL|HEZNODE_PRICEUPDATER_FIAT_URL|Optional|"https://api.exchangeratesapi.io/v1/"|Url of the external service -|PriceUpdater.Fiat|BaseCurrency|HEZNODE_PRICEUPDATER_FIAT_BASECURRENCY|Optional|"USD"|Base currency used to get prices -|PriceUpdater.Fiat|Currencies|HEZNODE_PRICEUPDATER_FIAT_CURRENCIES|Optional|"CNY,EUR,JPY,GBP"|Currencies that the hermez node is going to store -|PriceUpdater.Provider|Provider|HEZNODE_PRICEUPDATER_PROVIDER_PROVIDER|Optional|"bitfinexV2"|Provider name used in "Priority" field -|PriceUpdater.Provider|BASEURL|HEZNODE_PRICEUPDATER_PROVIDER_BASEURL|Optional|"https://api-pub.bitfinex.com/v2/"|Provider api base url -|PriceUpdater.Provider|URL|HEZNODE_PRICEUPDATER_PROVIDER_URL|Optional|"ticker/t"| Provider url -|PriceUpdater.Provider|URLExtraParams|HEZNODE_PRICEUPDATER_PROVIDER_URLEXTRAPARAMS|Optional|"USD"|Second part of the url -|PriceUpdater.Provider|Symbols|HEZNODE_PRICEUPDATER_PROVIDER_SYMBOLS|Optional|"2=UST,3=UDC,5=WBT,7=XAUT:,9=SUSHI:,10=COMP:,12=AAVE:,14=LINK:,24=GNT,27=ignore,28=ignore,29=ignore,30=ignore,31=ignore,32=ignore"|Symbols that the price updater should use in order to get the prices successfully -|PriceUpdater.Provider|Addresses|HEZNODE_PRICEUPDATER_PROVIDER_ADDRESSES|Optional|Addresses="6=0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2,17=0xde30da39c46104798bb5aa3fe8b9e0e1f348163f,18=0x7d1afa7b718fb893db30a3abc0cfc608aacfebb0,21=0xc944e90c64b2c07662a292be6244bdf05cda44a7,26=0xD533a949740bb3306d119CC777fa900bA034cd52,27=ignore,28=ignore,29=ignore,30=ignore,31=ignore,32=ignore"|Addresses that the price updater should use in order to get the prices successfully |Debug|APIAddress|HEZNODE_DEBUG_APIADDRESS|Optional|"0.0.0.0:12345"|If it is set, the debug api will listen in this address and port |Debug|MeddlerLogs|HEZNODE_DEBUG_MEDDLERLOGS|Optional|true|Enables meddler debug mode, where unused columns and struct fields will be logged |Debug|GinDebugMode|HEZNODE_DEBUG_GINDEBUGMODE|Optional|false|Sets the web framework Gin-Gonic to run in debug mode diff --git a/config/config.go b/config/config.go index bafd27a6f..5e8e5776d 100644 --- a/config/config.go +++ b/config/config.go @@ -3,7 +3,6 @@ package config import ( "fmt" "math/big" - "os" "strings" "time" @@ -12,7 +11,6 @@ import ( "github.com/hermeznetwork/hermez-node/api/stateapiupdater" "github.com/hermeznetwork/hermez-node/common" "github.com/hermeznetwork/hermez-node/log" - "github.com/hermeznetwork/hermez-node/priceupdater" "github.com/hermeznetwork/tracerr" "github.com/iden3/go-iden3-crypto/babyjub" "gopkg.in/go-playground/validator.v9" @@ -287,18 +285,6 @@ type NodeDebug struct { // Node is the hermez node configuration. type Node struct { - PriceUpdater struct { - // Interval between price updater calls - Interval Duration `validate:"required" env:"HEZNODE_PRICEUPDATER_INTERVAL"` - // Priority option defines the priority provider - Priority string `validate:"required" env:"HEZNODE_PRICEUPDATER_PRIORITY"` - // TokensConfig to specify how each token get it's price updated - Provider []priceupdater.Provider - // Statictokens defines the static prices for tokens - Statictokens string `env:"HEZNODE_PRICEUPDATER_STATICTOKENS"` - // Fiat defines the prices for fiat currencies - Fiat priceupdater.Fiat - } `validate:"required"` StateDB struct { // Path where the synchronizer StateDB is stored Path string `validate:"required" env:"HEZNODE_STATEDB_PATH"` @@ -406,33 +392,7 @@ func LoadNode(path string, coordinator bool) (*Node, error) { } log.Warn(err.Error()) } - //Get env arrays - //Price Updater - if os.Getenv("HEZNODE_PRICEUPDATER_PROVIDER_PROVIDER") != "" { - provider := priceupdater.Provider{ - Provider: os.Getenv("HEZNODE_PRICEUPDATER_PROVIDER_PROVIDER"), - } - if os.Getenv("HEZNODE_PRICEUPDATER_PROVIDER_BASEURL") != "" { - provider.BaseURL = os.Getenv("HEZNODE_PRICEUPDATER_PROVIDER_BASEURL") - } - if os.Getenv("HEZNODE_PRICEUPDATER_PROVIDER_URL") != "" { - provider.URL = os.Getenv("HEZNODE_PRICEUPDATER_PROVIDER_URL") - } - if os.Getenv("HEZNODE_PRICEUPDATER_PROVIDER_URLEXTRAPARAMS") != "" { - provider.URLExtraParams = os.Getenv("HEZNODE_PRICEUPDATER_PROVIDER_URLEXTRAPARAMS") - } - if os.Getenv("HEZNODE_PRICEUPDATER_PROVIDER_SYMBOLS") != "" { - provider.Symbols = os.Getenv("HEZNODE_PRICEUPDATER_PROVIDER_SYMBOLS") - } - if os.Getenv("HEZNODE_PRICEUPDATER_PROVIDER_ADDRESSES") != "" { - provider.Addresses = os.Getenv("HEZNODE_PRICEUPDATER_PROVIDER_ADDRESSES") - } - var providers []priceupdater.Provider - providers = append(providers, provider) - cfg.PriceUpdater.Provider = providers - } validate := validator.New() - validate.RegisterStructValidation(priceupdater.ProviderValidation, priceupdater.Provider{}) if err := validate.Struct(cfg); err != nil { return nil, tracerr.Wrap(fmt.Errorf("error validating configuration file: %w", err)) } @@ -456,7 +416,6 @@ func LoadAPIServer(path string, coordinator bool) (*APIServer, error) { log.Warn(err.Error()) } validate := validator.New() - validate.RegisterStructValidation(priceupdater.ProviderValidation, priceupdater.Provider{}) if err := validate.Struct(cfg); err != nil { return nil, tracerr.Wrap(fmt.Errorf("error validating configuration file: %w", err)) } diff --git a/config/default.go b/config/default.go index 95e516c44..e6d478a4b 100644 --- a/config/default.go +++ b/config/default.go @@ -10,36 +10,6 @@ UpdateRecommendedFeeInterval = "10s" MaxSQLConnections = 100 SQLConnectionTimeout = "2s" -[PriceUpdater] -Interval = "60s" -Priority = "bitfinexV2,CoinGeckoV3" -Statictokens="" # =,= - -[PriceUpdater.Fiat] -APIKey="" -URL="https://api.exchangeratesapi.io/v1/" -BaseCurrency="USD" -Currencies="CNY,EUR,JPY,GBP" - -[[PriceUpdater.Provider]] -Provider = "bitfinexV2" -BASEURL = "https://api-pub.bitfinex.com/v2/" -URL = "ticker/t" -URLExtraParams = "USD" -Symbols = "2=UST,3=UDC,5=WBT,7=XAUT:,9=SUSHI:,10=COMP:,12=AAVE:,14=LINK:,24=GNT,27=ignore,28=ignore,29=ignore,30=ignore,31=ignore,32=ignore" - -[[PriceUpdater.Provider]] -Provider = "CoinGeckoV3" -BASEURL = "https://api.coingecko.com/api/v3/" -URL = "simple/token_price/ethereum?contract_addresses=" -URLExtraParams = "&vs_currencies=usd" -Addresses="6=0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2,17=0xde30da39c46104798bb5aa3fe8b9e0e1f348163f,18=0x7d1afa7b718fb893db30a3abc0cfc608aacfebb0,21=0xc944e90c64b2c07662a292be6244bdf05cda44a7,26=0xD533a949740bb3306d119CC777fa900bA034cd52,27=ignore,28=ignore,29=ignore,30=ignore,31=ignore,32=ignore" - -[Debug] -APIAddress = "0.0.0.0:12345" -MeddlerLogs = true -GinDebugMode = false - [StateDB] Path = "/var/hermez/statedb" Keep = 256 diff --git a/db/historydb/historydb.go b/db/historydb/historydb.go index b5db64a3d..66af84233 100644 --- a/db/historydb/historydb.go +++ b/db/historydb/historydb.go @@ -486,26 +486,6 @@ func (hdb *HistoryDB) UpdateTokenValueByTokenID(tokenID uint, value float64) err return tracerr.Wrap(err) } -// UpdateFiatPrice updates the USD value per currency -func (hdb *HistoryDB) UpdateFiatPrice(currency string, baseCurrency string, price float64) error { - // last_update field is gonna be updated automatically by the trigger trigger_fiat_price_update - _, err := hdb.dbWrite.Exec( - "UPDATE fiat SET price = $1 WHERE currency = $2 AND base_currency = $3;", - price, currency, baseCurrency, - ) - return tracerr.Wrap(err) -} - -// CreateFiatPrice creates a new entry for a new currency or pair currency/baseCurrency -func (hdb *HistoryDB) CreateFiatPrice(currency string, baseCurrency string, price float64) error { - // last_update field is gonna be filled automatically by the trigger trigger_fiat_price_update - _, err := hdb.dbWrite.Exec( - "INSERT INTO fiat(currency, base_currency, price) VALUES ($1, $2, $3);", - currency, baseCurrency, price, - ) - return tracerr.Wrap(err) -} - // GetFiatPrice recover the price for a currency func (hdb *HistoryDB) GetFiatPrice(currency, baseCurrency string) (FiatCurrency, error) { var currencyPrice = &FiatCurrency{} diff --git a/db/migrations/0008_test.go b/db/migrations/0008_test.go index 8490d1449..df4dd5e3a 100644 --- a/db/migrations/0008_test.go +++ b/db/migrations/0008_test.go @@ -7,7 +7,7 @@ import ( "github.com/stretchr/testify/assert" ) -// This migration creates the fiat table +// This migration updates the tx_pool table type migrationTest0008 struct{} @@ -86,7 +86,7 @@ func (m migrationTest0008) RunAssertsAfterMigrationUp(t *testing.T, db *sqlx.DB) } func (m migrationTest0008) RunAssertsAfterMigrationDown(t *testing.T, db *sqlx.DB) { - // check that the fiat table is not created and I can't insert data + // check that the new fields can't be inserted in tx_pool table const queryInsert = `INSERT INTO tx_pool (tx_id, from_idx, effective_from_eth_addr, diff --git a/node/node.go b/node/node.go index 8e19a6b3b..3b8cab6d6 100644 --- a/node/node.go +++ b/node/node.go @@ -44,7 +44,6 @@ import ( "github.com/hermeznetwork/hermez-node/eth" "github.com/hermeznetwork/hermez-node/etherscan" "github.com/hermeznetwork/hermez-node/log" - "github.com/hermeznetwork/hermez-node/priceupdater" "github.com/hermeznetwork/hermez-node/prover" "github.com/hermeznetwork/hermez-node/synchronizer" "github.com/hermeznetwork/hermez-node/test/debugapi" @@ -76,7 +75,6 @@ type Node struct { nodeAPI *NodeAPI stateAPIUpdater *stateapiupdater.Updater debugAPI *debugapi.DebugAPI - priceUpdater *priceupdater.PriceUpdater // Coordinator coord *coordinator.Coordinator @@ -471,22 +469,11 @@ func NewNode(mode Mode, cfg *config.Node, version string) (*Node, error) { if cfg.Debug.APIAddress != "" { debugAPI = debugapi.NewDebugAPI(cfg.Debug.APIAddress, stateDB, sync) } - priceUpdater, err := priceupdater.NewPriceUpdater( - cfg.PriceUpdater.Priority, - cfg.PriceUpdater.Provider, - cfg.PriceUpdater.Statictokens, - cfg.PriceUpdater.Fiat, - historyDB, - ) - if err != nil { - return nil, tracerr.Wrap(err) - } ctx, cancel := context.WithCancel(context.Background()) return &Node{ stateAPIUpdater: stateAPIUpdater, nodeAPI: nodeAPI, debugAPI: debugAPI, - priceUpdater: priceUpdater, coord: coord, sync: sync, cfg: cfg, @@ -811,26 +798,6 @@ func (n *Node) StartSynchronizer() { } } }() - - n.wg.Add(1) - go func() { - for { - select { - case <-n.ctx.Done(): - log.Info("PriceUpdater done") - n.wg.Done() - return - case <-time.After(n.cfg.PriceUpdater.Interval.Duration): - if err := n.priceUpdater.UpdateFiatPrices(n.ctx); err != nil { - log.Errorw("PriceUpdater.UpdateFiatPrices()", "err", err) - } - if err := n.priceUpdater.UpdateTokenList(); err != nil { - log.Errorw("PriceUpdater.UpdateTokenList()", "err", err) - } - n.priceUpdater.UpdatePrices(n.ctx) - } - } - }() } // StartDebugAPI starts the DebugAPI diff --git a/priceupdater/priceupdater.go b/priceupdater/priceupdater.go deleted file mode 100644 index 263cab53f..000000000 --- a/priceupdater/priceupdater.go +++ /dev/null @@ -1,453 +0,0 @@ -package priceupdater - -import ( - "context" - "fmt" - "net/http" - "strconv" - "strings" - "time" - - "github.com/dghubble/sling" - ethCommon "github.com/ethereum/go-ethereum/common" - "github.com/hermeznetwork/hermez-node/common" - "github.com/hermeznetwork/hermez-node/db/historydb" - "github.com/hermeznetwork/hermez-node/log" - "github.com/hermeznetwork/tracerr" - "gopkg.in/go-playground/validator.v9" -) - -const ( - defaultMaxIdleConns = 10 - defaultIdleConnTimeout = 2 * time.Second -) - -const ( - // UpdateMethodTypeBitFinexV2 is the http API used by bitfinex V2 - UpdateMethodTypeBitFinexV2 string = "bitfinexV2" - // UpdateMethodTypeCoingeckoV3 is the http API used by copingecko V3 - UpdateMethodTypeCoingeckoV3 string = "CoinGeckoV3" - // UpdateMethodTypeIgnore indicates to not update the value, to set value 0 - // it's better to use UpdateMethodTypeStatic - UpdateMethodTypeIgnore string = "ignore" -) - -// Fiat definition -type Fiat struct { - APIKey string `env:"HEZNODE_PRICEUPDATER_FIAT_APIKEY"` - URL string `env:"HEZNODE_PRICEUPDATER_FIAT_URL"` - BaseCurrency string `env:"HEZNODE_PRICEUPDATER_FIAT_BASECURRENCY"` - Currencies string `env:"HEZNODE_PRICEUPDATER_FIAT_CURRENCIES"` -} - -// Provider definition -type Provider struct { - Provider string `env:"HEZNODE_PRICEUPDATER_PROVIDER_PROVIDER"` - BaseURL string `env:"HEZNODE_PRICEUPDATER_PROVIDER_BASEURL"` - URL string `env:"HEZNODE_PRICEUPDATER_PROVIDER_URL"` - URLExtraParams string `env:"HEZNODE_PRICEUPDATER_PROVIDER_URLEXTRAPARAMS"` - SymbolsMap symbolsMap - AddressesMap addressesMap - Symbols string `env:"HEZNODE_PRICEUPDATER_PROVIDER_SYMBOLS"` - Addresses string `env:"HEZNODE_PRICEUPDATER_PROVIDER_ADDRESSES"` -} - -type staticMap struct { - Statictokens map[uint]float64 -} - -// strToStaticTokensMap converts Statictokens mapping from text. -func (d *staticMap) strToStaticTokensMap(str string) error { - var lastErr error - if str != "" { - mapping := make(map[uint]float64) - elements := strings.Split(str, ",") - for i := 0; i < len(elements); i++ { - values := strings.Split(elements[i], "=") - tokenID, err := strconv.Atoi(values[0]) - if err != nil { - log.Error("Error converting string to int. Avoiding element: ", elements[i]) - lastErr = err - continue - } - if price, err := strconv.ParseFloat(values[1], 64); err != nil { - log.Error("function strToStaticTokensMap. Error converting string to float64. Avoiding element: ", - elements[i], " Error: ", err) - lastErr = err - continue - } else { - mapping[uint(tokenID)] = price - } - } - d.Statictokens = mapping - log.Debug("StaticToken mapping from config file: ", mapping) - } - return lastErr -} - -type symbolsMap struct { - Symbols map[uint]string -} - -// strToMapSymbol converts Symbols mapping from text. -func (d *symbolsMap) strToMapSymbol(str string) error { - var lastErr error - if str != "" { - mapping := make(map[uint]string) - elements := strings.Split(str, ",") - for i := 0; i < len(elements); i++ { - values := strings.Split(elements[i], "=") - tokenID, err := strconv.Atoi(values[0]) - if err != nil { - log.Error("function strToMapSymbol. Error converting string to int. Avoiding element: ", elements[i]) - lastErr = err - continue - } - if values[1] == UpdateMethodTypeIgnore || values[1] == "" { - mapping[uint(tokenID)] = UpdateMethodTypeIgnore - } else { - mapping[uint(tokenID)] = values[1] - } - } - d.Symbols = mapping - log.Debug("Symbol mapping from config file: ", mapping) - } else { - d.Symbols = make(map[uint]string) - } - return lastErr -} - -type addressesMap struct { - Addresses map[uint]ethCommon.Address -} - -// strToMapAddress converts addresses mapping from text. -func (d *addressesMap) strToMapAddress(str string) error { - var lastErr error - if str != "" { - mapping := make(map[uint]ethCommon.Address) - elements := strings.Split(str, ",") - for i := 0; i < len(elements); i++ { - values := strings.Split(elements[i], "=") - tokenID, err := strconv.Atoi(values[0]) - if err != nil { - log.Error("function strToMapAddress. Error converting string to int. Avoiding element: ", elements[i]) - lastErr = err - continue - } - if values[1] == UpdateMethodTypeIgnore || values[1] == "" { - mapping[uint(tokenID)] = common.FFAddr - } else { - mapping[uint(tokenID)] = ethCommon.HexToAddress(values[1]) - } - } - d.Addresses = mapping - log.Debug("Address mapping from config file: ", mapping) - } else { - d.Addresses = make(map[uint]ethCommon.Address) - } - return lastErr -} - -// ProviderValidation method is for validation of Provider struct -func ProviderValidation(sl validator.StructLevel) { - Provider := sl.Current().Interface().(Provider) - if Provider.Symbols == "" && Provider.Addresses != "" { - sl.ReportError(Provider.Addresses, "Addresses", "Addresses", "notokens", "") - sl.ReportError(Provider.Symbols, "Symbols", "Symbols", "notokens", "") - return - } -} - -// PriceUpdater definition -type PriceUpdater struct { - db *historydb.HistoryDB - updateMethodsPriority []string - tokensList map[uint]historydb.TokenSymbolAndAddr - providers map[string]Provider - statictokensMap staticMap - fiat Fiat - clientProviders map[string]*sling.Sling -} - -// NewPriceUpdater is the constructor for the updater -func NewPriceUpdater( - updateMethodTypesPriority string, - providers []Provider, - staticTokens string, - fiat Fiat, - db *historydb.HistoryDB, -) (*PriceUpdater, error) { - priorityArr := strings.Split(string(updateMethodTypesPriority), ",") - var staticTokensMap staticMap - err := staticTokensMap.strToStaticTokensMap(staticTokens) - if err != nil { - return nil, tracerr.Wrap(err) - } - clientProviders := make(map[string]*sling.Sling) - // Init - tr := &http.Transport{ - MaxIdleConns: defaultMaxIdleConns, - IdleConnTimeout: defaultIdleConnTimeout, - DisableCompression: true, - } - httpClient := &http.Client{Transport: tr} - providersMap := make(map[string]Provider) - for i := 0; i < len(providers); i++ { - // create mappings - err := providers[i].SymbolsMap.strToMapSymbol(providers[i].Symbols) - if err != nil { - return nil, tracerr.Wrap(err) - } - err = providers[i].AddressesMap.strToMapAddress(providers[i].Addresses) - if err != nil { - return nil, tracerr.Wrap(err) - } - // Create Client providers for each provider - clientProviders[providers[i].Provider] = sling.New().Base(providers[i].BaseURL).Client(httpClient) - clientProviders["fiat"] = sling.New().Base(fiat.URL).Client(httpClient) - // Add provider to providersMap - providersMap[providers[i].Provider] = providers[i] - } - return &PriceUpdater{ - db: db, - updateMethodsPriority: priorityArr, - tokensList: map[uint]historydb.TokenSymbolAndAddr{}, - providers: providersMap, - statictokensMap: staticTokensMap, - fiat: fiat, - clientProviders: clientProviders, - }, nil -} - -func (p *PriceUpdater) getTokenPriceFromProvider(ctx context.Context, tokenID uint) (float64, error) { - for i := 0; i < len(p.updateMethodsPriority); i++ { - provider := p.providers[p.updateMethodsPriority[i]] - var url string - if _, ok := provider.AddressesMap.Addresses[tokenID]; ok { - if provider.AddressesMap.Addresses[tokenID] == common.EmptyAddr { - url = "simple/price?ids=ethereum" + provider.URLExtraParams - } else { - url = provider.URL + provider.AddressesMap.Addresses[tokenID].String() + provider.URLExtraParams - } - } else { - url = provider.URL + provider.SymbolsMap.Symbols[tokenID] + provider.URLExtraParams - } - req, err := p.clientProviders[provider.Provider].New().Get(url).Request() - if err != nil { - return 0, tracerr.Wrap(err) - } - var ( - res *http.Response - result float64 - isEmptyResult bool - ) - switch provider.Provider { - case UpdateMethodTypeBitFinexV2: - var data interface{} - res, err = p.clientProviders[provider.Provider].Do(req.WithContext(ctx), &data, nil) - if data != nil { - // The token price is received inside an array in the sixth position - result = data.([]interface{})[6].(float64) - } else { - isEmptyResult = true - } - case UpdateMethodTypeCoingeckoV3: - if provider.AddressesMap.Addresses[tokenID] == common.EmptyAddr { - var data map[string]map[string]float64 - res, err = p.clientProviders[provider.Provider].Do(req.WithContext(ctx), &data, nil) - result = data["ethereum"]["usd"] - if len(data) == 0 { - isEmptyResult = true - } - } else { - var data map[ethCommon.Address]map[string]float64 - res, err = p.clientProviders[provider.Provider].Do(req.WithContext(ctx), &data, nil) - result = data[provider.AddressesMap.Addresses[tokenID]]["usd"] - if len(data) == 0 { - isEmptyResult = true - } - } - default: - log.Error("Unknown price provider: ", provider.Provider) - return 0, tracerr.Wrap(fmt.Errorf("Error: Unknown price provider: " + provider.Provider)) - } - if err != nil || isEmptyResult || res.StatusCode != http.StatusOK { - var errMsg strings.Builder - errMsg.WriteString("Trying another price provider if it's possible.") - if err != nil { - errMsg.WriteString(" - Error: " + err.Error()) - } - if res != nil { - errMsg.WriteString(fmt.Sprintf(" - HTTP Error: %d %s", res.StatusCode, res.Status)) - } - errMsg.WriteString(fmt.Sprintf(" - TokenID: %d - URL: %s", tokenID, url)) - log.Warn(errMsg.String()) - continue - } else { - return result, nil - } - } - return 0, tracerr.Wrap(fmt.Errorf("Error getting price. All providers have failed")) -} - -// UpdatePrices is triggered by the Coordinator, and internally will update the -// token prices in the db -func (p *PriceUpdater) UpdatePrices(ctx context.Context) { - // Update static prices - for tokenID, price := range p.statictokensMap.Statictokens { - if err := p.db.UpdateTokenValueByTokenID(tokenID, price); err != nil { - log.Errorw("token price not updated (db error)", - "err", err) - } - } - // Update token prices but ignore ones - for _, token := range p.tokensList { - if p.providers[p.updateMethodsPriority[0]].AddressesMap.Addresses[token.TokenID] != common.FFAddr || - p.providers[p.updateMethodsPriority[0]].SymbolsMap.Symbols[token.TokenID] == UpdateMethodTypeIgnore { - tokenPrice, err := p.getTokenPriceFromProvider(ctx, token.TokenID) - if err != nil { - log.Errorw("token price from provider error", "err", err, "token", token.Symbol) - } else if err := p.db.UpdateTokenValueByTokenID(token.TokenID, tokenPrice); err != nil { - log.Errorw("token price not updated (db error)", - "err", err, "token", token.Symbol) - } - } - } -} - -// UpdateTokenList get the registered token symbols from HistoryDB -func (p *PriceUpdater) UpdateTokenList() error { - dbTokens, err := p.db.GetTokenSymbolsAndAddrs() - if err != nil { - return tracerr.Wrap(err) - } - // For each token from the DB - for _, dbToken := range dbTokens { - // If the token doesn't exists in the config list, - // add it with default update method - if _, ok := p.statictokensMap.Statictokens[dbToken.TokenID]; ok { - continue - } else { - if !(p.providers[p.updateMethodsPriority[0]].SymbolsMap.Symbols[dbToken.TokenID] == UpdateMethodTypeIgnore || - p.providers[p.updateMethodsPriority[0]].AddressesMap.Addresses[dbToken.TokenID] == common.FFAddr) { - p.tokensList[dbToken.TokenID] = dbToken - } - } - for _, provider := range p.providers { - switch provider.Provider { - case UpdateMethodTypeBitFinexV2: - if _, ok := provider.SymbolsMap.Symbols[dbToken.TokenID]; !ok { - provider.SymbolsMap.Symbols[dbToken.TokenID] = dbToken.Symbol - } - case UpdateMethodTypeCoingeckoV3: - if _, ok := provider.AddressesMap.Addresses[dbToken.TokenID]; !ok { - provider.AddressesMap.Addresses[dbToken.TokenID] = dbToken.Addr - } - default: - log.Warn("This price provider is not supported: ", provider.Provider) - return tracerr.Wrap(fmt.Errorf("Error: Unknown price provider: " + provider.Provider)) - } - } - } - return nil -} - -type fiatExchangeAPI struct { - Base string - Rates interface{} -} - -func (p *PriceUpdater) getFiatPrices(ctx context.Context) (map[string]interface{}, error) { - url := "latest?base=" + p.fiat.BaseCurrency + "&symbols=" + p.fiat.Currencies + "&access_key=" + p.fiat.APIKey - req, err := p.clientProviders["fiat"].New().Get(url).Request() - if err != nil { - return make(map[string]interface{}), tracerr.Wrap(err) - } - var ( - res *http.Response - result map[string]interface{} - data *fiatExchangeAPI - ) - res, err = p.clientProviders["fiat"].Do(req.WithContext(ctx), &data, nil) - if err != nil { - return make(map[string]interface{}), tracerr.Wrap(err) - } - if data != nil { - result = data.Rates.(map[string]interface{}) - } else { - log.Error("Error: data got are empty. Http code: ", res.StatusCode, ". URL: ", url) - return make(map[string]interface{}), tracerr.Wrap(fmt.Errorf("Empty data received from the fiat provider")) - } - return result, nil -} - -// UpdateFiatPrices updates the fiat prices -func (p *PriceUpdater) UpdateFiatPrices(ctx context.Context) error { - log.Debug("Updating fiat prices") - // Retrieve fiat prices - prices, err := p.getFiatPrices(ctx) - if err != nil { - return tracerr.Wrap(err) - } - // Getting all price from database with baseCurrency USD - currencies, err := p.db.GetAllFiatPrice("USD") - if err != nil { - return tracerr.Wrap(err) - } - for token, pr := range prices { - price := pr.(float64) - var exist bool - for i := 0; i < len(currencies); i++ { - if token == currencies[i].Currency { - exist = true - } - } - if exist { - if err = p.db.UpdateFiatPrice(token, "USD", price); err != nil { - log.Error("DB error updating fiat currency price: ", token, ", ", price, " Error: ", err) - } - } else { - if err = p.db.CreateFiatPrice(token, "USD", price); err != nil { - log.Error("DB error creating fiat currency price: ", token, ", ", price, " Error: ", err) - } - } - } - return err -} - -// UpdateFiatPricesMock updates the fiat prices -func (p *PriceUpdater) UpdateFiatPricesMock(ctx context.Context) error { - log.Debug("Updating fiat prices") - // Retrieve fiat prices - prices := make(map[string]interface{}) - prices["CNY"] = 6.4306 - prices["EUR"] = 0.817675 - prices["JPY"] = 108.709503 - prices["GBP"] = 0.70335 - - // Getting all price from database with baseCurrency USD - currencies, err := p.db.GetAllFiatPrice("USD") - if err != nil { - return tracerr.Wrap(err) - } - for token, pr := range prices { - price := pr.(float64) - var exist bool - for i := 0; i < len(currencies); i++ { - if token == currencies[i].Currency { - exist = true - } - } - if exist { - if err = p.db.UpdateFiatPrice(token, "USD", price); err != nil { - log.Error("DB error updating fiat currency price: ", token, ", ", price, " Error: ", err) - } - } else { - if err = p.db.CreateFiatPrice(token, "USD", price); err != nil { - log.Error("DB error creating fiat currency price: ", token, ", ", price, " Error: ", err) - } - } - } - return err -} diff --git a/priceupdater/priceupdater_test.go b/priceupdater/priceupdater_test.go deleted file mode 100644 index 6e5a66621..000000000 --- a/priceupdater/priceupdater_test.go +++ /dev/null @@ -1,160 +0,0 @@ -package priceupdater - -import ( - "context" - "testing" - - ethCommon "github.com/ethereum/go-ethereum/common" - "github.com/hermeznetwork/hermez-node/common" - dbUtils "github.com/hermeznetwork/hermez-node/db" - "github.com/hermeznetwork/hermez-node/db/historydb" - "github.com/hermeznetwork/hermez-node/test" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" -) - -var historyDB *historydb.HistoryDB - -const usdtAddr = "0xdac17f958d2ee523a2206206994597c13d831ec7" -const daiAddr = "0x6b175474e89094c44da98b954eedeac495271d0f" - -func TestPriceUpdater(t *testing.T) { - // Init DB - db, err := dbUtils.InitTestSQLDB() - if err != nil { - panic(err) - } - historyDB = historydb.NewHistoryDB(db, db, nil) - // Clean DB - test.WipeDB(historyDB.DB()) - // Populate DB - // Gen blocks and add them to DB - blocks := test.GenBlocks(1, 2) - require.NoError(t, historyDB.AddBlocks(blocks)) - // Gen tokens and add them to DB - tokens := []common.Token{ - { - TokenID: 1, - EthBlockNum: blocks[0].Num, - EthAddr: ethCommon.HexToAddress(daiAddr), - Name: "DAI", - Symbol: "DAI", - Decimals: 18, - }, // Used to test get by token symbol - { - TokenID: 2, - EthBlockNum: blocks[0].Num, - EthAddr: ethCommon.HexToAddress(usdtAddr), - Name: "Tether", - Symbol: "USDT", - Decimals: 18, - }, // Used to test get by SC addr - { - TokenID: 3, - EthBlockNum: blocks[0].Num, - EthAddr: ethCommon.HexToAddress("0x2"), - Name: "FOO", - Symbol: "FOO", - Decimals: 18, - }, // Used to test ignore - { - TokenID: 4, - EthBlockNum: blocks[0].Num, - EthAddr: ethCommon.HexToAddress("0x3"), - Name: "BAR", - Symbol: "BAR", - Decimals: 18, - }, // Used to test static - { - TokenID: 5, - EthBlockNum: blocks[0].Num, - EthAddr: ethCommon.HexToAddress("0x1f9840a85d5af5bf1d1762f925bdaddc4201f984"), - Name: "Uniswap", - Symbol: "UNI", - Decimals: 18, - }, // Used to test default - } - require.NoError(t, historyDB.AddTokens(tokens)) // ETH token exist in DB by default - // Update token price used to test ignore - ignoreValue := 44.44 - require.NoError(t, historyDB.UpdateTokenValue(tokens[2].EthAddr, ignoreValue)) - - // Prepare token config - tc := []Provider{ - { - Provider: "bitfinexV2", - BaseURL: "https://api-pub.bitfinex.com/v2/", - URL: "ticker/t", - URLExtraParams: "USD", - Symbols: "1=DAI,2=USDT,3=ignore,5=UNI", - }, - { - Provider: "CoinGeckoV3", - BaseURL: "https://api.coingecko.com/api/v3/", - URL: "simple/token_price/ethereum?contract_addresses=", - URLExtraParams: "&vs_currencies=usd", - Addresses: "1=" + daiAddr + ",2=" + usdtAddr + ",3=ignore,5=0x1f9840a85d5af5bf1d1762f925bdaddc4201f984", - }, - } - var priority string = "bitfinexV2,CoinGeckoV3" - var staticTokens = "4=30.02" - var fiat = Fiat{ - APIKey: "FFFFFFFFFF", - URL: "https://api.exchangeratesapi.io/v1/", - BaseCurrency: "USD", - Currencies: "CNY,EUR,JPY,GBP", - } - - // Init price updater - pu, err := NewPriceUpdater( - priority, - tc, - staticTokens, - fiat, - historyDB, - ) - require.NoError(t, err) - - // Update token list - require.NoError(t, pu.UpdateTokenList()) - - // Update prices - pu.UpdatePrices(context.Background()) - - // Check results: get tokens from DB - fetchedTokens, err := historyDB.GetTokensTest() - require.NoError(t, err) - // Check that tokens that are updated via API have value: - // ETH - require.NotNil(t, fetchedTokens[0].USDUpdate) - assert.Greater(t, *fetchedTokens[0].USD, 0.0) - // DAI - require.NotNil(t, fetchedTokens[1].USDUpdate) - assert.Greater(t, *fetchedTokens[1].USD, 0.0) - // USDT - require.NotNil(t, fetchedTokens[2].USDUpdate) - assert.Greater(t, *fetchedTokens[2].USD, 0.0) - // UNI - require.NotNil(t, fetchedTokens[5].USDUpdate) - assert.Greater(t, *fetchedTokens[5].USD, 0.0) - // Check ignored token - assert.Equal(t, ignoreValue, *fetchedTokens[3].USD) - // Check static value - assert.Equal(t, 30.02, *fetchedTokens[4].USD) - - // Get fiat currencies: get currencies from DB - fetchedcurrencies, err := historyDB.GetAllFiatPrice("USD") - require.NoError(t, err) - assert.Equal(t, 0, len(fetchedcurrencies)) - //Update fiat currencies - err = pu.UpdateFiatPricesMock(context.Background()) - require.NoError(t, err) - - // Check results: get fiat currencies from DB - fetchedcurrencies, err = historyDB.GetAllFiatPrice("USD") - require.NoError(t, err) - assert.Greater(t, fetchedcurrencies[0].Price, 0.0) - assert.Greater(t, fetchedcurrencies[1].Price, 0.0) - assert.Greater(t, fetchedcurrencies[2].Price, 0.0) - assert.Greater(t, fetchedcurrencies[3].Price, 0.0) -}