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. // Constants for explorer service.
const ( const (
explorerPortDifference = 4000 explorerPortDifference = 4000
txViewNone = "NONE"
txViewAll = "ALL"
) )
// HTTPError is an HTTP error. // 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").Queries("id", "{[0-9A-Fa-fx]*?}").HandlerFunc(s.GetExplorerTransaction).Methods("GET")
s.router.Path("/tx").HandlerFunc(s.GetExplorerTransaction) s.router.Path("/tx").HandlerFunc(s.GetExplorerTransaction)
// Set up router for address. // Set up router for account.
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("/account").Queries("id", fmt.Sprintf("{([0-9A-Fa-fx]*?)|(t?one1[%s]{38})}", bech32.Charset)).HandlerFunc(s.GetExplorerAccount).Methods("GET")
s.router.Path("/address").HandlerFunc(s.GetExplorerAddress) s.router.Path("/account").HandlerFunc(s.GetExplorerAccount)
// 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("/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) s.router.Path("/shard").HandlerFunc(s.GetExplorerShard)
// Set up router for committee. // 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").Queries("shard_id", "{[0-9]*?}", "epoch", "{[0-9]*?}").HandlerFunc(s.GetExplorerCommittee).Methods("GET")
s.router.Path("/committee").HandlerFunc(s.GetCommittee).Methods("GET") s.router.Path("/committee").HandlerFunc(s.GetExplorerCommittee).Methods("GET")
// 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")
@ -174,12 +176,15 @@ func (s *Service) GetExplorerBlocks(w http.ResponseWriter, r *http.Request) {
}() }()
if from == "" { if from == "" {
utils.Logger().Warn().Msg("Missing from parameter")
w.WriteHeader(400)
return return
} }
db := s.storage.GetDB() db := s.storage.GetDB()
fromInt, err := strconv.Atoi(from) fromInt, err := strconv.Atoi(from)
if err != nil { 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 return
} }
var toInt int var toInt int
@ -195,7 +200,8 @@ func (s *Service) GetExplorerBlocks(w http.ResponseWriter, r *http.Request) {
toInt, err = strconv.Atoi(to) toInt, err = strconv.Atoi(to)
} }
if err != nil { 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 return
} }
@ -296,24 +302,28 @@ func (s *Service) GetExplorerTransaction(w http.ResponseWriter, r *http.Request)
} }
}() }()
if id == "" { if id == "" {
utils.Logger().Warn().Msg("invalid id parameter")
w.WriteHeader(400)
return return
} }
db := s.storage.GetDB() db := s.storage.GetDB()
bytes, err := db.Get([]byte(GetTXKey(id))) bytes, err := db.Get([]byte(GetTXKey(id)))
if err != nil { if err != nil {
utils.Logger().Warn().Err(err).Str("id", id).Msg("cannot read TX") utils.Logger().Warn().Err(err).Str("id", id).Msg("cannot read TX")
w.WriteHeader(500)
return return
} }
tx := new(Transaction) tx := new(Transaction)
if rlp.DecodeBytes(bytes, tx) != nil { if rlp.DecodeBytes(bytes, tx) != nil {
utils.Logger().Warn().Str("id", id).Msg("cannot convert data from DB") utils.Logger().Warn().Str("id", id).Msg("cannot convert data from DB")
w.WriteHeader(500)
return return
} }
data.TX = *tx data.TX = *tx
} }
// GetCommittee servers /comittee end-point. // GetExplorerCommittee servers /comittee end-point.
func (s *Service) GetCommittee(w http.ResponseWriter, r *http.Request) { func (s *Service) GetExplorerCommittee(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "application/json") w.Header().Set("Content-Type", "application/json")
shardIDRead := r.FormValue("shard_id") shardIDRead := r.FormValue("shard_id")
epochRead := r.FormValue("epoch") epochRead := r.FormValue("epoch")
@ -392,44 +402,73 @@ func (s *Service) GetCommittee(w http.ResponseWriter, r *http.Request) {
} }
} }
// GetExplorerAddress serves /address end-point. // GetExplorerAccount serves /account end-point.
func (s *Service) GetExplorerAddress(w http.ResponseWriter, r *http.Request) { func (s *Service) GetExplorerAccount(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "application/json") w.Header().Set("Content-Type", "application/json")
id := r.FormValue("id") id := r.FormValue("id")
key := GetAddressKey(id) key := GetAccountKey(id)
txViewParam := r.FormValue("tx_view")
utils.Logger().Info().Str("address", id).Msg("Querying address") txView := txViewNone
if txViewParam != "" {
txView = txViewParam
}
utils.Logger().Info().Str("account", id).Msg("Querying account")
data := &Data{} data := &Data{}
defer func() { 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, ctxerror.Warn(utils.WithCallerSkip(utils.GetLogInstance(), 1), err,
"cannot JSON-encode address") "cannot JSON-encode account")
} }
}() }()
if id == "" { if id == "" {
utils.Logger().Warn().Msg("missing account address param")
w.WriteHeader(400)
return 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() db := s.storage.GetDB()
bytes, err := db.Get([]byte(key)) bytes, err := db.Get([]byte(key))
if err != nil { 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 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") utils.Logger().Warn().Str("id", id).Msg("cannot convert data from DB")
w.WriteHeader(500)
return 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. // 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") w.Header().Set("Content-Type", "application/json")
if err := json.NewEncoder(w).Encode(len(s.GetNodeIDs())); err != nil { if err := json.NewEncoder(w).Encode(len(s.GetNodeIDs())); err != nil {
utils.Logger().Warn().Msg("cannot JSON-encode node count") 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 { if err := json.NewEncoder(w).Encode(Shard{Nodes: nodes}); err != nil {
utils.Logger().Warn().Msg("cannot JSON-encode shard info") utils.Logger().Warn().Msg("cannot JSON-encode shard info")
w.WriteHeader(500)
} }
} }

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

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

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

Loading…
Cancel
Save