diff --git a/api/service/explorer/service.go b/api/service/explorer/service.go index 32f6430f7..91b7357d5 100644 --- a/api/service/explorer/service.go +++ b/api/service/explorer/service.go @@ -4,18 +4,22 @@ import ( "context" "encoding/json" "fmt" + "math/big" "net" "net/http" "strconv" "time" + ethCommon "github.com/ethereum/go-ethereum/common" "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/common" "github.com/harmony-one/harmony/internal/utils" + "github.com/harmony-one/harmony/numeric" "github.com/harmony-one/harmony/p2p" stakingReward "github.com/harmony-one/harmony/staking/reward" ) @@ -84,7 +88,8 @@ func (s *Service) Init() { // Run is to run serving explorer. func (s *Service) Run() *http.Server { // Init address. - addr := net.JoinHostPort("", GetExplorerPort(s.Port)) + port := GetExplorerPort(s.Port) + addr := net.JoinHostPort("", port) s.router = mux.NewRouter() @@ -94,11 +99,11 @@ func (s *Service) Run() *http.Server { 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. + // Set up router for supply info + s.router.Path("/burn-addresses").Queries().HandlerFunc(s.GetInaccessibleAddressInfo).Methods("GET") + s.router.Path("/burn-addresses").HandlerFunc(s.GetInaccessibleAddressInfo) 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) @@ -152,7 +157,72 @@ func (s *Service) GetAddresses(w http.ResponseWriter, r *http.Request) { } } +var ( + // InaccessibleAddresses are a list of known eth addresses that cannot spend ONE tokens. + InaccessibleAddresses = []ethCommon.Address{ + // one10000000000000000000000000000dead5shlag + ethCommon.HexToAddress("0x7bDeF7Bdef7BDeF7BDEf7bDef7bdef7bdeF6E7AD"), + } +) + +// InaccessibleAddressInfo .. +type InaccessibleAddressInfo struct { + EthAddress ethCommon.Address `json:"eth-address"` + Address string `json:"address"` + Balance numeric.Dec `json:"balance"` + Nonce uint64 `json:"nonce"` +} + +// getAllInaccessibleAddresses information according to InaccessibleAddresses +func (s *Service) getAllInaccessibleAddresses() ([]*InaccessibleAddressInfo, error) { + state, err := s.blockchain.StateAt(s.blockchain.CurrentHeader().Root()) + if err != nil { + return nil, err + } + + accs := []*InaccessibleAddressInfo{} + for _, addr := range InaccessibleAddresses { + accs = append(accs, &InaccessibleAddressInfo{ + EthAddress: addr, + Address: common.MustAddressToBech32(addr), + Balance: numeric.NewDecFromBigIntWithPrec(state.GetBalance(addr), 18), + Nonce: state.GetNonce(addr), + }) + } + + return accs, nil +} + +// getTotalInaccessibleTokens in ONE at the latest header. +func (s *Service) getTotalInaccessibleTokens() (numeric.Dec, error) { + addrInfos, err := s.getAllInaccessibleAddresses() + if err != nil { + return numeric.Dec{}, err + } + + total := numeric.NewDecFromBigIntWithPrec(big.NewInt(0), 18) + for _, addr := range addrInfos { + total = total.Add(addr.Balance) + } + return total, nil +} + +// GetInaccessibleAddressInfo serves /burn-addresses end-point. +func (s *Service) GetInaccessibleAddressInfo(w http.ResponseWriter, r *http.Request) { + w.Header().Set("Content-Type", "application/json") + accInfos, err := s.getAllInaccessibleAddresses() + if err != nil { + utils.Logger().Warn().Err(err).Msg("unable to fetch inaccessible addresses") + w.WriteHeader(http.StatusInternalServerError) + } + if err := json.NewEncoder(w).Encode(accInfos); err != nil { + utils.Logger().Warn().Msg("cannot JSON-encode inaccessible account info") + w.WriteHeader(http.StatusInternalServerError) + } +} + // GetCirculatingSupply serves /circulating-supply end-point. +// Note that known InaccessibleAddresses have their funds removed from the supply for this endpoint. 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) @@ -160,13 +230,19 @@ func (s *Service) GetCirculatingSupply(w http.ResponseWriter, r *http.Request) { utils.Logger().Warn().Err(err).Msg("unable to fetch circulating supply") w.WriteHeader(http.StatusInternalServerError) } - if err := json.NewEncoder(w).Encode(circulatingSupply); err != nil { + totalInaccessible, err := s.getTotalInaccessibleTokens() + if err != nil { + utils.Logger().Warn().Err(err).Msg("unable to fetch inaccessible tokens") + w.WriteHeader(http.StatusInternalServerError) + } + if err := json.NewEncoder(w).Encode(circulatingSupply.Sub(totalInaccessible)); err != nil { utils.Logger().Warn().Msg("cannot JSON-encode circulating supply") w.WriteHeader(http.StatusInternalServerError) } } // GetTotalSupply serves /total-supply end-point. +// Note that known InaccessibleAddresses have their funds removed from the supply for this endpoint. func (s *Service) GetTotalSupply(w http.ResponseWriter, r *http.Request) { w.Header().Set("Content-Type", "application/json") totalSupply, err := stakingReward.GetTotalTokens(s.blockchain) @@ -174,7 +250,12 @@ func (s *Service) GetTotalSupply(w http.ResponseWriter, r *http.Request) { utils.Logger().Warn().Err(err).Msg("unable to fetch total supply") w.WriteHeader(http.StatusInternalServerError) } - if err := json.NewEncoder(w).Encode(totalSupply); err != nil { + totalInaccessible, err := s.getTotalInaccessibleTokens() + if err != nil { + utils.Logger().Warn().Err(err).Msg("unable to fetch inaccessible tokens") + w.WriteHeader(http.StatusInternalServerError) + } + if err := json.NewEncoder(w).Encode(totalSupply.Sub(totalInaccessible)); err != nil { utils.Logger().Warn().Msg("cannot JSON-encode total supply") w.WriteHeader(http.StatusInternalServerError) }