Skip to content
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
98 changes: 47 additions & 51 deletions service.go
Original file line number Diff line number Diff line change
Expand Up @@ -67,10 +67,10 @@ type Service struct {
Router *http.ServeMux

// Data cached by the service, protected by a mutex.
sync.RWMutex
Vsps vspSet
WebInfo webInfo
PriceInfo priceInfo
Mutex sync.RWMutex
}

// NewService creates a new dcrwebapi service.
Expand All @@ -83,7 +83,6 @@ func NewService() *Service {
Timeout: time.Second * 10,
},
Router: http.NewServeMux(),
Mutex: sync.RWMutex{},

Vsps: vspSet{
"teststakepool.decred.org": Vsp{
Expand Down Expand Up @@ -152,12 +151,12 @@ func NewService() *Service {
// Start update ticker.
go func() {
for {
vspData(&service)
err := info(&service)
service.vspData()
err := service.info()
if err != nil {
log.Printf("Error updating web info: %v", err)
}
err = price(&service)
err = service.price()
if err != nil {
log.Printf("Error updating price info: %v", err)
}
Expand All @@ -172,14 +171,14 @@ func NewService() *Service {

// getHTTP will use the services HTTP client to send a GET request to the
// provided URL. Returns the response body, or an error.
func (service *Service) getHTTP(url string) ([]byte, error) {
func (s *Service) getHTTP(url string) ([]byte, error) {
req, err := http.NewRequest(http.MethodGet, url, nil)
if err != nil {
return nil, fmt.Errorf("%v: failed to create request: %w", url, err)
}

req.Header.Set("User-Agent", "decred/dcrweb bot")
poolResp, err := service.HTTPClient.Do(req)
poolResp, err := s.HTTPClient.Do(req)
if err != nil {
return nil, fmt.Errorf("%v: failed to send request: %w", url, err)
}
Expand All @@ -198,15 +197,10 @@ func (service *Service) getHTTP(url string) ([]byte, error) {
return respBody, nil
}

func vspStats(service *Service, url string) error {
var vsp Vsp

service.Mutex.RLock()
vsp = service.Vsps[url]
service.Mutex.RUnlock()
func (s *Service) vspStats(url string, vsp Vsp) error {
infoURL := fmt.Sprintf("https://%s/api/v3/vspinfo", url)

infoResp, err := service.getHTTP(infoURL)
infoResp, err := s.getHTTP(infoURL)
if err != nil {
return err
}
Expand All @@ -230,31 +224,33 @@ func vspStats(service *Service, url string) error {

vsp.LastUpdated = time.Now().Unix()

service.Mutex.Lock()
service.Vsps[url] = vsp
service.Mutex.Unlock()
s.Lock()
s.Vsps[url] = vsp
s.Unlock()

return nil
}

func vspData(service *Service) {
var waitGroup sync.WaitGroup
waitGroup.Add(len(service.Vsps))
for url := range service.Vsps {
go func(url string) {
defer waitGroup.Done()
err := vspStats(service, url)
func (s *Service) vspData() {
var wg sync.WaitGroup
s.RLock()
wg.Add(len(s.Vsps))
for url, vsp := range s.Vsps {
go func(url string, vsp Vsp) {
defer wg.Done()
err := s.vspStats(url, vsp)
if err != nil {
log.Println(err)
}
}(url)
}(url, vsp)
}
waitGroup.Wait()
s.RUnlock()
wg.Wait()
}

// dcrdata gets an API response from dcrdata and unmarshals it.
func (service *Service) dcrdata(path string, response interface{}) error {
body, err := service.getHTTP("https://dcrdata.decred.org/api" + path)
func (s *Service) dcrdata(path string, response interface{}) error {
body, err := s.getHTTP("https://dcrdata.decred.org/api" + path)
if err != nil {
return err
}
Expand All @@ -267,48 +263,48 @@ func (service *Service) dcrdata(path string, response interface{}) error {
return nil
}

func price(service *Service) error {
func (s *Service) price() error {
var exchange struct {
DcrPrice float64 `json:"dcrPrice"`
BtcPrice float64 `json:"btcPrice"`
}
err := service.dcrdata("/exchangerate", &exchange)
err := s.dcrdata("/exchangerate", &exchange)
if err != nil {
return err
}

service.Mutex.Lock()
service.PriceInfo = priceInfo{
s.Lock()
s.PriceInfo = priceInfo{
BitcoinUSD: exchange.BtcPrice,
DecredUSD: exchange.DcrPrice,
LastUpdated: time.Now().Unix(),
}
service.Mutex.Unlock()
s.Unlock()

return nil
}

func info(service *Service) error {
func (s *Service) info() error {
var supply apitypes.CoinSupply
err := service.dcrdata("/supply", &supply)
err := s.dcrdata("/supply", &supply)
if err != nil {
return err
}

var bestBlock apitypes.BlockDataBasic
err = service.dcrdata("/block/best", &bestBlock)
err = s.dcrdata("/block/best", &bestBlock)
if err != nil {
return err
}

var treasury dbtypes.TreasuryBalance
err = service.dcrdata("/treasury/balance", &treasury)
err = s.dcrdata("/treasury/balance", &treasury)
if err != nil {
return err
}

var subsidy apitypes.BlockSubsidies
err = service.dcrdata("/block/best/subsidy", &subsidy)
err = s.dcrdata("/block/best/subsidy", &subsidy)
if err != nil {
return err
}
Expand All @@ -318,8 +314,8 @@ func info(service *Service) error {
return dcrutil.Amount(atoms).ToCoin()
}

service.Mutex.Lock()
service.WebInfo = webInfo{
s.Lock()
s.WebInfo = webInfo{
Circulating: toDCR(supply.Mined),
Ultimate: toDCR(supply.Ultimate),
Staked: bestBlock.PoolInfo.Value,
Expand All @@ -329,20 +325,20 @@ func info(service *Service) error {
Height: bestBlock.Height,
LastUpdated: time.Now().Unix(),
}
service.Mutex.Unlock()
s.Unlock()

return nil
}

// HandleRoutes is the handler func for all endpoints exposed by the service
func (service *Service) HandleRoutes(writer http.ResponseWriter, request *http.Request) {
func (s *Service) HandleRoutes(writer http.ResponseWriter, request *http.Request) {
route := request.URL.Query().Get("c")
switch route {

case "vsp":
service.Mutex.RLock()
respJSON, err := json.Marshal(service.Vsps)
service.Mutex.RUnlock()
s.RLock()
respJSON, err := json.Marshal(s.Vsps)
s.RUnlock()
if err != nil {
writeJSONErrorResponse(&writer, err)
return
Expand All @@ -352,9 +348,9 @@ func (service *Service) HandleRoutes(writer http.ResponseWriter, request *http.R
return

case "webinfo":
service.Mutex.RLock()
respJSON, err := json.Marshal(service.WebInfo)
service.Mutex.RUnlock()
s.RLock()
respJSON, err := json.Marshal(s.WebInfo)
s.RUnlock()
if err != nil {
writeJSONErrorResponse(&writer, err)
return
Expand All @@ -364,9 +360,9 @@ func (service *Service) HandleRoutes(writer http.ResponseWriter, request *http.R
return

case "price":
service.Mutex.RLock()
respJSON, err := json.Marshal(service.PriceInfo)
service.Mutex.RUnlock()
s.RLock()
respJSON, err := json.Marshal(s.PriceInfo)
s.RUnlock()
if err != nil {
writeJSONErrorResponse(&writer, err)
return
Expand Down
Loading