The core protocol of WoopChain
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
woop/api/service/explorer/service.go

207 lines
6.6 KiB

package explorer
import (
"context"
"encoding/json"
"fmt"
"net"
"net/http"
"strconv"
"time"
"github.com/ethereum/go-ethereum/rpc"
"github.com/gorilla/mux"
msg_pb "github.com/harmony-one/harmony/api/proto/message"
"github.com/harmony-one/harmony/api/service/syncing"
"github.com/harmony-one/harmony/core"
"github.com/harmony-one/harmony/internal/chain"
"github.com/harmony-one/harmony/internal/utils"
"github.com/harmony-one/harmony/p2p"
stakingReward "github.com/harmony-one/harmony/staking/reward"
)
// Constants for explorer service.
const (
explorerPortDifference = 4000
defaultPageSize = "1000"
maxAddresses = 100000
)
// HTTPError is an HTTP error.
type HTTPError struct {
Code int
Msg string
}
// Service is the struct for explorer service.
type Service struct {
router *mux.Router
IP string
Port string
Storage *Storage
server *http.Server
messageChan chan *msg_pb.Message
stateSync *syncing.StateSync
blockchain *core.BlockChain
}
// New returns explorer service.
func New(selfPeer *p2p.Peer, ss *syncing.StateSync, bc *core.BlockChain) *Service {
return &Service{IP: selfPeer.IP, Port: selfPeer.Port, stateSync: ss, blockchain: bc}
}
// StartService starts explorer service.
func (s *Service) StartService() {
utils.Logger().Info().Msg("Starting explorer service.")
s.Init()
s.server = s.Run()
}
// StopService shutdowns explorer service.
func (s *Service) StopService() {
utils.Logger().Info().Msg("Shutting down explorer service.")
if err := s.server.Shutdown(context.Background()); err != nil {
utils.Logger().Error().Err(err).Msg("Error when shutting down explorer server")
} else {
utils.Logger().Info().Msg("Shutting down explorer server successfully")
}
}
// GetExplorerPort returns the port serving explorer dashboard. This port is explorerPortDifference less than the node port.
func GetExplorerPort(nodePort string) string {
if port, err := strconv.Atoi(nodePort); err == nil {
return fmt.Sprintf("%d", port-explorerPortDifference)
}
utils.Logger().Error().Msg("error on parsing.")
return ""
}
// Init is to initialize for ExplorerService.
func (s *Service) Init() {
s.Storage = GetStorageInstance(s.IP, s.Port)
}
// Run is to run serving explorer.
func (s *Service) Run() *http.Server {
// Init address.
addr := net.JoinHostPort("", GetExplorerPort(s.Port))
s.router = mux.NewRouter()
// Set up router for addresses.
// Fetch addresses request, accepts parameter size: how much addresses to read,
// parameter prefix: from which address prefix start
s.router.Path("/addresses").Queries("size", "{[0-9]*?}", "prefix", "{[a-zA-Z0-9]*?}").HandlerFunc(s.GetAddresses).Methods("GET")
s.router.Path("/addresses").HandlerFunc(s.GetAddresses)
// Set up router for node count.
s.router.Path("/circulating-supply").Queries().HandlerFunc(s.GetCirculatingSupply).Methods("GET")
s.router.Path("/circulating-supply").HandlerFunc(s.GetCirculatingSupply)
// Set up router for node count.
s.router.Path("/total-supply").Queries().HandlerFunc(s.GetTotalSupply).Methods("GET")
s.router.Path("/total-supply").HandlerFunc(s.GetTotalSupply)
// Set up router for node health check.
s.router.Path("/node-sync").Queries().HandlerFunc(s.GetNodeSync).Methods("GET")
s.router.Path("/node-sync").HandlerFunc(s.GetNodeSync)
// Do serving now.
utils.Logger().Info().Str("port", GetExplorerPort(s.Port)).Msg("[Explorer] Server started.")
server := &http.Server{
Addr: addr,
Handler: s.router,
ReadTimeout: 5 * time.Second,
WriteTimeout: 10 * time.Second,
IdleTimeout: 120 * time.Second,
}
go func() {
defer func() { utils.Logger().Debug().Msg("[Explorer] Server closed.") }()
if err := server.ListenAndServe(); err != nil {
utils.Logger().Warn().Err(err).Msg("[Explorer] Server error.")
}
}()
return server
}
// GetAddresses serves end-point /addresses, returns size of addresses from address with prefix.
func (s *Service) GetAddresses(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "application/json")
sizeStr := r.FormValue("size")
prefix := r.FormValue("prefix")
if sizeStr == "" {
sizeStr = defaultPageSize
}
data := &Data{}
defer func() {
if err := json.NewEncoder(w).Encode(data.Addresses); err != nil {
utils.Logger().Warn().Err(err).Msg("cannot JSON-encode addresses")
}
}()
size, err := strconv.Atoi(sizeStr)
if err != nil || size > maxAddresses {
w.WriteHeader(http.StatusBadRequest)
return
}
data.Addresses, err = s.Storage.GetAddresses(size, prefix)
if err != nil {
w.WriteHeader(http.StatusInternalServerError)
utils.Logger().Warn().Err(err).Msg("wasn't able to fetch addresses from storage")
return
}
}
// GetCirculatingSupply serves /circulating-supply end-point.
func (s *Service) GetCirculatingSupply(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "application/json")
circulatingSupply, err := chain.GetCirculatingSupply(context.Background(), s.blockchain)
if err != nil {
utils.Logger().Warn().Err(err).Msg("unable to fetch circulating supply")
w.WriteHeader(http.StatusInternalServerError)
}
if err := json.NewEncoder(w).Encode(circulatingSupply); err != nil {
utils.Logger().Warn().Msg("cannot JSON-encode circulating supply")
w.WriteHeader(http.StatusInternalServerError)
}
}
// GetTotalSupply serves /total-supply end-point.
func (s *Service) GetTotalSupply(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "application/json")
totalSupply, err := stakingReward.GetTotalTokens(s.blockchain)
if err != nil {
utils.Logger().Warn().Err(err).Msg("unable to fetch total supply")
w.WriteHeader(http.StatusInternalServerError)
}
if err := json.NewEncoder(w).Encode(totalSupply); err != nil {
utils.Logger().Warn().Msg("cannot JSON-encode total supply")
w.WriteHeader(http.StatusInternalServerError)
}
}
// GetNodeSync returns status code 500 if node is not in sync
func (s *Service) GetNodeSync(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "application/json")
sync := !s.stateSync.IsOutOfSync(s.blockchain, false)
if !sync {
w.WriteHeader(http.StatusTeapot)
}
if err := json.NewEncoder(w).Encode(sync); err != nil {
utils.Logger().Warn().Msg("cannot JSON-encode total supply")
w.WriteHeader(http.StatusInternalServerError)
}
}
// NotifyService notify service.
func (s *Service) NotifyService(params map[string]interface{}) {}
// SetMessageChan sets up message channel to service.
func (s *Service) SetMessageChan(messageChan chan *msg_pb.Message) {
s.messageChan = messageChan
}
// APIs for the services.
func (s *Service) APIs() []rpc.API {
return nil
}