* First price-updater implementation * Lint corrections * Structs revision * Read Write Mutex * Review price updater and apply small changes Co-authored-by: Toni Ramírez <toni@iden3.com> Co-authored-by: Eduard S <eduard@iden3.io>feature/sql-semaphore1
@ -0,0 +1,136 @@ |
|||||
|
package priceupdater |
||||
|
|
||||
|
import ( |
||||
|
"errors" |
||||
|
"fmt" |
||||
|
"net/http" |
||||
|
"sync" |
||||
|
"time" |
||||
|
|
||||
|
"github.com/dghubble/sling" |
||||
|
) |
||||
|
|
||||
|
var ( |
||||
|
// ErrSymbolDoesNotExistInDatabase is used when trying to get a token that is not in the DB
|
||||
|
ErrSymbolDoesNotExistInDatabase = errors.New("symbol does not exist in database") |
||||
|
) |
||||
|
|
||||
|
// ConfigPriceUpdater contains the configuration set by the coordinator
|
||||
|
type ConfigPriceUpdater struct { |
||||
|
RecommendedFee uint64 // in dollars
|
||||
|
RecommendedCreateAccountFee uint64 // in dollars
|
||||
|
TokensList []string |
||||
|
APIURL string |
||||
|
} |
||||
|
|
||||
|
// TokenInfo contains the updated value for the token
|
||||
|
type TokenInfo struct { |
||||
|
Symbol string |
||||
|
Value float64 |
||||
|
LastUpdated time.Time |
||||
|
} |
||||
|
|
||||
|
// PriceUpdater definition
|
||||
|
type PriceUpdater struct { |
||||
|
db map[string]TokenInfo |
||||
|
config ConfigPriceUpdater |
||||
|
mu sync.RWMutex |
||||
|
} |
||||
|
|
||||
|
// NewPriceUpdater is the constructor for the updater
|
||||
|
func NewPriceUpdater(config ConfigPriceUpdater) PriceUpdater { |
||||
|
|
||||
|
return PriceUpdater{ |
||||
|
db: make(map[string]TokenInfo), |
||||
|
config: config, |
||||
|
} |
||||
|
|
||||
|
} |
||||
|
|
||||
|
// UpdatePrices is triggered by the Coordinator, and internally will update the token prices in the db
|
||||
|
func (p *PriceUpdater) UpdatePrices() error { |
||||
|
|
||||
|
tr := &http.Transport{ |
||||
|
MaxIdleConns: 10, |
||||
|
IdleConnTimeout: 10 * time.Second, |
||||
|
DisableCompression: true, |
||||
|
} |
||||
|
httpClient := &http.Client{Transport: tr} |
||||
|
client := sling.New().Base(p.config.APIURL).Client(httpClient) |
||||
|
|
||||
|
state := [10]float64{} |
||||
|
|
||||
|
for _, tokenSymbol := range p.config.TokensList { |
||||
|
|
||||
|
resp, err := client.New().Get("ticker/t" + tokenSymbol + "USD").ReceiveSuccess(&state) |
||||
|
if err != nil { |
||||
|
return err |
||||
|
} |
||||
|
if resp.StatusCode != 200 { |
||||
|
return fmt.Errorf("Unexpected response status code: %v", resp.StatusCode) |
||||
|
} |
||||
|
|
||||
|
tinfo := TokenInfo{ |
||||
|
Symbol: tokenSymbol, |
||||
|
Value: state[6], |
||||
|
LastUpdated: time.Now(), |
||||
|
} |
||||
|
|
||||
|
p.UpdateTokenInfo(tinfo) |
||||
|
|
||||
|
} |
||||
|
|
||||
|
return nil |
||||
|
} |
||||
|
|
||||
|
// UpdateConfig allows to update the price-updater configuration
|
||||
|
func (p *PriceUpdater) UpdateConfig(config ConfigPriceUpdater) { |
||||
|
|
||||
|
p.mu.Lock() |
||||
|
defer p.mu.Unlock() |
||||
|
|
||||
|
p.config = config |
||||
|
|
||||
|
} |
||||
|
|
||||
|
// Get one token information
|
||||
|
func (p *PriceUpdater) Get(tokenSymbol string) (TokenInfo, error) { |
||||
|
|
||||
|
var info TokenInfo |
||||
|
|
||||
|
// Check if symbol exists in database
|
||||
|
p.mu.RLock() |
||||
|
defer p.mu.RUnlock() |
||||
|
|
||||
|
if info, ok := p.db[tokenSymbol]; ok { |
||||
|
return info, nil |
||||
|
} |
||||
|
|
||||
|
return info, ErrSymbolDoesNotExistInDatabase |
||||
|
|
||||
|
} |
||||
|
|
||||
|
// GetPrices gets all the prices contained in the db
|
||||
|
func (p *PriceUpdater) GetPrices() map[string]TokenInfo { |
||||
|
|
||||
|
var info = make(map[string]TokenInfo) |
||||
|
|
||||
|
p.mu.RLock() |
||||
|
defer p.mu.RUnlock() |
||||
|
|
||||
|
for key, value := range p.db { |
||||
|
info[key] = value |
||||
|
} |
||||
|
|
||||
|
return info |
||||
|
} |
||||
|
|
||||
|
// UpdateTokenInfo updates one token info
|
||||
|
func (p *PriceUpdater) UpdateTokenInfo(tokenInfo TokenInfo) { |
||||
|
|
||||
|
p.mu.Lock() |
||||
|
defer p.mu.Unlock() |
||||
|
|
||||
|
p.db[tokenInfo.Symbol] = tokenInfo |
||||
|
|
||||
|
} |
@ -0,0 +1,40 @@ |
|||||
|
package priceupdater |
||||
|
|
||||
|
import ( |
||||
|
"testing" |
||||
|
|
||||
|
"github.com/stretchr/testify/assert" |
||||
|
) |
||||
|
|
||||
|
func TestCon(t *testing.T) { |
||||
|
|
||||
|
config := ConfigPriceUpdater{ |
||||
|
|
||||
|
RecommendedFee: 1, |
||||
|
RecommendedCreateAccountFee: 1, |
||||
|
TokensList: []string{"ETH", "NEC"}, |
||||
|
APIURL: "https://api-pub.bitfinex.com/v2/", |
||||
|
} |
||||
|
|
||||
|
pud := NewPriceUpdater(config) |
||||
|
|
||||
|
err := pud.UpdatePrices() |
||||
|
assert.Equal(t, err, nil) |
||||
|
|
||||
|
info, _ := pud.Get("ETH") |
||||
|
assert.NotZero(t, info.Value) |
||||
|
|
||||
|
info2, _ := pud.Get("NEC") |
||||
|
assert.NotZero(t, info2.Value) |
||||
|
|
||||
|
info3, err := pud.Get("INVENTED") |
||||
|
if assert.Error(t, err) { |
||||
|
assert.Equal(t, ErrSymbolDoesNotExistInDatabase, err) |
||||
|
} |
||||
|
assert.Equal(t, info3.Value, float64(0)) |
||||
|
|
||||
|
prices := pud.GetPrices() |
||||
|
assert.Equal(t, prices["ETH"], info) |
||||
|
assert.Equal(t, prices["NEC"], info2) |
||||
|
|
||||
|
} |