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