diff --git a/api/service/explorer/service.go b/api/service/explorer/service.go index 603eecfde..ed6248ac4 100644 --- a/api/service/explorer/service.go +++ b/api/service/explorer/service.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) } } diff --git a/api/service/explorer/storage.go b/api/service/explorer/storage.go index bd4706e9c..f95e3da9d 100644 --- a/api/service/explorer/storage.go +++ b/api/service/explorer/storage.go @@ -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") diff --git a/api/service/explorer/storage_test.go b/api/service/explorer/storage_test.go index e710e0c20..c6a5dd0fd 100644 --- a/api/service/explorer/storage_test.go +++ b/api/service/explorer/storage_test.go @@ -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 diff --git a/api/service/explorer/structs.go b/api/service/explorer/structs.go index 8d3f79c0f..57368e837 100644 --- a/api/service/explorer/structs.go +++ b/api/service/explorer/structs.go @@ -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: "", } } diff --git a/api/service/explorer/structs_test.go b/api/service/explorer/structs_test.go index 120f4d51c..b3f611695 100644 --- a/api/service/explorer/structs_test.go +++ b/api/service/explorer/structs_test.go @@ -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()") }