Add queries (#2318)

pull/2320/head
flicker-harmony 5 years ago committed by GitHub
parent c6b2739315
commit ec07541f79
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 550
      api/service/explorer/service.go

@ -7,31 +7,25 @@ import (
"math/big" "math/big"
"net" "net"
"net/http" "net/http"
"os"
"sort"
"strconv" "strconv"
"strings" "time"
"github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/rlp"
"github.com/ethereum/go-ethereum/rpc" "github.com/ethereum/go-ethereum/rpc"
"github.com/gorilla/mux" "github.com/gorilla/mux"
libp2p_peer "github.com/libp2p/go-libp2p-peer" libp2p_peer "github.com/libp2p/go-libp2p-peer"
msg_pb "github.com/harmony-one/harmony/api/proto/message" msg_pb "github.com/harmony-one/harmony/api/proto/message"
"github.com/harmony-one/harmony/core/types" "github.com/harmony-one/harmony/consensus/reward"
"github.com/harmony-one/harmony/internal/bech32"
common2 "github.com/harmony-one/harmony/internal/common"
"github.com/harmony-one/harmony/internal/utils" "github.com/harmony-one/harmony/internal/utils"
"github.com/harmony-one/harmony/numeric"
"github.com/harmony-one/harmony/p2p" "github.com/harmony-one/harmony/p2p"
) )
// Constants for explorer service. // Constants for explorer service.
const ( const (
explorerPortDifference = 4000 explorerPortDifference = 4000
paginationOffset = 10 totalSupply = 12600000000
txViewNone = "NONE"
txViewAll = "ALL"
) )
// HTTPError is an HTTP error. // HTTPError is an HTTP error.
@ -111,27 +105,14 @@ func (s *Service) Run() *http.Server {
addr := net.JoinHostPort("", GetExplorerPort(s.Port)) addr := net.JoinHostPort("", GetExplorerPort(s.Port))
s.router = mux.NewRouter() s.router = mux.NewRouter()
// Set up router for blocks.
// Blocks are divided into pages in consequent groups of offset size.
s.router.Path("/blocks").Queries("from", "{[0-9]*?}", "to", "{[0-9]*?}", "page", "{[0-9]*?}", "offset", "{[0-9]*?}").HandlerFunc(s.GetExplorerBlocks).Methods("GET")
s.router.Path("/blocks").HandlerFunc(s.GetExplorerBlocks)
// Set up router for tx.
s.router.Path("/tx").Queries("id", "{[0-9A-Fa-fx]*?}").HandlerFunc(s.GetExplorerTransaction).Methods("GET")
s.router.Path("/tx").HandlerFunc(s.GetExplorerTransaction)
// Set up router for address.
// Address transactions are divided into pages in consequent groups of offset size.
s.router.Path("/address").Queries("id", fmt.Sprintf("{([0-9A-Fa-fx]*?)|(t?one1[%s]{38})}", bech32.Charset), "tx_view", "{[A-Z]*?}", "page", "{[0-9]*?}", "offset", "{[0-9]*?}").HandlerFunc(s.GetExplorerAddress).Methods("GET")
s.router.Path("/address").HandlerFunc(s.GetExplorerAddress)
// Set up router for node count. // Set up router for node count.
s.router.Path("/nodes").HandlerFunc(s.GetExplorerNodeCount) // TODO(ricl): this is going to be replaced by /node-count s.router.Path("/circulating-supply").Queries().HandlerFunc(s.GetCirculatingSupply).Methods("GET")
s.router.Path("/node-count").HandlerFunc(s.GetExplorerNodeCount) s.router.Path("/circulating-supply").HandlerFunc(s.GetCirculatingSupply)
// Set up router for shard // Set up router for node count.
s.router.Path("/shard").Queries("id", "{[0-9]*?}").HandlerFunc(s.GetExplorerShard).Methods("GET") s.router.Path("/total-supply").Queries().HandlerFunc(s.GetTotalSupply).Methods("GET")
s.router.Path("/shard").HandlerFunc(s.GetExplorerShard) s.router.Path("/total-supply").HandlerFunc(s.GetTotalSupply)
// Do serving now. // Do serving now.
utils.Logger().Info().Str("port", GetExplorerPort(s.Port)).Msg("Listening") utils.Logger().Info().Str("port", GetExplorerPort(s.Port)).Msg("Listening")
@ -144,499 +125,6 @@ func (s *Service) Run() *http.Server {
return server return server
} }
// ReadBlocksFromDB returns a list of types.Block to server blocks end-point.
func (s *Service) ReadBlocksFromDB(from, to int) []*types.Block {
blocks := []*types.Block{}
for i := from - 1; i <= to+1; i++ {
if i < 0 {
blocks = append(blocks, nil)
continue
}
key := GetBlockKey(i)
data, err := s.Storage.db.Get([]byte(key))
if err != nil {
blocks = append(blocks, nil)
continue
}
block := new(types.Block)
if rlp.DecodeBytes(data, block) != nil {
utils.Logger().Error().Msg("Error on getting from db")
os.Exit(1)
}
blocks = append(blocks, block)
}
return blocks
}
// GetExplorerBlocks serves end-point /blocks
func (s *Service) GetExplorerBlocks(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "application/json")
from := r.FormValue("from")
to := r.FormValue("to")
pageParam := r.FormValue("page")
offsetParam := r.FormValue("offset")
order := r.FormValue("order")
data := &Data{
Blocks: []*Block{},
}
defer func() {
if err := json.NewEncoder(w).Encode(data.Blocks); err != nil {
utils.Logger().Warn().Err(err).Msg("cannot JSON-encode blocks")
}
}()
if from == "" {
utils.Logger().Warn().Msg("Missing from parameter")
w.WriteHeader(http.StatusBadRequest)
return
}
db := s.Storage.GetDB()
fromInt, err := strconv.Atoi(from)
if err != nil {
utils.Logger().Warn().Err(err).Msg("invalid from parameter")
w.WriteHeader(http.StatusBadRequest)
return
}
var toInt int
if to == "" {
toInt, err = func() (int, error) {
bytes, err := db.Get([]byte(BlockHeightKey))
if err == nil {
return strconv.Atoi(string(bytes))
}
return toInt, err
}()
} else {
toInt, err = strconv.Atoi(to)
}
if err != nil {
utils.Logger().Warn().Err(err).Msg("invalid to parameter")
w.WriteHeader(http.StatusBadRequest)
return
}
var offset int
if offsetParam != "" {
offset, err = strconv.Atoi(offsetParam)
if err != nil || offset < 1 {
utils.Logger().Warn().Msg("invalid offset parameter")
w.WriteHeader(http.StatusBadRequest)
return
}
} else {
offset = paginationOffset
}
var page int
if pageParam != "" {
page, err = strconv.Atoi(pageParam)
if err != nil {
utils.Logger().Warn().Err(err).Msg("invalid page parameter")
w.WriteHeader(http.StatusBadRequest)
return
}
} else {
page = 0
}
accountBlocks := s.ReadBlocksFromDB(fromInt, toInt)
for id, accountBlock := range accountBlocks {
if id == 0 || id == len(accountBlocks)-1 || accountBlock == nil {
continue
}
block := NewBlock(accountBlock, id+fromInt-1)
// Populate transactions
for _, tx := range accountBlock.Transactions() {
transaction := GetTransaction(tx, accountBlock)
if transaction != nil {
block.TXs = append(block.TXs, transaction)
}
}
if accountBlocks[id-1] == nil {
block.BlockTime = int64(0)
block.PrevBlock = RefBlock{
ID: "",
Height: "",
}
} else {
block.BlockTime = accountBlock.Time().Int64() - accountBlocks[id-1].Time().Int64()
block.PrevBlock = RefBlock{
ID: accountBlocks[id-1].Hash().Hex(),
Height: strconv.Itoa(id + fromInt - 2),
}
}
if accountBlocks[id+1] == nil {
block.NextBlock = RefBlock{
ID: "",
Height: "",
}
} else {
block.NextBlock = RefBlock{
ID: accountBlocks[id+1].Hash().Hex(),
Height: strconv.Itoa(id + fromInt),
}
}
data.Blocks = append(data.Blocks, block)
}
if offset*page >= len(data.Blocks) {
data.Blocks = []*Block{}
} else if offset*page+offset > len(data.Blocks) {
data.Blocks = data.Blocks[offset*page:]
} else {
data.Blocks = data.Blocks[offset*page : offset*page+offset]
}
if order == "DESC" {
sort.Slice(data.Blocks[:], func(i, j int) bool {
return data.Blocks[i].Timestamp > data.Blocks[j].Timestamp
})
} else {
sort.Slice(data.Blocks[:], func(i, j int) bool {
return data.Blocks[i].Timestamp < data.Blocks[j].Timestamp
})
}
}
// GetExplorerBlocks rpc end-point.
func (s *ServiceAPI) GetExplorerBlocks(ctx context.Context, from, to, page, offset int, order string) ([]*Block, error) {
if offset == 0 {
offset = paginationOffset
}
db := s.Service.Storage.GetDB()
if to == 0 {
bytes, err := db.Get([]byte(BlockHeightKey))
if err == nil {
to, err = strconv.Atoi(string(bytes))
if err != nil {
utils.Logger().Info().Msg("failed to fetch block height")
return nil, err
}
}
}
blocks := make([]*Block, 0)
accountBlocks := s.Service.ReadBlocksFromDB(from, to)
for id, accountBlock := range accountBlocks {
if id == 0 || id == len(accountBlocks)-1 || accountBlock == nil {
continue
}
block := NewBlock(accountBlock, id+from-1)
// Populate transactions
for _, tx := range accountBlock.Transactions() {
transaction := GetTransaction(tx, accountBlock)
if transaction != nil {
block.TXs = append(block.TXs, transaction)
}
}
if accountBlocks[id-1] == nil {
block.BlockTime = int64(0)
block.PrevBlock = RefBlock{
ID: "",
Height: "",
}
} else {
block.BlockTime = accountBlock.Time().Int64() - accountBlocks[id-1].Time().Int64()
block.PrevBlock = RefBlock{
ID: accountBlocks[id-1].Hash().Hex(),
Height: strconv.Itoa(id + from - 2),
}
}
if accountBlocks[id+1] == nil {
block.NextBlock = RefBlock{
ID: "",
Height: "",
}
} else {
block.NextBlock = RefBlock{
ID: accountBlocks[id+1].Hash().Hex(),
Height: strconv.Itoa(id + from),
}
}
blocks = append(blocks, block)
}
if offset*page >= len(blocks) {
blocks = []*Block{}
} else if offset*page+offset > len(blocks) {
blocks = blocks[offset*page:]
} else {
blocks = blocks[offset*page : offset*page+offset]
}
if order == "DESC" {
sort.Slice(blocks[:], func(i, j int) bool {
return blocks[i].Timestamp > blocks[j].Timestamp
})
} else {
sort.Slice(blocks[:], func(i, j int) bool {
return blocks[i].Timestamp < blocks[j].Timestamp
})
}
return blocks, nil
}
// GetExplorerTransaction servers /tx end-point.
func (s *Service) GetExplorerTransaction(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "application/json")
id := r.FormValue("id")
data := &Data{}
defer func() {
if err := json.NewEncoder(w).Encode(data.TX); err != nil {
utils.Logger().Warn().Err(err).Msg("cannot JSON-encode TX")
}
}()
if id == "" {
utils.Logger().Warn().Msg("invalid id parameter")
w.WriteHeader(http.StatusBadRequest)
return
}
db := s.Storage.GetDB()
bytes, err := db.Get([]byte(GetTXKey(id)))
if err != nil {
utils.Logger().Warn().Err(err).Str("id", id).Msg("cannot read TX")
w.WriteHeader(http.StatusInternalServerError)
return
}
tx := new(Transaction)
if rlp.DecodeBytes(bytes, tx) != nil {
utils.Logger().Warn().Str("id", id).Msg("cannot convert data from DB")
w.WriteHeader(http.StatusInternalServerError)
return
}
data.TX = *tx
}
// GetExplorerTransaction rpc end-point.
func (s *ServiceAPI) GetExplorerTransaction(ctx context.Context, id string) (*Transaction, error) {
if id == "" {
utils.Logger().Warn().Msg("invalid id parameter")
return nil, nil
}
db := s.Service.Storage.GetDB()
bytes, err := db.Get([]byte(GetTXKey(id)))
if err != nil {
utils.Logger().Warn().Err(err).Str("id", id).Msg("cannot read TX")
return nil, err
}
tx := new(Transaction)
if rlp.DecodeBytes(bytes, tx) != nil {
utils.Logger().Warn().Str("id", id).Msg("cannot convert data from DB")
return nil, err
}
return tx, nil
}
// GetExplorerAddress serves /address end-point.
func (s *Service) GetExplorerAddress(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "application/json")
id := r.FormValue("id")
if strings.HasPrefix(id, "0x") {
parsedAddr := common2.ParseAddr(id)
oneAddr, err := common2.AddressToBech32(parsedAddr)
if err != nil {
utils.Logger().Warn().Err(err).Msg("unrecognized address format")
w.WriteHeader(http.StatusBadRequest)
return
}
id = oneAddr
}
key := GetAddressKey(id)
txViewParam := r.FormValue("tx_view")
pageParam := r.FormValue("page")
offsetParam := r.FormValue("offset")
order := r.FormValue("order")
txView := txViewNone
if txViewParam != "" {
txView = txViewParam
}
utils.Logger().Info().Str("Address", id).Msg("Querying address")
data := &Data{}
defer func() {
if err := json.NewEncoder(w).Encode(data.Address); err != nil {
utils.Logger().Warn().Err(err).Msg("cannot JSON-encode Address")
}
}()
if id == "" {
utils.Logger().Warn().Msg("missing address id param")
w.WriteHeader(http.StatusBadRequest)
return
}
var err error
var offset int
if offsetParam != "" {
offset, err = strconv.Atoi(offsetParam)
if err != nil || offset < 1 {
utils.Logger().Warn().Msg("invalid offset parameter")
w.WriteHeader(http.StatusBadRequest)
return
}
} else {
offset = paginationOffset
}
var page int
if pageParam != "" {
page, err = strconv.Atoi(pageParam)
if err != nil {
utils.Logger().Warn().Err(err).Msg("invalid page parameter")
w.WriteHeader(http.StatusBadRequest)
return
}
} else {
page = 0
}
data.Address.ID = id
// Try to populate the banace by directly calling get balance.
// Check the balance from blockchain rather than local DB dump
balanceAddr := big.NewInt(0)
if s.GetAccountBalance != nil {
address := common2.ParseAddr(id)
balance, err := s.GetAccountBalance(address)
if err == nil {
balanceAddr = balance
}
}
db := s.Storage.GetDB()
bytes, err := db.Get([]byte(key))
if err != nil {
utils.Logger().Warn().Err(err).Str("id", id).Msg("cannot fetch address from db")
data.Address.Balance = balanceAddr
return
}
if err = rlp.DecodeBytes(bytes, &data.Address); err != nil {
utils.Logger().Warn().Str("id", id).Msg("cannot convert address data")
data.Address.Balance = balanceAddr
return
}
data.Address.Balance = balanceAddr
switch txView {
case txViewNone:
data.Address.TXs = nil
case Received:
receivedTXs := make([]*Transaction, 0)
for _, tx := range data.Address.TXs {
if tx.Type == Received {
receivedTXs = append(receivedTXs, tx)
}
}
data.Address.TXs = receivedTXs
case Sent:
sentTXs := make([]*Transaction, 0)
for _, tx := range data.Address.TXs {
if tx.Type == Sent {
sentTXs = append(sentTXs, tx)
}
}
data.Address.TXs = sentTXs
}
if offset*page >= len(data.Address.TXs) {
data.Address.TXs = []*Transaction{}
} else if offset*page+offset > len(data.Address.TXs) {
data.Address.TXs = data.Address.TXs[offset*page:]
} else {
data.Address.TXs = data.Address.TXs[offset*page : offset*page+offset]
}
if order == "DESC" {
sort.Slice(data.Address.TXs[:], func(i, j int) bool {
return data.Address.TXs[i].Timestamp > data.Address.TXs[j].Timestamp
})
} else {
sort.Slice(data.Address.TXs[:], func(i, j int) bool {
return data.Address.TXs[i].Timestamp < data.Address.TXs[j].Timestamp
})
}
}
// GetExplorerAddress rpc end-point.
func (s *ServiceAPI) GetExplorerAddress(ctx context.Context, id, txView string, page, offset int, order string) (*Address, error) {
if strings.HasPrefix(id, "0x") {
parsedAddr := common2.ParseAddr(id)
oneAddr, err := common2.AddressToBech32(parsedAddr)
if err != nil {
utils.Logger().Warn().Msg("unrecognized address format")
return nil, err
}
id = oneAddr
}
if offset == 0 {
offset = paginationOffset
}
if txView == "" {
txView = txViewNone
}
utils.Logger().Info().Str("Address", id).Msg("Querying address")
if id == "" {
utils.Logger().Warn().Msg("missing address id param")
return nil, nil
}
address := &Address{}
address.ID = id
// Try to populate the banace by directly calling get balance.
// Check the balance from blockchain rather than local DB dump
balanceAddr := big.NewInt(0)
if s.Service.GetAccountBalance != nil {
addr := common2.ParseAddr(id)
balance, err := s.Service.GetAccountBalance(addr)
if err == nil {
balanceAddr = balance
}
}
key := GetAddressKey(id)
db := s.Service.Storage.GetDB()
bytes, err := db.Get([]byte(key))
if err != nil {
utils.Logger().Warn().Err(err).Str("id", id).Msg("cannot fetch address from db")
address.Balance = balanceAddr
return address, err
}
if err = rlp.DecodeBytes(bytes, &address); err != nil {
utils.Logger().Warn().Str("id", id).Msg("cannot convert address data")
address.Balance = balanceAddr
return address, err
}
address.Balance = balanceAddr
switch txView {
case txViewNone:
address.TXs = nil
case Received:
receivedTXs := make([]*Transaction, 0)
for _, tx := range address.TXs {
if tx.Type == Received {
receivedTXs = append(receivedTXs, tx)
}
}
address.TXs = receivedTXs
case Sent:
sentTXs := make([]*Transaction, 0)
for _, tx := range address.TXs {
if tx.Type == Sent {
sentTXs = append(sentTXs, tx)
}
}
address.TXs = sentTXs
}
if offset*page >= len(address.TXs) {
address.TXs = []*Transaction{}
} else if offset*page+offset > len(address.TXs) {
address.TXs = address.TXs[offset*page:]
} else {
address.TXs = address.TXs[offset*page : offset*page+offset]
}
if order == "DESC" {
sort.Slice(address.TXs[:], func(i, j int) bool {
return address.TXs[i].Timestamp > address.TXs[j].Timestamp
})
} else {
sort.Slice(address.TXs[:], func(i, j int) bool {
return address.TXs[i].Timestamp < address.TXs[j].Timestamp
})
}
return address, nil
}
// GetExplorerNodeCount serves /nodes end-point. // GetExplorerNodeCount serves /nodes end-point.
func (s *Service) GetExplorerNodeCount(w http.ResponseWriter, r *http.Request) { func (s *Service) GetExplorerNodeCount(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "application/json") w.Header().Set("Content-Type", "application/json")
@ -677,6 +165,26 @@ func (s *ServiceAPI) GetExplorerShard(ctx context.Context) *Shard {
return &Shard{Nodes: nodes} return &Shard{Nodes: nodes}
} }
// GetCirculatingSupply serves /circulating-supply end-point.
func (s *Service) GetCirculatingSupply(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "application/json")
timestamp := time.Now().Unix()
circulatingSupply := reward.PercentageForTimeStamp(timestamp).Mul(numeric.NewDec(totalSupply))
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")
if err := json.NewEncoder(w).Encode(totalSupply); err != nil {
utils.Logger().Warn().Msg("cannot JSON-encode total supply")
w.WriteHeader(http.StatusInternalServerError)
}
}
// NotifyService notify service. // NotifyService notify service.
func (s *Service) NotifyService(params map[string]interface{}) { func (s *Service) NotifyService(params map[string]interface{}) {
return return

Loading…
Cancel
Save