From 6f434a446c92415ebd7ce690419036b0a7eb0846 Mon Sep 17 00:00:00 2001 From: trung-nt3 <59964019+trung-nt3@users.noreply.github.com> Date: Fri, 4 Nov 2022 08:43:51 +0700 Subject: [PATCH] feat: improve search api (#8) * 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 --- cmd/astra-indexing/routes/routes.go | 1 + infrastructure/blockscout/httpclient.go | 43 +++++- .../blockscout/search_results_response.go | 1 + infrastructure/httpapi/handlers/search.go | 126 +++++++----------- 4 files changed, 87 insertions(+), 84 deletions(-) diff --git a/cmd/astra-indexing/routes/routes.go b/cmd/astra-indexing/routes/routes.go index c80bc0b..42d890d 100644 --- a/cmd/astra-indexing/routes/routes.go +++ b/cmd/astra-indexing/routes/routes.go @@ -31,6 +31,7 @@ func InitRouteRegistry( ) blockscoutClient := blockscout_infrastructure.NewHTTPClient( + logger, config.BlockscoutApp.HTTPRPCUrl, ) diff --git a/infrastructure/blockscout/httpclient.go b/infrastructure/blockscout/httpclient.go index 90dee8d..10190dc 100644 --- a/infrastructure/blockscout/httpclient.go +++ b/infrastructure/blockscout/httpclient.go @@ -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" ) @@ -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 @@ -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"), @@ -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() @@ -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 } diff --git a/infrastructure/blockscout/search_results_response.go b/infrastructure/blockscout/search_results_response.go index 68da5f0..42fa1a9 100644 --- a/infrastructure/blockscout/search_results_response.go +++ b/infrastructure/blockscout/search_results_response.go @@ -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"` } diff --git a/infrastructure/httpapi/handlers/search.go b/infrastructure/httpapi/handlers/search.go index 364ffad..b9a0202 100644 --- a/infrastructure/httpapi/handlers/search.go +++ b/infrastructure/httpapi/handlers/search.go @@ -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) { @@ -77,77 +101,32 @@ 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) } } } @@ -155,18 +134,6 @@ func (search *Search) Search(ctx *fasthttp.RequestCtx) { 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 { @@ -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)