Skip to content

Commit

Permalink
feat: improve search api (#8)
Browse files Browse the repository at this point in the history
* feat: improve search api

* refactor: remove unused code

* refactor: add logger to blockscout httpclient

* refactor: refactor and add comment for search

* chore: fix typo

* chore: remove unused code

* refactor search api
  • Loading branch information
trungnt1811 authored Nov 4, 2022
1 parent 5fbc4f7 commit 6f434a4
Show file tree
Hide file tree
Showing 4 changed files with 87 additions and 84 deletions.
1 change: 1 addition & 0 deletions cmd/astra-indexing/routes/routes.go
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ func InitRouteRegistry(
)

blockscoutClient := blockscout_infrastructure.NewHTTPClient(
logger,
config.BlockscoutApp.HTTPRPCUrl,
)

Expand Down
43 changes: 38 additions & 5 deletions infrastructure/blockscout/httpclient.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import (
"strings"

"github.com/AstraProtocol/astra-indexing/external/cache"
applogger "github.com/AstraProtocol/astra-indexing/external/logger"
"github.com/hashicorp/go-retryablehttp"
jsoniter "github.com/json-iterator/go"
)
Expand All @@ -22,6 +23,7 @@ const GET_SEARCH_RESULTS = "/token-autocomplete?q="
const TX_NOT_FOUND = "transaction not found"

type HTTPClient struct {
logger applogger.Logger
httpClient *retryablehttp.Client
url string
httpCache *cache.AstraCache
Expand Down Expand Up @@ -102,12 +104,15 @@ func (client *HTTPClient) request(endpoint string, queryParams ...string) (io.Re
return rawResp.Body, nil
}

func NewHTTPClient(url string) *HTTPClient {
func NewHTTPClient(logger applogger.Logger, url string) *HTTPClient {
httpClient := retryablehttp.NewClient()
httpClient.Logger = nil
httpClient.CheckRetry = defaultRetryPolicy

return &HTTPClient{
logger.WithFields(applogger.LogFields{
"module": "BlockscoutHttpClient",
}),
httpClient,
strings.TrimSuffix(url, "/"),
cache.NewCache("blockscout"),
Expand Down Expand Up @@ -135,12 +140,13 @@ func (client *HTTPClient) GetDetailEvmTx(txHash string) (*TransactionEvm, error)
return &txResp.Result, nil
}

func (client *HTTPClient) GetSearchResults(keyword string) ([]SearchResult, error) {
func (client *HTTPClient) GetSearchResults(keyword string) []SearchResult {
rawRespBody, err := client.request(
client.getUrl(GET_SEARCH_RESULTS, keyword), "",
)
if err != nil {
return []SearchResult{}, err
client.logger.Errorf("error getting search results from blockscout: %v", err)
return []SearchResult{}
}
defer rawRespBody.Close()

Expand All @@ -149,8 +155,35 @@ func (client *HTTPClient) GetSearchResults(keyword string) ([]SearchResult, erro

var seachResults []SearchResult
if err := json.Unmarshal(respBody.Bytes(), &seachResults); err != nil {
return []SearchResult{}, err
client.logger.Errorf("error parsing search results from blockscout: %v", err)
return []SearchResult{}
}

return seachResults, nil
return seachResults
}

func (client *HTTPClient) GetSearchResultsAsync(keyword string, results chan []SearchResult) {
// Make sure we close these channels when we're done with them\\
defer func() {
close(results)
}()

rawRespBody, err := client.request(
client.getUrl(GET_SEARCH_RESULTS, keyword), "",
)
if err != nil {
client.logger.Errorf("error getting search results from blockscout: %v", err)
results <- []SearchResult{}
return
}
defer rawRespBody.Close()

var respBody bytes.Buffer
respBody.ReadFrom(rawRespBody)

var seachResults []SearchResult
if err := json.Unmarshal(respBody.Bytes(), &seachResults); err != nil {
client.logger.Errorf("error parsing search results from blockscout: %v", err)
}
results <- seachResults
}
1 change: 1 addition & 0 deletions infrastructure/blockscout/search_results_response.go
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ type ValidatorResult struct {
ConsensusNodeAddress string `json:"consensusNodeAddress"`
InitialDelegatorAddress string `json:"initialDelegatorAddress"`
InitialDelegatorAddressHash string `json:"initialDelegatorAddressHash"`
Moniker string `json:"moniker"`
Status string `json:"status"`
Jailed bool `json:"jailed"`
}
Expand Down
126 changes: 47 additions & 79 deletions infrastructure/httpapi/handlers/search.go
Original file line number Diff line number Diff line change
Expand Up @@ -49,24 +49,48 @@ func NewSearch(logger applogger.Logger, blockscoutClient blockscout_infrastructu
}

func (search *Search) Search(ctx *fasthttp.RequestCtx) {
resultsChan := make(chan []blockscout_infrastructure.SearchResult)

keyword := string(ctx.QueryArgs().Peek("keyword"))

var results SearchResults

blocks, err := search.blocksView.Search(keyword)
if err != nil {
if errors.Is(err, rdb.ErrNoRows) {
blocks = nil
if !tmcosmosutils.IsValidCosmosAddress(keyword) {
// Using simultaneously blockscout and chainindexing search
go search.blockscoutClient.GetSearchResultsAsync(keyword, resultsChan)
} else {
if strings.Contains(keyword, "valoper") {
// If keyword is validator address (e.g: "astravaloper16mqptvptnds4098cmdmz846lmazenegc270ljs")
// use chainindexing's validator search only
validators, err := search.validatorsView.Search(keyword)
if err != nil {
if errors.Is(err, rdb.ErrNoRows) {
validators = nil
} else {
search.logger.Errorf("error searching validator: %v", err)
httpapi.InternalServerError(ctx)
return
}
}
if len(validators) > 0 {
results.Validators = parseValidators(validators)
httpapi.Success(ctx, results)
return
}
} else {
search.logger.Errorf("error searching block: %v", err)
httpapi.InternalServerError(ctx)
// If keyword is bech32 address (e.g: "astra1g9v3fp9wkhar696e7896x6wu3hqjsy5cpxdzff")
// It must be converted to hex address then using blockscout's api search only
_, converted, _ := tmcosmosutils.DecodeAddressToHex(keyword)
hex_address := "0x" + hex.EncodeToString(converted)
blockscoutAddressResults := search.blockscoutClient.GetSearchResults(hex_address)
results.Addresses = blockscout_infrastructure.SearchResultsToAddresses(blockscoutAddressResults)
httpapi.Success(ctx, results)
return
}
}
if len(blocks) > 0 {
results.Blocks = parseBlocks(blocks)
}

// If keyword is cosmos tx (e.g: "90FEE96EE94CA74AD67FCF155E15488B901B3AE2530EBE4D35A9E77B609EB348")
// use chainindexing search for cosmos tx then merge with evm tx from blockscout's search result (if exist)
transactions, err := search.transactionsView.Search(keyword)
if err != nil {
if errors.Is(err, rdb.ErrNoRows) {
Expand All @@ -77,96 +101,39 @@ func (search *Search) Search(ctx *fasthttp.RequestCtx) {
return
}
}
if len(transactions) > 0 {
blockscoutSearchResults, err := search.blockscoutClient.GetSearchResults(keyword)
if err == nil {
results.Transactions = parseTransactions(transactions, blockscoutSearchResults)
} else {
search.logger.Errorf("error parsing search results from blockscout: %v", err)
}
}

if tmcosmosutils.IsValidCosmosAddress(keyword) {
_, converted, _ := tmcosmosutils.DecodeAddressToHex(keyword)
hex_address := hex.EncodeToString(converted)
blockscoutSearchResults, err := search.blockscoutClient.GetSearchResults("0x" + hex_address)
if err == nil {
results.Addresses = blockscout_infrastructure.SearchResultsToAddresses(blockscoutSearchResults)
} else {
search.logger.Errorf("error parsing search results from blockscout: %v", err)
results.Addresses = nil
}
}

// If keyword contains a "/", then it is a {account}/{memo} combination
strs := strings.SplitN(keyword, "/", 2)
if len(strs) == 2 {
address := strs[0]
if tmcosmosutils.IsValidCosmosAddress(address) {
_, converted, _ := tmcosmosutils.DecodeAddressToHex(address)
hex_address := hex.EncodeToString(converted)
blockscoutSearchResults, err := search.blockscoutClient.GetSearchResults("0x" + hex_address)
if err == nil {
results.Addresses = blockscout_infrastructure.SearchResultsToAddresses(blockscoutSearchResults)
} else {
search.logger.Errorf("error parsing search results from blockscout: %v", err)
results.Addresses = nil
}
}
}
// Get blockscout's search result from channel
blockscoutSearchResults := <-resultsChan

validators, err := search.validatorsView.Search(keyword)
if err != nil {
if errors.Is(err, rdb.ErrNoRows) {
validators = nil
} else {
search.logger.Errorf("error searching validator: %v", err)
httpapi.InternalServerError(ctx)
return
}
}
if len(validators) > 0 {
results.Addresses = nil
results.Validators = parseValidators(validators)
if len(transactions) > 0 {
// merge with evm tx from blockscout's search result (if exist)
results.Transactions = parseTransactions(transactions, blockscoutSearchResults)
httpapi.Success(ctx, results)
return
}

// Using blockscout search when search results in chainindexing are empty
// Using blockscout's search results when chainindexing's search results are empty
// mostly using for token, block search or in case of keyword is hex type
if isResultsEmpty(results) {
blockscoutSearchResults, err := search.blockscoutClient.GetSearchResults(keyword)
if err != nil {
search.logger.Errorf("error parsing search results from blockscout: %v", err)
httpapi.InternalServerError(ctx)
return
}
if len(blockscoutSearchResults) > 0 {
switch blockscoutSearchResults[0].Type {
case "token":
results.Tokens = blockscout_infrastructure.SearchResultsToTokens(blockscoutSearchResults)
case "block":
results.Blocks = blockscout_infrastructure.SearchResultsToBlocks(blockscoutSearchResults)
case "address":
results.Addresses = blockscout_infrastructure.SearchResultsToAddresses(blockscoutSearchResults)
case "transaction":
results.Transactions = blockscout_infrastructure.SearchResultsToTransactions(blockscoutSearchResults)
default:
results.Blocks = blockscout_infrastructure.SearchResultsToBlocks(blockscoutSearchResults)
case "transaction_cosmos":
results.Transactions = blockscout_infrastructure.SearchResultsToTransactions(blockscoutSearchResults)
}
}
}

httpapi.Success(ctx, results)
}

func parseBlocks(data []block_view.Block) []blockscout_infrastructure.BlockResult {
var blocks []blockscout_infrastructure.BlockResult
for _, block_data := range data {
var block blockscout_infrastructure.BlockResult
block.BlockHash = block_data.Hash
block.BlockNumber = int(block_data.Height)
block.InsertedAt = block_data.Time
blocks = append(blocks, block)
}
return blocks
}

func parseValidators(data []validator_view.ValidatorRow) []blockscout_infrastructure.ValidatorResult {
var validators []blockscout_infrastructure.ValidatorResult
for _, validator_data := range data {
Expand All @@ -175,6 +142,7 @@ func parseValidators(data []validator_view.ValidatorRow) []blockscout_infrastruc
validator.Status = validator_data.Status
validator.ConsensusNodeAddress = validator_data.ConsensusNodeAddress
validator.InitialDelegatorAddress = validator_data.InitialDelegatorAddress
validator.Moniker = validator_data.Moniker
_, converted, _ := tmcosmosutils.DecodeAddressToHex(validator_data.InitialDelegatorAddress)
validator.InitialDelegatorAddressHash = "0x" + hex.EncodeToString(converted)
validators = append(validators, validator)
Expand Down

0 comments on commit 6f434a4

Please sign in to comment.