github.com/0chain/gosdk@v1.17.11/zcnbridge/http/rest.go (about)

     1  package http
     2  
     3  import (
     4  	"crypto/sha1"
     5  	"encoding/hex"
     6  	"fmt"
     7  	"io"
     8  	"io/ioutil"
     9  	"net/http"
    10  	"net/url"
    11  	"sync"
    12  
    13  	"go.uber.org/zap"
    14  	"gopkg.in/natefinch/lumberjack.v2"
    15  
    16  	"github.com/0chain/gosdk/core/logger"
    17  	"github.com/0chain/gosdk/core/util"
    18  	"github.com/0chain/gosdk/zcnbridge/errors"
    19  	"github.com/0chain/gosdk/zcncore"
    20  )
    21  
    22  const (
    23  	// SCRestAPIPrefix represents base URL path to execute smart contract rest points.
    24  	SCRestAPIPrefix        = "v1/screst/"
    25  	RestPrefix             = SCRestAPIPrefix + zcncore.ZCNSCSmartContractAddress
    26  	PathGetAuthorizerNodes = "/getAuthorizerNodes?active=%t"
    27  	PathGetGlobalConfig    = "/getGlobalConfig"
    28  	PathGetAuthorizer      = "/getAuthorizer"
    29  )
    30  
    31  type Params map[string]string
    32  
    33  var Logger logger.Logger
    34  var defaultLogLevel = logger.DEBUG
    35  var logVerbose = true
    36  
    37  func init() {
    38  	Logger.Init(defaultLogLevel, "zcnbridge-http-sdk")
    39  
    40  	Logger.SetLevel(logger.DEBUG)
    41  	ioWriter := &lumberjack.Logger{
    42  		Filename:   "bridge.log",
    43  		MaxSize:    100, // MB
    44  		MaxBackups: 5,   // number of backups
    45  		MaxAge:     28,  //days
    46  		LocalTime:  false,
    47  		Compress:   false, // disabled by default
    48  	}
    49  	Logger.SetLogFile(ioWriter, true)
    50  }
    51  
    52  func SetLogFile(logFile string, verbose bool) {
    53  	Logger.Init(defaultLogLevel, "zcnbridge-sdk")
    54  	Logger.SetLevel(logger.DEBUG)
    55  
    56  	ioWriter := &lumberjack.Logger{
    57  		Filename:   logFile,
    58  		MaxSize:    100, // MB
    59  		MaxBackups: 5,   // number of backups
    60  		MaxAge:     28,  //days
    61  		LocalTime:  false,
    62  		Compress:   false, // disabled by default
    63  	}
    64  	logVerbose = verbose
    65  	Logger.SetLogFile(ioWriter, logVerbose)
    66  }
    67  
    68  // MakeSCRestAPICall calls smart contract with provided address
    69  // and makes retryable request to smart contract resource with provided relative path using params.
    70  func MakeSCRestAPICall(opCode int, relativePath string, params Params, cb zcncore.GetInfoCallback) {
    71  	var (
    72  		resMaxCounterBody []byte
    73  		hashMaxCounter    int
    74  		msg               string
    75  		hashCounters      = make(map[string]int)
    76  		sharders          = extractSharders()
    77  	)
    78  
    79  	type queryResult struct {
    80  		hash string
    81  		body []byte
    82  	}
    83  
    84  	results := make(chan *queryResult, len(sharders))
    85  	defer close(results)
    86  
    87  	var client = NewRetryableClient(logVerbose)
    88  
    89  	wg := &sync.WaitGroup{}
    90  	for _, sharder := range sharders {
    91  		wg.Add(1)
    92  		go func(sharderUrl string) {
    93  			defer wg.Done()
    94  
    95  			var u = makeURL(params, sharderUrl, relativePath)
    96  			Logger.Info("Query ", u.String())
    97  			resp, err := client.Get(u.String())
    98  			if err != nil {
    99  				Logger.Error("MakeSCRestAPICall - failed to get response from", zap.String("URL", sharderUrl), zap.Any("error", err))
   100  				return
   101  			}
   102  			if resp.StatusCode != http.StatusInternalServerError {
   103  				//goland:noinspection ALL
   104  				defer resp.Body.Close()
   105  			}
   106  
   107  			if err != nil {
   108  				Logger.Error("MakeSCRestAPICall - failed to get response from", zap.String("URL", sharderUrl), zap.Any("error", err))
   109  				return
   110  			}
   111  
   112  			if resp.StatusCode != http.StatusOK {
   113  				Logger.Error("MakeSCRestAPICall - error getting response from", zap.String("URL", sharderUrl), zap.Any("error", err))
   114  				return
   115  			}
   116  
   117  			Logger.Info("MakeSCRestAPICall successful query")
   118  
   119  			hash, body, err := hashAndBytesOfReader(resp.Body)
   120  			if err != nil {
   121  				Logger.Error("MakeSCRestAPICall - error while reading response body", zap.String("URL", sharderUrl), zap.Any("error", err))
   122  				return
   123  			}
   124  
   125  			Logger.Info("MakeSCRestAPICall push body to results: ", string(body))
   126  
   127  			results <- &queryResult{hash: hash, body: body}
   128  		}(sharder)
   129  	}
   130  
   131  	Logger.Info("MakeSCRestAPICall waiting for response from all sharders")
   132  	wg.Wait()
   133  	Logger.Info("MakeSCRestAPICall closing results")
   134  
   135  	select {
   136  	case result := <-results:
   137  		Logger.Debug("request_sharders", zap.String("received result", result.hash), zap.String("received body", string(result.body)))
   138  		hashCounters[result.hash]++
   139  		if hashCounters[result.hash] > hashMaxCounter {
   140  			hashMaxCounter = hashCounters[result.hash]
   141  			resMaxCounterBody = result.body
   142  		}
   143  	default:
   144  	}
   145  
   146  	if hashMaxCounter == 0 {
   147  		err := errors.New("request_sharders", "no valid responses, last err: "+msg)
   148  		cb.OnInfoAvailable(opCode, zcncore.StatusError, "", err.Error())
   149  		Logger.Error(err)
   150  		return
   151  	}
   152  
   153  	cb.OnInfoAvailable(opCode, zcncore.StatusSuccess, string(resMaxCounterBody), "")
   154  }
   155  
   156  // hashAndBytesOfReader computes hash of readers data and returns hash encoded to hex and bytes of reader data.
   157  // If error occurs while reading data from reader, it returns non nil error.
   158  func hashAndBytesOfReader(r io.Reader) (string, []byte, error) {
   159  	h := sha1.New()
   160  	teeReader := io.TeeReader(r, h)
   161  	readerBytes, err := ioutil.ReadAll(teeReader)
   162  	if err != nil {
   163  		return "", nil, err
   164  	}
   165  
   166  	return hex.EncodeToString(h.Sum(nil)), readerBytes, nil
   167  }
   168  
   169  // extractSharders returns string slice of randomly ordered sharders existing in the current network.
   170  func extractSharders() []string {
   171  	sharders := zcncore.Sharders.Healthy()
   172  	return util.GetRandom(sharders, len(sharders))
   173  }
   174  
   175  // makeURL creates url.URL to make smart contract request to sharder.
   176  func makeURL(params Params, baseURL, relativePath string) *url.URL {
   177  	uString := fmt.Sprintf("%v/%v%v", baseURL, RestPrefix, relativePath)
   178  	u, _ := url.Parse(uString)
   179  	q := u.Query()
   180  	for k, v := range params {
   181  		q.Add(k, v)
   182  	}
   183  	u.RawQuery = q.Encode()
   184  
   185  	return u
   186  }