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.

117 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,
  39. error) {
  40. tokenSymbols := []string{}
  41. if !apiType.valid() {
  42. return nil, tracerr.Wrap(fmt.Errorf("Invalid apiType: %v", apiType))
  43. }
  44. return &PriceUpdater{
  45. db: db,
  46. apiURL: apiURL,
  47. apiType: apiType,
  48. tokenSymbols: tokenSymbols,
  49. }, nil
  50. }
  51. func getTokenPriceBitfinex(ctx context.Context, client *sling.Sling,
  52. tokenSymbol string) (float64, error) {
  53. state := [10]float64{}
  54. req, err := client.New().Get("ticker/t" + tokenSymbol + "USD").Request()
  55. if err != nil {
  56. return 0, tracerr.Wrap(err)
  57. }
  58. res, err := client.Do(req.WithContext(ctx), &state, nil)
  59. if err != nil {
  60. return 0, tracerr.Wrap(err)
  61. }
  62. if res.StatusCode != http.StatusOK {
  63. return 0, tracerr.Wrap(fmt.Errorf("http response is not is %v", res.StatusCode))
  64. }
  65. return state[6], nil
  66. }
  67. // UpdatePrices is triggered by the Coordinator, and internally will update the
  68. // token prices in the db
  69. func (p *PriceUpdater) UpdatePrices(ctx context.Context) {
  70. tr := &http.Transport{
  71. MaxIdleConns: defaultMaxIdleConns,
  72. IdleConnTimeout: defaultIdleConnTimeout,
  73. DisableCompression: true,
  74. }
  75. httpClient := &http.Client{Transport: tr}
  76. client := sling.New().Base(p.apiURL).Client(httpClient)
  77. for _, tokenSymbol := range p.tokenSymbols {
  78. var tokenPrice float64
  79. var err error
  80. switch p.apiType {
  81. case APITypeBitFinexV2:
  82. tokenPrice, err = getTokenPriceBitfinex(ctx, client, tokenSymbol)
  83. }
  84. if ctx.Err() != nil {
  85. return
  86. }
  87. if err != nil {
  88. log.Warnw("token price not updated (get error)",
  89. "err", err, "token", tokenSymbol, "apiType", p.apiType)
  90. }
  91. if err = p.db.UpdateTokenValue(tokenSymbol, tokenPrice); err != nil {
  92. log.Errorw("token price not updated (db error)",
  93. "err", err, "token", tokenSymbol, "apiType", p.apiType)
  94. }
  95. }
  96. }
  97. // UpdateTokenList get the registered token symbols from HistoryDB
  98. func (p *PriceUpdater) UpdateTokenList() error {
  99. tokenSymbols, err := p.db.GetTokenSymbols()
  100. if err != nil {
  101. return tracerr.Wrap(err)
  102. }
  103. p.tokenSymbols = tokenSymbols
  104. return nil
  105. }