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 }