1
0
Fork 0

refactor code organization

This commit is contained in:
Massaki Archambault 2021-09-24 21:30:22 -04:00
parent 7f8b73055e
commit b8172f06ab
9 changed files with 413 additions and 214 deletions

View File

@ -22,19 +22,46 @@ type metricInfo struct {
// Exporter collect metrics from the helium blockchain api and exports them as prometheus metrics.
type Exporter struct {
Accounts []Account
Accounts []Account
StartTime time.Time
}
// NewExporter returns an initialized Exporter
func NewExporter(accountAddress []string) (*Exporter, error) {
accounts := make([]Account, 0)
for _, accountAddress := range accountAddress {
if accountAddress != "" {
accounts = append(accounts, NewAccount(accountAddress))
}
}
return &Exporter{
Accounts: accounts,
StartTime: time.Now(),
}, nil
}
// Account represents a helium account
type Account struct {
hotspots []Hotspot
hash string
Address string
Tx AccountTx
}
// Hotspot represent a helium hotspot
type Hotspot struct {
name string
hash string
func NewAccount(address string) Account {
return Account{
Address: address,
Tx: AccountTx{
DepositTotal: 0,
WithdrawalTotal: 0,
LastUpdate: time.Now(),
// LastUpdate: time.Time{},
},
}
}
type AccountTx struct {
DepositTotal int
WithdrawalTotal int
LastUpdate time.Time
}
const (
@ -42,7 +69,24 @@ const (
)
var (
// lables
// Activity classification
accountDepositActivities = []string{
"payment_v1", // if payee == account_address
"payment_v2", // if payee == account_address
"rewards_v1",
"unstake_validator_v1",
}
accountWithdrawalActivities = []string{
"add_gateway_v1",
"assert_location_v1",
"assert_location_v2",
"payment_v1", // if payer == account_address
"payment_v2", // if payer == account_address
"stake_validator_v1",
"token_burn_v1",
}
// labels
commonAccountLabels = []string{"account"}
commonHotspotLabels = append(commonAccountLabels, "hotspot", "hotspot_name")
@ -61,43 +105,43 @@ var (
// helium stats metrics
statsValidators = metricInfo{
prometheus.NewDesc(
prometheus.BuildFQName(namespace, "stats", "validators_total"),
prometheus.BuildFQName(namespace, "stats", "validators"),
"The total number of validators.",
nil, nil,
),
prometheus.CounterValue,
prometheus.GaugeValue,
}
statsOuis = metricInfo{
prometheus.NewDesc(
prometheus.BuildFQName(namespace, "stats", "ouis_total"),
prometheus.BuildFQName(namespace, "stats", "ouis"),
"The total number of organization unique identifiers.",
nil, nil,
),
prometheus.CounterValue,
prometheus.GaugeValue,
}
statsHotspotsDataOnly = metricInfo{
prometheus.NewDesc(
prometheus.BuildFQName(namespace, "stats", "hotspots_dataonly_total"),
prometheus.BuildFQName(namespace, "stats", "hotspots_dataonly"),
"The total number of data only hotspots.",
nil, nil,
),
prometheus.CounterValue,
prometheus.GaugeValue,
}
statsBlocks = metricInfo{
prometheus.NewDesc(
prometheus.BuildFQName(namespace, "stats", "blocks_total"),
prometheus.BuildFQName(namespace, "stats", "blocks"),
"The total height/number of blocks in the blockchain.",
nil, nil,
),
prometheus.CounterValue,
prometheus.GaugeValue,
}
statsChallenges = metricInfo{
prometheus.NewDesc(
prometheus.BuildFQName(namespace, "stats", "challenges_total"),
prometheus.BuildFQName(namespace, "stats", "challenges"),
"The total number of challenges.",
nil, nil,
),
prometheus.CounterValue,
prometheus.GaugeValue,
}
statsCities = metricInfo{
prometheus.NewDesc(
@ -109,7 +153,7 @@ var (
}
statsConsensusGroups = metricInfo{
prometheus.NewDesc(
prometheus.BuildFQName(namespace, "stats", "consensus_groups_total"),
prometheus.BuildFQName(namespace, "stats", "consensus_groups"),
"The total number of consensus groups.",
nil, nil,
),
@ -125,15 +169,15 @@ var (
}
statsHotspots = metricInfo{
prometheus.NewDesc(
prometheus.BuildFQName(namespace, "stats", "hotspots_total"),
prometheus.BuildFQName(namespace, "stats", "hotspots"),
"The total number of hotspots.",
nil, nil,
),
prometheus.CounterValue,
prometheus.GaugeValue,
}
statsTokenSupply = metricInfo{
prometheus.NewDesc(
prometheus.BuildFQName(namespace, "stats", "token_supply"),
prometheus.BuildFQName(namespace, "stats", "token"),
"The total supply of HNT tokens in circulation.",
nil, nil,
),
@ -165,22 +209,22 @@ var (
),
prometheus.CounterValue,
}
// accountDepositsHnt = metricInfo{
// prometheus.NewDesc(
// prometheus.BuildFQName(namespace, "account", "deposits_hnt_total"),
// "The number of HNT token deposited to this account.",
// commonAccountLabels, nil,
// ),
// prometheus.CounterValue,
// }
// accountWithdrawalsHnt = metricInfo{
// prometheus.NewDesc(
// prometheus.BuildFQName(namespace, "account", "withdrawals_hnt_total"),
// "The number of HNT token withdrawn from this account.",
// commonAccountLabels, nil,
// ),
// prometheus.CounterValue,
// }
accountDepositsHnt = metricInfo{
prometheus.NewDesc(
prometheus.BuildFQName(namespace, "account", "deposits_hnt_total"),
"The number of HNT tokens deposited to this account.",
commonAccountLabels, nil,
),
prometheus.CounterValue,
}
accountWithdrawalsHnt = metricInfo{
prometheus.NewDesc(
prometheus.BuildFQName(namespace, "account", "withdrawals_hnt_total"),
"The number of HNT tokens withdrawn from this account.",
commonAccountLabels, nil,
),
prometheus.CounterValue,
}
// helium hotspot metrics
hotspotUp = metricInfo{
@ -275,8 +319,8 @@ func (e *Exporter) Describe(ch chan<- *prometheus.Desc) {
ch <- accountBalanceHnt.Desc
ch <- accountActivity.Desc
ch <- accountRewardsHnt.Desc
// ch <- accountDepositsHnt.Desc
// ch <- accountWithdrawalsHnt.Desc
ch <- accountDepositsHnt.Desc
ch <- accountWithdrawalsHnt.Desc
ch <- hotspotUp.Desc
ch <- hotspotRelayed.Desc
@ -354,19 +398,24 @@ func (e *Exporter) collectStatsMetrics(ch chan<- prometheus.Metric) {
// collectStatsMetrics collect metrics in the account group from the helium api
func (e *Exporter) collectAccountMetrics(ch chan<- prometheus.Metric, account *Account) {
accountForAddress, err := heliumapi.GetAccountForAddress(account.hash)
accountForAddress, err := heliumapi.GetAccountForAddress(account.Address)
if err != nil {
fmt.Println(err)
return
}
accountActivityForAddress, err := heliumapi.GetActivityCountsForAccount(account.hash)
accountActivityForAddress, err := heliumapi.GetActivityCountsForAccount(account.Address)
if err != nil {
fmt.Println(err)
return
}
accountRewardTotalsForAddress, err := heliumapi.GetRewardTotalsForAccount(account.hash, &time.Time{}, nil)
accountRewardTotalsForAddress, err := heliumapi.GetRewardTotalsForAccount(account.Address, &e.StartTime, nil)
if err != nil {
fmt.Println(err)
return
}
tx, err := account.computeTransactions()
if err != nil {
fmt.Println(err)
return
@ -374,23 +423,31 @@ func (e *Exporter) collectAccountMetrics(ch chan<- prometheus.Metric, account *A
ch <- prometheus.MustNewConstMetric(
accountBalanceHnt.Desc, accountBalanceHnt.Type, float64(accountForAddress.Data.Balance),
account.hash,
account.Address,
)
for accType, count := range accountActivityForAddress.Data {
ch <- prometheus.MustNewConstMetric(
accountActivity.Desc, accountActivity.Type, float64(count),
account.hash, accType,
account.Address, accType,
)
}
ch <- prometheus.MustNewConstMetric(
accountRewardsHnt.Desc, accountRewardsHnt.Type, accountRewardTotalsForAddress.Data.Sum,
account.hash,
account.Address,
)
ch <- prometheus.MustNewConstMetric(
accountDepositsHnt.Desc, accountDepositsHnt.Type, float64(tx.DepositTotal),
account.Address,
)
ch <- prometheus.MustNewConstMetric(
accountWithdrawalsHnt.Desc, accountWithdrawalsHnt.Type, float64(tx.WithdrawalTotal),
account.Address,
)
}
// collectStatsMetrics collect metrics in the hotspot group from the helium api
func (e *Exporter) collectHotspotMetrics(ch chan<- prometheus.Metric, account *Account) {
hotspotsForAddress, err := heliumapi.GetHotspotsForAccount(account.hash)
hotspotsForAddress, err := heliumapi.GetHotspotsForAccount(account.Address)
if err != nil {
fmt.Println(err)
return
@ -403,7 +460,7 @@ func (e *Exporter) collectHotspotMetrics(ch chan<- prometheus.Metric, account *A
return
}
hotspotRewardTotalsForAddress, err := heliumapi.GetRewardsTotalForHotspot(hotspotData.Address, &time.Time{}, nil)
hotspotRewardTotalsForAddress, err := heliumapi.GetRewardsTotalForHotspot(hotspotData.Address, &e.StartTime, nil)
if err != nil {
fmt.Println(err)
return
@ -411,58 +468,52 @@ func (e *Exporter) collectHotspotMetrics(ch chan<- prometheus.Metric, account *A
ch <- prometheus.MustNewConstMetric(
hotspotUp.Desc, hotspotUp.Type, bool2Float64(hotspotData.Status.Online == "online"),
account.hash, hotspotData.Address, hotspotData.Name,
account.Address, hotspotData.Address, hotspotData.Name,
)
ch <- prometheus.MustNewConstMetric(
hotspotRelayed.Desc, hotspotRelayed.Type, bool2Float64(len(hotspotData.Status.ListenAddrs) > 0 && strings.HasPrefix(hotspotData.Status.ListenAddrs[0], "/p2p")),
account.hash, hotspotData.Address, hotspotData.Name,
account.Address, hotspotData.Address, hotspotData.Name,
)
ch <- prometheus.MustNewConstMetric(
hotspotBlocks.Desc, hotspotBlocks.Type, float64(hotspotData.Status.Height),
account.hash, hotspotData.Address, hotspotData.Name,
account.Address, hotspotData.Address, hotspotData.Name,
)
ch <- prometheus.MustNewConstMetric(
hotspotRewardsScale.Desc, hotspotRewardsScale.Type, float64(hotspotData.RewardScale),
account.hash, hotspotData.Address, hotspotData.Name,
account.Address, hotspotData.Address, hotspotData.Name,
)
ch <- prometheus.MustNewConstMetric(
hotspotGeocodeInfo.Desc, hotspotGeocodeInfo.Type, 1.0,
account.hash, hotspotData.Address, hotspotData.Name, strconv.FormatFloat(hotspotData.Lng, 'f', 6, 64), strconv.FormatFloat(hotspotData.Lat, 'f', 6, 64), hotspotData.Geocode.LongStreet, hotspotData.Geocode.LongState, hotspotData.Geocode.LongCountry, hotspotData.Geocode.LongCity,
account.Address, hotspotData.Address, hotspotData.Name, strconv.FormatFloat(hotspotData.Lng, 'f', 6, 64), strconv.FormatFloat(hotspotData.Lat, 'f', 6, 64), hotspotData.Geocode.LongStreet, hotspotData.Geocode.LongState, hotspotData.Geocode.LongCountry, hotspotData.Geocode.LongCity,
)
ch <- prometheus.MustNewConstMetric(
hotspotAntennaInfo.Desc, hotspotAntennaInfo.Type, 1.0,
account.hash, hotspotData.Address, hotspotData.Name, strconv.Itoa(hotspotData.Gain), strconv.Itoa(hotspotData.Elevation),
account.Address, hotspotData.Address, hotspotData.Name, strconv.Itoa(hotspotData.Gain), strconv.Itoa(hotspotData.Elevation),
)
for accType, count := range hotspotActivityForAddress.Data {
ch <- prometheus.MustNewConstMetric(
hotspotActivity.Desc, hotspotActivity.Type, float64(count),
account.hash, hotspotData.Address, hotspotData.Name, accType,
account.Address, hotspotData.Address, hotspotData.Name, accType,
)
}
ch <- prometheus.MustNewConstMetric(
hotspotRewardsHnt.Desc, hotspotRewardsHnt.Type, hotspotRewardTotalsForAddress.Data.Sum,
account.hash, hotspotData.Address, hotspotData.Name,
account.Address, hotspotData.Address, hotspotData.Name,
)
}
}
// NewExporter returns an initialized Exporter
func NewExporter(accountHashs []string) (*Exporter, error) {
accounts := make([]Account, 0)
for _, accountHash := range accountHashs {
if accountHash != "" {
accounts = append(accounts, NewAccount(accountHash))
}
func (a *Account) computeTransactions() (*AccountTx, error) {
now := time.Now()
_, err := heliumapi.GetActivityForAccount(a.Address, []string{}, &a.Tx.LastUpdate, &now)
if err != nil {
return nil, err
}
return &Exporter{
Accounts: accounts,
}, nil
}
func NewAccount(hash string) Account {
return Account{
hash: hash,
}
// fmt.Println(activities)
a.Tx.LastUpdate = now
return &a.Tx, nil
}
func main() {

View File

@ -4,61 +4,10 @@ import (
"encoding/json"
"fmt"
"time"
"github.com/helium-blockchain-exporter/heliumapi/activity"
)
type Account struct {
Data struct {
Address string `json:"address"`
Balance int `json:"balance"`
Block int `json:"block"`
DCBalance int `json:"dc_balance"`
DCNonce int `json:"dc_nonce"`
SECBalance int `json:"sec_balance"`
SECNonce int `json:"sec_nonce"`
SpeculativeNonce int `json:"speculative_nonce"`
} `json:"data"`
}
type AccountHotspots struct {
Data []struct {
Lng float64 `json:"lng"`
Lat float64 `json:"lat"`
TimestampAdded string `json:"timestamp_added"`
Status struct {
Timestamp string `json:"timestamp"`
Online string `json:"online"`
ListenAddrs []string `json:"listen_addrs"`
Height int `json:"height"`
} `json:"status"`
RewardScale float64 `json:"reward_scale"`
Payer string `json:"payer"`
Owner string `json:"owner"`
Nonce int `json:"nonce"`
Name string `json:"name"`
Mode string `json:"mode"`
LocationHex string `json:"location_hex"`
Location string `json:"location"`
LastPocChallenge int `json:"last_poc_challenge"`
LastChangeBlock int `json:"last_change_block"`
Geocode struct {
ShortStreet string `json:"short_street"`
ShortState string `json:"short_state"`
ShortCountry string `json:"short_country"`
ShortCity string `json:"short_city"`
LongStreet string `json:"long_street"`
LongState string `json:"long_state"`
LongCountry string `json:"long_country"`
LongCity string `json:"long_city"`
CityID string `json:"city_id"`
} `json:"geocode"`
Gain int `json:"gain"`
Elevation int `json:"elevation"`
BlockAdded int `json:"block_added"`
Block int `json:"block"`
Address string `json:"address"`
} `json:"data"`
}
func GetAccountForAddress(account string) (*Account, error) {
path := "/v1/accounts/" + account
@ -78,6 +27,36 @@ func GetAccountForAddress(account string) (*Account, error) {
return &respobject, nil
}
func GetActivityForAccount(account string, filterTypes []string, minTime *time.Time, maxTime *time.Time) (*activity.Activities, error) {
path := "/v1/accounts/" + account + "/activity"
params := map[string]string{}
if minTime != nil {
params["min_time"] = minTime.UTC().Format("2006-01-02T15:04:05Z")
}
if maxTime != nil {
params["max_time"] = maxTime.UTC().Format("2006-01-02T15:04:05Z")
}
// query the api
resBodies, err := getHeliumApiWithCursor(path, &params)
if err != nil {
return nil, err
}
// unmarshal the responses and merge them all together
combinedResp := activity.ActivityResp{}
for _, respBody := range resBodies {
activityResp := activity.ActivityResp{}
err = json.Unmarshal(respBody, &activityResp)
if err != nil {
return nil, fmt.Errorf("failed to unmarshal response from path %v: %v", path, err)
}
combinedResp.Data = append(combinedResp.Data, activityResp.Data...)
}
return activity.NewActivities(combinedResp)
}
func GetActivityCountsForAccount(account string) (*ActivityCounts, error) {
path := "/v1/accounts/" + account + "/activity/count"

View File

@ -0,0 +1,43 @@
package activity
import (
"encoding/json"
"fmt"
)
type ActivityResp struct {
Data []json.RawMessage `json:"data"`
}
type Activities struct{}
func NewActivities(resp ActivityResp) (*Activities, error) {
type ActivityType struct {
Type string `json:"type"`
}
activityType := ActivityType{}
activities := Activities{}
for _, activityRaw := range resp.Data {
if err := json.Unmarshal(activityRaw, &activityType); err != nil {
return nil, fmt.Errorf("failed to unmarshal activity: %v", err)
}
fmt.Println(activityType.Type)
switch activityType.Type {
case "add_gateway_v1":
case "assert_location_v1":
case "assert_location_v2":
case "payment_v1":
case "payment_v2":
case "rewards_v1":
case "stake_validator_v1":
case "token_burn_v1":
case "unstake_validator_v1":
default:
fmt.Printf("unimplemented activy type: %v", activityType.Type)
}
}
return &activities, nil
}

View File

@ -0,0 +1 @@
package activity

View File

@ -6,27 +6,6 @@ import (
"time"
)
type ActivityCounts struct {
Data map[string]int
}
type RewardTotal struct {
Meta struct {
MinTime string `json:"min_time"`
MaxTime string `json:"max_time"`
} `json:"meta"`
Data struct {
Total float64 `json:"total"`
Sum float64 `json:"sum"`
Stddev float64 `json:"stddev"`
Min float64 `json:"min"`
Median float64 `json:"median"`
Max float64 `json:"max"`
Avg float64 `json:"avg"`
} `json:"data"`
}
func GetHotspotActivityCount(hotspot string) (*ActivityCounts, error) {
path := "/v1/hotspots/" + hotspot + "/activity/count"

View File

@ -5,13 +5,6 @@ import (
"fmt"
)
type CurrentOraclePrice struct {
Data struct {
Price int `json:"price"`
Block int `json:"block"`
} `json:"data"`
}
func GetCurrentOraclePrice() (*CurrentOraclePrice, error) {
const path = "/v1/oracle/prices/current"

View File

@ -1,6 +1,7 @@
package heliumapi
import (
"encoding/json"
"fmt"
"io/ioutil"
"net/http"
@ -14,7 +15,7 @@ var (
client = &http.Client{}
)
func getHeliumApi(path string, params *map[string]string) ([]byte, error) {
func createGetRequest(path string, params map[string]string) (*http.Request, error) {
req, err := http.NewRequest("GET", fmt.Sprintf("%s/%s", apiUrl, path), nil)
// setup headers
@ -23,7 +24,7 @@ func getHeliumApi(path string, params *map[string]string) ([]byte, error) {
// setup query param if there are any
if params != nil {
query := req.URL.Query()
for k, v := range *params {
for k, v := range params {
query.Add(k, v)
}
if err != nil {
@ -32,6 +33,20 @@ func getHeliumApi(path string, params *map[string]string) ([]byte, error) {
req.URL.RawQuery = query.Encode()
}
return req, err
}
func getHeliumApi(path string, params *map[string]string) ([]byte, error) {
// if params is nil, set it to an empty map
if params == nil {
params = &map[string]string{}
}
req, err := createGetRequest(path, *params)
if err != nil {
return nil, fmt.Errorf("failed to create query request %v: %v", path, err)
}
// query the api
resp, err := client.Do(req)
if err != nil {
@ -48,6 +63,56 @@ func getHeliumApi(path string, params *map[string]string) ([]byte, error) {
return body, nil
}
func getHeliumApiWithCursor(path string, params *map[string]string) ([]byte, error) {
return nil, nil
func getHeliumApiWithCursor(path string, params *map[string]string) ([][]byte, error) {
// if params is nil, set it to an empty map
if params == nil {
params = &map[string]string{}
}
type ResponseWithCursor struct {
Cursor string `json:"cursor"`
}
res := [][]byte{}
respCursor := ResponseWithCursor{}
req, err := createGetRequest(path, *params)
if err != nil {
return nil, fmt.Errorf("failed to create query request %v: %v", path, err)
}
for {
// query the api
resp, err := client.Do(req)
if err != nil {
return nil, fmt.Errorf("failed to query path %v: %v", path, err)
}
defer resp.Body.Close()
// read the response body and add it to the result array
body, err := ioutil.ReadAll(resp.Body)
if err != nil {
return nil, fmt.Errorf("failed to read response body of path %v: %v", path, err)
}
res = append(res, body)
// parse the response body for a cursor
respCursor.Cursor = ""
err = json.Unmarshal(body, &respCursor)
if err != nil {
return nil, fmt.Errorf("failed to unmarshal response from path %v: %v", path, err)
}
// continue querying until there is no longer a cursor
if respCursor.Cursor != "" {
params = &map[string]string{"cursor": respCursor.Cursor}
req, err = createGetRequest(path, *params)
if err != nil {
return nil, fmt.Errorf("failed to create query request %v: %v", path, err)
}
} else {
break
}
}
return res, nil
}

View File

@ -5,64 +5,6 @@ import (
"fmt"
)
type BlockchainStats struct {
Data struct {
BlockTime struct {
LastDay struct {
Avg float64 `json:"avg"`
Stddev float64 `json:"stddev"`
} `json:"last_day"`
LastHour struct {
Avg float64 `json:"avg"`
Stddev float64 `json:"stddev"`
} `json:"last_hour"`
LastMonth struct {
Avg float64 `json:"avg"`
Stddev float64 `json:"stddev"`
} `json:"last_month"`
LastWeek struct {
Avg float64 `json:"avg"`
Stddev float64 `json:"stddev"`
} `json:"last_week"`
} `json:"block_times"`
ChallengeCount struct {
Active int `json:"active"`
LastDay int `json:"last_day"`
} `json:"challenge_counts"`
Counts struct {
Validators int `json:"validators"`
Ouis int `json:"ouis"`
HotspotsDataonly int `json:"hotspots_dataonly"`
Blocks int `json:"blocks"`
Challenges int `json:"challenges"`
Cities int `json:"cities"`
ConsensusGroups int `json:"consensus_groups"`
Countries int `json:"countries"`
Hotspots int `json:"hotspots"`
Transactions int `json:"transactions"`
} `json:"counts"`
ElectionTimes struct {
LastDay struct {
Avg float64 `json:"avg"`
Stddev float64 `json:"stddev"`
} `json:"last_day"`
LastHour struct {
Avg float64 `json:"avg"`
Stddev float64 `json:"stddev"`
} `json:"last_hour"`
LastMonth struct {
Avg float64 `json:"avg"`
Stddev float64 `json:"stddev"`
} `json:"last_month"`
LastWeek struct {
Avg float64 `json:"avg"`
Stddev float64 `json:"stddev"`
} `json:"last_week"`
} `json:"election_times"`
TokenSupply float64 `json:"token_supply"`
} `json:"data"`
}
func GetBlockchainStats() (*BlockchainStats, error) {
const path = "/v1/stats"

146
heliumapi/types.go Normal file
View File

@ -0,0 +1,146 @@
package heliumapi
type Account struct {
Data struct {
Address string `json:"address"`
Balance int `json:"balance"`
Block int `json:"block"`
DCBalance int `json:"dc_balance"`
DCNonce int `json:"dc_nonce"`
SECBalance int `json:"sec_balance"`
SECNonce int `json:"sec_nonce"`
SpeculativeNonce int `json:"speculative_nonce"`
} `json:"data"`
}
// Time int `json:"time"`
// StartEpoch int `json:"start_epoch"`
// Height int `json:"height"`
// Hash string `json:"hash"`
// EndEpoch int `json:"end_epoch"`
type AccountHotspots struct {
Data []struct {
Lng float64 `json:"lng"`
Lat float64 `json:"lat"`
TimestampAdded string `json:"timestamp_added"`
Status struct {
Timestamp string `json:"timestamp"`
Online string `json:"online"`
ListenAddrs []string `json:"listen_addrs"`
Height int `json:"height"`
} `json:"status"`
RewardScale float64 `json:"reward_scale"`
Payer string `json:"payer"`
Owner string `json:"owner"`
Nonce int `json:"nonce"`
Name string `json:"name"`
Mode string `json:"mode"`
LocationHex string `json:"location_hex"`
Location string `json:"location"`
LastPocChallenge int `json:"last_poc_challenge"`
LastChangeBlock int `json:"last_change_block"`
Geocode struct {
ShortStreet string `json:"short_street"`
ShortState string `json:"short_state"`
ShortCountry string `json:"short_country"`
ShortCity string `json:"short_city"`
LongStreet string `json:"long_street"`
LongState string `json:"long_state"`
LongCountry string `json:"long_country"`
LongCity string `json:"long_city"`
CityID string `json:"city_id"`
} `json:"geocode"`
Gain int `json:"gain"`
Elevation int `json:"elevation"`
BlockAdded int `json:"block_added"`
Block int `json:"block"`
Address string `json:"address"`
} `json:"data"`
}
type ActivityCounts struct {
Data map[string]int
}
type RewardTotal struct {
Meta struct {
MinTime string `json:"min_time"`
MaxTime string `json:"max_time"`
} `json:"meta"`
Data struct {
Total float64 `json:"total"`
Sum float64 `json:"sum"`
Stddev float64 `json:"stddev"`
Min float64 `json:"min"`
Median float64 `json:"median"`
Max float64 `json:"max"`
Avg float64 `json:"avg"`
} `json:"data"`
}
type CurrentOraclePrice struct {
Data struct {
Price int `json:"price"`
Block int `json:"block"`
} `json:"data"`
}
type BlockchainStats struct {
Data struct {
BlockTime struct {
LastDay struct {
Avg float64 `json:"avg"`
Stddev float64 `json:"stddev"`
} `json:"last_day"`
LastHour struct {
Avg float64 `json:"avg"`
Stddev float64 `json:"stddev"`
} `json:"last_hour"`
LastMonth struct {
Avg float64 `json:"avg"`
Stddev float64 `json:"stddev"`
} `json:"last_month"`
LastWeek struct {
Avg float64 `json:"avg"`
Stddev float64 `json:"stddev"`
} `json:"last_week"`
} `json:"block_times"`
ChallengeCount struct {
Active int `json:"active"`
LastDay int `json:"last_day"`
} `json:"challenge_counts"`
Counts struct {
Validators int `json:"validators"`
Ouis int `json:"ouis"`
HotspotsDataonly int `json:"hotspots_dataonly"`
Blocks int `json:"blocks"`
Challenges int `json:"challenges"`
Cities int `json:"cities"`
ConsensusGroups int `json:"consensus_groups"`
Countries int `json:"countries"`
Hotspots int `json:"hotspots"`
Transactions int `json:"transactions"`
} `json:"counts"`
ElectionTimes struct {
LastDay struct {
Avg float64 `json:"avg"`
Stddev float64 `json:"stddev"`
} `json:"last_day"`
LastHour struct {
Avg float64 `json:"avg"`
Stddev float64 `json:"stddev"`
} `json:"last_hour"`
LastMonth struct {
Avg float64 `json:"avg"`
Stddev float64 `json:"stddev"`
} `json:"last_month"`
LastWeek struct {
Avg float64 `json:"avg"`
Stddev float64 `json:"stddev"`
} `json:"last_week"`
} `json:"election_times"`
TokenSupply float64 `json:"token_supply"`
} `json:"data"`
}