github.com/0chain/gosdk@v1.17.11/zboxcore/sdk/commitworker.go (about) 1 package sdk 2 3 import ( 4 "bytes" 5 "context" 6 "encoding/hex" 7 "encoding/json" 8 "fmt" 9 "io" 10 "io/ioutil" 11 "mime/multipart" 12 "net/http" 13 "strconv" 14 "strings" 15 "sync" 16 "time" 17 18 "github.com/0chain/errors" 19 thrown "github.com/0chain/errors" 20 "github.com/0chain/gosdk/zboxcore/allocationchange" 21 "github.com/0chain/gosdk/zboxcore/blockchain" 22 "github.com/0chain/gosdk/zboxcore/client" 23 "github.com/0chain/gosdk/zboxcore/fileref" 24 "github.com/0chain/gosdk/zboxcore/logger" 25 l "github.com/0chain/gosdk/zboxcore/logger" 26 "github.com/0chain/gosdk/zboxcore/marker" 27 "github.com/0chain/gosdk/zboxcore/zboxutil" 28 "github.com/minio/sha256-simd" 29 ) 30 31 type ReferencePathResult struct { 32 *fileref.ReferencePath 33 LatestWM *marker.WriteMarker `json:"latest_write_marker"` 34 Version string `json:"version"` 35 } 36 37 type CommitResult struct { 38 Success bool `json:"success"` 39 ErrorMessage string `json:"error_msg,omitempty"` 40 } 41 42 func ErrorCommitResult(errMsg string) *CommitResult { 43 result := &CommitResult{Success: false, ErrorMessage: errMsg} 44 return result 45 } 46 47 func SuccessCommitResult() *CommitResult { 48 result := &CommitResult{Success: true} 49 return result 50 } 51 52 const MARKER_VERSION = "v2" 53 54 type CommitRequest struct { 55 changes []allocationchange.AllocationChange 56 blobber *blockchain.StorageNode 57 allocationID string 58 allocationTx string 59 connectionID string 60 sig string 61 wg *sync.WaitGroup 62 result *CommitResult 63 timestamp int64 64 blobberInd uint64 65 } 66 67 var commitChan map[string]chan *CommitRequest 68 var initCommitMutex sync.Mutex 69 70 func InitCommitWorker(blobbers []*blockchain.StorageNode) { 71 initCommitMutex.Lock() 72 defer initCommitMutex.Unlock() 73 if commitChan == nil { 74 commitChan = make(map[string]chan *CommitRequest) 75 } 76 77 for _, blobber := range blobbers { 78 if _, ok := commitChan[blobber.ID]; !ok { 79 commitChan[blobber.ID] = make(chan *CommitRequest, 1) 80 blobberChan := commitChan[blobber.ID] 81 go startCommitWorker(blobberChan, blobber.ID) 82 } 83 } 84 85 } 86 87 func startCommitWorker(blobberChan chan *CommitRequest, blobberID string) { 88 for { 89 commitreq, open := <-blobberChan 90 if !open { 91 break 92 } 93 commitreq.processCommit() 94 } 95 initCommitMutex.Lock() 96 defer initCommitMutex.Unlock() 97 delete(commitChan, blobberID) 98 } 99 100 func (commitreq *CommitRequest) processCommit() { 101 defer commitreq.wg.Done() 102 start := time.Now() 103 l.Logger.Debug("received a commit request") 104 paths := make([]string, 0) 105 for _, change := range commitreq.changes { 106 paths = append(paths, change.GetAffectedPath()...) 107 } 108 if len(paths) == 0 { 109 l.Logger.Debug("Nothing to commit") 110 commitreq.result = SuccessCommitResult() 111 return 112 } 113 var req *http.Request 114 var lR ReferencePathResult 115 req, err := zboxutil.NewReferencePathRequest(commitreq.blobber.Baseurl, commitreq.allocationID, commitreq.allocationTx, commitreq.sig, paths) 116 if err != nil { 117 l.Logger.Error("Creating ref path req", err) 118 return 119 } 120 ctx, cncl := context.WithTimeout(context.Background(), (time.Second * 30)) 121 err = zboxutil.HttpDo(ctx, cncl, req, func(resp *http.Response, err error) error { 122 if err != nil { 123 l.Logger.Error("Ref path error:", err) 124 return err 125 } 126 defer resp.Body.Close() 127 if resp.StatusCode != http.StatusOK { 128 l.Logger.Error("Ref path response : ", resp.StatusCode) 129 } 130 resp_body, err := ioutil.ReadAll(resp.Body) 131 if err != nil { 132 l.Logger.Error("Ref path: Resp", err) 133 return err 134 } 135 if resp.StatusCode != http.StatusOK { 136 return errors.New( 137 strconv.Itoa(resp.StatusCode), 138 fmt.Sprintf("Reference path error response: Status: %d - %s ", 139 resp.StatusCode, string(resp_body))) 140 } 141 err = json.Unmarshal(resp_body, &lR) 142 if err != nil { 143 l.Logger.Error("Reference path json decode error: ", err) 144 return err 145 } 146 return nil 147 }) 148 149 if err != nil { 150 commitreq.result = ErrorCommitResult(err.Error()) 151 return 152 } 153 rootRef, err := lR.GetDirTree(commitreq.allocationID) 154 155 if err != nil { 156 commitreq.result = ErrorCommitResult(err.Error()) 157 return 158 } 159 hasher := sha256.New() 160 if lR.LatestWM != nil { 161 err = lR.LatestWM.VerifySignature(client.GetClientPublicKey()) 162 if err != nil { 163 e := errors.New("signature_verification_failed", err.Error()) 164 commitreq.result = ErrorCommitResult(e.Error()) 165 return 166 } 167 if commitreq.timestamp <= lR.LatestWM.Timestamp { 168 commitreq.timestamp = lR.LatestWM.Timestamp + 1 169 } 170 171 rootRef.CalculateHash() 172 prevAllocationRoot := rootRef.Hash 173 if prevAllocationRoot != lR.LatestWM.AllocationRoot { 174 l.Logger.Error("Allocation root from latest writemarker mismatch. Expected: " + prevAllocationRoot + " got: " + lR.LatestWM.AllocationRoot) 175 errMsg := fmt.Sprintf( 176 "calculated allocation root mismatch from blobber %s. Expected: %s, Got: %s", 177 commitreq.blobber.Baseurl, prevAllocationRoot, lR.LatestWM.AllocationRoot) 178 commitreq.result = ErrorCommitResult(errMsg) 179 return 180 } 181 if lR.LatestWM.ChainHash != "" { 182 prevChainHash, err := hex.DecodeString(lR.LatestWM.ChainHash) 183 if err != nil { 184 commitreq.result = ErrorCommitResult(err.Error()) 185 return 186 } 187 hasher.Write(prevChainHash) //nolint:errcheck 188 } 189 } 190 191 var size int64 192 fileIDMeta := make(map[string]string) 193 194 for _, change := range commitreq.changes { 195 err = change.ProcessChange(rootRef, fileIDMeta) 196 if err != nil { 197 if !errors.Is(err, allocationchange.ErrRefNotFound) { 198 commitreq.result = ErrorCommitResult(err.Error()) 199 return 200 } 201 } else { 202 size += change.GetSize() 203 } 204 } 205 rootRef.CalculateHash() 206 var chainHash string 207 if lR.Version == MARKER_VERSION { 208 decodedHash, _ := hex.DecodeString(rootRef.Hash) 209 hasher.Write(decodedHash) //nolint:errcheck 210 chainHash = hex.EncodeToString(hasher.Sum(nil)) 211 } 212 err = commitreq.commitBlobber(rootRef, chainHash, lR.LatestWM, size, fileIDMeta) 213 if err != nil { 214 commitreq.result = ErrorCommitResult(err.Error()) 215 return 216 } 217 l.Logger.Debug("[commitBlobber]", time.Since(start).Milliseconds()) 218 commitreq.result = SuccessCommitResult() 219 } 220 221 func (req *CommitRequest) commitBlobber( 222 rootRef *fileref.Ref, chainHash string, latestWM *marker.WriteMarker, size int64, 223 fileIDMeta map[string]string) (err error) { 224 225 fileIDMetaData, err := json.Marshal(fileIDMeta) 226 if err != nil { 227 l.Logger.Error("Marshalling inode metadata failed: ", err) 228 return err 229 } 230 231 wm := &marker.WriteMarker{} 232 wm.AllocationRoot = rootRef.Hash 233 wm.ChainSize = size 234 if latestWM != nil { 235 wm.PreviousAllocationRoot = latestWM.AllocationRoot 236 wm.ChainSize += latestWM.ChainSize 237 } else { 238 wm.PreviousAllocationRoot = "" 239 } 240 if wm.AllocationRoot == wm.PreviousAllocationRoot { 241 l.Logger.Debug("Allocation root and previous allocation root are same") 242 return nil 243 } 244 wm.ChainHash = chainHash 245 wm.FileMetaRoot = rootRef.FileMetaHash 246 wm.AllocationID = req.allocationID 247 wm.Size = size 248 wm.BlobberID = req.blobber.ID 249 wm.Timestamp = req.timestamp 250 wm.ClientID = client.GetClientID() 251 err = wm.Sign() 252 if err != nil { 253 l.Logger.Error("Signing writemarker failed: ", err) 254 return err 255 } 256 wmData, err := json.Marshal(wm) 257 if err != nil { 258 l.Logger.Error("Creating writemarker failed: ", err) 259 return err 260 } 261 262 l.Logger.Debug("Committing to blobber." + req.blobber.Baseurl) 263 var ( 264 resp *http.Response 265 shouldContinue bool 266 ) 267 for retries := 0; retries < 6; retries++ { 268 err, shouldContinue = func() (err error, shouldContinue bool) { 269 body := new(bytes.Buffer) 270 formWriter, err := getFormWritter(req.connectionID, wmData, fileIDMetaData, body) 271 if err != nil { 272 l.Logger.Error("Creating form writer failed: ", err) 273 return 274 } 275 httpreq, err := zboxutil.NewCommitRequest(req.blobber.Baseurl, req.allocationID, req.allocationTx, body) 276 if err != nil { 277 l.Logger.Error("Error creating commit req: ", err) 278 return 279 } 280 httpreq.Header.Add("Content-Type", formWriter.FormDataContentType()) 281 reqCtx, ctxCncl := context.WithTimeout(context.Background(), time.Second*60) 282 resp, err = zboxutil.Client.Do(httpreq.WithContext(reqCtx)) 283 defer ctxCncl() 284 285 if err != nil { 286 logger.Logger.Error("Commit: ", err) 287 return 288 } 289 290 if resp.Body != nil { 291 defer resp.Body.Close() 292 } 293 294 var respBody []byte 295 respBody, err = io.ReadAll(resp.Body) 296 if err != nil { 297 logger.Logger.Error("Response read: ", err) 298 return 299 } 300 if resp.StatusCode == http.StatusOK { 301 logger.Logger.Debug(req.blobber.Baseurl, " committed") 302 return 303 } 304 305 if resp.StatusCode == http.StatusTooManyRequests { 306 logger.Logger.Debug(req.blobber.Baseurl, 307 " got too many request error. Retrying") 308 309 var r int 310 r, err = zboxutil.GetRateLimitValue(resp) 311 if err != nil { 312 logger.Logger.Error(err) 313 return 314 } 315 316 time.Sleep(time.Duration(r) * time.Second) 317 shouldContinue = true 318 return 319 } 320 321 if strings.Contains(string(respBody), "pending_markers:") { 322 logger.Logger.Debug("Commit pending for blobber ", 323 req.blobber.Baseurl, " Retrying") 324 time.Sleep(5 * time.Second) 325 shouldContinue = true 326 return 327 } 328 329 if strings.Contains(string(respBody), "chain_length_exceeded") { 330 l.Logger.Error("Chain length exceeded for blobber ", 331 req.blobber.Baseurl, " Retrying") 332 time.Sleep(5 * time.Second) 333 shouldContinue = true 334 return 335 } 336 337 err = thrown.New("commit_error", 338 fmt.Sprintf("Got error response %s with status %d", respBody, resp.StatusCode)) 339 return 340 }() 341 if shouldContinue { 342 continue 343 } 344 return 345 } 346 return thrown.New("commit_error", fmt.Sprintf("Commit failed with response status %d", resp.StatusCode)) 347 } 348 349 func AddCommitRequest(req *CommitRequest) { 350 commitChan[req.blobber.ID] <- req 351 } 352 353 func (commitreq *CommitRequest) calculateHashRequest(ctx context.Context, paths []string) error { //nolint 354 var req *http.Request 355 req, err := zboxutil.NewCalculateHashRequest(commitreq.blobber.Baseurl, commitreq.allocationID, commitreq.allocationTx, paths) 356 if err != nil || len(paths) == 0 { 357 l.Logger.Error("Creating calculate hash req", err) 358 return err 359 } 360 ctx, cncl := context.WithTimeout(ctx, (time.Second * 30)) 361 err = zboxutil.HttpDo(ctx, cncl, req, func(resp *http.Response, err error) error { 362 if err != nil { 363 l.Logger.Error("Calculate hash error:", err) 364 return err 365 } 366 defer resp.Body.Close() 367 if resp.StatusCode != http.StatusOK { 368 l.Logger.Error("Calculate hash response : ", resp.StatusCode) 369 } 370 resp_body, err := ioutil.ReadAll(resp.Body) 371 if err != nil { 372 l.Logger.Error("Calculate hash: Resp", err) 373 return err 374 } 375 if resp.StatusCode != http.StatusOK { 376 return errors.New(strconv.Itoa(resp.StatusCode), fmt.Sprintf("Calculate hash error response: Body: %s ", string(resp_body))) 377 } 378 return nil 379 }) 380 return err 381 } 382 383 func getFormWritter(connectionID string, wmData, fileIDMetaData []byte, body *bytes.Buffer) (*multipart.Writer, error) { 384 formWriter := multipart.NewWriter(body) 385 err := formWriter.WriteField("connection_id", connectionID) 386 if err != nil { 387 return nil, err 388 } 389 390 err = formWriter.WriteField("write_marker", string(wmData)) 391 if err != nil { 392 return nil, err 393 } 394 395 err = formWriter.WriteField("file_id_meta", string(fileIDMetaData)) 396 if err != nil { 397 return nil, err 398 } 399 formWriter.Close() 400 return formWriter, nil 401 }