github.com/0chain/gosdk@v1.17.11/zboxcore/sdk/blockdownloadworker.go (about)

     1  package sdk
     2  
     3  import (
     4  	"context"
     5  	"encoding/json"
     6  	"fmt"
     7  	"net/http"
     8  	"sync"
     9  	"syscall"
    10  	"time"
    11  
    12  	"github.com/0chain/errors"
    13  	"github.com/0chain/gosdk/core/common"
    14  	"github.com/0chain/gosdk/core/util"
    15  	"github.com/0chain/gosdk/zboxcore/blockchain"
    16  	"github.com/0chain/gosdk/zboxcore/client"
    17  	"github.com/0chain/gosdk/zboxcore/fileref"
    18  	zlogger "github.com/0chain/gosdk/zboxcore/logger"
    19  	"github.com/0chain/gosdk/zboxcore/marker"
    20  	"github.com/0chain/gosdk/zboxcore/zboxutil"
    21  	"github.com/hitenjain14/fasthttp"
    22  	"golang.org/x/sync/semaphore"
    23  )
    24  
    25  const (
    26  	LockExists     = "lock_exists"
    27  	RateLimitError = "rate_limit_error"
    28  )
    29  
    30  type BlockDownloadRequest struct {
    31  	blobber            *blockchain.StorageNode
    32  	blobberFile        *blobberFile
    33  	allocationID       string
    34  	allocationTx       string
    35  	allocOwnerID       string
    36  	blobberIdx         int
    37  	maskIdx            int
    38  	remotefilepath     string
    39  	remotefilepathhash string
    40  	chunkSize          int
    41  	blockNum           int64
    42  	encryptedKey       string
    43  	contentMode        string
    44  	numBlocks          int64
    45  	authTicket         *marker.AuthTicket
    46  	ctx                context.Context
    47  	result             chan *downloadBlock
    48  	shouldVerify       bool
    49  	connectionID       string
    50  	respBuf            []byte
    51  }
    52  
    53  type downloadResponse struct {
    54  	Nodes   [][][]byte
    55  	Indexes [][]int
    56  	Data    []byte
    57  }
    58  
    59  type downloadBlock struct {
    60  	BlockChunks [][]byte
    61  	Success     bool               `json:"success"`
    62  	LatestRM    *marker.ReadMarker `json:"latest_rm"`
    63  	idx         int
    64  	maskIdx     int
    65  	err         error
    66  	timeTaken   int64
    67  }
    68  
    69  var downloadBlockChan map[string]chan *BlockDownloadRequest
    70  var initDownloadMutex sync.Mutex
    71  
    72  func InitBlockDownloader(blobbers []*blockchain.StorageNode, workerCount int) {
    73  	initDownloadMutex.Lock()
    74  	defer initDownloadMutex.Unlock()
    75  	if downloadBlockChan == nil {
    76  		downloadBlockChan = make(map[string]chan *BlockDownloadRequest)
    77  	}
    78  
    79  	for _, blobber := range blobbers {
    80  		if _, ok := downloadBlockChan[blobber.ID]; !ok {
    81  			downloadBlockChan[blobber.ID] = make(chan *BlockDownloadRequest, workerCount)
    82  			go startBlockDownloadWorker(downloadBlockChan[blobber.ID], workerCount)
    83  		}
    84  	}
    85  }
    86  
    87  func startBlockDownloadWorker(blobberChan chan *BlockDownloadRequest, workers int) {
    88  	sem := semaphore.NewWeighted(int64(workers))
    89  	fastClient := zboxutil.GetFastHTTPClient()
    90  	for {
    91  		blockDownloadReq, open := <-blobberChan
    92  		if !open {
    93  			break
    94  		}
    95  		if err := sem.Acquire(blockDownloadReq.ctx, 1); err != nil {
    96  			blockDownloadReq.result <- &downloadBlock{Success: false, idx: blockDownloadReq.blobberIdx, err: err}
    97  			continue
    98  		}
    99  		go func() {
   100  			blockDownloadReq.downloadBlobberBlock(fastClient)
   101  			sem.Release(1)
   102  		}()
   103  	}
   104  }
   105  
   106  func splitData(buf []byte, lim int) [][]byte {
   107  	var chunk []byte
   108  	chunks := make([][]byte, 0, common.MustAddInt(len(buf)/lim, 1))
   109  	for len(buf) >= lim {
   110  		chunk, buf = buf[:lim], buf[lim:]
   111  		chunks = append(chunks, chunk)
   112  	}
   113  	if len(buf) > 0 {
   114  		chunks = append(chunks, buf[:])
   115  	}
   116  	return chunks
   117  }
   118  
   119  func (req *BlockDownloadRequest) downloadBlobberBlock(fastClient *fasthttp.Client) {
   120  	if req.numBlocks <= 0 {
   121  		req.result <- &downloadBlock{Success: false, idx: req.blobberIdx, err: errors.New("invalid_request", "Invalid number of blocks for download")}
   122  		return
   123  	}
   124  	retry := 0
   125  	var err error
   126  	for retry < 3 {
   127  		if len(req.remotefilepath) > 0 {
   128  			req.remotefilepathhash = fileref.GetReferenceLookup(req.allocationID, req.remotefilepath)
   129  		}
   130  
   131  		httpreq, err := zboxutil.NewFastDownloadRequest(req.blobber.Baseurl, req.allocationID, req.allocationTx)
   132  		if err != nil {
   133  			req.result <- &downloadBlock{Success: false, idx: req.blobberIdx, err: errors.Wrap(err, "Error creating download request")}
   134  			return
   135  		}
   136  
   137  		header := &DownloadRequestHeader{}
   138  		header.PathHash = req.remotefilepathhash
   139  		header.BlockNum = req.blockNum
   140  		header.NumBlocks = req.numBlocks
   141  		header.VerifyDownload = req.shouldVerify
   142  		header.ConnectionID = req.connectionID
   143  		header.Version = "v2"
   144  
   145  		if req.authTicket != nil {
   146  			header.AuthToken, _ = json.Marshal(req.authTicket) //nolint: errcheck
   147  		}
   148  		if len(req.contentMode) > 0 {
   149  			header.DownloadMode = req.contentMode
   150  		}
   151  		if req.chunkSize == 0 {
   152  			req.chunkSize = CHUNK_SIZE
   153  		}
   154  		shouldRetry := false
   155  
   156  		header.ToFastHeader(httpreq)
   157  
   158  		err = func() error {
   159  			now := time.Now()
   160  			statuscode, respBuf, err := fastClient.GetWithRequest(httpreq, req.respBuf)
   161  			fasthttp.ReleaseRequest(httpreq)
   162  			timeTaken := time.Since(now).Milliseconds()
   163  			if err != nil {
   164  				zlogger.Logger.Error("Error downloading block: ", err)
   165  				if errors.Is(err, fasthttp.ErrConnectionClosed) || errors.Is(err, syscall.EPIPE) {
   166  					shouldRetry = true
   167  					return errors.New("connection_closed", "Connection closed")
   168  				}
   169  				return err
   170  			}
   171  
   172  			if statuscode == http.StatusTooManyRequests {
   173  				shouldRetry = true
   174  				time.Sleep(time.Second * 2)
   175  				return errors.New(RateLimitError, "Rate limit error")
   176  			}
   177  
   178  			if statuscode == http.StatusInternalServerError {
   179  				shouldRetry = true
   180  				return errors.New("internal_server_error", "Internal server error")
   181  			}
   182  
   183  			var rspData downloadBlock
   184  			if statuscode != http.StatusOK {
   185  				zlogger.Logger.Error(fmt.Sprintf("downloadBlobberBlock FAIL - blobberID: %v, clientID: %v, blockNum: %d, retry: %d, response: %v", req.blobber.ID, client.GetClientID(), header.BlockNum, retry, string(respBuf)))
   186  				if err = json.Unmarshal(respBuf, &rspData); err == nil {
   187  					return errors.New("download_error", fmt.Sprintf("Response status: %d, Error: %v,", statuscode, rspData.err))
   188  				}
   189  				return errors.New("response_error", string(respBuf))
   190  			}
   191  
   192  			dR := downloadResponse{}
   193  			if req.shouldVerify {
   194  				err = json.Unmarshal(respBuf, &dR)
   195  				if err != nil {
   196  					return err
   197  				}
   198  			} else {
   199  				dR.Data = respBuf
   200  			}
   201  			if req.contentMode == DOWNLOAD_CONTENT_FULL && req.shouldVerify {
   202  
   203  				vmp := util.MerklePathForMultiLeafVerification{
   204  					Nodes:    dR.Nodes,
   205  					Index:    dR.Indexes,
   206  					RootHash: req.blobberFile.validationRoot,
   207  					DataSize: req.blobberFile.size,
   208  				}
   209  				zlogger.Logger.Info("verifying multiple blocks")
   210  				err = vmp.VerifyMultipleBlocks(dR.Data)
   211  				if err != nil {
   212  					return errors.New("merkle_path_verification_error", err.Error())
   213  				}
   214  			}
   215  
   216  			rspData.idx = req.blobberIdx
   217  			rspData.maskIdx = req.maskIdx
   218  			rspData.timeTaken = timeTaken
   219  			rspData.Success = true
   220  
   221  			if req.encryptedKey != "" {
   222  				if req.authTicket != nil {
   223  					// ReEncryptionHeaderSize for the additional header bytes for ReEncrypt,  where chunk_size - EncryptionHeaderSize is the encrypted data size
   224  					rspData.BlockChunks = splitData(dR.Data, req.chunkSize-EncryptionHeaderSize+ReEncryptionHeaderSize)
   225  				} else {
   226  					rspData.BlockChunks = splitData(dR.Data, req.chunkSize)
   227  				}
   228  			} else {
   229  				if req.chunkSize == 0 {
   230  					req.chunkSize = CHUNK_SIZE
   231  				}
   232  				rspData.BlockChunks = splitData(dR.Data, req.chunkSize)
   233  			}
   234  
   235  			zlogger.Logger.Debug(fmt.Sprintf("downloadBlobberBlock 200 OK: blobberID: %v, clientID: %v, blockNum: %d", req.blobber.ID, client.GetClientID(), header.BlockNum))
   236  
   237  			req.result <- &rspData
   238  			return nil
   239  		}()
   240  
   241  		if err != nil {
   242  			if shouldRetry {
   243  				if retry >= 3 {
   244  					req.result <- &downloadBlock{Success: false, idx: req.blobberIdx, err: err}
   245  					return
   246  				}
   247  				shouldRetry = false
   248  				zlogger.Logger.Debug("Retrying for Error occurred: ", err)
   249  				retry++
   250  				continue
   251  			} else {
   252  				req.result <- &downloadBlock{Success: false, idx: req.blobberIdx, err: err, maskIdx: req.maskIdx}
   253  			}
   254  		}
   255  		return
   256  	}
   257  
   258  	req.result <- &downloadBlock{Success: false, idx: req.blobberIdx, err: err, maskIdx: req.maskIdx}
   259  
   260  }
   261  
   262  func AddBlockDownloadReq(ctx context.Context, req *BlockDownloadRequest, rb zboxutil.DownloadBuffer, effectiveBlockSize int) {
   263  	if rb != nil {
   264  		reqCtx, cncl := context.WithTimeout(ctx, (time.Second * 45))
   265  		defer cncl()
   266  		req.respBuf = rb.RequestChunk(reqCtx, int(req.blockNum))
   267  		if len(req.respBuf) == 0 {
   268  			req.respBuf = make([]byte, int(req.numBlocks)*effectiveBlockSize)
   269  		}
   270  	} else {
   271  		req.respBuf = make([]byte, int(req.numBlocks)*effectiveBlockSize)
   272  	}
   273  	downloadBlockChan[req.blobber.ID] <- req
   274  }