You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

115 lines
2.9 KiB

  1. package priceupdater
  2. import (
  3. "context"
  4. "fmt"
  5. "net/http"
  6. "time"
  7. "github.com/dghubble/sling"
  8. "github.com/hermeznetwork/hermez-node/db/historydb"
  9. "github.com/hermeznetwork/hermez-node/log"
  10. "github.com/hermeznetwork/tracerr"
  11. )
  12. const (
  13. defaultMaxIdleConns = 10
  14. defaultIdleConnTimeout = 2 * time.Second
  15. )
  16. // APIType defines the token exchange API
  17. type APIType string
  18. const (
  19. // APITypeBitFinexV2 is the http API used by bitfinex V2
  20. APITypeBitFinexV2 APIType = "bitfinexV2"
  21. )
  22. func (t *APIType) valid() bool {
  23. switch *t {
  24. case APITypeBitFinexV2:
  25. return true
  26. default:
  27. return false
  28. }
  29. }
  30. // PriceUpdater definition
  31. type PriceUpdater struct {
  32. db *historydb.HistoryDB
  33. apiURL string
  34. apiType APIType
  35. tokenSymbols []string
  36. }
  37. // NewPriceUpdater is the constructor for the updater
  38. func NewPriceUpdater(apiURL string, apiType APIType, db *historydb.HistoryDB) (*PriceUpdater, error) {
  39. tokenSymbols := []string{}
  40. if !apiType.valid() {
  41. return nil, tracerr.Wrap(fmt.Errorf("Invalid apiType: %v", apiType))
  42. }
  43. return &PriceUpdater{
  44. db: db,
  45. apiURL: apiURL,
  46. apiType: apiType,
  47. tokenSymbols: tokenSymbols,
  48. }, nil
  49. }
  50. func getTokenPriceBitfinex(ctx context.Context, client *sling.Sling,
  51. tokenSymbol string) (float64, error) {
  52. state := [10]float64{}
  53. req, err := client.New().Get("ticker/t" + tokenSymbol + "USD").Request()
  54. if err != nil {
  55. return 0, tracerr.Wrap(err)
  56. }
  57. res, err := client.Do(req.WithContext(ctx), &state, nil)
  58. if err != nil {
  59. return 0, tracerr.Wrap(err)
  60. }
  61. if res.StatusCode != http.StatusOK {
  62. return 0, tracerr.Wrap(fmt.Errorf("http response is not is %v", res.StatusCode))
  63. }
  64. return state[6], nil
  65. }
  66. // UpdatePrices is triggered by the Coordinator, and internally will update the token prices in the db
  67. func (p *PriceUpdater) UpdatePrices(ctx context.Context) {
  68. tr := &http.Transport{
  69. MaxIdleConns: defaultMaxIdleConns,
  70. IdleConnTimeout: defaultIdleConnTimeout,
  71. DisableCompression: true,
  72. }
  73. httpClient := &http.Client{Transport: tr}
  74. client := sling.New().Base(p.apiURL).Client(httpClient)
  75. for _, tokenSymbol := range p.tokenSymbols {
  76. var tokenPrice float64
  77. var err error
  78. switch p.apiType {
  79. case APITypeBitFinexV2:
  80. tokenPrice, err = getTokenPriceBitfinex(ctx, client, tokenSymbol)
  81. }
  82. if ctx.Err() != nil {
  83. return
  84. }
  85. if err != nil {
  86. log.Warnw("token price not updated (get error)",
  87. "err", err, "token", tokenSymbol, "apiType", p.apiType)
  88. }
  89. if err = p.db.UpdateTokenValue(tokenSymbol, tokenPrice); err != nil {
  90. log.Errorw("token price not updated (db error)",
  91. "err", err, "token", tokenSymbol, "apiType", p.apiType)
  92. }
  93. }
  94. }
  95. // UpdateTokenList get the registered token symbols from HistoryDB
  96. func (p *PriceUpdater) UpdateTokenList() error {
  97. tokenSymbols, err := p.db.GetTokenSymbols()
  98. if err != nil {
  99. return tracerr.Wrap(err)
  100. }
  101. p.tokenSymbols = tokenSymbols
  102. return nil
  103. }