* 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) |
|||
|
|||
} |