Explorer updates

pull/1510/head
flicker-harmony 5 years ago
parent e1ae274649
commit c84f119739
  1. 97
      api/service/explorer/service.go
  2. 43
      api/service/explorer/storage.go
  3. 6
      api/service/explorer/storage_test.go
  4. 40
      api/service/explorer/structs.go
  5. 3
      api/service/explorer/structs_test.go

@ -30,6 +30,8 @@ import (
// Constants for explorer service.
const (
explorerPortDifference = 4000
txViewNone = "NONE"
txViewAll = "ALL"
)
// HTTPError is an HTTP error.
@ -107,9 +109,9 @@ func (s *Service) Run() *http.Server {
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.
s.router.Path("/address").Queries("id", fmt.Sprintf("{([0-9A-Fa-fx]*?)|(t?one1[%s]{38})}", bech32.Charset)).HandlerFunc(s.GetExplorerAddress).Methods("GET")
s.router.Path("/address").HandlerFunc(s.GetExplorerAddress)
// Set up router for account.
s.router.Path("/account").Queries("id", fmt.Sprintf("{([0-9A-Fa-fx]*?)|(t?one1[%s]{38})}", bech32.Charset)).HandlerFunc(s.GetExplorerAccount).Methods("GET")
s.router.Path("/account").HandlerFunc(s.GetExplorerAccount)
// Set up router for node count.
s.router.Path("/nodes").HandlerFunc(s.GetExplorerNodeCount) // TODO(ricl): this is going to be replaced by /node-count
@ -120,8 +122,8 @@ func (s *Service) Run() *http.Server {
s.router.Path("/shard").HandlerFunc(s.GetExplorerShard)
// Set up router for committee.
s.router.Path("/committee").Queries("shard_id", "{[0-9]*?}", "epoch", "{[0-9]*?}").HandlerFunc(s.GetCommittee).Methods("GET")
s.router.Path("/committee").HandlerFunc(s.GetCommittee).Methods("GET")
s.router.Path("/committee").Queries("shard_id", "{[0-9]*?}", "epoch", "{[0-9]*?}").HandlerFunc(s.GetExplorerCommittee).Methods("GET")
s.router.Path("/committee").HandlerFunc(s.GetExplorerCommittee).Methods("GET")
// Do serving now.
utils.Logger().Info().Str("port", GetExplorerPort(s.Port)).Msg("Listening")
@ -174,12 +176,15 @@ func (s *Service) GetExplorerBlocks(w http.ResponseWriter, r *http.Request) {
}()
if from == "" {
utils.Logger().Warn().Msg("Missing from parameter")
w.WriteHeader(400)
return
}
db := s.storage.GetDB()
fromInt, err := strconv.Atoi(from)
if err != nil {
utils.Logger().Warn().Err(err).Str("from", from).Msg("invalid from parameter")
utils.Logger().Warn().Err(err).Msg("invalid from parameter")
w.WriteHeader(400)
return
}
var toInt int
@ -195,7 +200,8 @@ func (s *Service) GetExplorerBlocks(w http.ResponseWriter, r *http.Request) {
toInt, err = strconv.Atoi(to)
}
if err != nil {
utils.Logger().Warn().Err(err).Str("to", to).Msg("invalid to parameter")
utils.Logger().Warn().Err(err).Msg("invalid to parameter")
w.WriteHeader(400)
return
}
@ -296,24 +302,28 @@ func (s *Service) GetExplorerTransaction(w http.ResponseWriter, r *http.Request)
}
}()
if id == "" {
utils.Logger().Warn().Msg("invalid id parameter")
w.WriteHeader(400)
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(500)
return
}
tx := new(Transaction)
if rlp.DecodeBytes(bytes, tx) != nil {
utils.Logger().Warn().Str("id", id).Msg("cannot convert data from DB")
w.WriteHeader(500)
return
}
data.TX = *tx
}
// GetCommittee servers /comittee end-point.
func (s *Service) GetCommittee(w http.ResponseWriter, r *http.Request) {
// GetExplorerCommittee servers /comittee end-point.
func (s *Service) GetExplorerCommittee(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "application/json")
shardIDRead := r.FormValue("shard_id")
epochRead := r.FormValue("epoch")
@ -392,44 +402,73 @@ func (s *Service) GetCommittee(w http.ResponseWriter, r *http.Request) {
}
}
// GetExplorerAddress serves /address end-point.
func (s *Service) GetExplorerAddress(w http.ResponseWriter, r *http.Request) {
// GetExplorerAccount serves /account end-point.
func (s *Service) GetExplorerAccount(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "application/json")
id := r.FormValue("id")
key := GetAddressKey(id)
utils.Logger().Info().Str("address", id).Msg("Querying address")
key := GetAccountKey(id)
txViewParam := r.FormValue("tx_view")
txView := txViewNone
if txViewParam != "" {
txView = txViewParam
}
utils.Logger().Info().Str("account", id).Msg("Querying account")
data := &Data{}
defer func() {
if err := json.NewEncoder(w).Encode(data.Address); err != nil {
if err := json.NewEncoder(w).Encode(data.Account); err != nil {
ctxerror.Warn(utils.WithCallerSkip(utils.GetLogInstance(), 1), err,
"cannot JSON-encode address")
"cannot JSON-encode account")
}
}()
if id == "" {
utils.Logger().Warn().Msg("missing account address param")
w.WriteHeader(400)
return
}
data.Address.ID = id
// Try to populate the banace by directly calling get balance.
// Check the balance from blockchain rather than local DB dump
if s.GetAccountBalance != nil {
address := common2.ParseAddr(id)
balance, err := s.GetAccountBalance(address)
if err == nil {
data.Address.Balance = balance
}
}
db := s.storage.GetDB()
bytes, err := db.Get([]byte(key))
if err != nil {
utils.Logger().Warn().Err(err).Str("id", id).Msg("cannot read address from db")
utils.Logger().Warn().Err(err).Str("id", id).Msg("cannot read account from db")
w.WriteHeader(500)
return
}
if err = rlp.DecodeBytes(bytes, &data.Address); err != nil {
if err = rlp.DecodeBytes(bytes, &data.Account); err != nil {
utils.Logger().Warn().Str("id", id).Msg("cannot convert data from DB")
w.WriteHeader(500)
return
}
data.Account.ID = id
// Try to populate the banace by directly calling get balance.
// Check the balance from blockchain rather than local DB dump
if s.GetAccountBalance != nil {
address := common2.ParseAddr(id)
balance, err := s.GetAccountBalance(address)
if err == nil {
data.Account.Balance = balance
}
}
switch txView {
case txViewNone:
data.Account.TXs = nil
case Received:
receivedTXs := make([]*Transaction, 0)
for _, tx := range data.Account.TXs {
if tx.Type == Received {
receivedTXs = append(receivedTXs, tx)
}
}
data.Account.TXs = receivedTXs
case Sent:
sentTXs := make([]*Transaction, 0)
for _, tx := range data.Account.TXs {
if tx.Type == Sent {
sentTXs = append(sentTXs, tx)
}
}
data.Account.TXs = sentTXs
}
}
// GetExplorerNodeCount serves /nodes end-point.
@ -437,6 +476,7 @@ func (s *Service) GetExplorerNodeCount(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "application/json")
if err := json.NewEncoder(w).Encode(len(s.GetNodeIDs())); err != nil {
utils.Logger().Warn().Msg("cannot JSON-encode node count")
w.WriteHeader(500)
}
}
@ -451,6 +491,7 @@ func (s *Service) GetExplorerShard(w http.ResponseWriter, r *http.Request) {
}
if err := json.NewEncoder(w).Encode(Shard{Nodes: nodes}); err != nil {
utils.Logger().Warn().Msg("cannot JSON-encode shard info")
w.WriteHeader(500)
}
}

