github.com/0chain/gosdk@v1.17.11/zboxcore/sdk/filerefsworker.go (about) 1 package sdk 2 3 import ( 4 "context" 5 "encoding/hex" 6 "encoding/json" 7 "fmt" 8 "io/ioutil" 9 "math" 10 "net/http" 11 "sync" 12 "time" 13 14 "github.com/0chain/errors" 15 "github.com/0chain/gosdk/core/common" 16 "github.com/0chain/gosdk/zboxcore/blockchain" 17 l "github.com/0chain/gosdk/zboxcore/logger" 18 "github.com/0chain/gosdk/zboxcore/marker" 19 "github.com/0chain/gosdk/zboxcore/zboxutil" 20 ) 21 22 type ObjectTreeResult struct { 23 TotalPages int64 `json:"total_pages"` 24 OffsetPath string `json:"offset_path"` 25 OffsetDate string `json:"offset_date"` 26 Refs []ORef `json:"refs"` 27 LatestWM *marker.WriteMarker `json:"latest_write_marker"` 28 } 29 30 const INVALID_PATH = "invalid_path" 31 32 type ObjectTreeRequest struct { 33 allocationID string 34 allocationTx string 35 sig string 36 blobbers []*blockchain.StorageNode 37 authToken string 38 pathHash string 39 remotefilepath string 40 pageLimit int // numbers of refs that will be returned by blobber at max 41 level int 42 fileType string 43 refType string 44 offsetPath string 45 updatedDate string // must have "2006-01-02T15:04:05.99999Z07:00" format 46 offsetDate string // must have "2006-01-02T15:04:05.99999Z07:00" format 47 ctx context.Context 48 Consensus 49 } 50 51 type oTreeResponse struct { 52 oTResult *ObjectTreeResult 53 err error 54 hash string 55 idx int 56 } 57 58 // Paginated tree should not be collected as this will stall the client 59 // It should rather be handled by application that uses gosdk 60 func (o *ObjectTreeRequest) GetRefs() (*ObjectTreeResult, error) { 61 totalBlobbersCount := len(o.blobbers) 62 oTreeResponses := make([]oTreeResponse, totalBlobbersCount) 63 respChan := make(chan *oTreeResponse, totalBlobbersCount) 64 for i, blob := range o.blobbers { 65 l.Logger.Debug(fmt.Sprintf("Getting file refs for path %v from blobber %v", o.remotefilepath, blob.Baseurl)) 66 idx := i 67 baseURL := blob.Baseurl 68 go o.getFileRefs(baseURL, respChan, idx) 69 } 70 hashCount := make(map[string]int) 71 hashRefsMap := make(map[string]*ObjectTreeResult) 72 oTreeResponseErrors := make([]error, totalBlobbersCount) 73 var successCount int 74 for i := 0; i < totalBlobbersCount; i++ { 75 select { 76 case <-o.ctx.Done(): 77 return nil, o.ctx.Err() 78 case oTreeResponse := <-respChan: 79 oTreeResponseErrors[oTreeResponse.idx] = oTreeResponse.err 80 if oTreeResponse.err != nil { 81 if code, _ := zboxutil.GetErrorMessageCode(oTreeResponse.err.Error()); code != INVALID_PATH { 82 l.Logger.Error("Error while getting file refs from blobber:", oTreeResponse.err) 83 } 84 continue 85 } 86 successCount++ 87 hash := oTreeResponse.hash 88 if _, ok := hashCount[hash]; ok { 89 hashCount[hash]++ 90 } else { 91 hashCount[hash]++ 92 hashRefsMap[hash] = oTreeResponse.oTResult 93 } 94 if hashCount[hash] == o.consensusThresh { 95 return oTreeResponse.oTResult, nil 96 } 97 } 98 } 99 var selected *ObjectTreeResult 100 if successCount < o.consensusThresh { 101 majorError := zboxutil.MajorError(oTreeResponseErrors) 102 majorErrorMsg := "" 103 if majorError != nil { 104 majorErrorMsg = majorError.Error() 105 } 106 if code, _ := zboxutil.GetErrorMessageCode(majorErrorMsg); code == INVALID_PATH { 107 return &ObjectTreeResult{}, nil 108 } else { 109 return nil, majorError 110 } 111 } 112 // build the object tree result by using consensus on individual refs 113 refHash := make(map[string]int) 114 selected = &ObjectTreeResult{} 115 minPage := int64(math.MaxInt64) 116 for _, oTreeResponse := range oTreeResponses { 117 if oTreeResponse.err != nil { 118 continue 119 } 120 if oTreeResponse.oTResult.TotalPages < minPage { 121 minPage = oTreeResponse.oTResult.TotalPages 122 selected.TotalPages = minPage 123 } 124 for _, ref := range oTreeResponse.oTResult.Refs { 125 if refHash[ref.FileMetaHash] == o.consensusThresh { 126 continue 127 } 128 refHash[ref.FileMetaHash] += 1 129 if refHash[ref.FileMetaHash] == o.consensusThresh { 130 selected.Refs = append(selected.Refs, ref) 131 } 132 } 133 } 134 if len(selected.Refs) > 0 { 135 selected.OffsetPath = selected.Refs[len(selected.Refs)-1].Path 136 return selected, nil 137 } 138 return nil, errors.New("consensus_failed", "Refs consensus is less than consensus threshold") 139 } 140 141 func (o *ObjectTreeRequest) getFileRefs(bUrl string, respChan chan *oTreeResponse, idx int) { 142 oTR := &oTreeResponse{ 143 idx: idx, 144 } 145 defer func() { 146 respChan <- oTR 147 }() 148 oReq, err := zboxutil.NewRefsRequest( 149 bUrl, 150 o.allocationID, 151 o.sig, 152 o.allocationTx, 153 o.remotefilepath, 154 o.pathHash, 155 o.authToken, 156 o.offsetPath, 157 o.updatedDate, 158 o.offsetDate, 159 o.fileType, 160 o.refType, 161 o.level, 162 o.pageLimit, 163 ) 164 if err != nil { 165 oTR.err = err 166 return 167 } 168 oResult := ObjectTreeResult{} 169 ctx, cncl := context.WithTimeout(o.ctx, 2*time.Minute) 170 err = zboxutil.HttpDo(ctx, cncl, oReq, func(resp *http.Response, err error) error { 171 if err != nil { 172 l.Logger.Error(err) 173 return err 174 } 175 defer resp.Body.Close() 176 respBody, err := ioutil.ReadAll(resp.Body) 177 if err != nil { 178 l.Logger.Error(err) 179 return err 180 } 181 if resp.StatusCode == http.StatusOK { 182 err := json.Unmarshal(respBody, &oResult) 183 if err != nil { 184 l.Logger.Error(err) 185 return err 186 } 187 return nil 188 } else { 189 return errors.New("response_error", fmt.Sprintf("got status %d, err: %s", resp.StatusCode, respBody)) 190 } 191 }) 192 if err != nil { 193 oTR.err = err 194 return 195 } 196 oTR.oTResult = &oResult 197 similarFieldRefs := make([]byte, 0, 32*len(oResult.Refs)) 198 for _, ref := range oResult.Refs { 199 decodeBytes, _ := hex.DecodeString(ref.SimilarField.FileMetaHash) 200 similarFieldRefs = append(similarFieldRefs, decodeBytes...) 201 } 202 oTR.hash = zboxutil.GetRefsHash(similarFieldRefs) 203 } 204 205 // Blobber response will be different from each other so we should only consider similar fields 206 // i.e. we cannot calculate hash of response and have consensus on it 207 type ORef struct { 208 SimilarField 209 ID int64 `json:"id"` 210 CreatedAt common.Timestamp `json:"created_at"` 211 UpdatedAt common.Timestamp `json:"updated_at"` 212 Err error `json:"-"` 213 } 214 215 type SimilarField struct { 216 FileID string `json:"file_id"` 217 FileMetaHash string `json:"file_meta_hash"` 218 Type string `json:"type"` 219 AllocationID string `json:"allocation_id"` 220 LookupHash string `json:"lookup_hash"` 221 Name string `json:"name"` 222 Path string `json:"path"` 223 PathHash string `json:"path_hash"` 224 ParentPath string `json:"parent_path"` 225 PathLevel int `json:"level"` 226 Size int64 `json:"size"` 227 EncryptedKey string `json:"encrypted_key"` 228 ActualFileSize int64 `json:"actual_file_size"` 229 ActualFileHash string `json:"actual_file_hash"` 230 MimeType string `json:"mimetype"` 231 ActualThumbnailSize int64 `json:"actual_thumbnail_size"` 232 ActualThumbnailHash string `json:"actual_thumbnail_hash"` 233 CustomMeta string `json:"custom_meta"` 234 } 235 236 type RecentlyAddedRefRequest struct { 237 ctx context.Context 238 allocationID string 239 allocationTx string 240 sig string 241 blobbers []*blockchain.StorageNode 242 fromDate int64 243 offset int64 244 pageLimit int 245 wg *sync.WaitGroup 246 Consensus 247 } 248 249 type RecentlyAddedRefResult struct { 250 Offset int `json:"offset"` 251 Refs []ORef `json:"refs"` 252 } 253 254 type RecentlyAddedRefResponse struct { 255 Result *RecentlyAddedRefResult 256 err error 257 } 258 259 func (r *RecentlyAddedRefRequest) GetRecentlyAddedRefs() (*RecentlyAddedRefResult, error) { 260 totalBlobbers := len(r.blobbers) 261 responses := make([]*RecentlyAddedRefResponse, totalBlobbers) 262 for i := range responses { 263 responses[i] = &RecentlyAddedRefResponse{} 264 } 265 r.wg.Add(totalBlobbers) 266 267 for i, blob := range r.blobbers { 268 go r.getRecentlyAddedRefs(responses[i], blob.Baseurl) 269 } 270 r.wg.Wait() 271 272 hashCount := make(map[string]int) 273 hashRefsMap := make(map[string]*RecentlyAddedRefResult) 274 275 for _, response := range responses { 276 if response.err != nil { 277 l.Logger.Error(response.err) 278 continue 279 } 280 281 var similarFieldRefs []SimilarField 282 for _, ref := range response.Result.Refs { 283 similarFieldRefs = append(similarFieldRefs, ref.SimilarField) 284 } 285 286 refsMarshall, err := json.Marshal(similarFieldRefs) 287 if err != nil { 288 l.Logger.Error(err) 289 continue 290 } 291 292 hash := zboxutil.GetRefsHash(refsMarshall) 293 if _, ok := hashCount[hash]; ok { 294 hashCount[hash]++ 295 } else { 296 hashCount[hash]++ 297 hashRefsMap[hash] = response.Result 298 } 299 } 300 301 var selected *RecentlyAddedRefResult 302 for k, v := range hashCount { 303 if v >= r.consensusThresh { 304 selected = hashRefsMap[k] 305 break 306 } 307 } 308 309 if selected == nil { 310 return nil, errors.New("consensus_failed", "Refs consensus is less than consensus threshold") 311 } 312 return selected, nil 313 } 314 315 func (r *RecentlyAddedRefRequest) getRecentlyAddedRefs(resp *RecentlyAddedRefResponse, bUrl string) { 316 defer r.wg.Done() 317 req, err := zboxutil.NewRecentlyAddedRefsRequest(bUrl, r.allocationID, r.allocationTx, r.sig, r.fromDate, r.offset, r.pageLimit) 318 if err != nil { 319 resp.err = err 320 return 321 } 322 323 result := RecentlyAddedRefResult{} 324 ctx, cncl := context.WithTimeout(r.ctx, time.Second*30) 325 err = zboxutil.HttpDo(ctx, cncl, req, func(hResp *http.Response, err error) error { 326 if err != nil { 327 l.Logger.Error(err) 328 return err 329 } 330 defer hResp.Body.Close() 331 body, err := ioutil.ReadAll(hResp.Body) 332 if err != nil { 333 l.Logger.Error(err) 334 return err 335 } 336 if hResp.StatusCode != http.StatusOK { 337 return fmt.Errorf("Want code %d, got %d. Message: %s", 338 http.StatusOK, hResp.StatusCode, string(body)) 339 } 340 err = json.Unmarshal(body, &result) 341 if err != nil { 342 l.Logger.Error(err) 343 } 344 return err 345 346 }) 347 if err != nil { 348 resp.err = err 349 return 350 } 351 resp.Result = &result 352 }