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  }