github.com/0chain/gosdk@v1.17.11/zboxcore/sdk/rollback.go (about) 1 package sdk 2 3 import ( 4 "bytes" 5 "context" 6 "encoding/hex" 7 "encoding/json" 8 "fmt" 9 "io" 10 "mime/multipart" 11 "strings" 12 "sync" 13 "sync/atomic" 14 "time" 15 16 "net/http" 17 18 "errors" 19 20 "github.com/0chain/common/core/common" 21 thrown "github.com/0chain/errors" 22 "github.com/0chain/gosdk/zboxcore/blockchain" 23 "github.com/0chain/gosdk/zboxcore/client" 24 l "github.com/0chain/gosdk/zboxcore/logger" 25 "github.com/0chain/gosdk/zboxcore/marker" 26 "github.com/0chain/gosdk/zboxcore/zboxutil" 27 "github.com/minio/sha256-simd" 28 "go.uber.org/zap" 29 ) 30 31 type LatestPrevWriteMarker struct { 32 LatestWM *marker.WriteMarker `json:"latest_write_marker"` 33 PrevWM *marker.WriteMarker `json:"prev_write_marker"` 34 Version string `json:"version"` 35 } 36 37 type AllocStatus byte 38 39 const ( 40 Commit AllocStatus = iota 41 Repair 42 Broken 43 Rollback 44 ) 45 46 var ( 47 ErrRetryOperation = errors.New("retry_operation") 48 ErrRepairRequired = errors.New("repair_required") 49 ) 50 51 type RollbackBlobber struct { 52 blobber *blockchain.StorageNode 53 commitResult *CommitResult 54 lpm *LatestPrevWriteMarker 55 blobIndex int 56 } 57 58 type BlobberStatus struct { 59 ID string 60 Status string 61 } 62 63 func GetWritemarker(allocID, allocTx, sig, id, baseUrl string) (*LatestPrevWriteMarker, error) { 64 65 var lpm LatestPrevWriteMarker 66 67 req, err := zboxutil.NewWritemarkerRequest(baseUrl, allocID, allocTx, sig) 68 if err != nil { 69 return nil, err 70 } 71 ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) 72 defer cancel() 73 for retries := 0; retries < 3; retries++ { 74 75 resp, err := zboxutil.Client.Do(req.WithContext(ctx)) 76 if err != nil { 77 return nil, err 78 } 79 if resp.StatusCode == http.StatusTooManyRequests { 80 l.Logger.Info(baseUrl, "got too many requests, retrying") 81 var r int 82 r, err = zboxutil.GetRateLimitValue(resp) 83 if err != nil { 84 l.Logger.Error(err) 85 return nil, err 86 } 87 time.Sleep(time.Duration(r) * time.Second) 88 continue 89 } 90 body, err := io.ReadAll(resp.Body) 91 defer resp.Body.Close() 92 if resp.StatusCode != http.StatusOK { 93 return nil, fmt.Errorf("writemarker error response %s with status %d", body, resp.StatusCode) 94 } 95 96 if err != nil { 97 return nil, err 98 } 99 err = json.Unmarshal(body, &lpm) 100 if err != nil { 101 return nil, err 102 } 103 if lpm.LatestWM != nil { 104 err = lpm.LatestWM.VerifySignature(client.GetClientPublicKey()) 105 if err != nil { 106 return nil, fmt.Errorf("signature verification failed for latest writemarker: %s", err.Error()) 107 } 108 if lpm.PrevWM != nil { 109 err = lpm.PrevWM.VerifySignature(client.GetClientPublicKey()) 110 if err != nil { 111 return nil, fmt.Errorf("signature verification failed for latest writemarker: %s", err.Error()) 112 } 113 } 114 } 115 return &lpm, nil 116 } 117 118 return nil, fmt.Errorf("writemarker error response %d", http.StatusTooManyRequests) 119 } 120 121 func (rb *RollbackBlobber) processRollback(ctx context.Context, tx string) error { 122 123 wm := &marker.WriteMarker{} 124 wm.AllocationID = rb.lpm.LatestWM.AllocationID 125 wm.Timestamp = rb.lpm.LatestWM.Timestamp 126 wm.BlobberID = rb.lpm.LatestWM.BlobberID 127 wm.ClientID = client.GetClientID() 128 wm.Size = -rb.lpm.LatestWM.Size 129 wm.ChainSize = wm.Size + rb.lpm.LatestWM.ChainSize 130 131 if rb.lpm.PrevWM != nil { 132 wm.AllocationRoot = rb.lpm.PrevWM.AllocationRoot 133 wm.PreviousAllocationRoot = rb.lpm.PrevWM.AllocationRoot 134 wm.FileMetaRoot = rb.lpm.PrevWM.FileMetaRoot 135 if wm.AllocationRoot == rb.lpm.LatestWM.AllocationRoot { 136 return nil 137 } 138 } 139 if rb.lpm.Version == MARKER_VERSION { 140 decodedHash, _ := hex.DecodeString(wm.AllocationRoot) 141 prevChainHash, _ := hex.DecodeString(rb.lpm.LatestWM.ChainHash) 142 hasher := sha256.New() 143 hasher.Write(prevChainHash) //nolint:errcheck 144 hasher.Write(decodedHash) //nolint:errcheck 145 wm.ChainHash = hex.EncodeToString(hasher.Sum(nil)) 146 } else if rb.lpm.Version == "" { 147 wm.Size = 0 148 } 149 150 err := wm.Sign() 151 if err != nil { 152 l.Logger.Error("Signing writemarker failed: ", err) 153 return err 154 } 155 body := new(bytes.Buffer) 156 formWriter := multipart.NewWriter(body) 157 wmData, err := json.Marshal(wm) 158 if err != nil { 159 l.Logger.Error("Creating writemarker failed: ", err) 160 return err 161 } 162 connID := zboxutil.NewConnectionId() 163 formWriter.WriteField("write_marker", string(wmData)) 164 formWriter.WriteField("connection_id", connID) 165 formWriter.Close() 166 167 req, err := zboxutil.NewRollbackRequest(rb.blobber.Baseurl, wm.AllocationID, tx, body) 168 if err != nil { 169 l.Logger.Error("Creating rollback request failed: ", err) 170 return err 171 } 172 req.Header.Add("Content-Type", formWriter.FormDataContentType()) 173 174 l.Logger.Info("Sending Rollback request to blobber: ", rb.blobber.Baseurl) 175 176 var ( 177 shouldContinue bool 178 ) 179 180 for retries := 0; retries < 3; retries++ { 181 err, shouldContinue = func() (err error, shouldContinue bool) { 182 reqCtx, ctxCncl := context.WithTimeout(ctx, DefaultUploadTimeOut) 183 resp, err := zboxutil.Client.Do(req.WithContext(reqCtx)) 184 defer ctxCncl() 185 if err != nil { 186 l.Logger.Error("Rollback request failed: ", err) 187 return 188 } 189 190 if resp.Body != nil { 191 defer resp.Body.Close() 192 } 193 194 var respBody []byte 195 respBody, err = io.ReadAll(resp.Body) 196 if err != nil { 197 l.Logger.Error("Response read: ", err) 198 return 199 } 200 if resp.StatusCode == http.StatusOK { 201 l.Logger.Info(rb.blobber.Baseurl, connID, "rollbacked") 202 return 203 } 204 205 if resp.StatusCode == http.StatusTooManyRequests { 206 l.Logger.Info(rb.blobber.Baseurl, connID, "got too many request error. Retrying") 207 var r int 208 r, err = zboxutil.GetRateLimitValue(resp) 209 if err != nil { 210 l.Logger.Error(err) 211 return 212 } 213 214 time.Sleep(time.Duration(r) * time.Second) 215 shouldContinue = true 216 return 217 } 218 219 if strings.Contains(string(respBody), "pending_markers:") { 220 l.Logger.Info("Commit pending for blobber ", 221 rb.blobber.Baseurl, " Retrying") 222 time.Sleep(5 * time.Second) 223 shouldContinue = true 224 return 225 } 226 227 if strings.Contains(string(respBody), "chain_length_exceeded") { 228 l.Logger.Info("Chain length exceeded for blobber ", 229 rb.blobber.Baseurl, " Retrying") 230 time.Sleep(5 * time.Second) 231 shouldContinue = true 232 return 233 } 234 235 err = thrown.New("commit_error", 236 fmt.Sprintf("Got error response %s with status %d", respBody, resp.StatusCode)) 237 238 return 239 }() 240 if err != nil { 241 l.Logger.Error(err) 242 return err 243 } 244 if shouldContinue { 245 continue 246 } 247 return nil 248 249 } 250 251 return thrown.New("rolback_error", fmt.Sprint("Rollback failed")) 252 } 253 254 // CheckAllocStatus checks the status of the allocation 255 // and returns the status of the allocation and its blobbers. 256 func (a *Allocation) CheckAllocStatus() (AllocStatus, []BlobberStatus, error) { 257 258 wg := &sync.WaitGroup{} 259 markerChan := make(chan *RollbackBlobber, len(a.Blobbers)) 260 var errCnt int32 261 var markerError error 262 blobberRes := make([]BlobberStatus, len(a.Blobbers)) 263 for ind, blobber := range a.Blobbers { 264 265 wg.Add(1) 266 go func(blobber *blockchain.StorageNode, ind int) { 267 268 defer wg.Done() 269 blobStatus := BlobberStatus{ 270 ID: blobber.ID, 271 Status: "available", 272 } 273 wr, err := GetWritemarker(a.ID, a.Tx, a.sig, blobber.ID, blobber.Baseurl) 274 if err != nil { 275 atomic.AddInt32(&errCnt, 1) 276 markerError = err 277 l.Logger.Error("error during getWritemarker", zap.Error(err)) 278 blobStatus.Status = "unavailable" 279 } 280 if wr == nil { 281 markerChan <- nil 282 } else { 283 markerChan <- &RollbackBlobber{ 284 blobber: blobber, 285 lpm: wr, 286 commitResult: &CommitResult{}, 287 blobIndex: ind, 288 } 289 } 290 blobberRes[ind] = blobStatus 291 }(blobber, ind) 292 293 } 294 wg.Wait() 295 close(markerChan) 296 if (a.ParityShards > 0 && errCnt > int32(a.ParityShards)) || (a.ParityShards == 0 && errCnt > 0) { 297 return Broken, blobberRes, common.NewError("check_alloc_status_failed", markerError.Error()) 298 } 299 300 versionMap := make(map[string][]*RollbackBlobber) 301 302 var ( 303 prevVersion string 304 latestVersion string 305 highestTS int64 306 ) 307 308 for rb := range markerChan { 309 310 if rb == nil || rb.lpm.LatestWM == nil { 311 continue 312 } 313 314 version := rb.lpm.LatestWM.FileMetaRoot 315 316 if highestTS < rb.lpm.LatestWM.Timestamp { 317 prevVersion = latestVersion 318 highestTS = rb.lpm.LatestWM.Timestamp 319 latestVersion = version 320 } 321 322 if prevVersion == "" && version != latestVersion { 323 prevVersion = version 324 } 325 326 if _, ok := versionMap[version]; !ok { 327 versionMap[version] = make([]*RollbackBlobber, 0) 328 } 329 330 versionMap[version] = append(versionMap[version], rb) 331 } 332 333 req := a.DataShards 334 335 if len(versionMap) == 0 { 336 return Commit, blobberRes, nil 337 } 338 339 if len(versionMap[latestVersion]) > req || len(versionMap[prevVersion]) > req { 340 return Commit, blobberRes, nil 341 } 342 343 if len(versionMap[latestVersion]) >= req || len(versionMap[prevVersion]) >= req || len(versionMap) > 2 { 344 for _, rb := range versionMap[prevVersion] { 345 blobberRes[rb.blobIndex].Status = "repair" 346 } 347 return Repair, blobberRes, nil 348 } else { 349 l.Logger.Info("versionMapLen", zap.Int("versionMapLen", len(versionMap)), zap.Int("latestLen", len(versionMap[latestVersion])), zap.Int("prevLen", len(versionMap[prevVersion]))) 350 } 351 352 // rollback to previous version 353 l.Logger.Info("Rolling back to previous version") 354 fullConsensus := len(versionMap[latestVersion]) - (req - len(versionMap[prevVersion])) 355 errCnt = 0 356 l.Logger.Info("fullConsensus", zap.Int32("fullConsensus", int32(fullConsensus)), zap.Int("latestLen", len(versionMap[latestVersion])), zap.Int("prevLen", len(versionMap[prevVersion]))) 357 for _, rb := range versionMap[latestVersion] { 358 359 wg.Add(1) 360 go func(rb *RollbackBlobber) { 361 defer wg.Done() 362 err := rb.processRollback(context.TODO(), a.Tx) 363 if err != nil { 364 atomic.AddInt32(&errCnt, 1) 365 rb.commitResult = ErrorCommitResult(err.Error()) 366 l.Logger.Error("error during rollback", zap.Error(err)) 367 } else { 368 rb.commitResult = SuccessCommitResult() 369 } 370 }(rb) 371 } 372 373 wg.Wait() 374 if errCnt > int32(fullConsensus) { 375 return Broken, blobberRes, common.NewError("rollback_failed", "Rollback failed") 376 } 377 378 if errCnt == int32(fullConsensus) { 379 return Repair, blobberRes, nil 380 } 381 382 return Rollback, blobberRes, nil 383 } 384 385 // RollbackWithMask rolls back the latest operation from the allocation blobbers which ran it. 386 // The mask is used to specify which blobbers to rollback. 387 // - mask: 128-bitmask to specify which blobbers to rollback 388 func (a *Allocation) RollbackWithMask(mask zboxutil.Uint128) { 389 390 wg := &sync.WaitGroup{} 391 markerChan := make(chan *RollbackBlobber, mask.CountOnes()) 392 var pos uint64 393 for i := mask; !i.Equals64(0); i = i.And(zboxutil.NewUint128(1).Lsh(pos).Not()) { 394 pos = uint64(i.TrailingZeros()) 395 blobber := a.Blobbers[pos] 396 wg.Add(1) 397 go func(blobber *blockchain.StorageNode) { 398 399 defer wg.Done() 400 wr, err := GetWritemarker(a.ID, a.Tx, a.sig, blobber.ID, blobber.Baseurl) 401 if err != nil { 402 l.Logger.Error("error during getWritemarker", zap.Error(err)) 403 } 404 if wr == nil { 405 markerChan <- nil 406 } else { 407 markerChan <- &RollbackBlobber{ 408 blobber: blobber, 409 lpm: wr, 410 commitResult: &CommitResult{}, 411 } 412 } 413 }(blobber) 414 415 } 416 wg.Wait() 417 close(markerChan) 418 419 for rb := range markerChan { 420 if rb == nil || rb.lpm.LatestWM == nil { 421 continue 422 } 423 wg.Add(1) 424 go func(rb *RollbackBlobber) { 425 defer wg.Done() 426 err := rb.processRollback(context.TODO(), a.Tx) 427 if err != nil { 428 rb.commitResult = ErrorCommitResult(err.Error()) 429 l.Logger.Error("error during rollback", zap.Error(err)) 430 } else { 431 rb.commitResult = SuccessCommitResult() 432 } 433 }(rb) 434 } 435 436 wg.Wait() 437 }