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 }