Skip to content

Commit

Permalink
Add /status endpoint
Browse files Browse the repository at this point in the history
  • Loading branch information
mike76-dev committed Mar 18, 2024
1 parent 3b2339d commit 6a7ec5a
Show file tree
Hide file tree
Showing 4 changed files with 132 additions and 0 deletions.
15 changes: 15 additions & 0 deletions api/api.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,21 @@ type GatewayPeer struct {
Version string `json:"version"`
}

// Balance combines mature and immature values.
type Balance struct {
Siacoins types.Currency `json:"siacoins"`
ImmatureSiacoins types.Currency `json:"immatureSiacoins"`
}

// NodeStatusResponse is the response type for /node/status.
type NodeStatusResponse struct {
Version string `json:"version"`
Height uint64 `json:"heightMainnet"`
HeightZen uint64 `json:"heightZen"`
Balance Balance `json:"balanceMainnet"`
BalanceZen Balance `json:"balanceZen"`
}

// ConsensusTipResponse is the response type for /consensus/tip.
type ConsensusTipResponse struct {
Network string `json:"network"`
Expand Down
6 changes: 6 additions & 0 deletions api/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,12 @@ type Client struct {
c jape.Client
}

// NodeStatus returns the status of the node.
func (c *Client) NodeStatus() (resp NodeStatusResponse, err error) {
err = c.c.GET("/node/status", &resp)
return
}

// TxpoolTransactions returns all transactions in the transaction pool.
func (c *Client) TxpoolTransactions(network string) (txns []types.Transaction, v2txns []types.V2Transaction, err error) {
var resp TxpoolTransactionsResponse
Expand Down
50 changes: 50 additions & 0 deletions api/server.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import (
"time"

"github.com/mike76-dev/hostscore/hostdb"
"github.com/mike76-dev/hostscore/internal/build"
"github.com/mike76-dev/hostscore/internal/walletutil"
"go.sia.tech/core/consensus"
"go.sia.tech/core/types"
Expand Down Expand Up @@ -35,6 +36,53 @@ func isSynced(s *syncer.Syncer) bool {
return count >= 5
}

func (s *server) nodeStatusHandler(jc jape.Context) {
height := s.cm.TipState().Index.Height
heightZen := s.cmZen.TipState().Index.Height

scos, _, err := s.w.UnspentOutputs("mainnet")
if jc.Check("couldn't load Mainnet outputs", err) != nil {
return
}

var sc, immature types.Currency
for _, sco := range scos {
if height >= sco.MaturityHeight {
sc = sc.Add(sco.SiacoinOutput.Value)
} else {
immature = immature.Add(sco.SiacoinOutput.Value)
}
}

scosZen, _, err := s.w.UnspentOutputs("zen")
if jc.Check("couldn't load Zen outputs", err) != nil {
return
}

var scZen, immatureZen types.Currency
for _, sco := range scosZen {
if heightZen >= sco.MaturityHeight {
scZen = scZen.Add(sco.SiacoinOutput.Value)
} else {
immatureZen = immatureZen.Add(sco.SiacoinOutput.Value)
}
}

jc.Encode(NodeStatusResponse{
Version: build.NodeVersion,
Height: height,
HeightZen: heightZen,
Balance: Balance{
Siacoins: sc,
ImmatureSiacoins: immature,
},
BalanceZen: Balance{
Siacoins: scZen,
ImmatureSiacoins: immatureZen,
},
})
}

func (s *server) consensusNetworkHandler(jc jape.Context) {
var network string
if jc.DecodeForm("network", &network) != nil {
Expand Down Expand Up @@ -319,6 +367,8 @@ func NewServer(cm *chain.Manager, cmZen *chain.Manager, s *syncer.Syncer, sZen *
hdb: hdb,
}
return jape.Mux(map[string]jape.Handler{
"GET /node/status": srv.nodeStatusHandler,

"GET /consensus/network": srv.consensusNetworkHandler,
"GET /consensus/tip": srv.consensusTipHandler,
"GET /consensus/tipstate": srv.consensusTipStateHandler,
Expand Down
61 changes: 61 additions & 0 deletions cmd/hsc/api.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,12 +14,15 @@ import (
client "github.com/mike76-dev/hostscore/api"
"github.com/mike76-dev/hostscore/external"
"github.com/mike76-dev/hostscore/hostdb"
"github.com/mike76-dev/hostscore/internal/build"
rhpv2 "go.sia.tech/core/rhp/v2"
rhpv3 "go.sia.tech/core/rhp/v3"
"go.sia.tech/core/types"
"go.uber.org/zap"
)

var lowBalanceThreshold = types.Siacoins(200)

type APIResponse struct {
Status string `json:"status"`
Message string `json:"message"`
Expand Down Expand Up @@ -49,6 +52,22 @@ type benchmarksResponse struct {
Benchmarks []hostdb.BenchmarkHistory `json:"benchmarks"`
}

type nodeStatus struct {
Location string `json:"location"`
Status bool `json:"status"`
Version string `json:"version"`
Height uint64 `json:"heightMainnet"`
HeightZen uint64 `json:"heightZen"`
Balance string `json:"balanceMainnet"`
BalanceZen string `json:"balanceZen"`
}

type statusResponse struct {
APIResponse
Version string `json:"version"`
Nodes []nodeStatus `json:"nodes"`
}

type nodeInteractions struct {
Uptime time.Duration `json:"uptime"`
Downtime time.Duration `json:"downtime"`
Expand Down Expand Up @@ -171,6 +190,9 @@ func (api *portalAPI) buildHTTPRoutes() {
router.GET("/benchmarks", func(w http.ResponseWriter, req *http.Request, ps httprouter.Params) {
api.benchmarksHandler(w, req, ps)
})
router.GET("/status", func(w http.ResponseWriter, req *http.Request, ps httprouter.Params) {
api.statusHandler(w, req, ps)
})

api.mu.Lock()
api.router = *router
Expand Down Expand Up @@ -461,6 +483,45 @@ func (api *portalAPI) benchmarksHandler(w http.ResponseWriter, req *http.Request
})
}

func balanceStatus(balance types.Currency) string {
if balance.IsZero() {
return "empty"
}
if balance.Cmp(lowBalanceThreshold) < 0 {
return "low"
}
return "ok"
}

func (api *portalAPI) statusHandler(w http.ResponseWriter, _ *http.Request, _ httprouter.Params) {
var nodes []nodeStatus
for n, c := range api.clients {
status, err := c.NodeStatus()
if err != nil {
api.log.Error("couldn't get node status", zap.String("node", n), zap.Error(err))
nodes = append(nodes, nodeStatus{
Location: n,
Status: false,
})
} else {
nodes = append(nodes, nodeStatus{
Location: n,
Status: true,
Version: status.Version,
Height: status.Height,
HeightZen: status.HeightZen,
Balance: balanceStatus(status.Balance.Siacoins),
BalanceZen: balanceStatus(status.BalanceZen.Siacoins),
})
}
}
writeJSON(w, statusResponse{
APIResponse: APIResponse{Status: "ok"},
Version: build.ClientVersion,
Nodes: nodes,
})
}

func writeJSON(w http.ResponseWriter, obj interface{}) {
w.Header().Set("Content-Type", "application/json; charset=utf-8")
err := json.NewEncoder(w).Encode(obj)
Expand Down

0 comments on commit 6a7ec5a

Please sign in to comment.