@ -20,7 +20,7 @@ const (
BlockInfoPrefix = "bi"
BlockPrefix = "b"
TXPrefix = "tx"
AddressPrefix = "ad"
AccountPrefix = "ad"
CommitteePrefix = "cp"
)
@ -29,9 +29,9 @@ func GetBlockInfoKey(id int) string {
return fmt.Sprintf("%s_%d", BlockInfoPrefix, id)
}
// GetAddressKey ...
func GetAddressKey(address string) string {
return fmt.Sprintf("%s_%s", AddressPrefix, address)
// GetAccountKey ...
func GetAccountKey(address string) string {
return fmt.Sprintf("%s_%s", AccountPrefix, address)
}
// GetBlockKey ...
@ -112,12 +112,13 @@ func (storage *Storage) Dump(block *types.Block, height uint64) {
// Store txs
for _, tx := range block.Transactions() {
if tx.To() == nil {
utils.Logger().Info().Msgf("LOL Tx id %s", tx.Hash().Hex())
continue
}
explorerTransaction := GetTransaction(tx, block)
storage.UpdateTXStorage(batch, explorerTransaction, tx)
storage.UpdateAddress(batch, explorerTransaction, tx)
storage.UpdateAccount(batch, explorerTransaction, tx)
}
if err := batch.Write(); err != nil {
ctxerror.Warn(utils.GetLogger(), err, "cannot write batch")
@ -153,33 +154,35 @@ func (storage *Storage) UpdateTXStorage(batch ethdb.Batch, explorerTransaction *
}
}
// UpdateAddress ...
func (storage *Storage) UpdateAddress(batch ethdb.Batch, explorerTransaction *Transaction, tx *types.Transaction) {
storage.UpdateAddressStorage(batch, explorerTransaction.To, explorerTransaction, tx)
storage.UpdateAddressStorage(batch, explorerTransaction.From, explorerTransaction, tx)
// UpdateAccount ...
func (storage *Storage) UpdateAccount(batch ethdb.Batch, explorerTransaction *Transaction, tx *types.Transaction) {
explorerTransaction.Type = Received
storage.UpdateAccountStorage(batch, explorerTransaction.To, explorerTransaction, tx)
explorerTransaction.Type = Sent
storage.UpdateAccountStorage(batch, explorerTransaction.From, explorerTransaction, tx)
}
// UpdateAddressStorage updates specific addr address.
func (storage *Storage) UpdateAddressStorage(batch ethdb.Batch, addr string, explorerTransaction *Transaction, tx *types.Transaction) {
key := GetAddressKey(addr)
// UpdateAccountStorage updates specific addr account.
func (storage *Storage) UpdateAccountStorage(batch ethdb.Batch, addr string, explorerTransaction *Transaction, tx *types.Transaction) {
key := GetAccountKey(addr)
var address Address
var account Account
if data, err := storage.db.Get([]byte(key)); err == nil {
err = rlp.DecodeBytes(data, &address)
err = rlp.DecodeBytes(data, &account)
if err == nil {
address.Balance.Add(address.Balance, tx.Value())
account.Balance.Add(account.Balance, tx.Value())
} else {
utils.Logger().Error().Err(err).Msg("Failed to error")
}
} else {
address.Balance = tx.Value()
account.Balance = tx.Value()
}
address.ID = addr
address.TXs = append(address.TXs, explorerTransaction)
encoded, err := rlp.EncodeToBytes(address)
account.ID = addr
account.TXs = append(account.TXs, explorerTransaction)
encoded, err := rlp.EncodeToBytes(account)
if err == nil {
if err := batch.Put([]byte(key), encoded); err != nil {
utils.Logger().Warn().Err(err).Msg("cannot batch address")
utils.Logger().Warn().Err(err).Msg("cannot batch account")
}
} else {
utils.Logger().Error().Err(err).Msg("cannot encode address account")

@ -18,9 +18,9 @@ func TestGetBlockInfoKey(t *testing.T) {
assert.Equal(t, GetBlockInfoKey(3), "bi_3", "error")
}
// Test for GetAddressKey
func TestGetAddressKey(t *testing.T) {
assert.Equal(t, GetAddressKey("abcd"), "ad_abcd", "error")
// Test for GetAccountKey
func TestGetAccountKey(t *testing.T) {
assert.Equal(t, GetAccountKey("abcd"), "ad_abcd", "error")
}
// Test for GetBlockKey

@ -5,7 +5,10 @@ import (
"math/big"
"strconv"
"github.com/ethereum/go-ethereum/common"
"github.com/harmony-one/harmony/core/types"
common2 "github.com/harmony-one/harmony/internal/common"
"github.com/harmony-one/harmony/internal/utils"
)
@ -13,16 +16,22 @@ import (
* All the code here is work of progress for the sprint.
*/
// Tx types ...
const (
Received = "RECEIVED"
Sent = "SENT"
)
// Data ...
type Data struct {
Blocks []*Block `json:"blocks"`
// Block Block `json:"block"`
Address Address `json:"address"`
Account Account `json:"account"`
TX Transaction
}
// Address ...
type Address struct {
// Account ...
type Account struct {
ID string `json:"id"`
Balance *big.Int `json:"balance"`
TXs []*Transaction `json:"txs"`
@ -48,6 +57,7 @@ type Transaction struct {
Value *big.Int `json:"value"`
Bytes string `json:"bytes"`
Data string `json:"data"`
Type string `json:"type"`
}
// Block ...
@ -86,21 +96,6 @@ type Shard struct {
// NewBlock ...
func NewBlock(block *types.Block, height int) *Block {
// TODO(ricl): use block.Header().CommitBitmap and GetPubKeyFromMask
signers := []string{}
/*state, err := block.Header().GetShardState()
if err == nil {
for _, committee := range state {
if committee.ShardID == block.ShardID() {
for i, validator := range committee.NodeList {
oneAddress, err := common.AddressToBech32(validator.EcdsaAddress)
if err != nil && block.Header().LastCommitBitmap[i] != 0x0 {
continue
}
signers = append(signers, oneAddress)
}
}
}
}*/
return &Block{
Height: strconv.Itoa(height),
ID: block.Hash().Hex(),
@ -108,7 +103,7 @@ func NewBlock(block *types.Block, height int) *Block {
Timestamp: strconv.Itoa(int(block.Time().Int64() * 1000)),
MerkleRoot: block.Root().Hex(),
Bytes: strconv.Itoa(int(block.Size())),
Signers: signers,
Signers: []string{},
Epoch: block.Epoch().Uint64(),
ExtraData: string(block.Extra()),
}
@ -116,6 +111,7 @@ func NewBlock(block *types.Block, height int) *Block {
// GetTransaction ...
func GetTransaction(tx *types.Transaction, accountBlock *types.Block) *Transaction {
utils.Logger().Info().Msgf("Tx id %s", tx.Hash().Hex())
if tx.To() == nil {
return nil
}
@ -123,13 +119,15 @@ func GetTransaction(tx *types.Transaction, accountBlock *types.Block) *Transacti
if err != nil {
utils.Logger().Error().Err(err).Msg("Error when parsing tx into message")
}
utils.Logger().Info().Msg("OK done")
return &Transaction{
ID: tx.Hash().Hex(),
Timestamp: strconv.Itoa(int(accountBlock.Time().Int64() * 1000)),
From: msg.From().Hex(), // TODO ek – use bech32
To: msg.To().Hex(), // TODO ek – use bech32
From: common2.MustAddressToBech32(common.HexToAddress(msg.From().Hex())),
To: common2.MustAddressToBech32(common.HexToAddress(msg.To().Hex())),
Value: msg.Value(),
Bytes: strconv.Itoa(int(tx.Size())),
Data: hex.EncodeToString(tx.Data()),
Type: "",
}
}

@ -9,6 +9,7 @@ import (
"github.com/ethereum/go-ethereum/common"
"github.com/harmony-one/harmony/core/types"
common2 "github.com/harmony-one/harmony/internal/common"
)
// Test for GetBlockInfoKey
@ -22,6 +23,6 @@ func TestGetTransaction(t *testing.T) {
tx := GetTransaction(tx1, block)
assert.Equal(t, tx.ID, tx1.Hash().Hex(), "should be equal tx1.Hash()")
assert.Equal(t, tx.To, tx1.To().Hex(), "should be equal tx1.To()") // TODO ek – use bech32
assert.Equal(t, tx.To, common2.MustAddressToBech32(common.HexToAddress(tx1.To().Hex())), "should be equal tx1.To()")
assert.Equal(t, tx.Bytes, strconv.Itoa(int(tx1.Size())), "should be equal tx1.Size()")
}

Loading…
Cancel
Save