Skip to content

Commit

Permalink
Merge branch 'facs95/broadcast' into facs95/package-refractor
Browse files Browse the repository at this point in the history
  • Loading branch information
facs95 committed Jun 26, 2023
2 parents 8e2e366 + 9cc849a commit c605ece
Show file tree
Hide file tree
Showing 4 changed files with 87 additions and 23 deletions.
2 changes: 1 addition & 1 deletion cronjobs/redis_functions.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# Copyright Tharsis Labs Ltd.(Evmos)
# SPDX-License-Identifier:ENCL-1.0(https://github.com/evmos/backend/blob/main/LICENSE)


from __future__ import annotations
import json

Expand Down
1 change: 0 additions & 1 deletion internal/v1/db/endpoint.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
// Copyright Tharsis Labs Ltd.(Evmos)
// SPDX-License-Identifier:ENCL-1.0(https://github.com/evmos/backend/blob/main/LICENSE)

package db

import (
Expand Down
105 changes: 85 additions & 20 deletions internal/v2/node/rest/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,47 +5,112 @@ import (
"fmt"
"io"
"net/http"
"net/url"
"os"
"strconv"
"strings"
"time"

"github.com/tharsis/dashboard-backend/internal/db"

Check failure on line 14 in internal/v2/node/rest/client.go

View workflow job for this annotation

GitHub Actions / test-unit

no required module provides package github.com/tharsis/dashboard-backend/internal/db; to add it:
)

type Client struct {
apiAddress string
nodes []string
network string
}

// NewClient returns a new instance of a RestClient.
// It takes a network string as an argument, which is used to query a valid API address from redis
// It takes a network string as an argument, which is used to collect available REST node's endpoints from redis
// for the desired network.
func NewClient(network string) (*Client, error) {
apiAddress, err := getAPIAddress(network)
nodes, err := getAvailableNodes(network)
if err != nil {
return nil, fmt.Errorf("error while getting api address from redis: %w", err)
return nil, fmt.Errorf("error while getting available endpoints: %w", err)
}
return &Client{
apiAddress: apiAddress,
nodes: nodes,
network: network,
}, nil
}

func getAPIAddress(_ string) (string, error) {
// TODO: implement query to get domain from redis or decide what do to do from now on
return "http://localhost:1317", nil
}

// PostRequest defines a wrapper around an HTTP POST request with a provided URL and data.
// post defines a wrapper around an HTTP POST request with a provided URL and body.
// An error is returned if the request or reading the body fails.
func (c *Client) postRequest(url string, body []byte) ([]byte, error) {
// join the node's address with the endpoint's path
queryURL := fmt.Sprintf("%s/%s", c.apiAddress, url)

res, err := http.Post(queryURL, "application/json", bytes.NewBuffer(body)) //nolint:gosec
func (c *Client) post(endpoint string, body []byte) ([]byte, error) {
res, err := c.postRequestWithRetries(endpoint, body)
if err != nil {
return nil, fmt.Errorf("error while sending post request: %w", err)
return nil, fmt.Errorf("error while making post request: %w", err)
}
defer func() {
_ = res.Body.Close()
}()

bz, err := io.ReadAll(res.Body)
if err != nil {
return nil, fmt.Errorf("error reading response body: %w", err)
}
return bz, nil
}

// postRequestWithRetries performs a POST request to the provided URL with the provided body.
// It will retry the request with the next available node if the request fails.
func (c *Client) postRequestWithRetries(endpoint string, body []byte) (*http.Response, error) {
maxRetries := len(c.nodes)
// TODO: this should be in a config file
client := http.Client{
Timeout: time.Second * 5,
}

var errorMessages []string
for i := 0; i < maxRetries; i++ {
queryURL := joinURL(c.nodes[i], endpoint)
resp, err := client.Post(queryURL, "application/json", bytes.NewBuffer(body))
if err == nil && resp.StatusCode == http.StatusOK {
return resp, nil // success, no need to retry
}

// Collect errors in case no endpoint is available
if err != nil {
errorMessages = append(errorMessages, fmt.Sprintf("node %v error: %v", c.nodes[i], err))
} else {
errorMessages = append(errorMessages, fmt.Sprintf("node %v status code: %v", c.nodes[i], resp.StatusCode))
}
}

return nil, fmt.Errorf(
"failed to post request at endpoint %v for network %v after %v attempts: %v",
endpoint,
c.network,
maxRetries,
strings.Join(errorMessages, ", "),
)
}

// joinURL joins a base URL and a query path to form a valid URL.
func joinURL(baseURL string, queryPath string) string {
u, err := url.Parse(baseURL)
if err != nil {
return ""
}
u.Path = queryPath
return u.String()
}

// getAvailableNodes returns a list of available nodes for the provided network
// from redis.
func getAvailableNodes(network string) ([]string, error) {
// If env variable env == "local" then the only option is localhost
env := os.Getenv("ENV")
if env == "local" {
return []string{"http://localhost:1317"}, nil
}

// In production, query redis for the most up to date rest nodes for the network
// TODO: there should be a redis query that returns the array in one request
amountOfAvailableNodesPerNetwork := 4
nodes := make([]string, amountOfAvailableNodesPerNetwork-1)
for i := 1; i <= amountOfAvailableNodesPerNetwork; i++ {
endpoint, err := db.RedisGetEndpoint(network, "rest", strconv.Itoa(i))
if err != nil {
return []string{}, fmt.Errorf("error while getting endpoint %v for network %v from redis: %w", i, network, err)
}
nodes[i-1] = endpoint
}
return nodes, nil
}
2 changes: 1 addition & 1 deletion internal/v2/node/rest/tx.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ import (
// through its REST API.
func (c *Client) BroadcastTx(txBytes []byte) (tx.BroadcastTxResponse, error) {
broadcastTxEndpoint := "cosmos/tx/v1beta1/txs"
postResponse, err := c.postRequest(broadcastTxEndpoint, txBytes)
postResponse, err := c.post(broadcastTxEndpoint, txBytes)
if err != nil {
return tx.BroadcastTxResponse{}, err
}
Expand Down

0 comments on commit c605ece

Please sign in to comment.