Julien Riou
ebbd4ac2cd
- Return bytes for each request to the Flexpool API - Delegate JSON marshalling the higher functions - Use int64 when possible BREAKING CHANGE: database structure has to be updated to use integer instead of real. Please follow instructions to upgrade from 1.0 to 1.1. Signed-off-by: Julien Riou <julien@riou.xyz>
207 lines
5.3 KiB
Go
207 lines
5.3 KiB
Go
package main
|
|
|
|
import (
|
|
"encoding/json"
|
|
"fmt"
|
|
"io/ioutil"
|
|
"net/http"
|
|
"sort"
|
|
"time"
|
|
|
|
log "github.com/sirupsen/logrus"
|
|
)
|
|
|
|
// FlexpoolAPIURL constant to store Flexpool API URL
|
|
const FlexpoolAPIURL = "https://api.flexpool.io/v2"
|
|
|
|
// MaxIterations to avoid infinite loop while requesting paged routes on Flexpool API
|
|
const MaxIterations = 10
|
|
|
|
// UserAgent to identify ourselves on the Flexpool API
|
|
var UserAgent = fmt.Sprintf("flexassistant/%s", AppVersion)
|
|
|
|
// FlexpoolClient to store the HTTP client
|
|
type FlexpoolClient struct {
|
|
client *http.Client
|
|
}
|
|
|
|
// NewFlexpoolClient to create a client to manage Flexpool API calls
|
|
func NewFlexpoolClient() *FlexpoolClient {
|
|
return &FlexpoolClient{
|
|
client: &http.Client{Timeout: time.Second * 3},
|
|
}
|
|
}
|
|
|
|
// request to create an HTTPS request, call the Flexpool API, detect errors and return the result in bytes
|
|
func (f *FlexpoolClient) request(url string) ([]byte, error) {
|
|
log.Debugf("Requesting %s", url)
|
|
|
|
request, err := http.NewRequest("GET", url, nil)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
request.Header.Set("User-Agent", UserAgent)
|
|
|
|
resp, err := f.client.Do(request)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
defer resp.Body.Close()
|
|
|
|
jsonBody, err := ioutil.ReadAll(resp.Body)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
var result map[string]interface{}
|
|
json.Unmarshal(jsonBody, &result)
|
|
|
|
if result["error"] != nil {
|
|
return nil, fmt.Errorf("Flexpool API error: %s", result["error"].(string))
|
|
}
|
|
return jsonBody, nil
|
|
}
|
|
|
|
// BalanceResponse represents the JSON structure of the Flexpool API response for balance
|
|
type BalanceResponse struct {
|
|
Error string `json:"error"`
|
|
Result struct {
|
|
Balance int64 `json:"balance"`
|
|
} `json:"result"`
|
|
}
|
|
|
|
// MinerBalance returns the current unpaid balance
|
|
func (f *FlexpoolClient) MinerBalance(coin string, address string) (int64, error) {
|
|
body, err := f.request(fmt.Sprintf("%s/miner/balance?coin=%s&address=%s", FlexpoolAPIURL, coin, address))
|
|
if err != nil {
|
|
return 0, err
|
|
}
|
|
|
|
var response BalanceResponse
|
|
json.Unmarshal(body, &response)
|
|
return response.Result.Balance, nil
|
|
}
|
|
|
|
// PaymentsResponse represents the JSON structure of the Flexpool API response for payments
|
|
type PaymentsResponse struct {
|
|
Error string `json:"error"`
|
|
Result struct {
|
|
Data []struct {
|
|
Hash string `json:"hash"`
|
|
Value int64 `json:"value"`
|
|
Timestamp int64 `json:"timestamp"`
|
|
} `json:"data"`
|
|
} `json:"result"`
|
|
}
|
|
|
|
// MinerPayments returns an ordered list of payments
|
|
func (f *FlexpoolClient) MinerPayments(coin string, address string, limit int) (payments []*Payment, err error) {
|
|
page := 0
|
|
for {
|
|
body, err := f.request(fmt.Sprintf("%s/miner/payments/?coin=%s&address=%s&page=%d", FlexpoolAPIURL, coin, address, page))
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
var response PaymentsResponse
|
|
json.Unmarshal(body, &response)
|
|
|
|
for _, result := range response.Result.Data {
|
|
payment := NewPayment(
|
|
result.Hash,
|
|
result.Value,
|
|
result.Timestamp,
|
|
)
|
|
payments = append(payments, payment)
|
|
if len(payments) >= limit {
|
|
// sort by timestamp
|
|
sort.Slice(payments, func(p1, p2 int) bool {
|
|
return payments[p1].Timestamp > payments[p2].Timestamp
|
|
})
|
|
return payments, nil
|
|
}
|
|
}
|
|
page = page + 1
|
|
if page > MaxIterations {
|
|
return nil, fmt.Errorf("Max iterations of %d reached", MaxIterations)
|
|
}
|
|
}
|
|
}
|
|
|
|
// WorkersResponse represents the JSON structure of the Flexpool API response for workers
|
|
type WorkersResponse struct {
|
|
Error string `json:"error"`
|
|
Result []struct {
|
|
Name string `json:"name"`
|
|
IsOnline bool `json:"isOnline"`
|
|
LastSteen int64 `json:"lastSeen"`
|
|
} `json:"result"`
|
|
}
|
|
|
|
// MinerWorkers returns a list of workers given a miner address
|
|
func (f *FlexpoolClient) MinerWorkers(coin string, address string) (workers []*Worker, err error) {
|
|
body, err := f.request(fmt.Sprintf("%s/miner/workers?coin=%s&address=%s", FlexpoolAPIURL, coin, address))
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
var response WorkersResponse
|
|
json.Unmarshal(body, &response)
|
|
|
|
for _, result := range response.Result {
|
|
worker := NewWorker(
|
|
address,
|
|
result.Name,
|
|
result.IsOnline,
|
|
time.Unix(result.LastSteen, 0),
|
|
)
|
|
workers = append(workers, worker)
|
|
}
|
|
return workers, nil
|
|
}
|
|
|
|
// BlocksResponse represents the JSON structure of the Flexpool API response for blocks
|
|
type BlocksResponse struct {
|
|
Error string `json:"error"`
|
|
Result struct {
|
|
Data []struct {
|
|
Hash string `json:"hash"`
|
|
Number int64 `json:"number"`
|
|
Reward int64 `json:"reward"`
|
|
} `json:"data"`
|
|
} `json:"result"`
|
|
}
|
|
|
|
// PoolBlocks returns an ordered list of blocks
|
|
func (f *FlexpoolClient) PoolBlocks(coin string, limit int) (blocks []*Block, err error) {
|
|
page := 0
|
|
for {
|
|
body, err := f.request(fmt.Sprintf("%s/pool/blocks/?coin=%s&page=%d", FlexpoolAPIURL, coin, page))
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
var response BlocksResponse
|
|
json.Unmarshal(body, &response)
|
|
|
|
for _, result := range response.Result.Data {
|
|
block := NewBlock(
|
|
result.Hash,
|
|
result.Number,
|
|
result.Reward,
|
|
)
|
|
blocks = append(blocks, block)
|
|
if len(blocks) >= limit {
|
|
// sort by number
|
|
sort.Slice(blocks, func(b1, b2 int) bool {
|
|
return blocks[b1].Number < blocks[b2].Number
|
|
})
|
|
return blocks, nil
|
|
}
|
|
}
|
|
page = page + 1
|
|
if page > MaxIterations {
|
|
return nil, fmt.Errorf("Max iterations of %d reached", MaxIterations)
|
|
}
|
|
}
|
|
}
|