github.com/0chain/gosdk@v1.17.11/zboxcore/sdk/chunked_upload_blobber.go (about) 1 package sdk 2 3 import ( 4 "bytes" 5 "context" 6 "encoding/json" 7 "fmt" 8 "io" 9 "mime/multipart" 10 "net/http" 11 "strings" 12 "syscall" 13 "time" 14 15 "github.com/0chain/errors" 16 thrown "github.com/0chain/errors" 17 "github.com/0chain/gosdk/constants" 18 "github.com/0chain/gosdk/zboxcore/allocationchange" 19 "github.com/0chain/gosdk/zboxcore/blockchain" 20 "github.com/0chain/gosdk/zboxcore/client" 21 "github.com/0chain/gosdk/zboxcore/fileref" 22 "github.com/0chain/gosdk/zboxcore/logger" 23 "github.com/0chain/gosdk/zboxcore/marker" 24 "github.com/0chain/gosdk/zboxcore/zboxutil" 25 "github.com/hitenjain14/fasthttp" 26 "github.com/valyala/bytebufferpool" 27 "golang.org/x/sync/errgroup" 28 ) 29 30 // ChunkedUploadBlobber client of blobber's upload 31 type ChunkedUploadBlobber struct { 32 writeMarkerMutex *WriteMarkerMutex 33 blobber *blockchain.StorageNode 34 fileRef *fileref.FileRef 35 progress *UploadBlobberStatus 36 37 commitChanges []allocationchange.AllocationChange 38 commitResult *CommitResult 39 } 40 41 func (sb *ChunkedUploadBlobber) sendUploadRequest( 42 ctx context.Context, su *ChunkedUpload, 43 isFinal bool, 44 encryptedKey string, dataBuffers []*bytes.Buffer, 45 formData ChunkedUploadFormMetadata, contentSlice []string, 46 pos uint64, consensus *Consensus) (err error) { 47 48 defer func() { 49 50 if err != nil { 51 su.maskMu.Lock() 52 su.uploadMask = su.uploadMask.And(zboxutil.NewUint128(1).Lsh(pos).Not()) 53 su.maskMu.Unlock() 54 } 55 }() 56 57 if formData.FileBytesLen == 0 { 58 //fixed fileRef in last chunk on stream. io.EOF with nil bytes 59 if isFinal { 60 sb.fileRef.ChunkSize = su.chunkSize 61 sb.fileRef.Size = su.shardUploadedSize 62 sb.fileRef.Path = su.fileMeta.RemotePath 63 sb.fileRef.ActualFileHash = su.fileMeta.ActualHash 64 sb.fileRef.ActualFileSize = su.fileMeta.ActualSize 65 66 sb.fileRef.EncryptedKey = encryptedKey 67 sb.fileRef.CalculateHash() 68 consensus.Done() 69 } 70 } 71 72 eg, _ := errgroup.WithContext(ctx) 73 74 for dataInd := 0; dataInd < len(dataBuffers); dataInd++ { 75 ind := dataInd 76 eg.Go(func() error { 77 var ( 78 shouldContinue bool 79 ) 80 var req *fasthttp.Request 81 for i := 0; i < 3; i++ { 82 req, err = zboxutil.NewFastUploadRequest( 83 sb.blobber.Baseurl, su.allocationObj.ID, su.allocationObj.Tx, dataBuffers[ind].Bytes(), su.httpMethod) 84 if err != nil { 85 return err 86 } 87 88 req.Header.Add("Content-Type", contentSlice[ind]) 89 err, shouldContinue = func() (err error, shouldContinue bool) { 90 resp := fasthttp.AcquireResponse() 91 defer fasthttp.ReleaseResponse(resp) 92 err = zboxutil.FastHttpClient.DoTimeout(req, resp, su.uploadTimeOut) 93 fasthttp.ReleaseRequest(req) 94 if err != nil { 95 logger.Logger.Error("Upload : ", err) 96 if errors.Is(err, fasthttp.ErrConnectionClosed) || errors.Is(err, syscall.EPIPE) { 97 return err, true 98 } 99 return fmt.Errorf("Error while doing reqeust. Error %s", err), false 100 } 101 102 if resp.StatusCode() == http.StatusOK { 103 return 104 } 105 106 respbody := resp.Body() 107 if resp.StatusCode() == http.StatusTooManyRequests { 108 logger.Logger.Error("Got too many request error") 109 var r int 110 r, err = zboxutil.GetFastRateLimitValue(resp) 111 if err != nil { 112 logger.Logger.Error(err) 113 return 114 } 115 time.Sleep(time.Duration(r) * time.Second) 116 shouldContinue = true 117 return 118 } 119 120 msg := string(respbody) 121 logger.Logger.Error(sb.blobber.Baseurl, 122 " Upload error response: ", resp.StatusCode(), 123 "err message: ", msg) 124 err = errors.Throw(constants.ErrBadRequest, msg) 125 return 126 }() 127 128 if shouldContinue { 129 continue 130 } 131 buff := &bytebufferpool.ByteBuffer{ 132 B: dataBuffers[ind].Bytes(), 133 } 134 formDataPool.Put(buff) 135 136 if err != nil { 137 return err 138 } 139 140 break 141 } 142 return err 143 }) 144 } 145 err = eg.Wait() 146 if err != nil { 147 return err 148 } 149 consensus.Done() 150 151 if formData.ThumbnailBytesLen > 0 { 152 153 sb.fileRef.ThumbnailSize = int64(formData.ThumbnailBytesLen) 154 sb.fileRef.ThumbnailHash = formData.ThumbnailContentHash 155 156 sb.fileRef.ActualThumbnailSize = su.fileMeta.ActualThumbnailSize 157 sb.fileRef.ActualThumbnailHash = su.fileMeta.ActualThumbnailHash 158 } 159 160 // fixed fileRef in last chunk on stream 161 if isFinal { 162 sb.fileRef.FixedMerkleRoot = formData.FixedMerkleRoot 163 sb.fileRef.ValidationRoot = formData.ValidationRoot 164 165 sb.fileRef.ChunkSize = su.chunkSize 166 sb.fileRef.Size = su.shardUploadedSize 167 sb.fileRef.Path = su.fileMeta.RemotePath 168 sb.fileRef.ActualFileHash = su.fileMeta.ActualHash 169 sb.fileRef.ActualFileSize = su.fileMeta.ActualSize 170 171 sb.fileRef.EncryptedKey = encryptedKey 172 sb.fileRef.CalculateHash() 173 } 174 175 return nil 176 } 177 178 func (sb *ChunkedUploadBlobber) processCommit(ctx context.Context, su *ChunkedUpload, pos uint64, timestamp int64) (err error) { 179 defer func() { 180 if err != nil { 181 182 su.maskMu.Lock() 183 su.uploadMask = su.uploadMask.And(zboxutil.NewUint128(1).Lsh(pos).Not()) 184 su.maskMu.Unlock() 185 } 186 }() 187 188 rootRef, latestWM, size, fileIDMeta, err := sb.processWriteMarker(ctx, su) 189 if err != nil { 190 logger.Logger.Error(err) 191 return err 192 } 193 194 wm := &marker.WriteMarker{} 195 wm.AllocationRoot = rootRef.Hash 196 if latestWM != nil { 197 wm.PreviousAllocationRoot = latestWM.AllocationRoot 198 } else { 199 wm.PreviousAllocationRoot = "" 200 } 201 202 wm.FileMetaRoot = rootRef.FileMetaHash 203 wm.AllocationID = su.allocationObj.ID 204 wm.Size = size 205 wm.BlobberID = sb.blobber.ID 206 207 wm.Timestamp = timestamp 208 wm.ClientID = client.GetClientID() 209 err = wm.Sign() 210 if err != nil { 211 logger.Logger.Error("Signing writemarker failed: ", err) 212 return err 213 } 214 body := new(bytes.Buffer) 215 formWriter := multipart.NewWriter(body) 216 wmData, err := json.Marshal(wm) 217 if err != nil { 218 logger.Logger.Error("Creating writemarker failed: ", err) 219 return err 220 } 221 222 fileIDMetaData, err := json.Marshal(fileIDMeta) 223 if err != nil { 224 logger.Logger.Error("Error marshalling file ID Meta: ", err) 225 return err 226 } 227 228 err = formWriter.WriteField("file_id_meta", string(fileIDMetaData)) 229 if err != nil { 230 return err 231 } 232 233 err = formWriter.WriteField("connection_id", su.progress.ConnectionID) 234 if err != nil { 235 return err 236 } 237 238 err = formWriter.WriteField("write_marker", string(wmData)) 239 if err != nil { 240 return err 241 } 242 243 formWriter.Close() 244 245 req, err := zboxutil.NewCommitRequest(sb.blobber.Baseurl, su.allocationObj.ID, su.allocationObj.Tx, body) 246 if err != nil { 247 logger.Logger.Error("Error creating commit req: ", err) 248 return err 249 } 250 req.Header.Add("Content-Type", formWriter.FormDataContentType()) 251 252 logger.Logger.Info("Committing to blobber. " + sb.blobber.Baseurl) 253 254 var ( 255 resp *http.Response 256 shouldContinue bool 257 ) 258 259 for retries := 0; retries < 3; retries++ { 260 err, shouldContinue = func() (err error, shouldContinue bool) { 261 reqCtx, ctxCncl := context.WithTimeout(ctx, su.commitTimeOut) 262 resp, err = su.client.Do(req.WithContext(reqCtx)) 263 defer ctxCncl() 264 265 if err != nil { 266 logger.Logger.Error("Commit: ", err) 267 return 268 } 269 270 if resp.Body != nil { 271 defer resp.Body.Close() 272 } 273 274 var respBody []byte 275 if resp.StatusCode == http.StatusOK { 276 logger.Logger.Info(sb.blobber.Baseurl, su.progress.ConnectionID, " committed") 277 su.consensus.Done() 278 return 279 } 280 281 if resp.StatusCode == http.StatusTooManyRequests { 282 logger.Logger.Info(sb.blobber.Baseurl, su.progress.ConnectionID, 283 " got too many request error. Retrying") 284 285 var r int 286 r, err = zboxutil.GetRateLimitValue(resp) 287 if err != nil { 288 logger.Logger.Error(err) 289 return 290 } 291 292 time.Sleep(time.Duration(r) * time.Second) 293 shouldContinue = true 294 return 295 } 296 297 respBody, err = io.ReadAll(resp.Body) 298 if err != nil { 299 logger.Logger.Error("Response read: ", err) 300 return 301 } 302 303 if strings.Contains(string(respBody), "pending_markers:") { 304 logger.Logger.Info("Commit pending for blobber ", 305 sb.blobber.Baseurl, "with connection id: ", su.progress.ConnectionID, " Retrying again") 306 time.Sleep(5 * time.Second) 307 shouldContinue = true 308 return 309 } 310 311 err = thrown.New("commit_error", 312 fmt.Sprintf("Got error response %s with status %d", respBody, resp.StatusCode)) 313 return 314 }() 315 if shouldContinue { 316 continue 317 } 318 return 319 } 320 return thrown.New("commit_error", fmt.Sprintf("Commit failed with response status %d", resp.StatusCode)) 321 } 322 323 func (sb *ChunkedUploadBlobber) processWriteMarker( 324 ctx context.Context, su *ChunkedUpload) ( 325 *fileref.Ref, *marker.WriteMarker, int64, map[string]string, error) { 326 327 logger.Logger.Info("received a commit request") 328 paths := make([]string, 0) 329 for _, change := range sb.commitChanges { 330 paths = append(paths, change.GetAffectedPath()...) 331 } 332 333 var lR ReferencePathResult 334 req, err := zboxutil.NewReferencePathRequest(sb.blobber.Baseurl, su.allocationObj.ID, su.allocationObj.Tx, su.allocationObj.sig, paths) 335 if err != nil || len(paths) == 0 { 336 logger.Logger.Error("Creating ref path req", err) 337 return nil, nil, 0, nil, err 338 } 339 340 resp, err := su.client.Do(req) 341 342 if err != nil { 343 logger.Logger.Error("Ref path error:", err) 344 return nil, nil, 0, nil, err 345 } 346 defer resp.Body.Close() 347 if resp.StatusCode != http.StatusOK { 348 logger.Logger.Error("Ref path response : ", resp.StatusCode) 349 } 350 body, err := io.ReadAll(resp.Body) 351 if err != nil { 352 logger.Logger.Error("Ref path: Resp", err) 353 return nil, nil, 0, nil, err 354 } 355 if resp.StatusCode != http.StatusOK { 356 return nil, nil, 0, nil, fmt.Errorf("Reference path error response: Status: %d - %s ", resp.StatusCode, string(body)) 357 } 358 359 err = json.Unmarshal(body, &lR) 360 if err != nil { 361 logger.Logger.Error("Reference path json decode error: ", err) 362 return nil, nil, 0, nil, err 363 } 364 365 rootRef, err := lR.GetDirTree(su.allocationObj.ID) 366 if err != nil { 367 return nil, nil, 0, nil, err 368 } 369 370 if lR.LatestWM != nil { 371 rootRef.CalculateHash() 372 prevAllocationRoot := rootRef.Hash 373 if prevAllocationRoot != lR.LatestWM.AllocationRoot { 374 logger.Logger.Info("Allocation root from latest writemarker mismatch. Expected: " + 375 prevAllocationRoot + " got: " + lR.LatestWM.AllocationRoot) 376 return nil, nil, 0, nil, fmt.Errorf( 377 "calculated allocation root mismatch from blobber %s. Expected: %s, Got: %s", 378 sb.blobber.Baseurl, prevAllocationRoot, lR.LatestWM.AllocationRoot) 379 } 380 } 381 382 var size int64 383 fileIDMeta := make(map[string]string) 384 for _, change := range sb.commitChanges { 385 err = change.ProcessChange(rootRef, fileIDMeta) 386 if err != nil { 387 logger.Logger.Error(err) 388 return nil, nil, 0, nil, err 389 } 390 size += change.GetSize() 391 } 392 rootRef.CalculateHash() 393 return rootRef, lR.LatestWM, size, fileIDMeta, nil 394 }